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.