Updated event banner to fit top screen Updated table thermal colours to max hot and min cold for 2h, 3h & 4h Ground Lab preview
318 lines
42 KiB
JavaScript
318 lines
42 KiB
JavaScript
// ------------------------------------------------------------------------
|
||
// config.js - Pure-data configuration constants for the UTCIForecast app.
|
||
//
|
||
// Moved out of app.js so the main component is easier to read and edit.
|
||
// Nothing here has state or side effects - just constants and lookup
|
||
// tables imported by app.js (and anywhere else that needs them).
|
||
//
|
||
// Where to find things:
|
||
// FREE_DAYS .................. how many days the free tier shows
|
||
// FILTER_PROFILES ............ preset column sets (Basic, Home, etc.)
|
||
// OUTDOORS_VARIANTS .......... Places & Activities sub-variants
|
||
// POLLEN_TYPES ............... pollen-column pulldown options
|
||
// UTCI_ENVIRONMENTS .......... environment modifiers for UTCI+P column
|
||
// variantIcons ............... emoji glyph per place/activity variant
|
||
// profileButtonOrder ......... left-to-right order of the profile buttons
|
||
// activityVariantKeys ........ which variants appear in the Activities menu
|
||
// placeVariantKeys ........... which variants appear in the Places menu
|
||
// COL_DESCRIPTIONS ........... tooltip text for each table column header
|
||
// ------------------------------------------------------------------------
|
||
|
||
// How many days the free tier shows. Days beyond this get a -.
|
||
// Bump this number if you want to give free users more access.
|
||
export const FREE_DAYS = 14;
|
||
|
||
// Filter profile presets - each preset defines which columns are visible
|
||
// when that profile is selected.
|
||
export const FILTER_PROFILES = {
|
||
basic: {
|
||
label: 'Basic',
|
||
icon: '🌡️',
|
||
scene: "url('assets/images/profiles/basic.png') center / cover no-repeat, linear-gradient(160deg,#b8d8e8,#daeef8)",
|
||
// Basic is the "just tell me" preset - it opens in Quick view (see `view`
|
||
// below) with the smallest useful column set behind it.
|
||
view: 'simple',
|
||
cols: { hour: true, air: false, shadeT: false, 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: false, lightning: false, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
home: {
|
||
label: 'Home',
|
||
icon: '🏠',
|
||
scene: "url('assets/images/profiles/home.png') center / cover no-repeat, linear-gradient(160deg,#b0d890,#d0ecb8)",
|
||
cols: { hour: true, air: true, shadeT: false, rh: true, dew: true, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: true, managedT: true, vis: false, aqi: true, pollen: true, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
vehicle: {
|
||
label: 'Vehicle',
|
||
icon: '🚗',
|
||
scene: "url('assets/images/profiles/vehicle.png') center / cover no-repeat, linear-gradient(160deg,#a8bcc8,#ccd8e4)",
|
||
cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: false, 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, lightning: true, soilT: false, soilT6: false, soilM: true, concreteT: false, vehicleT: true, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
pets: {
|
||
label: 'Pets',
|
||
icon: '🐾',
|
||
scene: "url('assets/images/profiles/pets.png') center / cover no-repeat, linear-gradient(160deg,#e0c0a0,#f0ddc8)",
|
||
cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: true, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true, furSurfaceT: true, pawT: true, petShadeT: true, petHomeT: true },
|
||
},
|
||
farming: {
|
||
label: 'Farming',
|
||
icon: '🌾',
|
||
scene: "url('assets/images/profiles/farming.png') center / cover no-repeat, linear-gradient(160deg,#d8bc90,#ece0b8)",
|
||
// Farming sits in the Work menu alongside the outdoors variants, so it
|
||
// opens the detailed table the same way they do.
|
||
view: 'table',
|
||
cols: { hour: true, air: true, shadeT: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, lightning: true, soilT: true, soilT6: true, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
outdoors: {
|
||
label: 'Places',
|
||
icon: '🌤️',
|
||
scene: "url('assets/images/profiles/places.png') center / cover no-repeat, linear-gradient(160deg,#98ccc0,#c4e4dc)",
|
||
view: 'table',
|
||
// Default cols match the first variant (urban). Switching variant updates visibleCols.
|
||
cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
alltemps: {
|
||
label: 'Temps',
|
||
icon: '🌡️',
|
||
scene: "url('assets/images/profiles/temps.png') center / cover no-repeat, linear-gradient(160deg,#d8b0b0,#ecd0c8)",
|
||
proOnly: true,
|
||
cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: false, dir: false, cloud: false, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: true, precip: false, precipProb: false, lightning: false, soilT: true, soilT6: false, soilM: false, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: false, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
showall: {
|
||
label: 'Show All',
|
||
icon: '⚙️',
|
||
scene: "url('assets/images/profiles/all.png') center / cover no-repeat, linear-gradient(160deg,#b4b4d4,#d4d4ec)",
|
||
proOnly: true,
|
||
cols: { hour: true, air: true, shadeT: true, rh: true, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: true, diffuse: true, tmrt: true, delta: true, utci: true, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: true, soilT6: true, soilM: true, concreteT: true, vehicleT: true, indoorT: true, managedT: true, vis: true, aqi: true, pollen: true, furSurfaceT: true, pawT: true, petShadeT: true, petHomeT: true },
|
||
},
|
||
custom: {
|
||
label: 'Custom',
|
||
icon: '✏️',
|
||
scene: "url('assets/images/profiles/custom.png') center / cover no-repeat, linear-gradient(160deg,#d8b0cc,#ecd0e4)",
|
||
proOnly: true,
|
||
// Blank slate - only Hour is on. User enables whichever columns they want.
|
||
cols: { hour: true, air: false, shadeT: false, rh: false, dew: false, wind: false, dir: false, cloud: false, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: false, utciP: false, precip: false, precipProb: false, lightning: false, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false },
|
||
},
|
||
};
|
||
|
||
// Places sub-variants - each has its own column set.
|
||
// Selecting a variant applies its cols to visibleCols.
|
||
export const OUTDOORS_VARIANTS = {
|
||
urban: { name: 'Urban', scene: "url('assets/images/profiles/urban.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
beach: { name: 'Beach', scene: "url('assets/images/profiles/beach.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
events: { name: 'Events', scene: "url('assets/images/profiles/events.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: true, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
||
festival: { name: 'Festival', scene: "url('assets/images/profiles/festival.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: true, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
||
wintersports: { name: 'Winter Sports', proOnly: true, scene: "url('assets/images/profiles/winter.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: true, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
naturist: { name: 'Naturist', proOnly: true, scene: "url('assets/images/profiles/naturist.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: true, wind: true, dir: false, 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: true, pollen: true } },
|
||
sailing: { name: 'Sailing', proOnly: true, scene: "url('assets/images/profiles/sailing.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: false, dew: true, wind: true, dir: true, cloud: true, sun: true, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
// Places - free
|
||
park: { name: 'Park / Picnic', scene: "url('assets/images/profiles/park.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
||
forest: { name: 'Forest / Woodland', scene: "url('assets/images/profiles/forest.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: false, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: true } },
|
||
airport: { name: 'Airport / Travel', scene: "url('assets/images/profiles/airport.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, 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: false, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
// Places - Pro
|
||
construction: { name: 'Construction', proOnly: true, scene: "url('assets/images/profiles/construction.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: true, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: false, uvB: false, burn: false, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: true, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
// Activities - free
|
||
cycling: { name: 'Cycling', scene: "url('assets/images/profiles/cycling.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, 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: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
running: { name: 'Running', scene: "url('assets/images/profiles/running.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: true, dew: false, wind: true, dir: false, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: true, uvA: false, uvB: false, burn: true, utciP: true, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
||
dogwalk: { name: 'Dog Walking', scene: "url('assets/images/profiles/walking.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: false, 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: false, aqi: false, pollen: true } },
|
||
// Activities - Pro
|
||
hiking: { name: 'Hiking', proOnly: true, scene: "url('assets/images/profiles/hiking.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
photography: { name: 'Photography', proOnly: true, scene: "url('assets/images/profiles/photography.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
fishing: { name: 'Fishing', proOnly: true, scene: "url('assets/images/profiles/fishing.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
// Work variants
|
||
market: { name: 'Market Trading', proOnly: true, scene: "url('assets/images/profiles/market.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
windowcleaning: { name: 'Window Cleaning', proOnly: true, scene: "url('assets/images/profiles/window.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
office: { name: 'Office', proOnly: true, scene: "url('assets/images/profiles/office.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, 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, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: false, indoorT: false, managedT: true, vis: false, aqi: true, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
driver: { name: 'Driver / Trucker', proOnly: true, scene: "url('assets/images/profiles/vehicle.png') center / cover no-repeat, linear-gradient(160deg,#c2d4cc,#e2ece6)", cols: { hour: true, air: true, shadeT: false, rh: false, dew: false, wind: true, dir: true, cloud: true, sun: false, direct: false, diffuse: false, tmrt: false, delta: false, utci: false, uvA: true, uvB: false, burn: false, utciP: false, precip: true, precipProb: true, lightning: true, soilT: false, soilT6: false, soilM: false, concreteT: false, vehicleT: true, indoorT: false, managedT: false, vis: true, aqi: false, pollen: false, furSurfaceT: false, pawT: false, petShadeT: false, petHomeT: false } },
|
||
};
|
||
|
||
// Pollen type options shown in the pulldown selector.
|
||
export const POLLEN_TYPES = {
|
||
all_pollen: { name: 'All pollen', unit: 'grains/m³' },
|
||
grass_pollen: { name: 'Grass pollen', unit: 'grains/m³' },
|
||
birch_pollen: { name: 'Birch pollen', unit: 'grains/m³' },
|
||
alder_pollen: { name: 'Alder pollen', unit: 'grains/m³' },
|
||
mugwort_pollen: { name: 'Mugwort pollen', unit: 'grains/m³' },
|
||
olive_pollen: { name: 'Olive pollen', unit: 'grains/m³' },
|
||
ragweed_pollen: { name: 'Ragweed pollen', unit: 'grains/m³' },
|
||
};
|
||
|
||
// UTCI+P environment modifiers - affect solar inputs and air temp used
|
||
// in the UTCI calculation to reflect shaded or modified environments.
|
||
// dirFactor - fraction of direct radiation that reaches the person (0-1)
|
||
// difFactor - fraction of diffuse radiation that reaches the person (0-1)
|
||
// globFactor - fraction of global shortwave radiation that reaches the person (0-1)
|
||
// taOffset - offset applied to air temperature in degrees C (evapotranspiration etc.)
|
||
// label - short display name
|
||
// note - shown in column header tooltip to flag modified calculation
|
||
// shade - per-environment microclimate inputs for the Shade air-temp
|
||
// column (see calcShadeAirTemp in physics.js). The env air-temp
|
||
// shift is NOT here - it comes from taOffset via the shared
|
||
// TaEnv baseline. This block only carries local sun-trap effects:
|
||
// shelter 0-1 wind shelter (high = enclosed, calm pocket)
|
||
// sun 0-~1.2 how sun-baked the surroundings are
|
||
// type short label of the typical shade for the tooltip
|
||
export const UTCI_ENVIRONMENTS = {
|
||
open: { shortLabel: null, label: 'Grass / Open Field', dirFactor: 1.00, difFactor: 1.00, globFactor: 1.00, taOffset: 0.0, note: null, shade: { shelter: 0.45, sun: 0.50, type: 'hedge / scattered trees' } },
|
||
urban: { shortLabel: 'City', label: 'Town / City', dirFactor: 0.70, difFactor: 0.75, globFactor: 0.82, taOffset: 2.5, note: 'Urban heat island estimate - UHI raises air temp ~2–3 °C, building canyons reduce direct and diffuse solar load, reflections from concrete and glass partially offset sky-view reduction', shade: { shelter: 0.75, sun: 0.85, type: 'building shadow' } },
|
||
beach: { shortLabel: 'Beach', label: 'Beach / Lakeside', dirFactor: 1.12, difFactor: 1.06, globFactor: 1.10, taOffset: 0.0, note: 'Reflected radiation from water and sand surfaces increases effective solar load - no shade, higher UV dose', shade: { shelter: 0.25, sun: 0.95, type: 'beach umbrella' } },
|
||
river: { shortLabel: 'Riverbank', label: 'River / Riverbank', dirFactor: 0.50, difFactor: 0.60, globFactor: 0.55, taOffset: -2.5, note: 'Shaded riverbank estimate - canopy and valley walls reduce direct sun, evaporative cooling from water surface lowers apparent air temp', shade: { shelter: 0.55, sun: 0.40, type: 'trees & bushes' } },
|
||
forest: { shortLabel: 'Forest', label: 'Forest / Woodland', dirFactor: 0.15, difFactor: 0.40, globFactor: 0.20, taOffset: -2.0, note: 'Canopy-shaded estimate - direct radiation reduced 85%, air temp adjusted for evapotranspiration cooling', shade: { shelter: 0.80, sun: 0.20, type: 'tree canopy' } },
|
||
openwater: { shortLabel: 'Open Water', label: 'Open Water', dirFactor: 1.14, difFactor: 1.08, globFactor: 1.12, taOffset: 0.0, note: 'Open water - full solar exposure with additional radiation reflected from the water surface, no shade or shelter, no ground heat contribution', shade: { shelter: 0.10, sun: 0.25, type: 'boat panels' } },
|
||
alpine: { shortLabel: 'Alpine', label: 'High Altitude / Alpine', dirFactor: 1.15, difFactor: 1.10, globFactor: 1.12, taOffset: -1.0, note: 'Alpine estimate assuming approx. 2000 m elevation - radiation enhanced ~15% through thinner atmosphere, adjusted for exposed ridge conditions', shade: { shelter: 0.35, sun: 0.40, type: 'trees & rocks' } },
|
||
desert: { shortLabel: 'Desert', label: 'Desert / Arid', dirFactor: 1.22, difFactor: 1.10, globFactor: 1.18, taOffset: 3.5, note: 'Extreme radiation load from high albedo sand and bare ground - ground-level heat significantly raises effective felt temperature', shade: { shelter: 0.40, sun: 1.00, type: 'cactus / rockface' } },
|
||
};
|
||
|
||
// Maps OUTDOORS_VARIANTS keys to a default UTCI_ENVIRONMENTS key.
|
||
// When a variant is selected via the Places or Activities dropdown the
|
||
// UTCI+P environment modifier is automatically switched to match.
|
||
// Variants not listed here default to open.
|
||
export const VARIANT_DEFAULT_ENV = {
|
||
forest: 'forest',
|
||
park: 'open',
|
||
beach: 'beach',
|
||
sailing: 'openwater',
|
||
hiking: 'alpine',
|
||
wintersports:'alpine',
|
||
fishing: 'river',
|
||
naturist: 'beach',
|
||
urban: 'urban',
|
||
farming: 'open',
|
||
construction:'urban',
|
||
office: 'urban',
|
||
windowcleaning:'urban',
|
||
driver: 'urban',
|
||
};
|
||
|
||
// Left-to-right order of the profile buttons in the top filter bar.
|
||
export const profileButtonOrder = ['basic', 'home', 'vehicle', 'pets', 'alltemps', 'showall', 'custom'];
|
||
|
||
// Derives which config selector / table metric is the "main" thermal
|
||
// selection for the active profile — the one whose calc drives the day-tab
|
||
// hi/lo readout. Shared by DayTabs (day-tab numbers + summary chip),
|
||
// ConfigPanel (the gold "main" badge) and app.js (the edge-tab label).
|
||
export function deriveProfileMain(activeProfile, outdoorsVariant) {
|
||
const isHomeOrOffice = activeProfile === 'home' || (activeProfile === 'outdoors' && outdoorsVariant === 'office');
|
||
const isVehicleProfile = activeProfile === 'vehicle' || (activeProfile === 'outdoors' && outdoorsVariant === 'driver');
|
||
const isPetsProfile = activeProfile === 'pets';
|
||
const mainConfigKey = isHomeOrOffice ? 'indoor' : isVehicleProfile ? 'vehicle' : isPetsProfile ? 'fur' : 'solar';
|
||
const mainField = isHomeOrOffice ? 'indoorT' : isVehicleProfile ? 'vehicleT' : isPetsProfile ? 'furSurfaceT' : 'utciAdj';
|
||
const secondaryField = isHomeOrOffice ? 'managedT' : isPetsProfile ? 'shadeT' : isVehicleProfile ? null : 'shadeT';
|
||
const mainLabel = activeProfile === 'outdoors'
|
||
? (OUTDOORS_VARIANTS[outdoorsVariant]?.name || FILTER_PROFILES.outdoors.label)
|
||
: (FILTER_PROFILES[activeProfile]?.label || 'SunSoak');
|
||
|
||
// Human names for the two numbers stacked in each day tab, so the label
|
||
// column beside the strip can say what they are. Pulled from
|
||
// COL_DESCRIPTIONS so they never drift from the table column tooltips —
|
||
// every field matches its column key except utciAdj, whose column is utciP.
|
||
const colKey = mainField === 'utciAdj' ? 'utciP' : mainField;
|
||
const mainMetric = COL_DESCRIPTIONS[colKey] || {};
|
||
// Vehicle/Driver has no secondary column — the small number is a live
|
||
// re-run of the cabin model with ventilation forced on (see DayTabs).
|
||
const secondaryMetric = isVehicleProfile
|
||
? { title: 'Windows Open', desc: 'The same vehicle interior estimate, recalculated with the windows open — how much cooler the cabin would run if it were ventilated.' }
|
||
: (secondaryField ? (COL_DESCRIPTIONS[secondaryField] || {}) : null);
|
||
|
||
return {
|
||
isHomeOrOffice, isVehicleProfile, isPetsProfile,
|
||
mainConfigKey, mainField, secondaryField, mainLabel,
|
||
mainMetricLabel: mainMetric.title || '',
|
||
mainMetricDesc: mainMetric.desc || '',
|
||
secondaryMetricLabel: secondaryMetric?.title || '',
|
||
secondaryMetricDesc: secondaryMetric?.desc || '',
|
||
};
|
||
}
|
||
|
||
// Emoji glyph per place/activity variant.
|
||
export const variantIcons = {
|
||
urban: '🏙️',
|
||
beach: '🏖️',
|
||
events: '🎪',
|
||
park: '🌳',
|
||
forest: '🌲',
|
||
airport: '✈️',
|
||
festival: '⛺',
|
||
construction: '🏗️',
|
||
cycling: '🚴',
|
||
running: '🏃',
|
||
dogwalk: '🐾',
|
||
sailing: '⛵',
|
||
wintersports: '🎿',
|
||
naturist: '☀️',
|
||
hiking: '🏔️',
|
||
photography: '📸',
|
||
fishing: '🎣',
|
||
market: '🏪',
|
||
windowcleaning: '🪟',
|
||
office: '🏢',
|
||
driver: '🚚',
|
||
};
|
||
|
||
// Which variant keys appear in the Activities dropdown.
|
||
export const activityVariantKeys = ['dogwalk', 'running', 'cycling', 'hiking', 'fishing', 'sailing', 'wintersports', 'photography', 'naturist'];
|
||
|
||
// Which variant keys appear in the Places dropdown.
|
||
export const placeVariantKeys = ['urban', 'park', 'forest', 'beach', 'events', 'festival', 'airport'];
|
||
|
||
// Which variant keys appear in the Work dropdown.
|
||
export const workVariantKeys = ['farming', 'construction', 'market', 'windowcleaning', 'driver', 'office'];
|
||
|
||
// Tooltip text shown when the user clicks/hovers a table column header.
|
||
export const COL_DESCRIPTIONS = {
|
||
hour: { title: 'Rows', desc: 'Local wall-clock time for this forecast row. Each row covers the interval set by the stepper in this header, clock-aligned from midnight — at 3h, a row labelled 09:00 spans 09:00–11:59. Totals like rainfall are summed across the block, risk figures show the block\'s peak, and smooth readings like air temperature are averaged.' },
|
||
air: { title: 'Air Temperature', desc: 'Measured air temperature at 2 m above the ground. This is the standard thermometer reading — it does not account for sun, wind, or humidity.' },
|
||
shadeT: { title: 'Shade', desc: 'Estimated air temperature sitting in the typical shade of the selected Solar Model — building shadow, beach umbrella, tree canopy, boat panels, and so on. Sheltered, sun-warmed surroundings push it above the official open-ground Air figure on calm sunny hours; wind and cloud pull it back toward it. This is an estimate, not a measurement, and differs from the standardised 2 m Air reading.' },
|
||
rh: { title: 'Relative Humidity', desc: 'How much moisture the air holds relative to its maximum capacity at that temperature. High RH makes warm days feel stickier and cold days feel rawer.' },
|
||
dew: { title: 'Dew Point', desc: 'The temperature at which air becomes saturated and moisture begins to condense. A useful measure of absolute humidity — above 16 °C it starts to feel muggy; above 21 °C it feels oppressive.' },
|
||
wind: { title: 'Wind Speed', desc: 'Mean wind speed at 10 m in mph, with peak gust in brackets where significantly higher. Gust colour indicates Beaufort scale severity: amber = Near Gale (32+ mph), orange = Gale (39+ mph), red = Severe Gale or above (55+ mph). Banners above the table classify conditions from Strong Breeze through to Violent Storm using standard wind categories. Wind dramatically increases heat loss from exposed skin — the basis of wind chill.' },
|
||
dir: { title: 'Wind Direction', desc: 'The compass direction the wind is blowing from, shown as an arrow and abbreviated label (e.g. SW = south-westerly).' },
|
||
cloud: { title: 'Cloud Cover', desc: 'Total cloud cover as a percentage of the sky. High cloud blocks solar radiation and reduces both daytime heating and overnight cooling.' },
|
||
sun: { title: 'Sun Elevation', desc: 'The angle of the sun above the horizon in degrees. Below 0° the sun has set. The higher the elevation, the more intense the solar radiation reaching the ground.' },
|
||
direct: { title: 'Direct Radiation', desc: 'Shortwave solar radiation arriving in a direct beam from the sun (W/m²). The primary driver of skin heating on sunny days.' },
|
||
diffuse: { title: 'Diffuse Radiation', desc: 'Scattered solar radiation arriving from all directions across the sky (W/m²). Present even under cloud; contributes to overall solar load.' },
|
||
tmrt: { title: 'Mean Radiant Temp', desc: 'The combined heat from direct sun, scattered sky light, and ground reflection expressed as a temperature. Typically well above air temperature even on overcast days, and is one of the inputs used to calculate UTCI.' },
|
||
delta: { title: 'UTCI − Air Delta', desc: 'The difference between the UTCI felt temperature and the plain air temperature. A large positive value means solar radiation is adding significant heat stress beyond what the thermometer shows.' },
|
||
utci: { title: 'UTCI', desc: 'Universal Thermal Climate Index — the raw felt temperature combining air temp, humidity, wind, and solar radiation. Does not include precipitation effects.' },
|
||
uvA: { title: 'UV-A Index', desc: 'Estimated UV-A radiation index. UV-A penetrates deeper into the skin and contributes to long-term ageing and some skin cancers, even through glass.' },
|
||
uvB: { title: 'UV-B Index', desc: 'Estimated UV-B radiation index. UV-B causes sunburn and is the main driver of vitamin D production. Intensity depends strongly on solar elevation and cloud cover.' },
|
||
burn: { title: 'Burn Time', desc: 'Estimated time to reach one Minimal Erythemal Dose (MED) — the threshold for sunburn — based on the UV index and your selected skin type. This is a guide, not a medical measurement.' },
|
||
utciP: { title: 'SunSoak', desc: 'SunScope\'s felt-temperature index. Based on UTCI — the standard combining air temp, humidity, wind, and solar radiation — with added adjustments for rain/snow and your chosen surroundings.' },
|
||
precip: { title: 'Precipitation', desc: 'Expected rainfall or snowfall in mm per hour. Snow is shown in cm. Even light drizzle meaningfully reduces felt temperature when combined with wind.' },
|
||
precipProb: { title: 'Rain Probability', desc: 'Probability of precipitation for this hour as a percentage (0-100%). Derived from Open-Meteo ensemble model runs. A high value means rain is likely even if the expected rate is low - useful for spotting showers that the deterministic forecast may miss.' },
|
||
lightning: { title: 'Lightning Potential', desc: 'Lightning Potential Index (LPI) from Open-Meteo in J/kg. A convective energy index — values above 5 suggest moderate lightning risk, above 25 suggest high risk.' },
|
||
soilT: { title: 'Soil Temperature', desc: 'Temperature of the soil at the surface (0 cm depth). Useful for planting decisions — most seeds germinate above 7–10 °C.' },
|
||
soilT6: { title: 'Soil Temp 6 cm', desc: 'Temperature of the soil at 6 cm depth, the root zone for many crops and seedlings. Lags behind surface temperature by several hours.' },
|
||
soilM: { title: 'Soil Moisture', desc: 'Water saturation of the top 1 cm of soil as a percentage. Above 40% suggests saturated ground; below 20% indicates dry conditions.' },
|
||
concreteT: { title: 'Concrete Surface', desc: 'Estimated temperature of sun-exposed urban concrete or paving. Concrete absorbs more solar energy than grass and cannot cool itself through evaporation — surface temps can run 15–25 °C above air temperature on sunny days.' },
|
||
vehicleT: { title: 'Interior', desc: 'Estimated ambient cabin temperature inside a parked vehicle. Cars heat rapidly through thin body panels and glass; motorhomes and caravans are modelled as insulated occupied living spaces that warm more slowly but hold heat longer, with gain through the windscreen and rooflights and a little retained living-space warmth. Accounts for ambient wind over the bodywork, road speed, and whether the windows are open. Dangerous for children and pets above 35 °C; potentially fatal above 45 °C.' },
|
||
indoorT: { title: 'Indoors', desc: 'Estimated ambient temperature inside a selected building type with windows closed and no air conditioning. Accounts for retained warmth, window solar gain, internal gains, and thermal lag without repeatedly accumulating solar heat hour after hour.' },
|
||
managedT: { title: 'Managed Indoors', desc: 'Estimated indoor temperature with curtains closed and windows opened when outdoor air is cooler than inside — the standard UK heatwave advice. Curtains block most direct solar gain; smart ventilation pulls the temperature down during cooler periods.' },
|
||
vis: { title: 'Visibility', desc: 'Horizontal visibility in kilometres, sourced from the CAMS air quality model. Values below 1 km indicate fog or very thick haze; below 10 km suggests mist, smoke, or significant pollution. Relevant for driving, flying, and photography.' },
|
||
aqi: { title: 'Air Quality Index', desc: 'European Air Quality Index (0–100+), combining PM2.5, PM10, ozone, nitrogen dioxide, and sulphur dioxide. 0–20 = Good; 20–40 = Fair; 40–60 = Moderate; 60–80 = Poor; 80–100 = Very Poor; 100+ = Extremely Poor. Sourced from Copernicus CAMS.' },
|
||
pollen: { title: 'Pollen', desc: 'Pollen concentration in grains per cubic metre for the selected pollen type, sourced from the Copernicus CAMS pollen forecast. Low = <10; Moderate = 10–50; High = 50–200; Very High = 200+. Values vary by species and season.' },
|
||
furSurfaceT: { title: 'Fur Temp', desc: 'Estimated fur surface temperature a cat or small dog experiences in direct sun, calibrated for a thin/short coat rather than a heavily-insulated breed. Runs hotter than air temperature on sunny, still days — set fur colour with the selector alongside this column.' },
|
||
pawT: { title: 'Paw Temp', desc: 'Estimated ground/pavement surface temperature for paw contact, colour-coded by burn risk: green below 40 °C (safe), amber 40–52 °C (discomfort / possible burn on prolonged contact), red above 52 °C (can burn within 60 seconds). If you can\'t hold the back of your hand on the surface for 7 seconds, it\'s too hot to walk your pet.' },
|
||
petShadeT: { title: 'Pet Shade', desc: 'Shaded air temperature for a resting pet, using the same shade estimate as the Shade column but with a lower, pet-specific alert threshold — small pets have far less capacity to dump heat than humans.' },
|
||
petHomeT: { title: 'Pet Home', desc: 'Indoor temperature estimate for a pet left home alone, using the same model as the Indoors column but with a lower, pet-specific alert threshold (26 °C) rather than the human heatwave guidance threshold.' },
|
||
};
|
||
|
||
// Seasonal crop calendar for the Farming "At a glance" sow/harvest advice.
|
||
// Months are 1-12 on a UK / Northern-temperate basis (auto-flipped +6 for the
|
||
// southern hemisphere in computeCropAdvice). `minSoilT` is the surface-soil
|
||
// germination floor in °C. `dry: true` marks crops that should only be
|
||
// harvested in a dry spell (grains, rape and onions need to be brought in /
|
||
// cured dry); `dry: false` crops can be lifted whenever they are in window.
|
||
export const CROP_CALENDAR = [
|
||
{ key: 'wheat', label: 'Wheat', icon: '🌾', sow: { months: [9, 10], minSoilT: 8 }, harvest: { months: [8, 9], dry: true } },
|
||
{ key: 'barley', label: 'Barley', icon: '🌾', sow: { months: [2, 3, 9, 10], minSoilT: 6 }, harvest: { months: [7, 8], dry: true } },
|
||
{ key: 'oats', label: 'Oats', icon: '🌾', sow: { months: [2, 3, 4], minSoilT: 5 }, harvest: { months: [8, 9], dry: true } },
|
||
{ key: 'osr', label: 'Oilseed rape', icon: '🌻', sow: { months: [8, 9], minSoilT: 10 }, harvest: { months: [7, 8], dry: true } },
|
||
{ key: 'fieldbean', label: 'Field beans', icon: '🫘', sow: { months: [2, 3, 10, 11], minSoilT: 3 }, harvest: { months: [8, 9], dry: true } },
|
||
{ key: 'potato', label: 'Potatoes', icon: '🥔', sow: { months: [3, 4, 5], minSoilT: 7 }, harvest: { months: [6, 7, 8, 9], dry: false } },
|
||
{ key: 'carrot', label: 'Carrots', icon: '🥕', sow: { months: [3, 4, 5, 6, 7], minSoilT: 7 }, harvest: { months: [6, 7, 8, 9, 10], dry: false } },
|
||
{ key: 'tomato', label: 'Tomatoes', icon: '🍅', sow: { months: [3, 4], minSoilT: 14 }, harvest: { months: [7, 8, 9], dry: false } },
|
||
{ key: 'onion', label: 'Onions', icon: '🧅', sow: { months: [3, 4], minSoilT: 7 }, harvest: { months: [7, 8], dry: true } },
|
||
{ key: 'pea', label: 'Peas', icon: '🟢', sow: { months: [3, 4, 5, 6], minSoilT: 8 }, harvest: { months: [6, 7, 8], dry: false } },
|
||
{ key: 'lettuce', label: 'Lettuce', icon: '🥬', sow: { months: [3, 4, 5, 6, 7, 8], minSoilT: 5 }, harvest: { months: [5, 6, 7, 8, 9], dry: false } },
|
||
{ key: 'beetroot', label: 'Beetroot', icon: '🟣', sow: { months: [4, 5, 6, 7], minSoilT: 7 }, harvest: { months: [7, 8, 9, 10], dry: false } },
|
||
];
|