feat: compacter instellingenmenu voor acht widgets (v0.4.80-beta)
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
Bij veel widgets nam de instellingenstrook te veel hoogte in, vooral bij een smal widgetvenster waar de rij naar meerdere regels uitwaaierde. Nieuw gedeeld patroon: besturing die voortdurend nodig is (kleur, niveau, woordbron) blijft in één compacte rij zichtbaar; besturing die maar zelden wordt aangepast (thema, geluid aan/uit, invoerwijze, bereik) gaat achter een inklapbaar ⚙-paneel. Dit patroon bestond al voor klok/geld/rekengetallen/ wereld (learningShell() in learning.js) - nu als widgetGear()/gearField() in core.js beschikbaar voor de rest van de app, met dezelfde .edu-settings- opmaak zodat het overal hetzelfde oogt en werkt. - kleurplaten: twee volle rijen (thema/plaat + kwast/emmer/gum/kleur/dikte/ hulp/reset/klaar) teruggebracht naar één vaste rij (gereedschap, kleur, dikte, klaar) plus het ingeklapte paneel (thema, plaat, kleurhulp, reset). Losstaand: coloring.css gebruikte @media(max-width:620px) in plaats van de @container widget-conventie die de rest van de app gebruikt, waardoor die regels nooit afgingen bij het verkleinen van het widgetvenster zelf - nu gecorrigeerd. - letterblokken: geluid, invoerwijze en aantal kaarten in het paneel; niveau en woordbron blijven vast. - rekenwidgets (optellen/aftrekken/keer/delen/procenten): geluid en invoerwijze in het paneel; niveau blijft vast. - ballonnenwoord: geluid in het paneel. - flitswoorden: flitstijd in het paneel. - getallenlijn: het hele bereik-instelblok (van/tot/stap/nieuw) in het paneel - dat stond toch al uit voor leerlingen met een beheerd niveau. - zinsbouwer: het rijtje voor eigen woorden toevoegen in het paneel; de speelknoppen (verrassing/ongedaan/wissen/voorlezen) blijven vast. - timer: geluid en weergavevorm in het paneel; starttijden en start/reset blijven vast. - geluidsmeter: de twee drempelschuifjes plus de privacynotitie in het paneel. Widgets die dit patroon al hadden (klok, geld, breuken, plaatswaarde, woordsoorten, bomen/vogels/planten, topografie, stemming, stemming-poll, dagplanning) of waar de rij al compact genoeg was (schrijfblad, hakken en plakken, dobbelstenen, notities, naamtrekker) zijn ongewijzigd gelaten. Geverifieerd door elke aangepaste widget live via de echte makeWidget()/ REGISTRY in een headless browser te openen, het paneel open en dicht te klappen en te controleren op consolefouten - ook op de minimale widgetbreedte (coloring, minW 390). 202 tests groen. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_011YErxhic5w9K57zmt8Q1uS
This commit is contained in:
parent
f4a4cbd28c
commit
d8610b010d
12 changed files with 93 additions and 47 deletions
2
VERSION
2
VERSION
|
|
@ -1 +1 @@
|
|||
0.4.79-beta
|
||||
0.4.80-beta
|
||||
|
|
|
|||
|
|
@ -6,11 +6,15 @@
|
|||
bestand zet nooit een `fill` op een kleurvak - dat zou van de vulling van de
|
||||
verfemmer winnen en de verf onzichtbaar maken. */
|
||||
.coloring-app{--coloring-ink:#1c293d;--coloring-detail:#2d3e56;height:100%;min-height:0;display:flex;flex-direction:column;background:linear-gradient(180deg,color-mix(in srgb,var(--orange) 4%,var(--surface)),var(--surface));color:var(--ink)}
|
||||
.coloring-choices,.coloring-tools{display:flex;align-items:center;gap:8px;flex-wrap:wrap;padding:9px 11px;border-bottom:1px solid color-mix(in srgb,var(--line) 72%,var(--orange));background:linear-gradient(100deg,color-mix(in srgb,var(--orange) 10%,var(--surface)),color-mix(in srgb,var(--purple) 5%,var(--surface)))}
|
||||
/* de veelgebruikte rij (kwast/emmer/gum, kleur, dikte) blijft altijd
|
||||
zichtbaar; thema, plaat, kleurhulp en reset - die je zelden aanpast -
|
||||
zitten achter het ⚙-tandwiel (.edu-settings, zie teach.css) zodat ze niet
|
||||
permanent ruimte innemen naast wat je continu gebruikt */
|
||||
.coloring-tools{display:flex;align-items:center;gap:7px;flex-wrap:wrap;padding:9px 11px;border-bottom:1px solid color-mix(in srgb,var(--line) 72%,var(--orange));background:color-mix(in srgb,var(--surface) 96%,var(--orange))}
|
||||
.coloring-choices{gap:8px}
|
||||
.coloring-choices label{display:grid;grid-template-columns:auto minmax(120px,1fr);align-items:center;gap:6px;color:var(--muted);font-size:11px;font-weight:850}
|
||||
.coloring-choices select{height:36px;max-width:230px;border:1px solid color-mix(in srgb,var(--line) 72%,var(--orange));border-radius:11px;padding:0 30px 0 10px;background:var(--surface);color:var(--ink);box-shadow:0 2px 8px rgba(30,50,90,.07);font:inherit;font-weight:750}
|
||||
.coloring-fit{margin-left:auto;padding:6px 10px;border:1px solid color-mix(in srgb,var(--line) 75%,var(--orange));border-radius:999px;background:color-mix(in srgb,var(--surface) 92%,var(--orange));color:var(--muted);box-shadow:0 2px 8px rgba(30,50,90,.06);font-size:11px;font-weight:800;white-space:nowrap}
|
||||
.coloring-tools{gap:7px;background:color-mix(in srgb,var(--surface) 96%,var(--orange))}
|
||||
.coloring-fit{padding:6px 10px;border:1px solid color-mix(in srgb,var(--line) 75%,var(--orange));border-radius:999px;background:color-mix(in srgb,var(--surface) 92%,var(--orange));color:var(--muted);box-shadow:0 2px 8px rgba(30,50,90,.06);font-size:11px;font-weight:800;white-space:nowrap}
|
||||
.coloring-tool-buttons,.coloring-palette,.coloring-sizes{display:flex;align-items:center;gap:4px;padding:4px;border:1px solid color-mix(in srgb,var(--line) 78%,transparent);border-radius:13px;background:linear-gradient(180deg,color-mix(in srgb,var(--surface-2) 82%,#fff),var(--surface-2));box-shadow:inset 0 1px 0 rgba(255,255,255,.72),0 2px 7px rgba(30,50,90,.06)}
|
||||
.coloring-tools button{width:36px;height:36px;display:grid;place-items:center;border:0;border-radius:10px;background:transparent;color:var(--ink);font:inherit;font-size:18px;cursor:pointer;transition:transform .14s ease,background .14s ease,box-shadow .14s ease}
|
||||
.coloring-tool-buttons button{width:auto;min-width:72px;grid-auto-flow:column;grid-template-columns:auto auto;gap:5px;padding:0 9px}
|
||||
|
|
@ -24,10 +28,10 @@
|
|||
.coloring-size.on{outline:2px solid var(--orange);outline-offset:-2px}
|
||||
.coloring-custom{display:flex;align-items:center;gap:5px;color:var(--muted);font-size:10px;font-weight:800}
|
||||
.coloring-custom input{width:36px;height:36px;padding:3px;border:1px solid var(--line);border-radius:10px;background:var(--surface);box-shadow:0 2px 7px rgba(30,50,90,.08);cursor:pointer}
|
||||
.coloring-tools .coloring-guide-btn{margin-left:auto;background:color-mix(in srgb,var(--teal) 14%,var(--surface));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--teal) 20%,transparent)}
|
||||
.coloring-tools .coloring-guide-btn.on{outline:2px solid var(--teal);outline-offset:-2px}
|
||||
.coloring-tools .coloring-reset{background:var(--red-soft);color:var(--red);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--red) 18%,transparent)}
|
||||
.coloring-tools .coloring-done{width:auto;min-width:112px;padding:0 14px;color:#fff;box-shadow:0 4px 10px color-mix(in srgb,var(--orange) 24%,transparent);font-size:13px}
|
||||
.coloring-choices .coloring-guide-btn{background:color-mix(in srgb,var(--teal) 14%,var(--surface));box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--teal) 20%,transparent)}
|
||||
.coloring-choices .coloring-guide-btn.on{outline:2px solid var(--teal);outline-offset:-2px}
|
||||
.coloring-choices .coloring-reset{background:var(--red-soft);color:var(--red);box-shadow:inset 0 0 0 1px color-mix(in srgb,var(--red) 18%,transparent)}
|
||||
.coloring-tools .coloring-done{width:auto;min-width:112px;margin-left:auto;padding:0 14px;color:#fff;box-shadow:0 4px 10px color-mix(in srgb,var(--orange) 24%,transparent);font-size:13px}
|
||||
.coloring-stage{flex:1;min-height:0;overflow:auto;display:grid;place-items:center;padding:18px;background:radial-gradient(circle at 12% 18%,color-mix(in srgb,var(--orange) 13%,transparent),transparent 31%),radial-gradient(circle at 88% 82%,color-mix(in srgb,var(--purple) 10%,transparent),transparent 34%),linear-gradient(145deg,var(--surface-2),color-mix(in srgb,var(--purple) 5%,var(--surface)))}
|
||||
/* het papier vangt alle aanwijzing op; binnen de SVG wordt meetkundig
|
||||
aangewezen met isPointInFill/isPointInStroke */
|
||||
|
|
@ -57,17 +61,19 @@
|
|||
.coloring-app[data-tool="eraser"] .coloring-paper{cursor:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='30' height='30'%3E%3Ctext y='24' font-size='23'%3E%F0%9F%A7%BD%3C/text%3E%3C/svg%3E") 6 24,pointer}
|
||||
.widget[data-widget="coloring"]{--widget-accent:var(--orange)}
|
||||
|
||||
@media(max-width:620px){
|
||||
.coloring-choices,.coloring-tools{padding:6px;gap:4px}
|
||||
/* @container in plaats van @media: dit moet reageren op de breedte van het
|
||||
widget-venster (dat kan smal staan op een brede monitor), niet op de
|
||||
breedte van de hele browser - zie .widget-body{container-name:widget} */
|
||||
@container widget (max-width:620px){
|
||||
.coloring-tools{padding:6px;gap:4px}
|
||||
.coloring-choices label{grid-template-columns:1fr;gap:2px;flex:1;min-width:125px}
|
||||
.coloring-choices select{width:100%;max-width:none;height:32px}
|
||||
.coloring-fit{width:100%;margin:0;text-align:center}
|
||||
.coloring-fit{width:100%;text-align:center}
|
||||
.coloring-tools button{width:32px;height:32px;font-size:16px}
|
||||
.coloring-tool-buttons button{min-width:36px;width:36px;padding:0}.coloring-tool-label{display:none}
|
||||
.coloring-color i{width:18px;height:18px}
|
||||
.coloring-custom span{display:none}.coloring-custom input{width:32px;height:32px}
|
||||
.coloring-tools .coloring-guide-btn{margin-left:0}
|
||||
.coloring-tools .coloring-done{min-width:90px;font-size:11px}
|
||||
.coloring-tools .coloring-done{min-width:90px;margin-left:0;font-size:11px}
|
||||
.coloring-stage{place-items:start center;padding:8px}
|
||||
.coloring-paper{min-width:360px;border-radius:10px}
|
||||
.coloring-hint{font-size:10px}
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@
|
|||
"use strict";
|
||||
/* version — shown until /api/version resolves (or if the fetch fails, e.g. offline).
|
||||
Kept in sync by hand with the VERSION file at the repo root on every release. */
|
||||
const VERSION = "0.4.79-beta";
|
||||
const VERSION = "0.4.80-beta";
|
||||
/* Safari en oudere mobiele browsers behandelen 100vh/100% soms als de hoogte
|
||||
achter hun adres- en navigatiebalk. visualViewport geeft de werkelijk
|
||||
zichtbare hoogte; de CSS gebruikt deze pixelwaarde als betrouwbare bron en
|
||||
|
|
@ -118,7 +118,7 @@ const I18N = {
|
|||
wFull:"Maximaliseren", wUnfull:"Terugzetten",
|
||||
wZoom:"Inhoud groter of kleiner (voor slechtzienden)",
|
||||
wg_flits:"Flitswoorden", wg_flits_d:"Een woord flitst kort in beeld – lezen voordat het verdwijnt!",
|
||||
flFlash:"⚡ Flits", flShow:"👁 Toon", flNext:"Volgende ▸", flSec:"sec", flMission:"Lees het woord voordat het verdwijnt.", flQuestion:"Kon je het woord zelf lezen?", flGotIt:"🌟 Gelukt!", flAgain:"Nog oefenen", flScore:"{score} sterren",
|
||||
flFlash:"⚡ Flits", flShow:"👁 Toon", flNext:"Volgende ▸", flSec:"sec", flDurLabel:"Flitstijd", flMission:"Lees het woord voordat het verdwijnt.", flQuestion:"Kon je het woord zelf lezen?", flGotIt:"🌟 Gelukt!", flAgain:"Nog oefenen", flScore:"{score} sterren",
|
||||
wg_dice:"Dobbelstenen", wg_dice_d:"Gooi 1, 2 of 3 grote dobbelstenen.",
|
||||
diceRoll:"🎲 Gooi!", diceSum:"Samen", diceMission:"Gooi, tel de ogen en controleer de uitkomst.", diceHistory:"Vorige worpen",
|
||||
wg_light:"Klassenstoplicht", wg_light_d:"Maak in één oogopslag duidelijk of kinderen luisteren, fluisteren of rustig mogen werken.",
|
||||
|
|
@ -551,7 +551,7 @@ const I18N = {
|
|||
wFull:"Maximize", wUnfull:"Restore",
|
||||
wZoom:"Content bigger or smaller (low vision)",
|
||||
wg_flits:"Flash words", wg_flits_d:"A word flashes briefly – read it before it disappears!",
|
||||
flFlash:"⚡ Flash", flShow:"👁 Show", flNext:"Next ▸", flSec:"sec", flMission:"Read the word before it disappears.", flQuestion:"Could you read the word by yourself?", flGotIt:"🌟 Got it!", flAgain:"Practise again", flScore:"{score} stars",
|
||||
flFlash:"⚡ Flash", flShow:"👁 Show", flNext:"Next ▸", flSec:"sec", flDurLabel:"Flash time", flMission:"Read the word before it disappears.", flQuestion:"Could you read the word by yourself?", flGotIt:"🌟 Got it!", flAgain:"Practise again", flScore:"{score} stars",
|
||||
wg_dice:"Dice", wg_dice_d:"Roll 1, 2 or 3 big dice.",
|
||||
diceRoll:"🎲 Roll!", diceSum:"Total", diceMission:"Roll, count the dots and check the total.", diceHistory:"Previous rolls",
|
||||
wg_light:"Class traffic light", wg_light_d:"Show at a glance whether children listen, whisper or work quietly.",
|
||||
|
|
@ -917,6 +917,29 @@ const T = k => I18N[LANG][k] ?? I18N.nl[k] ?? k;
|
|||
are visual pixels, absolute positions are layout pixels → divide by VZ() */
|
||||
const VZ = ()=>{ const z = parseFloat(document.body.style.zoom); return z>0 ? z : 1; };
|
||||
|
||||
/* Herbruikbaar instellingen-tandwiel: zeldzaam gebruikte besturing (thema,
|
||||
niveau-stijl, achtergrond...) gaat ingeklapt achter een ⚙, zodat die niet
|
||||
in dezelfde rij hoeft te concurreren met knoppen die een leerling
|
||||
voortdurend gebruikt (kwast, kleur, dikte). Zelfde <details>-opbouw en
|
||||
CSS-klassen als learningShell() in widgets/learning.js, maar los
|
||||
bruikbaar: de aanroeper bepaalt zelf waar het tandwiel in de eigen
|
||||
layout wordt ingevoegd. */
|
||||
function widgetGear(controls,label){
|
||||
const details=document.createElement("details");details.className="edu-settings widget-gear";
|
||||
const summary=document.createElement("summary");
|
||||
summary.append(document.createTextNode("⚙ "));
|
||||
const span=document.createElement("span");span.textContent=label||T("widgetSettings");
|
||||
summary.appendChild(span);
|
||||
const box=document.createElement("div");box.className="edu-controls";
|
||||
box.append(...controls);
|
||||
details.append(summary,box);
|
||||
return details;
|
||||
}
|
||||
/* Kleine bouwstenen voor widgetGear()-inhoud, zelfde vorm als de
|
||||
gelijknamige helpers in widgets/learning.js (bewust hier gedupliceerd in
|
||||
plaats van gedeeld, want learning.js laadt pas ná de meeste widgets). */
|
||||
function gearField(label,el){const w=document.createElement("label");const s=document.createElement("span");s.textContent=label;w.append(s,el);return w}
|
||||
|
||||
function applyI18n(){
|
||||
document.querySelectorAll("[data-i18n]").forEach(el=>{ el.textContent = T(el.dataset.i18n); });
|
||||
document.querySelectorAll("#langToggle button").forEach(b=>b.classList.toggle("active", b.dataset.lang===LANG));
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ function mountBall(root, initState, opts){
|
|||
root.innerHTML = `
|
||||
<div class="mt">
|
||||
<div class="mt-top">
|
||||
<button class="lg-snd"></button>
|
||||
<select class="fl-sel bl-src"></select>
|
||||
<div class="mt-score bl-score"></div>
|
||||
</div>
|
||||
|
|
@ -26,10 +25,12 @@ function mountBall(root, initState, opts){
|
|||
<div class="lg-tray bl-kbd lg-kbd" style="display:flex;"></div>
|
||||
</div>`;
|
||||
const el = s => root.querySelector(s);
|
||||
const sndBtn=document.createElement("button");sndBtn.type="button";sndBtn.className="lg-snd";
|
||||
el(".mt-top").insertAdjacentElement("beforebegin",widgetGear([gearField(T("sndWidget"),sndBtn)]));
|
||||
const srcSel = el(".bl-src"), balloonsEl = el(".bl-balloons"), picEl=el(".bl-pic"),
|
||||
wordEl = el(".bl-word"), msgEl = el(".bl-msg"),
|
||||
nextBtn = el(".bl-next"), kbdEl = el(".bl-kbd"),
|
||||
scoreEl = el(".bl-score"), sndBtn = el(".lg-snd");
|
||||
scoreEl = el(".bl-score");
|
||||
|
||||
function list(){
|
||||
if(S.assignmentWords?.length)return languageWidgetWords(S.assignmentWords,S.yearGroup,S.level,"ball");
|
||||
|
|
|
|||
|
|
@ -191,20 +191,21 @@ function mountColoring(root,initState,opts={}){
|
|||
const clipStrokes=coloringClipsStrokes(yearGroup,level);
|
||||
|
||||
root.innerHTML=`<div class="coloring-app">
|
||||
<div class="coloring-choices">
|
||||
<label><span class="coloring-theme-label"></span><select class="coloring-theme"></select></label>
|
||||
<label><span class="coloring-page-label"></span><select class="coloring-page"></select></label>
|
||||
<span class="coloring-fit"></span>
|
||||
</div>
|
||||
<div class="coloring-tools">
|
||||
<div class="coloring-tool-buttons"></div>
|
||||
<div class="coloring-palette"></div>
|
||||
<label class="coloring-custom"><span></span><input type="color"></label>
|
||||
<div class="coloring-sizes"></div>
|
||||
<button class="coloring-guide-btn" type="button">🎯</button>
|
||||
<button class="coloring-reset" type="button">↺</button>
|
||||
<button class="coloring-done tbtn" type="button"></button>
|
||||
</div>
|
||||
<details class="edu-settings widget-gear"><summary>⚙ <span class="coloring-settings-label"></span></summary>
|
||||
<div class="coloring-choices edu-controls">
|
||||
<label><span class="coloring-theme-label"></span><select class="coloring-theme"></select></label>
|
||||
<label><span class="coloring-page-label"></span><select class="coloring-page"></select></label>
|
||||
<span class="coloring-fit"></span>
|
||||
<button class="coloring-guide-btn" type="button">🎯</button>
|
||||
<button class="coloring-reset" type="button">↺</button>
|
||||
</div></details>
|
||||
<div class="coloring-stage"><div class="coloring-paper" role="img"></div></div>
|
||||
<div class="coloring-hint" aria-live="polite"></div>
|
||||
</div>`;
|
||||
|
|
@ -337,6 +338,7 @@ function mountColoring(root,initState,opts={}){
|
|||
return T(tool==="bucket"?"colorBucketHint":tool==="eraser"?"colorEraserHint":"colorBrushHint")+" "+levelHint();
|
||||
}
|
||||
function updateTexts(){
|
||||
root.querySelector(".coloring-settings-label").textContent=T("widgetSettings");
|
||||
root.querySelector(".coloring-theme-label").textContent=T("colorTheme");
|
||||
root.querySelector(".coloring-page-label").textContent=T("colorPage");
|
||||
root.querySelector(".coloring-fit").textContent=T("colorProfile").replace("{year}",yearGroup).replace("{level}",level);
|
||||
|
|
|
|||
|
|
@ -13,7 +13,6 @@ function mountFlits(root, initState, opts){
|
|||
<div class="mt">
|
||||
<div class="mt-top">
|
||||
<select class="fl-sel fl-src"></select>
|
||||
<select class="fl-sel fl-dur"></select>
|
||||
<div class="mt-score fl-cnt"></div>
|
||||
</div>
|
||||
<p class="play-mission fl-mission"></p>
|
||||
|
|
@ -32,7 +31,9 @@ function mountFlits(root, initState, opts){
|
|||
</div>
|
||||
</div>`;
|
||||
const el = s => root.querySelector(s);
|
||||
const srcSel = el(".fl-src"), durSel = el(".fl-dur"),
|
||||
const durSel=document.createElement("select");durSel.className="fl-sel fl-dur";
|
||||
el(".mt").insertBefore(widgetGear([gearField(T("flDurLabel"),durSel)]),el(".mt-top").nextSibling);
|
||||
const srcSel = el(".fl-src"),
|
||||
wordEl = el(".fl-word"), picEl = el(".fl-pic"), cntEl = el(".fl-cnt"), reflect=el(".fl-reflect");
|
||||
|
||||
function list(){
|
||||
|
|
|
|||
|
|
@ -21,13 +21,10 @@ function mountLetterGame(root, initState, opts){
|
|||
root.innerHTML = `
|
||||
<div class="lg">
|
||||
<div class="lg-top">
|
||||
<button class="lg-snd"></button>
|
||||
<button class="lg-mode"></button>
|
||||
<div class="lg-levels">
|
||||
<button data-l="1"></button><button data-l="2"></button><button data-l="3"></button>
|
||||
</div>
|
||||
<div class="lg-cards"></div>
|
||||
<select class="lg-num"></select>
|
||||
<div class="lg-progress"><span class="lg-count"></span><span class="lg-stars"></span></div>
|
||||
</div>
|
||||
<div class="lg-instr"></div>
|
||||
|
|
@ -89,8 +86,16 @@ function mountLetterGame(root, initState, opts){
|
|||
instrEl = el(".lg-instr"), nextBtn = el(".lg-next"), doneEl = el(".lg-done"),
|
||||
countEl = el(".lg-count"), starsEl = el(".lg-stars"), cardsEl = el(".lg-cards");
|
||||
|
||||
/* zeldzaam gebruikte besturing (geluid, invoerwijze, aantal kaarten) achter
|
||||
een tandwiel: die concurreerden eerder met de niveauknoppen om ruimte in
|
||||
één brede rij, terwijl ze maar zelden worden aangepast */
|
||||
const numSel=document.createElement("select");numSel.className="lg-num";
|
||||
const sndBtn=document.createElement("button");sndBtn.type="button";sndBtn.className="lg-snd";
|
||||
const modeBtn=document.createElement("button");modeBtn.type="button";modeBtn.className="lg-mode";
|
||||
el(".lg-top").insertAdjacentElement("beforebegin",widgetGear([
|
||||
gearField(T("sndWidget"),sndBtn),gearField(T("amode"),modeBtn),gearField(T("numWords"),numSel)
|
||||
]));
|
||||
/* card buttons (built dynamically: N cards + star + edit) */
|
||||
const numSel = root.querySelector(".lg-num");
|
||||
function buildNumSel(){
|
||||
numSel.innerHTML = "";
|
||||
[4,6,8,10,"all"].forEach(v=>{
|
||||
|
|
@ -106,7 +111,6 @@ function mountLetterGame(root, initState, opts){
|
|||
S.count = numSel.value==="all" ? "all" : +numSel.value;
|
||||
newRound(); render();
|
||||
});
|
||||
const sndBtn = root.querySelector(".lg-snd");
|
||||
sndBtn.addEventListener("click", ()=>{
|
||||
S.snd = !S.snd;
|
||||
updateSndBtn();
|
||||
|
|
@ -119,7 +123,6 @@ function mountLetterGame(root, initState, opts){
|
|||
modeBtn.title = T("amode");
|
||||
root.querySelector(".lg").classList.toggle("typemode", S.amode==="type");
|
||||
}
|
||||
const modeBtn = root.querySelector(".lg-mode");
|
||||
modeBtn.addEventListener("click", ()=>{
|
||||
S.amode = S.amode==="blocks" ? "type" : "blocks";
|
||||
updateSndBtn();
|
||||
|
|
|
|||
|
|
@ -21,8 +21,6 @@ function mountMath(root, initState, mode, opts){
|
|||
root.innerHTML = `
|
||||
<div class="mt blocks">
|
||||
<div class="mt-top">
|
||||
<button class="lg-snd"></button>
|
||||
<button class="lg-mode"></button>
|
||||
<div class="lg-levels">
|
||||
<button data-l="1"></button><button data-l="2"></button><button data-l="3"></button>
|
||||
</div>
|
||||
|
|
@ -56,9 +54,11 @@ function mountMath(root, initState, mode, opts){
|
|||
fbEl.appendChild(cheer);
|
||||
}
|
||||
}
|
||||
const sndBtn=document.createElement("button");sndBtn.type="button";sndBtn.className="lg-snd";
|
||||
const modeBtn=document.createElement("button");modeBtn.type="button";modeBtn.className="lg-mode";
|
||||
el(".mt-top").insertAdjacentElement("beforebegin",widgetGear([gearField(T("sndWidget"),sndBtn),gearField(T("amode"),modeBtn)]));
|
||||
const mtEl = el(".mt"), qEl = el(".mt-q"), aEl = el(".mt-a"), fbEl = el(".mt-fb"),
|
||||
scoreEl = el(".mt-score"), doneEl = el(".mt-done"),
|
||||
sndBtn = el(".lg-snd"), modeBtn = el(".lg-mode"),
|
||||
padEl = el(".mt-pad"), slotsEl = el(".mt-slots"), trayEl = el(".mt-tray");
|
||||
|
||||
/* ---- digit blocks with dot patterns (like the letter blocks) ---- */
|
||||
|
|
|
|||
|
|
@ -21,11 +21,14 @@ function mountNoise(root, initState){
|
|||
</div>
|
||||
<output class="nm-status" aria-live="polite"></output>
|
||||
<button class="tbtn nm-toggle"></button>
|
||||
<details class="edu-settings widget-gear"><summary>⚙ <span class="nm-settings-label"></span></summary>
|
||||
<div class="edu-controls">
|
||||
<div class="nm-zones">
|
||||
<label class="nm-zone-row"><span class="nm-zone-lbl"></span><input type="range" class="nm-z1" min="10" max="90" step="5"></label>
|
||||
<label class="nm-zone-row"><span class="nm-zone-lbl"></span><input type="range" class="nm-z2" min="10" max="95" step="5"></label>
|
||||
</div>
|
||||
<p class="nm-privacy"></p>
|
||||
</div></details>
|
||||
</div>`;
|
||||
const gauge = root.querySelector(".nm-gauge"),
|
||||
fill = root.querySelector(".nm-fill"),
|
||||
|
|
@ -112,6 +115,7 @@ function mountNoise(root, initState){
|
|||
watchWarn();
|
||||
function labels(){
|
||||
root.querySelector(".nm-mission").textContent = T("wg_noise_d");
|
||||
root.querySelector(".nm-settings-label").textContent = T("widgetSettings");
|
||||
toggle.textContent = running ? "⏸ " + T("noiseStop") : "🎙️ " + T("noiseStart");
|
||||
root.querySelectorAll(".nm-zone-lbl")[0].textContent = T("noiseZoneGreen");
|
||||
root.querySelectorAll(".nm-zone-lbl")[1].textContent = T("noiseZoneYellow");
|
||||
|
|
|
|||
|
|
@ -5,12 +5,13 @@ function mountNumberline(root, initState){
|
|||
if(S.managedLearningLevel&&rules){S.min=rules.numberlineMin;S.max=rules.numberlineMax;S.step=rules.numberlineStep;S.value=S.min;S.target=S.min+S.step;}
|
||||
root.innerHTML=`
|
||||
<div class="nlw">
|
||||
<div class="nlw-settings">
|
||||
<details class="edu-settings widget-gear"><summary>⚙ <span class="nlw-settings-label"></span></summary>
|
||||
<div class="nlw-settings edu-controls">
|
||||
<label><span class="nlw-from-label"></span><input class="nlw-min" type="number" min="-1000000" max="999999"></label>
|
||||
<label><span class="nlw-to-label"></span><input class="nlw-max" type="number" min="-999999" max="1000000"></label>
|
||||
<label><span class="nlw-step-label"></span><select class="nlw-step"><option>1</option><option>2</option><option>5</option><option>10</option></select></label>
|
||||
<button class="tbtn ghost nlw-new" type="button"></button>
|
||||
</div>
|
||||
</div></details>
|
||||
<div class="nlw-playtop"><div class="nlw-task"></div><output class="nlw-score"></output></div>
|
||||
<div class="nlw-scroll"><div class="nlw-line"></div></div>
|
||||
<div class="nlw-actions">
|
||||
|
|
@ -62,6 +63,7 @@ function mountNumberline(root, initState){
|
|||
if(ok){if(!S.solved){S.score++;S.streak++;S.solved=true;}sndGood();confetti(root);}else{S.streak=0;S.solved=false;sndBad();}render();
|
||||
});
|
||||
function labels(){
|
||||
q('.nlw-settings-label').textContent=T('widgetSettings');
|
||||
q('.nlw-from-label').textContent=T('numberlineFrom'); q('.nlw-to-label').textContent=T('numberlineTo');
|
||||
q('.nlw-step-label').textContent=T('numberlineStep'); q('.nlw-new').textContent=T('numberlineNew');
|
||||
q('.nlw-check').textContent=T('numberlineCheck'); render();
|
||||
|
|
|
|||
|
|
@ -26,11 +26,12 @@ function mountSentence(root, initState){
|
|||
<div class="sb">
|
||||
<div class="sb-sentence" aria-live="polite"></div>
|
||||
<div class="sb-banks"></div>
|
||||
<div class="sb-add">
|
||||
<details class="edu-settings widget-gear"><summary>⚙ <span class="sb-add-label"></span></summary>
|
||||
<div class="sb-add edu-controls">
|
||||
<select class="sb-kind"><option value="who"></option><option value="does"></option><option value="what"></option></select>
|
||||
<input class="sb-word" type="text" maxlength="40">
|
||||
<button class="tbtn ghost sb-add-btn" type="button">+</button>
|
||||
</div>
|
||||
</div></details>
|
||||
<div class="sb-actions"><button class="tbtn ghost sb-surprise" type="button"></button><button class="tbtn ghost sb-back" type="button">↶</button><button class="tbtn ghost sb-clear" type="button"></button><button class="tbtn sb-speak" type="button"></button></div>
|
||||
</div>`;
|
||||
const q=s=>root.querySelector(s), sentenceEl=q('.sb-sentence'), banksEl=q('.sb-banks'), kind=q('.sb-kind'), word=q('.sb-word');
|
||||
|
|
@ -64,6 +65,7 @@ function mountSentence(root, initState){
|
|||
q('.sb-surprise').addEventListener('click',()=>{S.sentence=['who','does','what'].filter(k=>S.banks[k].length).map(kind=>({kind,word:S.banks[kind][Math.floor(Math.random()*S.banks[kind].length)]}));renderSentence();sndGood();confetti(sentenceEl)});
|
||||
q('.sb-speak').addEventListener('click',()=>{const text=sentenceText();if(text&&'speechSynthesis'in window){speechSynthesis.cancel();const u=new SpeechSynthesisUtterance(text);u.lang=LANG==='nl'?'nl-NL':'en-GB';speechSynthesis.speak(u);}});
|
||||
function labels(){
|
||||
q('.sb-add-label').textContent=T('widgetSettings');
|
||||
[...kind.options].forEach(o=>o.textContent=T('sentence_'+o.value)); word.placeholder=T('sentenceAdd');
|
||||
q('.sb-surprise').textContent=T('sentenceSurprise');q('.sb-clear').textContent=T('sentenceClear'); q('.sb-speak').textContent=T('sentenceSpeak'); renderSentence();renderBanks();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,14 +14,15 @@ function mountTimer(root, initState){
|
|||
}
|
||||
root.innerHTML = `
|
||||
<div class="timer m-both">
|
||||
<div class="topts">
|
||||
<details class="edu-settings widget-gear"><summary>⚙ <span class="topts-label"></span></summary>
|
||||
<div class="topts edu-controls">
|
||||
<button class="bsnd ticon"></button>
|
||||
<div class="tseg">
|
||||
<button data-m="dig">🔢</button>
|
||||
<button data-m="pie">🔴</button>
|
||||
<button data-m="both">🔢🔴</button>
|
||||
</div>
|
||||
</div>
|
||||
</div></details>
|
||||
<div class="tstage">
|
||||
<svg class="tpie" viewBox="0 0 150 150">
|
||||
<circle cx="75" cy="75" r="70" fill="#eef3f9" stroke="#d5dfeb" stroke-width="2"/>
|
||||
|
|
@ -78,6 +79,7 @@ function mountTimer(root, initState){
|
|||
drawPie();
|
||||
}
|
||||
function labels(){
|
||||
root.querySelector(".topts-label").textContent = T("widgetSettings");
|
||||
bstart.textContent = (iv ? "⏸ "+T("pause") : "▶ "+T("start"));
|
||||
breset.textContent = "↺ "+T("reset");
|
||||
presets.querySelectorAll("button").forEach(b=>b.textContent = b.dataset.m+" "+T("min"));
|
||||
|
|
|
|||
Loading…
Reference in a new issue