Improved scope
Improved profiles
This commit is contained in:
fraxle
2026-05-15 09:40:42 +01:00
parent f024fd0c2d
commit 5b0e62d1b5
6 changed files with 372 additions and 108 deletions
+77 -35
View File
@@ -9,6 +9,7 @@
// 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
// cloudCategory(total,low,mid,high) → 'clear'|'wispy'|'scattered'|'overcast'
// confidenceBand(i) day-tab gradient + label
// moonPhaseFraction(date) 0..1 synodic phase
@@ -122,6 +123,23 @@ export function burnLabel(mins) {
return `${Math.round(mins)}m`;
}
// ═══════════════════════════════════════════════════════════════════
// VEHICLE TYPES — presets for the cabin heat model.
// ───────────────────────────────────────────────────────────────────
// Each entry tweaks the three physical levers in calcVehicleInteriorTemp:
// albedo — how much solar the bodywork reflects (0 = black, 1 = mirror)
// glazingArea — relative side-window glass area (1.0 = typical car)
// hCabinLoss — how easily heat escapes the cabin (W/m²K); lower = hotter
// Motorhomes have thicker insulation → lower loss
// ═══════════════════════════════════════════════════════════════════
export const VEHICLE_TYPES = {
car: { name: 'Car / Hatchback', albedo: 0.25, glazingArea: 1.0, hCabinLoss: 2.0 },
mpv: { name: 'MPV / People Carrier', albedo: 0.25, glazingArea: 1.3, hCabinLoss: 2.0 },
suv: { name: 'SUV / 4×4', albedo: 0.22, glazingArea: 1.1, hCabinLoss: 1.9 },
motorhome: { name: 'Motorhome / Campervan', albedo: 0.35, glazingArea: 0.6, hCabinLoss: 1.2 },
caravan: { name: 'Caravan (towed)', albedo: 0.40, glazingArea: 0.4, hCabinLoss: 1.0 },
};
// ═══════════════════════════════════════════════════════════════════
// CLOUD CATEGORY — pick one of 4 icon styles from low/mid/high split.
// ───────────────────────────────────────────────────────────────────
@@ -220,49 +238,73 @@ export function moonGlyph(p) {
// Sunset palette → pinks, purples, mauves at the horizon.
// Both share the same deep blue zenith at high elevations.
// ═══════════════════════════════════════════════════════════════════
export function skyGradientForElev(e, isRising) {
// Full daytime — blue sky, paler toward horizon
if (e > 30) return { top: '#5bb8e8', bot: '#9fd3ef' };
if (e > 10) return { top: '#7cc5ec', bot: '#c8e3ee' };
// Sky colour keyframes — [elevation, topHex, botHex] for rising and setting sun.
// Colours are smoothly interpolated between adjacent keyframes.
const SKY_KEYS_RISING = [
[-18, '#0e0c28', '#1a1538'], // deep night
[-14, '#1e1a50', '#3a2f60'], // nautical twilight
[ -8, '#2a3a6a', '#a04828'], // pre-dawn: indigo → burnt sienna
[ -3, '#4a7aaa', '#e8622a'], // horizon band: deep blue → vivid red-orange
[ 3, '#6aaddb', '#ffb347'], // golden hour: blue → warm amber
[ 10, '#7cc5ec', '#c8e3ee'], // low sun: pale blue sky
[ 30, '#5bb8e8', '#9fd3ef'], // mid-day blue
[ 60, '#2e8fd4', '#7cc8ef'], // high noon: rich deep blue
[ 90, '#1a7abf', '#60b8e8'], // zenith (sun directly overhead)
];
const SKY_KEYS_SETTING = [
[-18, '#0e0c28', '#1a1538'],
[-14, '#1e1a50', '#3a2f60'],
[ -8, '#5a3572', '#c07080'], // dusk: purple → dusty rose/mauve
[ -3, '#7a5090', '#e8826a'], // horizon band: violet → coral/pink
[ 3, '#7b8fc4', '#ffb877'], // golden hour: blue-violet → golden
[ 10, '#7cc5ec', '#c8e3ee'],
[ 30, '#5bb8e8', '#9fd3ef'],
[ 60, '#2e8fd4', '#7cc8ef'],
[ 90, '#1a7abf', '#60b8e8'],
];
// Low sun — golden hour / near-horizon
if (e > 3) {
return isRising
? { top: '#6aaddb', bot: '#ffb347' } // sunrise: blue → warm amber/orange
: { top: '#7b8fc4', bot: '#ffb877' }; // sunset: blue-violet → golden
}
// Sun at/just below horizon — the dramatic band
if (e > -3) {
return isRising
? { top: '#4a7aaa', bot: '#e8622a' } // sunrise: deep blue → vivid red-orange
: { top: '#7a5090', bot: '#e8826a' }; // sunset: violet → coral/pink
}
// Civil twilight
if (e > -8) {
return isRising
? { top: '#2a3a6a', bot: '#a04828' } // pre-dawn: indigo → burnt sienna
: { top: '#5a3572', bot: '#c07080' }; // dusk: purple → dusty rose/mauve
}
// Nautical twilight
if (e > -14) return { top: '#1e1a50', bot: '#3a2f60' };
// Night
return { top: '#0e0c28', bot: '#1a1538' };
function hexToRgb(hex) {
const n = parseInt(hex.slice(1), 16);
return [(n >> 16) & 255, (n >> 8) & 255, n & 255];
}
function rgbToHex([r, g, b]) {
return '#' + [r, g, b].map(v => Math.round(v).toString(16).padStart(2, '0')).join('');
}
function lerpRgb(a, b, t) {
return a.map((v, i) => v + t * (b[i] - v));
}
// Convenience flat colour — returns the bot (horizon) colour for contexts
// that still need a single fill (e.g. moon shadow fill).
function interpolateSkyKeys(keys, e) {
// Clamp to key range
if (e <= keys[0][0]) return { top: keys[0][1], bot: keys[0][2] };
if (e >= keys[keys.length - 1][0]) {
const k = keys[keys.length - 1];
return { top: k[1], bot: k[2] };
}
// Find bracketing keyframes
let lo = keys[0], hi = keys[1];
for (let i = 1; i < keys.length; i++) {
if (keys[i][0] >= e) { lo = keys[i - 1]; hi = keys[i]; break; }
}
const t = (e - lo[0]) / (hi[0] - lo[0]);
const top = rgbToHex(lerpRgb(hexToRgb(lo[1]), hexToRgb(hi[1]), t));
const bot = rgbToHex(lerpRgb(hexToRgb(lo[2]), hexToRgb(hi[2]), t));
return { top, bot };
}
export function skyGradientForElev(e, isRising) {
return interpolateSkyKeys(isRising ? SKY_KEYS_RISING : SKY_KEYS_SETTING, e);
}
// Convenience flat colour for contexts needing a single fill.
export function skyFillForElev(e, isRising = false) {
return skyGradientForElev(e, isRising).bot;
}
// Grass palette — mirrors skyFillForElev but for the ground.
// `top` = grass blade tips (catches sky light), `bot` = soil shadow.
// Grass palette — mirrors sky but for the ground strip.
export function grassFillForElev(e) {
if (e > 30) return { top: '#86c44a', bot: '#558a2e' }; // bright noon grass
if (e > 60) return { top: '#92cc50', bot: '#5e9630' }; // blazing noon
if (e > 30) return { top: '#86c44a', bot: '#558a2e' }; // bright midday
if (e > 10) return { top: '#7ab846', bot: '#4d802c' }; // mid-day
if (e > 3) return { top: '#b8a83e', bot: '#7a6a26' }; // golden-hour glow
if (e > -3) return { top: '#c08048', bot: '#7a4a2a' }; // sunrise/sunset embers