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
+41 -22
View File
@@ -1,16 +1,18 @@
// ════════════════════════════════════════════════════════════════════════
// components.js — Preact UI components (SVG widgets).
//
// Exports:
// SkyScope({ elev, dt, size }) little porthole circle per hour
// WindVane({ bearing, size }) compass arrow
// CloudIcon({ category, size, elev, dt }) cloud/sun/moon icon
// ScopeReticle({ value, cat, loading, elev, dt }) big UTCI dial
// PrecipIcon({ precip, snow, size }) rain/snow icon for precip cell
// components.js — Preact UI components (SVG widgets and controls).
//
// All components use the html`` tagged template from htm + Preact.
// They depend on utils.js (skyFillForElev, grassFillForElev,
// moonPhaseFraction) but have no other external state.
// No external state; they depend only on utils.js and events.js.
//
// Exports (in order of appearance):
// CustomSelect({ value, options, onChange, ... }) styled dropdown pill
// VentPill({ checked, onChange, label, title }) checkbox fused to pill
// SkyScope({ elev, dt, glob, size }) small porthole per hour
// WindVane({ bearing, size }) compass-arrow widget
// CloudIcon({ category, size, elev, dt }) cloud/sun glyph
// ScopeReticle({ value, cat, loading, elev, dt, glob, activeEvent })
// large UTCI dial
// PrecipIcon({ precip, snow, size }) rain/snow icon
// ════════════════════════════════════════════════════════════════════════
import { h, Fragment } from '../vendor/preact.js';
@@ -91,8 +93,9 @@ export function CustomSelect({ value, options, onChange, hideLabel, hidingLabel,
>${hidingLabel || ('Hide ' + hideLabel)}</span>
<div class="cs-divider"></div>
`}
${options.map(opt => html`
<span
${options.map(opt => opt.divider
? html`<div key=${opt.value} class="cs-divider"></div>`
: html`<span
key=${opt.value}
class=${`cs-option${opt.value === value && isOn ? ' selected' : ''}${opt.disabled ? ' disabled' : ''}`}
role="option"
@@ -101,8 +104,8 @@ export function CustomSelect({ value, options, onChange, hideLabel, hidingLabel,
onChange(opt.value);
setOpen(false);
}}
>${opt.label}</span>
`)}
>${opt.label}</span>`
)}
</div>
`}
</span>`;
@@ -342,23 +345,39 @@ export function CloudIcon({ category, size = 30, elev = 90, dt = new Date() }) {
${line(size * 0.30, y0 + size * 0.18, size * 0.92, y0 + size * 0.18, ink, sw * 0.82, 0.72)}
</g>`;
// Sun shape — circle + 8 rays, centred at (cx, cy)
const sun = (cx, cy, cr) => {
const rays = Array.from({ length: 8 }, (_, k) => {
const angle = (k * Math.PI) / 4;
const r1 = cr * 1.55;
const r2 = cr * 2.1;
return html`<line key=${k}
x1=${cx + Math.cos(angle) * r1} y1=${cy + Math.sin(angle) * r1}
x2=${cx + Math.cos(angle) * r2} y2=${cy + Math.sin(angle) * r2}
stroke=${brass} stroke-width=${sw * 0.85} stroke-linecap="round" opacity="0.9" />`;
});
return html`<g>
${rays}
<circle cx=${cx} cy=${cy} r=${cr} fill="none" stroke=${brass} stroke-width=${sw} opacity="0.95" />
</g>`;
};
return html`
<svg width=${size} height=${size} viewBox=${`0 0 ${size} ${size}`}
style=${{ flexShrink: 0, display: 'inline-block', verticalAlign: 'middle', overflow: 'visible' }}>
${category === 'clear' && html`
${line(size * 0.26, size * 0.50, size * 0.74, size * 0.50, brass, sw, 0.9)}
${line(size * 0.38, size * 0.64, size * 0.62, size * 0.64, muted, sw * 0.75, 0.7)}`}
${sun(size * 0.50, size * 0.38, size * 0.16)}`}
${category === 'wispy' && html`
${mist(size * 0.38, brass)}
${line(size * 0.18, size * 0.78, size * 0.60, size * 0.78, muted, sw * 0.7, 0.6)}`}
${mist(size * 0.18, brass)}
${line(size * 0.18, size * 0.56, size * 0.60, size * 0.56, muted, sw * 0.7, 0.6)}`}
${category === 'scattered' && html`
<path d=${cloudPath(0, 0, 0.82)} fill="none" stroke=${ink}
<path d=${cloudPath(0, -size * 0.2425, 0.82)} fill="none" stroke=${ink}
stroke-width=${sw} stroke-linecap=${cap} stroke-linejoin=${join} />
${line(size * 0.20, size * 0.78, size * 0.48, size * 0.78, brass, sw * 0.75, 0.78)}`}
${line(size * 0.20, size * 0.58, size * 0.48, size * 0.58, brass, sw * 0.75, 0.78)}`}
${category === 'overcast' && html`
<path d=${cloudPath(-size * 0.02, -size * 0.10, 0.70)} fill="none" stroke=${muted}
<path d=${cloudPath(-size * 0.02, -size * 0.2275, 0.70)} fill="none" stroke=${muted}
stroke-width=${sw * 0.9} stroke-linecap=${cap} stroke-linejoin=${join} opacity="0.82" />
<path d=${cloudPath(0, size * 0.14, 0.88)} fill="none" stroke=${ink}
<path d=${cloudPath(0, -size * 0.25, 0.88)} fill="none" stroke=${ink}
stroke-width=${sw} stroke-linecap=${cap} stroke-linejoin=${join} />`}
</svg>`;
}