1.5.7
Profile memory Comment cleanup
This commit is contained in:
+83
-83
@@ -1,7 +1,7 @@
|
||||
// ════════════════════════════════════════════════════════════════════════
|
||||
// components.js — Preact UI components (SVG widgets and controls).
|
||||
// ------------------------------------------------------------------------
|
||||
// components.js - Preact UI components (SVG widgets and controls).
|
||||
//
|
||||
// All components use the html`` tagged template from htm + Preact.
|
||||
// All components use the html'' tagged template from htm + Preact.
|
||||
// No external state; they depend only on utils.js and events.js.
|
||||
//
|
||||
// Exports (in order of appearance):
|
||||
@@ -13,7 +13,7 @@
|
||||
// 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';
|
||||
import { useState, useEffect, useRef } from '../vendor/preact-hooks.js';
|
||||
@@ -26,19 +26,19 @@ import { getLensOverlaySVG } from './events.js';
|
||||
|
||||
const html = htm.bind(h);
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CUSTOMSELECT — cross-platform styled dropdown pill.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// CUSTOMSELECT - cross-platform styled dropdown pill.
|
||||
// -------------------------------------------------------------------
|
||||
// props:
|
||||
// value — current value string
|
||||
// options — array of { value, label, disabled }
|
||||
// onChange — fn(newValue)
|
||||
// hideLabel — label shown when value === 'off' (e.g. 'Burn')
|
||||
// hidingLabel — label shown for the 'off' option when active (e.g. 'Hide Burn')
|
||||
// isOn — bool — whether the pill shows as active (brass)
|
||||
// groupedLeft — bool — fuse right side with a VentPill
|
||||
// isLastChild — bool — restore right border-radius when no sibling follows
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// value - current value string
|
||||
// options - array of { value, label, disabled }
|
||||
// onChange - fn(newValue)
|
||||
// hideLabel - label shown when value === 'off' (e.g. 'Burn')
|
||||
// hidingLabel - label shown for the 'off' option when active (e.g. 'Hide Burn')
|
||||
// isOn - bool - whether the pill shows as active (brass)
|
||||
// groupedLeft - bool - fuse right side with a VentPill
|
||||
// isLastChild - bool - restore right border-radius when no sibling follows
|
||||
// -------------------------------------------------------------------
|
||||
export function CustomSelect({ value, options, onChange, hideLabel, hidingLabel, buttonLabel, isOn, noHide, groupedLeft, isLastChild }) {
|
||||
const [open, setOpen] = useState(false);
|
||||
const wrapRef = useRef(null);
|
||||
@@ -111,15 +111,15 @@ export function CustomSelect({ value, options, onChange, hideLabel, hidingLabel,
|
||||
</span>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// VENTPILL — the checkbox pill fused to the right of a CustomSelect.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// VENTPILL - the checkbox pill fused to the right of a CustomSelect.
|
||||
// -------------------------------------------------------------------
|
||||
// props:
|
||||
// checked — bool
|
||||
// onChange — fn()
|
||||
// label — string
|
||||
// title — tooltip string
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// checked - bool
|
||||
// onChange - fn()
|
||||
// label - string
|
||||
// title - tooltip string
|
||||
// -------------------------------------------------------------------
|
||||
export function VentPill({ checked, onChange, label, title }) {
|
||||
return html`
|
||||
<label class=${`cs-vent${checked ? ' on' : ''}`} title=${title}>
|
||||
@@ -134,23 +134,23 @@ export function VentPill({ checked, onChange, label, title }) {
|
||||
</label>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SKYSCOPE — the little porthole circle next to each hour.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// SKYSCOPE - the little porthole circle next to each hour.
|
||||
// -------------------------------------------------------------------
|
||||
// What gets drawn (back to front):
|
||||
// 1. Sky disk — colour from skyFillForElev() above
|
||||
// 2. Horizon line — faint dashed line at the middle
|
||||
// 3. Sun OR moon — sun positioned vertically by elevation;
|
||||
// 1. Sky disk - colour from skyFillForElev() above
|
||||
// 2. Horizon line - faint dashed line at the middle
|
||||
// 3. Sun OR moon - sun positioned vertically by elevation;
|
||||
// moon shows real phase (synodic period)
|
||||
// 4. Brass ring — the telescope/scope edge (#c8922a)
|
||||
// 5. Lens highlight — subtle inner glint
|
||||
// 4. Brass ring - the telescope/scope edge (#c8922a)
|
||||
// 5. Lens highlight - subtle inner glint
|
||||
//
|
||||
// Tweakable bits inside this function:
|
||||
// • size — pass a different size= when calling for bigger/smaller
|
||||
// • innerR — how thick the brass ring looks
|
||||
// • sunR — the sun's drawn radius
|
||||
// • the stroke colour #c8922a is the brass — change for a different metal
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// - size - pass a different size= when calling for bigger/smaller
|
||||
// - innerR - how thick the brass ring looks
|
||||
// - sunR - the sun's drawn radius
|
||||
// - the stroke colour #c8922a is the brass - change for a different metal
|
||||
// -------------------------------------------------------------------
|
||||
export function SkyScope({ elev, dt, glob = 0, size = 26 }) {
|
||||
const r = size / 2;
|
||||
const innerR = r - 1.2;
|
||||
@@ -165,14 +165,14 @@ export function SkyScope({ elev, dt, glob = 0, size = 26 }) {
|
||||
const isTwil = elev > -8 && !isDay; // moon shown in twilight too
|
||||
const isNight = elev <= -8;
|
||||
|
||||
// Sun position — clamped so it never goes below the horizon line (y = r)
|
||||
// Sun position - clamped so it never goes below the horizon line (y = r)
|
||||
const elevClamped = Math.max(0, Math.min(90, elev));
|
||||
const sunY = r - Math.sin((elevClamped * Math.PI) / 180) * (innerR - 2.5);
|
||||
const sunR = Math.max(2.2, innerR * 0.32);
|
||||
|
||||
// Radial glow: strength driven by glob (W/m²). Uses a radialGradient
|
||||
// Radial glow: strength driven by glob (W/m-). Uses a radialGradient
|
||||
// so it's intense at the sun centre and fades smoothly to transparent.
|
||||
const radFrac = Math.min(1, (glob || 0) / 900); // 0 (dark) → 1 (blazing)
|
||||
const radFrac = Math.min(1, (glob || 0) / 900); // 0 (dark) - 1 (blazing)
|
||||
const horizFrac = Math.max(0, 1 - elev / 20); // extra warmth near horizon
|
||||
const glowR = sunR + 4 + radFrac * 6; // total glow radius
|
||||
const glowPeak = 0.35 + radFrac * 0.45 + horizFrac * 0.15; // centre opacity
|
||||
@@ -253,20 +253,20 @@ export function SkyScope({ elev, dt, glob = 0, size = 26 }) {
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// WINDVANE — clean compass arrow on transparent background.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// WINDVANE - clean compass arrow on transparent background.
|
||||
// -------------------------------------------------------------------
|
||||
// Traditional weather-vane behaviour: the ARROWHEAD points INTO the
|
||||
// wind (toward the source). Wind from the north → head points north.
|
||||
// wind (toward the source). Wind from the north - head points north.
|
||||
//
|
||||
// props:
|
||||
// bearing — degrees, 0 = wind FROM north
|
||||
// size — pixel diameter (default 30 to match SkyScope)
|
||||
// bearing - degrees, 0 = wind FROM north
|
||||
// size - pixel diameter (default 30 to match SkyScope)
|
||||
//
|
||||
// Tweakable bits:
|
||||
// • arrowColor / nMarkerColor — line/fill colours
|
||||
// • Tiny N letter sits just above the arrow tail for orientation
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// - arrowColor / nMarkerColor - line/fill colours
|
||||
// - Tiny N letter sits just above the arrow tail for orientation
|
||||
// -------------------------------------------------------------------
|
||||
export function WindVane({ bearing, size = 30 }) {
|
||||
if (bearing == null || isNaN(bearing)) {
|
||||
return html`<span style=${{ opacity: 0.5, fontSize: '11px' }}>—</span>`;
|
||||
@@ -280,7 +280,7 @@ export function WindVane({ bearing, size = 30 }) {
|
||||
const nColor = '#9a7d5a';
|
||||
// Geometry of the single-ended arrow (drawn pointing DOWN by default).
|
||||
// After rotation by (bearing + 180), the head lands on the bearing
|
||||
// direction — i.e. the side the wind is coming FROM.
|
||||
// direction - i.e. the side the wind is coming FROM.
|
||||
const tipY = r * 0.15; // arrowhead tip
|
||||
const headBase = r * 0.58; // bottom of the triangle head
|
||||
const headW = r * 0.42;
|
||||
@@ -311,14 +311,14 @@ export function WindVane({ bearing, size = 30 }) {
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// CLOUDICON — Brass Line cloud / haze / overcast glyphs.
|
||||
// -------------------------------------------------------------------
|
||||
// CLOUDICON - Brass Line cloud / haze / overcast glyphs.
|
||||
// No sun or moon here; the time-cell SkyScope owns day/night imagery.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// category: 'clear' | 'wispy' | 'scattered' | 'overcast'
|
||||
// elev: solar elevation in degrees (negative = night)
|
||||
// dt: Date used for moon phase
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
export function CloudIcon({ category, size = 30, elev = 90, dt = new Date() }) {
|
||||
const r = size / 2;
|
||||
const brass = '#c8922a';
|
||||
@@ -345,7 +345,7 @@ 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)
|
||||
// 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;
|
||||
@@ -382,23 +382,23 @@ export function CloudIcon({ category, size = 30, elev = 90, dt = new Date() }) {
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// SCOPERETICLE — the big circular UTCI dial in the page header.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// -------------------------------------------------------------------
|
||||
// SCOPERETICLE - the big circular UTCI dial in the page header.
|
||||
// -------------------------------------------------------------------
|
||||
// This is the one with tick marks, the swept colour band, and the
|
||||
// needle pointing at the current felt-temperature.
|
||||
//
|
||||
// Tweakable bits:
|
||||
// • R = 86 outer ring radius (changes overall size)
|
||||
// • cx, cy = 100 centre point (leave alone unless you also
|
||||
// - R = 86 outer ring radius (changes overall size)
|
||||
// - cx, cy = 100 centre point (leave alone unless you also
|
||||
// change the viewBox="0 0 200 200" below)
|
||||
// • { length: 36 } number of tick marks (1 every 10°)
|
||||
// • stressBands[] the colour ramp around the rim (matches UTCI)
|
||||
// • (value + 10) / 60 maps UTCI -10..50 onto the 270° sweep —
|
||||
// - { length: 36 } number of tick marks (1 every 10-)
|
||||
// - stressBands[] the colour ramp around the rim (matches UTCI)
|
||||
// - (value + 10) / 60 maps UTCI -10..50 onto the 270- sweep -
|
||||
// widen the dial range by changing those numbers
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// -------------------------------------------------------------------
|
||||
export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), glob = 0, activeEvent = null }) {
|
||||
// ── Day/night scene behind the readout ─────────────────────────────
|
||||
// -- Day/night scene behind the readout -----------------------------
|
||||
// The whole back of the lens is a SkyScope-style scene: sky on top,
|
||||
// grass on the bottom, sun positioned by elevation (or moon at night
|
||||
// with its phase shadow). Mirrors the little hourly SkyScopes.
|
||||
@@ -415,7 +415,7 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
|
||||
const elevClamped = Math.max(0, Math.min(90, elev)); // clamp to above horizon
|
||||
const sunY = cy - Math.sin((elevClamped * Math.PI) / 180) * (lensR - 18);
|
||||
const sunDrawR = 14;
|
||||
// Radial glow for the big dial — sun is always at least faintly glowing
|
||||
// Radial glow for the big dial - sun is always at least faintly glowing
|
||||
// when above the horizon; scales up with radiation strength.
|
||||
const radFrac = Math.min(1, (glob || 0) / 900);
|
||||
const horizFrac = Math.max(0, 1 - elev / 20);
|
||||
@@ -628,22 +628,22 @@ export function ScopeReticle({ value, cat, loading, elev = 0, dt = new Date(), g
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// PRECIPICON — rain drop or snowflake SVG for the precip cell.
|
||||
// ───────────────────────────────────────────────────────────────────
|
||||
// precip — mm/h rainfall
|
||||
// snow — cm/h snowfall
|
||||
// size — pixel size (default 28 to match CloudIcon)
|
||||
// -------------------------------------------------------------------
|
||||
// PRECIPICON - rain drop or snowflake SVG for the precip cell.
|
||||
// -------------------------------------------------------------------
|
||||
// precip - mm/h rainfall
|
||||
// snow - cm/h snowfall
|
||||
// size - pixel size (default 28 to match CloudIcon)
|
||||
//
|
||||
// Intensity bands:
|
||||
// dry → faint dash
|
||||
// light rain (< 1 mm/h) → 1 drop
|
||||
// moderate rain(< 4 mm/h) → 2 drops
|
||||
// heavy rain (≥ 4 mm/h) → 3 drops
|
||||
// light snow (< 0.5cm/h) → 1 flake
|
||||
// heavy snow (≥ 0.5cm/h) → 2 flakes
|
||||
// mixed → 1 drop + 1 flake
|
||||
// ═══════════════════════════════════════════════════════════════════
|
||||
// dry - faint dash
|
||||
// light rain (< 1 mm/h) - 1 drop
|
||||
// moderate rain(< 4 mm/h) - 2 drops
|
||||
// heavy rain (- 4 mm/h) - 3 drops
|
||||
// light snow (< 0.5cm/h) - 1 flake
|
||||
// heavy snow (- 0.5cm/h) - 2 flakes
|
||||
// mixed - 1 drop + 1 flake
|
||||
// -------------------------------------------------------------------
|
||||
export function PrecipIcon({ precip = 0, snow = 0, size = 28 }) {
|
||||
const r = size / 2;
|
||||
const hasRain = precip > 0;
|
||||
@@ -673,7 +673,7 @@ export function PrecipIcon({ precip = 0, snow = 0, size = 28 }) {
|
||||
<line x1=${cx + fr * 0.68} y1=${cy - fr * 0.68} x2=${cx - fr * 0.68} y2=${cy + fr * 0.68} />
|
||||
</g>`;
|
||||
|
||||
// Dry — just a faint dash
|
||||
// Dry - just a faint dash
|
||||
if (!hasRain && !hasSnow) {
|
||||
return html`
|
||||
<svg width=${size} height=${size} viewBox=${`0 0 ${size} ${size}`}
|
||||
@@ -683,9 +683,9 @@ export function PrecipIcon({ precip = 0, snow = 0, size = 28 }) {
|
||||
</svg>`;
|
||||
}
|
||||
|
||||
// Rain intensity → number of drops
|
||||
// Rain intensity - number of drops
|
||||
const rainDrops = !hasRain ? 0 : precip < 1 ? 1 : precip < 4 ? 2 : 3;
|
||||
// Snow intensity → number of flakes
|
||||
// Snow intensity - number of flakes
|
||||
const snowFlakes = !hasSnow ? 0 : snow < 0.5 ? 1 : 2;
|
||||
const fr = size * 0.08; // flake arm length
|
||||
|
||||
|
||||
Reference in New Issue
Block a user