PageSourceSearch

https://opg.gg/js/today-scene.js?v=6e32d32e

js opg.gg collected 2026-09-26 08:14:01 UTC 8,821 bytes, 140 lines download raw bytes

1/**
2 * today-scene.js — weather scene and paper dolls for the Today screen.
3 *
4 * The backdrop takes its colour and motion from the real WMO condition code,
5 * and each child stands in it wearing what today calls for: a coat when it is
6 * cold, a hood in rain, wellies, hi-vis in fog, a sun hat when the UV is up,
7 * plus whatever their own activity kit says.
8 *
9 * Ported from frontend/lab/10-ambient.html. The lab page had seven hand-drawn
10 * scenes chosen from a picker; this derives the scene from the forecast.
11 */
12(function () {
13    'use strict';
14
15    const NAMED = { wolf: '#38bdf8', archer: '#f97316', theo: '#a78bfa' };
16    const PALETTE = ['#38bdf8', '#f97316', '#a78bfa', '#f472b6', '#facc15', '#2dd4bf'];
17
18    /**
19     * WMO weather code to scene.
20     * https://open-meteo.com/en/docs — 0 clear, 45 fog, 51+ drizzle/rain,
21     * 71+ snow, 95+ thunderstorm.
22     */
23    function sceneFor(w) {
24        if (!w) return 'cloud';
25        const code = w.code;
26        if (code == null) return w.rain === 'expected' ? 'rain' : 'cloud';
27        if (code >= 95) return 'storm';
28        if (code >= 71 && code <= 77) return 'snow';
29        if (code === 85 || code === 86) return 'snow';
30        if (code >= 51) return 'rain';
31        if (code === 45 || code === 48) return 'fog';
32        // Clear or cloudy — but a stiff wind is its own scene.
33        if ((w.wind_kph || 0) >= 30) return 'wind';
34        return code <= 1 ? 'sun' : 'cloud';
35    }
36
37    /**
38     * What today calls for, as garment names.
39     *
40     * The lab's figure keys off names rather than flags, so this produces the
41     * same vocabulary: the household's own clothing rules first, then whatever
42     * the forecast implies on top.
43     */
44    function wearFor(w, scene, recommendations, activity) {
45        const wear = (recommendations || []).map(r => String(r.item_name || r.name || ''));
46        const add = n => { if (!wear.some(x => x.toLowerCase() === n.toLowerCase())) wear.push(n); };
47
48        if (scene === 'rain' || scene === 'storm') { add('Waterproof'); add('Wellies'); }
49        if (scene === 'snow') { add('Big coat'); add('Hat'); add('Gloves'); add('Boots'); }
50        if (scene === 'fog') add('Hi-vis');
51        if (scene === 'sun') { add('Sun hat'); add('Bottle'); }
52        if (w && w.feels_like != null && w.feels_like <= 12) add('Jumper');
53        // Their own kit: running gear means shorts on the figure.
54        if ((activity || []).includes('running')) add('Shorts');
55        return wear;
56    }
57
58    /**
59     * One child, drawn as the lab draws them.
60     *
61     * Ported near-verbatim from frontend/lab/10-ambient.html so the figure
62     * matches the mock-up rather than being a simplified stand-in: coat with
63     * flaps and buttons, hood, hair, eyes, and the accessories the day needs.
64     */
65    function doll(colour, wear) {
66        const has = n => wear.some(w => w.toLowerCase().includes(n));
67        const coat = has('coat') || has('jacket') || has('waterproof');
68        const big = has('big coat'), hood = has('waterproof');
69        const jumper = has('jumper') || has('sweater') || has('hoodie') || has('fleece');
70        const sunhat = has('sun hat'), hat = !sunhat && (has('hat') || has('beanie'));
71        const scarf = has('scarf');
72        const wellies = has('wellie'), boots = !wellies && has('boot');
73        const gloves = has('glove'), shorts = has('short');
74        const umbrella = has('umbrella') || has('brolly'), shades = has('sunglass');
75        const hivis = has('hi-vis') || has('hi vis') || has('vest'), bottle = has('bottle');
76        const sleeved = jumper || coat;
77
78        let s = `<svg class="doll" viewBox="0 0 120 170" style="--c:${colour}">`;
79        s += shorts
80            ? `<rect class="skin" x="40" y="96" width="16" height="42" rx="6"/><rect class="skin" x="64" y="96" width="16" height="42" rx="6"/><rect class="cloth" x="38" y="94" width="20" height="24" rx="5"/><rect class="cloth" x="62" y="94" width="20" height="24" rx="5"/>`
81            : `<rect class="cloth" x="40" y="94" width="16" height="46" rx="6"/><rect class="cloth" x="64" y="94" width="16" height="46" rx="6"/>`;
82        if (wellies) s += `<path class="welly" d="M40 118h16v34H36a6 6 0 0 1-6-6v-3a6 6 0 0 1 6-6h4z"/><path class="welly" d="M64 118h16v19h4a6 6 0 0 1 6 6v3a6 6 0 0 1-6 6H64z"/><path class="line" d="M40 122h16M64 122h16" opacity=".6"/>`;
83        else if (boots) s += `<path class="fill" d="M38 132h18v20H34a5 5 0 0 1-5-5v-4a6 6 0 0 1 6-6z"/><path class="fill" d="M64 132h18a6 6 0 0 1 6 6v4a5 5 0 0 1-5 5H64z"/>`;
84        else s += `<rect class="fill" x="36" y="140" width="22" height="10" rx="4"/><rect class="fill" x="62" y="140" width="22" height="10" rx="4"/>`;
85        s += `<rect class="${sleeved ? 'cloth' : 'skin'}" x="18" y="52" width="16" height="46" rx="8"/><rect class="${sleeved ? 'cloth' : 'skin'}" x="86" y="52" width="16" height="46" rx="8"/>`;
86        s += `<rect class="cloth" x="34" y="46" width="52" height="54" rx="10"/>`;
87        if (jumper) s += `<path class="line" d="M48 46q12 10 24 0"/>
87<path class="line" d="M34 88h52"/><path class="line" d="M34 94h52" opacity=".5"/>`;
88        if (coat) {
89            s += `<g class="flap"><path class="coat" d="M30 50L46 42L60 60L74 42L90 50L94 114L26 114z"/><path class="line" d="M60 60v54"/><circle class="dotf" cx="66" cy="76" r="1.8"/><circle class="dotf" cx="66" cy="90" r="1.8"/><path class="line" d="M46 42L52 62L60 60"/><path class="line" d="M74 42L68 62L60 60"/><path class="line" d="M28 70h6M86 70h6"/>`;
90            if (big) s += `<path class="line" d="M29 74h64M28 90h66M27 104h66" opacity=".55"/>`;
91            s += `</g>`;
92        }
93        if (hivis) s += `<path class="hiv" d="M40 48h12l8 8 8-8h12v52H40z"/><path class="hivs" d="M40 66h40M40 84h40"/>`;
94        if (scarf) s += `<path class="accent" d="M42 46q18 12 36 0v9q-18 12-36 0z"/><rect class="accent" x="70" y="52" width="10" height="28" rx="4"/>`;
95        if (gloves) s += `<circle class="accent" cx="26" cy="100" r="7"/><circle class="accent" cx="94" cy="100" r="7"/>`;
96        if (bottle) s += `<rect class="accent" x="96" y="88" width="9" height="22" rx="3"/><rect class="fill" x="97.5" y="83" width="6" height="6" rx="1.5"/>`;
97        if (hood) s += `<path class="coat" d="M38 30a22 22 0 0 1 44 0v20H38z"/>`;
98        s += `<circle class="skin" cx="60" cy="26" r="16"/>`;
99        if (!hat && !sunhat && !hood) s += `<g class="hair"><path class="line" d="M50 14q2-6 7-3M57 11q3-6 8-2M65 12q3-5 7-1"/></g>`;
100        if (hat) s += `<path class="accent" d="M44 22a16 16 0 0 1 32 0v4H44z"/><rect class="accent" x="42" y="24" width="36" height="6" rx="3"/><circle class="accent" cx="60" cy="6" r="3"/>`;
101        if (sunhat) s += `<path class="accent" d="M46 18a14 12 0 0 1 28 0v2H46z"/><ellipse class="accent" cx="60" cy="19" rx="27" ry="4.5"/><path class="line" d="M46 15h28" opacity=".6"/>`;
102        if (shades) s += `<rect class="fill" x="48" y="21" width="10" height="7" rx="2"/><rect class="fill" x="62" y="21" width="10" height="7" rx="2"/><path class="line" d="M58 24h4"/>`;
103        else s += `<circle class="dotf" cx="54" cy="25" r="1.7"/><circle class="dotf" cx="66" cy="25" r="1.7"/>`;
104        s += `<path class="line" d="M55 32q5 4 10 0"/>`;
105        if (umbrella) s += `<path class="line" d="M106 62v52"/><path class="accent" d="M80 62a26 18 0 0 1 52 0z"/><path class="line" d="M106 114q0 6-5 6"/>`;
106        return s + '</svg>';
107    }
108
109    function render(container, data, children) {
110        const w = data && data.weather;
111        const scene = sceneFor(w);
112        const screen = document.getElementById('screen-today');
113        if (screen) screen.dataset.wx = scene;
114
115        // The canvas backdrop lives behind the screen and only runs while the
116        // screen is showing, so it costs nothing on the other nine.
117        if (window.TodaySky && screen) {
118            TodaySky.mount(screen);
119            TodaySky.setScene(scene);
120            TodaySky.setRunning(screen.classList.contains('active'));
121        }
122
123        const byChild = {};
124        for (const a of (data && data.activity_clothes) || []) byChild[a.child_id] = a.items || [];
125
126        container.innerHTML = children.map((c, i) => {
127            const colour = NAMED[String(c.id).toLowerCase()] || PALETTE[i % PALETTE.length];
128            const activity = byChild[c.id] || [];
129            const wear = wearFor(w, scene, data && data.recommendations, activity);
130            const kitText = wear.length ? wear.join(' · ') : 'as you are';
131            return `<div class="wx-kid" style="--kid:${colour}">
132                        ${doll(colour, wear)}
133                        <div class="nm">${c.name}</div>
134                        <div class="kit">${kitText}</div>
135                    </div>`;
136        }).join('');
137    }
138
139    window.TodayScene = { render, sceneFor, wearFor };
140})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.