diff --git a/assets/js/app.js b/assets/js/app.js index 6aa7595..495497f 100644 --- a/assets/js/app.js +++ b/assets/js/app.js @@ -261,13 +261,20 @@ export function UTCIForecast() { // (FREE_DAYS, FILTER_PROFILES and OUTDOORS_VARIANTS now live in ./config.js.) - // Current active filter profile - const [activeProfile, setActiveProfile] = useState('basic'); + // Current active filter profile - persisted in localStorage + const [activeProfile, setActiveProfile] = useState(() => { + try { return localStorage.getItem('sunscope_profile') || 'basic'; } catch (e) { return 'basic'; } + }); // Which columns appear in the hourly table by default. // true = visible on first load (and the only ones free users see) // false = hidden by default (Pro users can toggle these on) - const [visibleCols, setVisibleCols] = useState({ ...FILTER_PROFILES.basic.cols }); + const [visibleCols, setVisibleCols] = useState(() => { + try { + const saved = localStorage.getItem('sunscope_profile') || 'basic'; + return { ...(FILTER_PROFILES[saved]?.cols ?? FILTER_PROFILES.basic.cols) }; + } catch (e) { return { ...FILTER_PROFILES.basic.cols }; } + }); const toggleCol = (col) => setVisibleCols(prev => ({ ...prev, [col]: !prev[col] })); // Skin type for the sunburn-time column. Fitzpatrick II is typical UK fair. @@ -276,8 +283,14 @@ export function UTCIForecast() { // Vehicle type for the cabin heat column. 'car' is the default preset. const [vehicleType, setVehicleType] = useState('car'); - // Places profile sub-variant (urban / beach / events / festival / wintersports / naturist) - const [outdoorsVariant, setOutdoorsVariant] = useState('urban'); + // Places profile sub-variant - persisted in localStorage + const [outdoorsVariant, setOutdoorsVariant] = useState(() => { + try { return localStorage.getItem('sunscope_outdoors_variant') || 'urban'; } catch (e) { return 'urban'; } + }); + const setOutdoorsVariantAndSave = (v) => { + try { localStorage.setItem('sunscope_outdoors_variant', v); } catch (e) { /* ignore */ } + setOutdoorsVariant(v); + }; // Vehicle ventilation — true = windows open (high convective loss). const [vehicleVent, setVehicleVent] = useState(false); @@ -287,7 +300,13 @@ export function UTCIForecast() { // 'off' = hidden, 'on' = indoorT shown (managed or not depending on indoorManaged). const [buildingType, setBuildingType] = useState('brick'); const [indoorManaged, setIndoorManaged] = useState(false); - const [indoorMode, setIndoorMode] = useState('off'); + const [indoorMode, setIndoorMode] = useState(() => { + try { + const saved = localStorage.getItem('sunscope_profile') || 'basic'; + const cols = FILTER_PROFILES[saved]?.cols ?? FILTER_PROFILES.basic.cols; + return (cols['indoorT'] || cols['managedT']) ? 'on' : 'off'; + } catch (e) { return 'off'; } + }); // Pollen type for the pollen column. Persisted in localStorage. const [pollenType, setPollenType] = useState(() => { @@ -304,6 +323,7 @@ export function UTCIForecast() { const activateProfile = (key) => { const profile = FILTER_PROFILES[key]; + try { localStorage.setItem('sunscope_profile', key); } catch (e) { /* ignore */ } setActiveProfile(key); if (key !== 'custom') { setVisibleCols({ ...profile.cols }); @@ -544,7 +564,7 @@ export function UTCIForecast() {
- +
${activeEvents.length > 0 && (() => { const ev = activeEvents[bannerIndex] || activeEvents[0]; @@ -626,24 +646,7 @@ export function UTCIForecast() { glob=${currentRow?.glob ?? 0} activeEvent=${lensEvent} /> - {/* ── FUTURE FEATURE v2: Historical Context Line ───────────────── - Show a subtle single line directly below the dial readout: - e.g. "3.2°C above the May average for this location" - "Near normal for late August" - - Design notes: - • Must be visually elegant — same Fraunces/Manrope type pairing - as the rest of the dial area, small and muted - • Positive delta: warm amber tone; negative delta: cool blue tone - • Source: Open-Meteo has a free /climate endpoint that returns - monthly climate normals (ERA5 reanalysis) for any lat/lon. - Fetch once on location change, cache in a ref. Compare today's - peak air temp (or UTCI) to that month's normal. - • Could also show min/max historical context for the week: - "Warmest day forecast this week" / "Coolest night since March" - • The fetch is separate from the main forecast — handle its own - loading/error state independently so it doesn't block the UI. - ─────────────────────────────────────────────────────────────── */} +
@@ -694,12 +697,7 @@ export function UTCIForecast() { ${forecast && days.length > 0 && html` <${Fragment}> - +
- + ${proPromptDay !== null && days[proPromptDay] && (() => { const promptDate = new Date(days[proPromptDay].key + 'T00:00Z'); const dayName = promptDate.toLocaleDateString('en-GB', { weekday: 'long', timeZone: 'UTC' }); @@ -1001,13 +993,7 @@ export function UTCIForecast() {
`; })()} - +
Profile: ${profileButtonOrder.slice(0, 3).map((key) => { @@ -1048,8 +1034,9 @@ export function UTCIForecast() { setProPromptDay(0); return; } + try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ } setActiveProfile('outdoors'); - setOutdoorsVariant(v); + setOutdoorsVariantAndSave(v); setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols }); setIndoorMode('off'); setIndoorManaged(false); @@ -1077,8 +1064,9 @@ export function UTCIForecast() { setProPromptDay(0); return; } + try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ } setActiveProfile('outdoors'); - setOutdoorsVariant(v); + setOutdoorsVariantAndSave(v); setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols }); setIndoorMode('off'); setIndoorManaged(false); @@ -1108,12 +1096,7 @@ export function UTCIForecast() {
- + ${(isPro || activeCols['burn'] || activeCols['vehicleT'] || activeCols['indoorT'] || activeCols['managedT'] || activeCols['pollen']) && html`
Columns: @@ -1244,68 +1227,13 @@ ${isPro && (activeProfile === 'custom' || activeCols['air']) && html`