1.8.6
Integrate fonts Add office profile Fix onload pulldown selections
This commit is contained in:
@@ -1,3 +1,15 @@
|
|||||||
|
/* ── SELF-HOSTED FONTS ───────────────────────────────────────────────── */
|
||||||
|
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/manrope-400.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 500; font-display: swap; src: url('../fonts/manrope-500.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/manrope-600.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Manrope'; font-style: normal; font-weight: 700; font-display: swap; src: url('../fonts/manrope-700.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 700; font-display: swap; src: url('../fonts/fraunces-700-normal.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Fraunces'; font-style: normal; font-weight: 900; font-display: swap; src: url('../fonts/fraunces-900-normal.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Fraunces'; font-style: italic; font-weight: 300; font-display: swap; src: url('../fonts/fraunces-300-italic.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'Fraunces'; font-style: italic; font-weight: 700; font-display: swap; src: url('../fonts/fraunces-700-italic.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 400; font-display: swap; src: url('../fonts/jetbrains-mono-400.woff2') format('woff2'); }
|
||||||
|
@font-face { font-family: 'JetBrains Mono'; font-style: normal; font-weight: 600; font-display: swap; src: url('../fonts/jetbrains-mono-600.woff2') format('woff2'); }
|
||||||
|
|
||||||
/* ════════════════════════════════════════════════════════════════════════
|
/* ════════════════════════════════════════════════════════════════════════
|
||||||
base.css — Fonts, reset, page shell, and top navigation.
|
base.css — Fonts, reset, page shell, and top navigation.
|
||||||
════════════════════════════════════════════════════════════════════════
|
════════════════════════════════════════════════════════════════════════
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+5
-3
@@ -54,8 +54,8 @@ export function UTCIForecast() {
|
|||||||
visibleCols, setVisibleCols, toggleCol,
|
visibleCols, setVisibleCols, toggleCol,
|
||||||
showDecimals, toggleShowDecimals,
|
showDecimals, toggleShowDecimals,
|
||||||
showUnits, toggleShowUnits,
|
showUnits, toggleShowUnits,
|
||||||
activityOptions, placeOptions,
|
activityOptions, placeOptions, workOptions,
|
||||||
activityValue, activityLabel, placeValue, placeLabel,
|
activityValue, activityLabel, placeValue, placeLabel, workValue, workLabel,
|
||||||
skinType, setSkinType,
|
skinType, setSkinType,
|
||||||
vehicleType, setVehicleType,
|
vehicleType, setVehicleType,
|
||||||
vehicleVent, setVehicleVent,
|
vehicleVent, setVehicleVent,
|
||||||
@@ -321,12 +321,14 @@ export function UTCIForecast() {
|
|||||||
proPromptSource=${proPromptSource} setProPromptSource=${setProPromptSource}
|
proPromptSource=${proPromptSource} setProPromptSource=${setProPromptSource}
|
||||||
activeProfile=${activeProfile} activateProfile=${activateProfile} activeCols=${activeCols}
|
activeProfile=${activeProfile} activateProfile=${activateProfile} activeCols=${activeCols}
|
||||||
visibleCols=${visibleCols}
|
visibleCols=${visibleCols}
|
||||||
activityOptions=${activityOptions} placeOptions=${placeOptions}
|
activityOptions=${activityOptions} placeOptions=${placeOptions} workOptions=${workOptions}
|
||||||
activityValue=${activityValue} activityLabel=${activityLabel}
|
activityValue=${activityValue} activityLabel=${activityLabel}
|
||||||
placeValue=${placeValue} placeLabel=${placeLabel}
|
placeValue=${placeValue} placeLabel=${placeLabel}
|
||||||
|
workValue=${workValue} workLabel=${workLabel}
|
||||||
outdoorsVariant=${outdoorsVariant} setOutdoorsVariantAndSave=${setOutdoorsVariantAndSave}
|
outdoorsVariant=${outdoorsVariant} setOutdoorsVariantAndSave=${setOutdoorsVariantAndSave}
|
||||||
setActiveProfile=${setActiveProfile} setVisibleCols=${setVisibleCols}
|
setActiveProfile=${setActiveProfile} setVisibleCols=${setVisibleCols}
|
||||||
setIndoorMode=${setIndoorMode} setIndoorManaged=${setIndoorManaged}
|
setIndoorMode=${setIndoorMode} setIndoorManaged=${setIndoorManaged}
|
||||||
|
setBuildingType=${setBuildingType}
|
||||||
dayTabsRef=${dayTabsRef} canScrollLeft=${canScrollLeft} canScrollRight=${canScrollRight}
|
dayTabsRef=${dayTabsRef} canScrollLeft=${canScrollLeft} canScrollRight=${canScrollRight}
|
||||||
scrollDayTabs=${scrollDayTabs}
|
scrollDayTabs=${scrollDayTabs}
|
||||||
/>`}
|
/>`}
|
||||||
|
|||||||
@@ -22,10 +22,13 @@
|
|||||||
// visibleCols - object of col-key -> boolean visibility
|
// visibleCols - object of col-key -> boolean visibility
|
||||||
// activityOptions - dropdown options for Activities selector
|
// activityOptions - dropdown options for Activities selector
|
||||||
// placeOptions - dropdown options for Places selector
|
// placeOptions - dropdown options for Places selector
|
||||||
|
// workOptions - dropdown options for Work selector
|
||||||
// activityValue - current activity dropdown value
|
// activityValue - current activity dropdown value
|
||||||
// activityLabel - current activity dropdown label
|
// activityLabel - current activity dropdown label
|
||||||
// placeValue - current place dropdown value
|
// placeValue - current place dropdown value
|
||||||
// placeLabel - current place dropdown label
|
// placeLabel - current place dropdown label
|
||||||
|
// workValue - current work dropdown value
|
||||||
|
// workLabel - current work dropdown label
|
||||||
// outdoorsVariant - current outdoors sub-variant key
|
// outdoorsVariant - current outdoors sub-variant key
|
||||||
// setOutdoorsVariantAndSave - setter that also persists to localStorage
|
// setOutdoorsVariantAndSave - setter that also persists to localStorage
|
||||||
// setActiveProfile - raw setter for activeProfile
|
// setActiveProfile - raw setter for activeProfile
|
||||||
@@ -41,7 +44,7 @@
|
|||||||
import { h, Fragment } from '../../vendor/preact.js';
|
import { h, Fragment } from '../../vendor/preact.js';
|
||||||
import htm from '../../vendor/htm.js';
|
import htm from '../../vendor/htm.js';
|
||||||
import { confidenceBand } from '../utils.js';
|
import { confidenceBand } from '../utils.js';
|
||||||
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys } from '../config.js';
|
import { FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS, profileButtonOrder, activityVariantKeys, placeVariantKeys, workVariantKeys } from '../config.js';
|
||||||
import { CloudIcon, PrecipIcon, CustomSelect } from '../components.js';
|
import { CloudIcon, PrecipIcon, CustomSelect } from '../components.js';
|
||||||
|
|
||||||
const html = htm.bind(h);
|
const html = htm.bind(h);
|
||||||
@@ -54,12 +57,13 @@ export function DayTabs({
|
|||||||
proPromptSource, setProPromptSource,
|
proPromptSource, setProPromptSource,
|
||||||
activeProfile, activateProfile, activeCols,
|
activeProfile, activateProfile, activeCols,
|
||||||
visibleCols,
|
visibleCols,
|
||||||
activityOptions, placeOptions,
|
activityOptions, placeOptions, workOptions,
|
||||||
activityValue, activityLabel,
|
activityValue, activityLabel,
|
||||||
placeValue, placeLabel,
|
placeValue, placeLabel,
|
||||||
|
workValue, workLabel,
|
||||||
outdoorsVariant, setOutdoorsVariantAndSave,
|
outdoorsVariant, setOutdoorsVariantAndSave,
|
||||||
setActiveProfile, setVisibleCols,
|
setActiveProfile, setVisibleCols,
|
||||||
setIndoorMode, setIndoorManaged,
|
setIndoorMode, setIndoorManaged, setBuildingType,
|
||||||
dayTabsRef, canScrollLeft, canScrollRight, scrollDayTabs,
|
dayTabsRef, canScrollLeft, canScrollRight, scrollDayTabs,
|
||||||
}) {
|
}) {
|
||||||
return html`
|
return html`
|
||||||
@@ -398,11 +402,34 @@ export function DayTabs({
|
|||||||
<${CustomSelect}
|
<${CustomSelect}
|
||||||
key="activities"
|
key="activities"
|
||||||
value=${activityValue}
|
value=${activityValue}
|
||||||
isOn=${activeProfile === 'farming' || (activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant))}
|
isOn=${activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)}
|
||||||
noHide=${true}
|
noHide=${true}
|
||||||
hideLabel="🎯 Activities"
|
hideLabel="🎯 Activities"
|
||||||
buttonLabel=${activityValue === 'off' ? '🎯 Activities' : ` ${activityLabel}`}
|
buttonLabel=${activityValue === 'off' ? '🎯 Activities' : ` ${activityLabel}`}
|
||||||
options=${activityOptions}
|
options=${activityOptions}
|
||||||
|
onChange=${(v) => {
|
||||||
|
if (v === 'off') { activateProfile('basic'); return; }
|
||||||
|
if (OUTDOORS_VARIANTS[v]?.proOnly && !isPro) {
|
||||||
|
setProPromptSource(`variant:${v}`);
|
||||||
|
setProPromptDay(0);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
try { localStorage.setItem('sunscope_profile', 'outdoors'); } catch (e) { /* ignore */ }
|
||||||
|
setActiveProfile('outdoors');
|
||||||
|
setOutdoorsVariantAndSave(v);
|
||||||
|
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
||||||
|
setIndoorMode('off');
|
||||||
|
setIndoorManaged(false);
|
||||||
|
}}
|
||||||
|
/>
|
||||||
|
<${CustomSelect}
|
||||||
|
key="work"
|
||||||
|
value=${workValue}
|
||||||
|
isOn=${activeProfile === 'farming' || (activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant))}
|
||||||
|
noHide=${true}
|
||||||
|
hideLabel="💼 Work"
|
||||||
|
buttonLabel=${workValue === 'off' ? '💼 Work' : ` ${workLabel}`}
|
||||||
|
options=${workOptions}
|
||||||
onChange=${(v) => {
|
onChange=${(v) => {
|
||||||
if (v === 'off') { activateProfile('basic'); return; }
|
if (v === 'off') { activateProfile('basic'); return; }
|
||||||
if (v === 'farming') { activateProfile('farming'); return; }
|
if (v === 'farming') { activateProfile('farming'); return; }
|
||||||
@@ -415,8 +442,14 @@ export function DayTabs({
|
|||||||
setActiveProfile('outdoors');
|
setActiveProfile('outdoors');
|
||||||
setOutdoorsVariantAndSave(v);
|
setOutdoorsVariantAndSave(v);
|
||||||
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
setVisibleCols({ ...OUTDOORS_VARIANTS[v].cols });
|
||||||
|
if (v === 'office') {
|
||||||
|
setBuildingType('office');
|
||||||
|
setIndoorMode('on');
|
||||||
|
setIndoorManaged(false);
|
||||||
|
} else {
|
||||||
setIndoorMode('off');
|
setIndoorMode('off');
|
||||||
setIndoorManaged(false);
|
setIndoorManaged(false);
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
<span class="profile-divider" aria-hidden="true"></span>
|
<span class="profile-divider" aria-hidden="true"></span>
|
||||||
|
|||||||
+12
-2
@@ -94,6 +94,10 @@ export const OUTDOORS_VARIANTS = {
|
|||||||
hiking: { name: 'Hiking', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
hiking: { name: 'Hiking', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
photography: { name: 'Photography', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: true, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
photography: { name: 'Photography', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: true, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
fishing: { name: 'Fishing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
fishing: { name: 'Fishing', proOnly: true, cols: { hour: true, air: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
|
// Work variants
|
||||||
|
market: { name: 'Market Trading', cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
|
windowcleaning: { name: 'Window Cleaning', proOnly: true, cols: { hour: true, air: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false } },
|
||||||
|
office: { name: 'Office', proOnly: true, cols: { hour: true, air: true, rh: true, dew: false, wind: false, dir: false, cloud: true, sun: true, direct: true, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: true, vis: false, aqi: true, pollen: false } },
|
||||||
};
|
};
|
||||||
|
|
||||||
// Pollen type options shown in the pulldown selector.
|
// Pollen type options shown in the pulldown selector.
|
||||||
@@ -128,13 +132,19 @@ export const variantIcons = {
|
|||||||
hiking: '🏔️',
|
hiking: '🏔️',
|
||||||
photography: '📸',
|
photography: '📸',
|
||||||
fishing: '🎣',
|
fishing: '🎣',
|
||||||
|
market: '🏪',
|
||||||
|
windowcleaning: '🪟',
|
||||||
|
office: '🏢',
|
||||||
};
|
};
|
||||||
|
|
||||||
// Which variant keys appear in the Activities dropdown.
|
// Which variant keys appear in the Activities dropdown.
|
||||||
export const activityVariantKeys = ['cycling', 'running', 'dogwalk', 'sailing', 'wintersports', 'naturist', 'hiking', 'photography', 'fishing'];
|
export const activityVariantKeys = ['dogwalk', 'running', 'cycling', 'hiking', 'fishing', 'sailing', 'wintersports', 'photography', 'naturist'];
|
||||||
|
|
||||||
// Which variant keys appear in the Places dropdown.
|
// Which variant keys appear in the Places dropdown.
|
||||||
export const placeVariantKeys = ['urban', 'beach', 'events', 'park', 'airport', 'festival', 'construction'];
|
export const placeVariantKeys = ['urban', 'park', 'beach', 'events', 'festival', 'airport'];
|
||||||
|
|
||||||
|
// Which variant keys appear in the Work dropdown.
|
||||||
|
export const workVariantKeys = ['farming', 'construction', 'market', 'windowcleaning', 'office'];
|
||||||
|
|
||||||
// Tooltip text shown when the user clicks/hovers a table column header.
|
// Tooltip text shown when the user clicks/hovers a table column header.
|
||||||
export const COL_DESCRIPTIONS = {
|
export const COL_DESCRIPTIONS = {
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ import { useState, useEffect, useRef, useCallback } from '../../vendor/preact-ho
|
|||||||
import {
|
import {
|
||||||
FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS,
|
FREE_DAYS, FILTER_PROFILES, OUTDOORS_VARIANTS,
|
||||||
profileButtonOrder, variantIcons,
|
profileButtonOrder, variantIcons,
|
||||||
activityVariantKeys, placeVariantKeys,
|
activityVariantKeys, placeVariantKeys, workVariantKeys,
|
||||||
} from '../config.js';
|
} from '../config.js';
|
||||||
import { utciCategory } from '../utils.js';
|
import { utciCategory } from '../utils.js';
|
||||||
import { buildHourlyRows } from '../compute.js';
|
import { buildHourlyRows } from '../compute.js';
|
||||||
@@ -167,8 +167,20 @@ export function useAppState() {
|
|||||||
|
|
||||||
// ── 5. SKIN, VEHICLE, INDOOR, POLLEN ─────────────────────────────────
|
// ── 5. SKIN, VEHICLE, INDOOR, POLLEN ─────────────────────────────────
|
||||||
const [skinType, setSkinType] = useState('II');
|
const [skinType, setSkinType] = useState('II');
|
||||||
const [vehicleType, setVehicleType] = useState('car');
|
const [vehicleType, setVehicleType] = useState(() => {
|
||||||
const [vehicleVent, setVehicleVent] = useState(false);
|
try { return localStorage.getItem('sunscope_vehicle_type') || 'car'; } catch (e) { return 'car'; }
|
||||||
|
});
|
||||||
|
const setVehicleTypeAndSave = (v) => {
|
||||||
|
try { localStorage.setItem('sunscope_vehicle_type', v); } catch (e) { /* ignore */ }
|
||||||
|
setVehicleType(v);
|
||||||
|
};
|
||||||
|
const [vehicleVent, setVehicleVent] = useState(() => {
|
||||||
|
try { return localStorage.getItem('sunscope_vehicle_vent') === '1'; } catch (e) { return false; }
|
||||||
|
});
|
||||||
|
const setVehicleVentAndSave = (v) => {
|
||||||
|
try { localStorage.setItem('sunscope_vehicle_vent', v ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||||
|
setVehicleVent(v);
|
||||||
|
};
|
||||||
|
|
||||||
const [outdoorsVariant, setOutdoorsVariant] = useState(() => {
|
const [outdoorsVariant, setOutdoorsVariant] = useState(() => {
|
||||||
try { return localStorage.getItem('sunscope_outdoors_variant') || 'urban'; } catch (e) { return 'urban'; }
|
try { return localStorage.getItem('sunscope_outdoors_variant') || 'urban'; } catch (e) { return 'urban'; }
|
||||||
@@ -190,15 +202,40 @@ export function useAppState() {
|
|||||||
? (OUTDOORS_VARIANTS[outdoorsVariant]?.cols ?? FILTER_PROFILES.outdoors.cols)
|
? (OUTDOORS_VARIANTS[outdoorsVariant]?.cols ?? FILTER_PROFILES.outdoors.cols)
|
||||||
: (FILTER_PROFILES[activeProfile]?.cols ?? FILTER_PROFILES.basic.cols);
|
: (FILTER_PROFILES[activeProfile]?.cols ?? FILTER_PROFILES.basic.cols);
|
||||||
|
|
||||||
const [buildingType, setBuildingType] = useState('brick');
|
const [buildingType, setBuildingType] = useState(() => {
|
||||||
const [indoorManaged, setIndoorManaged] = useState(false);
|
try {
|
||||||
|
const saved = localStorage.getItem('sunscope_building_type');
|
||||||
|
if (saved) return saved;
|
||||||
|
// Fall back to office if the saved variant is office and no explicit building saved
|
||||||
|
const savedVariant = localStorage.getItem('sunscope_outdoors_variant') || '';
|
||||||
|
if (savedVariant === 'office') return 'office';
|
||||||
|
} catch (e) { /* ignore */ }
|
||||||
|
return 'brick';
|
||||||
|
});
|
||||||
|
const setBuildingTypeAndSave = (v) => {
|
||||||
|
try { localStorage.setItem('sunscope_building_type', v); } catch (e) { /* ignore */ }
|
||||||
|
setBuildingType(v);
|
||||||
|
};
|
||||||
|
const [indoorManaged, setIndoorManaged] = useState(() => {
|
||||||
|
try { return localStorage.getItem('sunscope_indoor_managed') === '1'; } catch (e) { return false; }
|
||||||
|
});
|
||||||
|
const setIndoorManagedAndSave = (v) => {
|
||||||
|
try { localStorage.setItem('sunscope_indoor_managed', v ? '1' : '0'); } catch (e) { /* ignore */ }
|
||||||
|
setIndoorManaged(v);
|
||||||
|
};
|
||||||
const [indoorMode, setIndoorMode] = useState(() => {
|
const [indoorMode, setIndoorMode] = useState(() => {
|
||||||
try {
|
try {
|
||||||
const saved = localStorage.getItem('sunscope_profile') || 'basic';
|
const saved = localStorage.getItem('sunscope_indoor_mode');
|
||||||
const cols = FILTER_PROFILES[saved]?.cols ?? FILTER_PROFILES.basic.cols;
|
if (saved) return saved;
|
||||||
|
const profile = localStorage.getItem('sunscope_profile') || 'basic';
|
||||||
|
const cols = FILTER_PROFILES[profile]?.cols ?? FILTER_PROFILES.basic.cols;
|
||||||
return (cols['indoorT'] || cols['managedT']) ? 'on' : 'off';
|
return (cols['indoorT'] || cols['managedT']) ? 'on' : 'off';
|
||||||
} catch (e) { return 'off'; }
|
} catch (e) { return 'off'; }
|
||||||
});
|
});
|
||||||
|
const setIndoorModeAndSave = (v) => {
|
||||||
|
try { localStorage.setItem('sunscope_indoor_mode', v); } catch (e) { /* ignore */ }
|
||||||
|
setIndoorMode(v);
|
||||||
|
};
|
||||||
|
|
||||||
const [pollenType, setPollenType] = useState(() => {
|
const [pollenType, setPollenType] = useState(() => {
|
||||||
try { return localStorage.getItem('sunscope_pollen_type') || 'all_pollen'; } catch (e) { return 'all_pollen'; }
|
try { return localStorage.getItem('sunscope_pollen_type') || 'all_pollen'; } catch (e) { return 'all_pollen'; }
|
||||||
@@ -233,19 +270,13 @@ export function useAppState() {
|
|||||||
const searchTimeout = useRef(null);
|
const searchTimeout = useRef(null);
|
||||||
|
|
||||||
// Derived selectors used by profile controls
|
// Derived selectors used by profile controls
|
||||||
const activityOptions = [
|
const activityOptions = activityVariantKeys.map((k) => {
|
||||||
{
|
|
||||||
value: 'farming',
|
|
||||||
label: `${FILTER_PROFILES.farming.icon} ${FILTER_PROFILES.farming.label}`,
|
|
||||||
},
|
|
||||||
...activityVariantKeys.map((k) => {
|
|
||||||
const v = OUTDOORS_VARIANTS[k];
|
const v = OUTDOORS_VARIANTS[k];
|
||||||
return {
|
return {
|
||||||
value: k,
|
value: k,
|
||||||
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||||
};
|
};
|
||||||
}),
|
});
|
||||||
];
|
|
||||||
const placeOptions = placeVariantKeys.map((k) => {
|
const placeOptions = placeVariantKeys.map((k) => {
|
||||||
const v = OUTDOORS_VARIANTS[k];
|
const v = OUTDOORS_VARIANTS[k];
|
||||||
return {
|
return {
|
||||||
@@ -253,10 +284,18 @@ export function useAppState() {
|
|||||||
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
label: `${variantIcons[k]} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
const workOptions = workVariantKeys.map((k) => {
|
||||||
|
const v = k === 'farming'
|
||||||
|
? { name: FILTER_PROFILES.farming.label, proOnly: false }
|
||||||
|
: OUTDOORS_VARIANTS[k];
|
||||||
|
const icon = k === 'farming' ? FILTER_PROFILES.farming.icon : variantIcons[k];
|
||||||
|
return {
|
||||||
|
value: k,
|
||||||
|
label: `${icon} ${v.name}${v.proOnly && !isPro ? ' 🔒' : ''}`,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
const activityValue = activeProfile === 'farming'
|
const activityValue = activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)
|
||||||
? 'farming'
|
|
||||||
: activeProfile === 'outdoors' && activityVariantKeys.includes(outdoorsVariant)
|
|
||||||
? outdoorsVariant
|
? outdoorsVariant
|
||||||
: 'off';
|
: 'off';
|
||||||
const activityLabel = activityOptions.find((o) => o.value === activityValue)?.label;
|
const activityLabel = activityOptions.find((o) => o.value === activityValue)?.label;
|
||||||
@@ -264,6 +303,12 @@ export function useAppState() {
|
|||||||
? outdoorsVariant
|
? outdoorsVariant
|
||||||
: 'off';
|
: 'off';
|
||||||
const placeLabel = placeOptions.find((o) => o.value === placeValue)?.label;
|
const placeLabel = placeOptions.find((o) => o.value === placeValue)?.label;
|
||||||
|
const workValue = activeProfile === 'farming'
|
||||||
|
? 'farming'
|
||||||
|
: activeProfile === 'outdoors' && workVariantKeys.includes(outdoorsVariant)
|
||||||
|
? outdoorsVariant
|
||||||
|
: 'off';
|
||||||
|
const workLabel = workOptions.find((o) => o.value === workValue)?.label;
|
||||||
|
|
||||||
// ── 6. TABLE REFS ────────────────────────────────────────────────────
|
// ── 6. TABLE REFS ────────────────────────────────────────────────────
|
||||||
const headStickyRef = useRef(null);
|
const headStickyRef = useRef(null);
|
||||||
@@ -439,17 +484,18 @@ export function useAppState() {
|
|||||||
activeProfile, setActiveProfile,
|
activeProfile, setActiveProfile,
|
||||||
activateProfile, activeCols,
|
activateProfile, activeCols,
|
||||||
visibleCols, setVisibleCols, toggleCol,
|
visibleCols, setVisibleCols, toggleCol,
|
||||||
activityOptions, placeOptions,
|
activityOptions, placeOptions, workOptions,
|
||||||
activityValue, activityLabel,
|
activityValue, activityLabel,
|
||||||
placeValue, placeLabel,
|
placeValue, placeLabel,
|
||||||
|
workValue, workLabel,
|
||||||
// skin, vehicle, indoor, pollen
|
// skin, vehicle, indoor, pollen
|
||||||
skinType, setSkinType,
|
skinType, setSkinType,
|
||||||
vehicleType, setVehicleType,
|
vehicleType, setVehicleType: setVehicleTypeAndSave,
|
||||||
vehicleVent, setVehicleVent,
|
vehicleVent, setVehicleVent: setVehicleVentAndSave,
|
||||||
outdoorsVariant, setOutdoorsVariantAndSave,
|
outdoorsVariant, setOutdoorsVariantAndSave,
|
||||||
buildingType, setBuildingType,
|
buildingType, setBuildingType: setBuildingTypeAndSave,
|
||||||
indoorManaged, setIndoorManaged,
|
indoorManaged, setIndoorManaged: setIndoorManagedAndSave,
|
||||||
indoorMode, setIndoorMode,
|
indoorMode, setIndoorMode: setIndoorModeAndSave,
|
||||||
pollenType, setPollenTypeAndSave,
|
pollenType, setPollenTypeAndSave,
|
||||||
showDecimals, toggleShowDecimals,
|
showDecimals, toggleShowDecimals,
|
||||||
showUnits, toggleShowUnits,
|
showUnits, toggleShowUnits,
|
||||||
|
|||||||
@@ -211,6 +211,7 @@ export const BUILDING_TYPES = {
|
|||||||
timber: { name: 'Timber Frame / New Build', uWall: 0.22, lagHours: 2, glazingRatio: 0.22, gValue: 0.35, orientFactor: 0.50, curtainBlock: 0.70, ventAlpha: 0.35, solarScale: 0.08, baseTemp: 17.0, internalGain: 0.8, retainedScale: 0.45 },
|
timber: { name: 'Timber Frame / New Build', uWall: 0.22, lagHours: 2, glazingRatio: 0.22, gValue: 0.35, orientFactor: 0.50, curtainBlock: 0.70, ventAlpha: 0.35, solarScale: 0.08, baseTemp: 17.0, internalGain: 0.8, retainedScale: 0.45 },
|
||||||
flat: { name: 'Top-floor Flat', uWall: 0.40, lagHours: 3, glazingRatio: 0.18, gValue: 0.63, orientFactor: 0.50, curtainBlock: 0.75, ventAlpha: 0.20, solarScale: 0.11, baseTemp: 17.0, internalGain: 0.9, retainedScale: 0.45 },
|
flat: { name: 'Top-floor Flat', uWall: 0.40, lagHours: 3, glazingRatio: 0.18, gValue: 0.63, orientFactor: 0.50, curtainBlock: 0.75, ventAlpha: 0.20, solarScale: 0.11, baseTemp: 17.0, internalGain: 0.9, retainedScale: 0.45 },
|
||||||
conservatory:{ name: 'Conservatory / Sun Room', uWall: 1.20, lagHours: 1, glazingRatio: 0.70, gValue: 0.72, orientFactor: 0.70, curtainBlock: 0.50, ventAlpha: 0.50, solarScale: 0.045, baseTemp: 12.0, internalGain: 0.2, retainedScale: 0.05 },
|
conservatory:{ name: 'Conservatory / Sun Room', uWall: 1.20, lagHours: 1, glazingRatio: 0.70, gValue: 0.72, orientFactor: 0.70, curtainBlock: 0.50, ventAlpha: 0.50, solarScale: 0.045, baseTemp: 12.0, internalGain: 0.2, retainedScale: 0.05 },
|
||||||
|
office: { name: 'Office Block', uWall: 0.25, lagHours: 2, glazingRatio: 0.45, gValue: 0.55, orientFactor: 0.65, curtainBlock: 0.50, ventAlpha: 0.15, solarScale: 0.055, baseTemp: 18.5, internalGain: 2.5, retainedScale: 0.30 },
|
||||||
};
|
};
|
||||||
|
|
||||||
// -------------------------------------------------------------------
|
// -------------------------------------------------------------------
|
||||||
|
|||||||
+1
-1
@@ -29,5 +29,5 @@
|
|||||||
|
|
||||||
@import "./css/base.css";
|
@import "./css/base.css";
|
||||||
@import "./css/header.css";
|
@import "./css/header.css";
|
||||||
@import "./css/table.css?v=2";
|
@import "./css/table.css";
|
||||||
@import "./css/ui.css";
|
@import "./css/ui.css";
|
||||||
|
|||||||
@@ -68,15 +68,7 @@
|
|||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin />
|
|
||||||
<link rel="preconnect" href="https://api.open-meteo.com" crossorigin />
|
<link rel="preconnect" href="https://api.open-meteo.com" crossorigin />
|
||||||
<link rel="preload" as="style"
|
|
||||||
href="https://fonts.bunny.net/css2?family=Fraunces:ital,wght@0,700;0,900;1,300;1,700&family=JetBrains+Mono:wght@400;600&family=Manrope:wght@400;500;600;700&display=swap"
|
|
||||||
onload="this.onload=null;this.rel='stylesheet'" />
|
|
||||||
<noscript>
|
|
||||||
<link rel="stylesheet"
|
|
||||||
href="https://fonts.bunny.net/css2?family=Fraunces:ital,wght@0,700;0,900;1,300;1,700&family=JetBrains+Mono:wght@400;600&family=Manrope:wght@400;500;600;700&display=swap" />
|
|
||||||
</noscript>
|
|
||||||
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" />
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'><text y='.9em' font-size='90'>☀️</text></svg>" />
|
||||||
<link rel="stylesheet" href="./assets/sunscope.css?v=15b">
|
<link rel="stylesheet" href="./assets/sunscope.css?v=15b">
|
||||||
</head>
|
</head>
|
||||||
|
|||||||
Generated
+32
@@ -7,6 +7,11 @@
|
|||||||
"": {
|
"": {
|
||||||
"name": "sunscope",
|
"name": "sunscope",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/fraunces": "^5.2.9",
|
||||||
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
|
"@fontsource/manrope": "^5.2.8"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.21.5"
|
"esbuild": "^0.21.5"
|
||||||
}
|
}
|
||||||
@@ -402,6 +407,33 @@
|
|||||||
"node": ">=12"
|
"node": ">=12"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@fontsource/fraunces": {
|
||||||
|
"version": "5.2.9",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource/fraunces/-/fraunces-5.2.9.tgz",
|
||||||
|
"integrity": "sha512-XDzuddBtoC7BZgZdBn6b7hsFZY2+V1hgN7yca5fBTKuHjb/lOd45a0Ji8dTUgFhPoL7RdGupo+bC2BFSt6UH8Q==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/jetbrains-mono": {
|
||||||
|
"version": "5.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource/jetbrains-mono/-/jetbrains-mono-5.2.8.tgz",
|
||||||
|
"integrity": "sha512-6w8/SG4kqvIMu7xd7wt6x3idn1Qux3p9N62s6G3rfldOUYHpWcc2FKrqf+Vo44jRvqWj2oAtTHrZXEP23oSKwQ==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@fontsource/manrope": {
|
||||||
|
"version": "5.2.8",
|
||||||
|
"resolved": "https://registry.npmjs.org/@fontsource/manrope/-/manrope-5.2.8.tgz",
|
||||||
|
"integrity": "sha512-gJHJmcuUk7qWcNCfcAri/DJQtXtBYqi9yKratr4jXhSo0I3xUtNNKI+igQIcw5c+m95g0vounk8ZnX/kb8o0TA==",
|
||||||
|
"license": "OFL-1.1",
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/sponsors/ayuhito"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/esbuild": {
|
"node_modules/esbuild": {
|
||||||
"version": "0.21.5",
|
"version": "0.21.5",
|
||||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
|
||||||
|
|||||||
@@ -9,5 +9,10 @@
|
|||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"esbuild": "^0.21.5"
|
"esbuild": "^0.21.5"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@fontsource/fraunces": "^5.2.9",
|
||||||
|
"@fontsource/jetbrains-mono": "^5.2.8",
|
||||||
|
"@fontsource/manrope": "^5.2.8"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user