Voeg thematische kleurplaten en creatieve niveaus toe (v0.4.65-beta) #3

Open
bes-r wants to merge 207 commits from bes-r/kleurplaten-v0-4-65 into main AGit
5 changed files with 97 additions and 22 deletions
Showing only changes of commit bab1018142 - Show all commits

View file

@ -1 +1 @@
0.4.15-beta
0.4.16-beta

View file

@ -1700,7 +1700,7 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2
/* Educatieve widgets en compacte widgetbibliotheek */
.gallery-head,.gallery-tools,.gallery-data{display:flex;align-items:center;gap:8px}.gallery-head h2{flex:1}.gallery-tools{position:sticky;top:-24px;z-index:2;background:var(--surface);padding:6px 0 10px}.gallery-tools input{flex:1;height:42px;border:1px solid var(--line);border-radius:12px;padding:0 13px;background:var(--surface);color:var(--ink);font:inherit}.gallery-data{margin:8px 0 14px;flex-wrap:wrap}#galleryTemplates{display:flex;gap:7px;overflow-x:auto;padding:2px 0 8px}.gcat-title{margin:18px 0 8px;font-size:13px;font-weight:900;color:var(--muted);text-transform:uppercase}.gcard{position:relative}.gstar{position:absolute;right:7px;top:7px;border:0;background:transparent;color:var(--orange);font-size:22px;cursor:pointer}.gallery-empty,.edu-empty{text-align:center;color:var(--muted);padding:28px}.widget.locked .widget-rs{display:none}.widget.locked{outline:2px solid var(--accent-soft)}.widget.preview .widget-head{position:absolute;z-index:4;width:100%;opacity:0}.widget.preview:hover .widget-head,.widget.preview:focus-within .widget-head{opacity:1}.widget.preview .widget-rs{display:none}.widget.preview{border-color:transparent;box-shadow:none}.wpreview.on{background:var(--accent-soft);color:var(--accent-ink)}
.edu{height:100%;display:flex;flex-direction:column;background:var(--surface);color:var(--ink)}.edu-settings{flex:none;background:var(--surface-2);border-bottom:1px solid var(--line)}.edu-settings summary{cursor:pointer;padding:9px 12px;font-weight:800;font-size:13px}.edu-controls{display:flex;align-items:end;gap:8px;flex-wrap:wrap;padding:0 12px 10px}.edu-controls label{display:flex;flex-direction:column;gap:3px;font-size:11px;font-weight:800;color:var(--muted)}.edu-controls input,.edu-controls select,.edu-controls textarea{min-height:34px;border:1px solid var(--line);border-radius:9px;padding:5px 8px;background:var(--surface);color:var(--ink);font:inherit}.edu-controls textarea{height:65px;resize:vertical}.edu-stage{flex:1;min-height:0;overflow:auto;padding:16px;display:flex;align-items:center;justify-content:center}
.edu{height:100%;display:flex;flex-direction:column;background:var(--surface);color:var(--ink)}.edu-settings{flex:none;background:var(--surface-2);border-bottom:1px solid var(--line)}.edu-settings summary{cursor:pointer;padding:9px 12px;font-weight:800;font-size:13px}.edu-controls{display:flex;align-items:end;gap:8px;flex-wrap:wrap;padding:0 12px 10px}.edu-controls label{display:flex;flex-direction:column;gap:3px;font-size:11px;font-weight:800;color:var(--muted)}.edu-controls [hidden]{display:none}.edu-controls input,.edu-controls select,.edu-controls textarea{min-height:34px;border:1px solid var(--line);border-radius:9px;padding:5px 8px;background:var(--surface);color:var(--ink);font:inherit}.edu-controls textarea{height:65px;resize:vertical}.edu-stage{flex:1;min-height:0;overflow:auto;padding:16px;display:flex;align-items:center;justify-content:center}
.fraction-game{width:min(100%,620px);display:flex;flex-direction:column;align-items:center;gap:9px}.fraction-summary{min-height:34px;padding:5px 13px;border-radius:999px;background:var(--surface-2);color:var(--purple);font-size:18px;font-weight:950}.fraction-wall{width:100%;display:flex;flex-direction:column;gap:5px}.fraction-wall>div{display:flex;gap:4px}.fraction-piece{flex:1;min-height:38px;border:2px solid hsl(var(--fraction-hue) 55% 43%);border-radius:7px;background:hsl(var(--fraction-hue) 75% 90%);font-weight:800;color:#20304a;cursor:pointer;transition:transform .12s,filter .12s}.fraction-piece:hover{filter:brightness(.96);transform:translateY(-1px)}.fraction-piece.selected{background:hsl(var(--fraction-hue) 70% 55%);color:#fff}.fraction-clear{align-self:flex-end;min-height:34px}
.place-grid{display:grid;grid-template-columns:repeat(6,1fr);width:100%;border:1px solid var(--line);border-radius:14px;overflow:hidden}.place-grid div{text-align:center;border-right:1px solid var(--line)}.place-grid b{display:block;padding:8px;background:var(--surface-2);font-size:12px}.place-grid output{display:block;padding:18px 4px;font-size:clamp(25px,5vw,48px);font-weight:900;color:var(--blue)}
.money-app{width:100%;min-height:100%;align-self:flex-start;display:flex;flex-direction:column;justify-content:flex-start;color:var(--ink)}
@ -1761,6 +1761,29 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
.clock-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.clock-feedback.ok{color:var(--green)}.clock-feedback.bad{color:var(--red)}.clock-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.clock-actions .tbtn:not(.ghost){background:var(--green);color:#fff}.clock-drag-hint{color:var(--muted);font-weight:800;text-align:center}
@media(max-width:480px){.clock-task-card{padding:12px}.clock-level-control>span{display:none}.clock-top{gap:5px}.clock{--clock-size:min(210px,68vw)}.clock-target{min-height:72px}.clock-block-tray.lg-tray{gap:7px;padding:9px}}
/* plaatswaarde: "Bouwen"-oefening hergebruikt hetzelfde blok/vak/tray-systeem
als de oefenklok/het geldwidget, alleen met een variabel aantal kolommen
(2/3/6 al naar niveau) en een kolomlabel (HD/TD/D/H/T/E) boven elk vak. */
.place-app{width:100%;min-height:100%;display:flex;flex-direction:column;justify-content:flex-start;color:var(--ink)}
.place-top{position:sticky;top:-16px;z-index:6;width:100%;display:flex;align-items:center;flex-wrap:wrap;gap:8px;margin-bottom:8px;padding:8px 4px;background:var(--surface);border-bottom:1px solid var(--line)}
.place-level-control{display:flex;align-items:center;gap:6px}.place-level-control>span{color:var(--muted);font-size:12px;font-weight:900}
.place-levels button{min-width:31px;padding:6px 9px;white-space:nowrap}
.place-score{margin-left:auto;padding:6px 10px;border-radius:999px;background:var(--surface-2);color:var(--muted);font-size:12px;font-weight:900}
.place-task-card{width:min(100%,620px);margin:0 auto;display:flex;flex-direction:column;align-items:center;gap:12px;padding:16px;border:1px solid var(--line);border-radius:20px;background:var(--surface);box-shadow:var(--shadow-1)}
.place-target{width:100%;min-height:88px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:6px;padding:10px;border-radius:15px;background:var(--surface-2)}
.place-target>span:first-child{color:var(--muted);font-size:12px;font-weight:900;text-transform:uppercase;letter-spacing:.5px}
.place-task-digital{font-size:clamp(48px,10vw,78px);line-height:1;font-weight:950;letter-spacing:2px;color:var(--blue)}
.place-block-answer{width:min(100%,520px);display:flex;flex-direction:column;align-items:center;gap:10px}.place-block-answer>p{margin:0;color:var(--muted);font-weight:800;text-align:center}
.place-answer-slots{display:flex;align-items:flex-end;justify-content:center;gap:7px;flex-wrap:wrap}
.place-slot-col{display:flex;flex-direction:column;align-items:center;gap:4px}.place-slot-col>b{font-size:11px;font-weight:900;color:var(--muted);text-transform:uppercase}
.place-digit-slot.slot.sm{width:46px;height:66px;padding-top:2px}.place-digit-slot.slot.sm .ghost{height:25px;font-size:20px}.place-digit-slot.slot.sm .slot-dots{margin-bottom:4px}.place-digit-slot.filled{padding:0}
.place-digit-tray.lg-tray{display:grid;grid-template-columns:repeat(5,38px);gap:7px;min-height:0;padding:11px 14px}.place-digit-tray .place-drag-block{width:38px;height:56px;min-height:56px;cursor:grab}.place-digit-tray .place-drag-block:active{cursor:grabbing}.place-digit-tray .place-drag-block:focus-visible{outline:3px solid var(--accent);outline-offset:2px}
.place-digit-slot.droptarget{border-color:var(--green);background:#e3f6ea;transform:scale(1.06)}
.place-digit-block.blok{width:42px;height:62px;min-height:62px;padding-top:3px;cursor:default}.place-digit-block.blok .bl{height:31px;font-size:27px;color:var(--purple)}.place-digit-block.blok .blok-dots{margin-bottom:5px}.place-digit-block.blok .dots,.place-digit-slot .dots{grid-template-columns:repeat(2,6px);grid-template-rows:repeat(3,6px);gap:2px}.place-digit-block.blok .dots i,.place-digit-slot .dots i{width:6px;height:6px}
.place-feedback{min-height:24px;margin:0;text-align:center;font-weight:900}.place-feedback.ok{color:var(--green)}.place-feedback.bad{color:var(--red)}
.place-actions{display:flex;justify-content:center;gap:8px;flex-wrap:wrap}.place-actions .tbtn:not(.ghost){background:var(--green);color:#fff}
@media(max-width:480px){.place-task-card{padding:12px}.place-level-control>span{display:none}.place-top{gap:5px}.place-digit-tray.lg-tray{gap:7px;padding:9px}}
/* ============================================================
Kindvriendelijke spel-laag
Eén accent, duidelijke missies en tastbare bediening voor elk widgettype.
@ -1903,7 +1926,7 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
.mood .tbtn{font-size:30px;padding:9px}
.poll h3{margin:8px 0}
.money-task-card,.clock-task-card,.world-task-card{gap:9px;padding:10px}
.money-task-card,.clock-task-card,.world-task-card,.place-task-card{gap:9px;padding:10px}
.world-top{top:-10px;gap:5px}
.world-gallery{grid-template-columns:1fr}
.world-visual.mixed{gap:6px}
@ -1918,8 +1941,8 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
.world-continent-label{font-size:12px}
.topo-world-legend{gap:4px}
.topo-world-legend span{padding:4px 6px;font-size:10px}
.money-top,.clock-top{top:-10px;gap:5px}
.money-level-control>span,.clock-level-control>span{display:none}
.money-top,.clock-top,.place-top{top:-10px;gap:5px}
.money-level-control>span,.clock-level-control>span,.place-level-control>span{display:none}
.money-shop-task{grid-template-columns:minmax(0,.8fr) minmax(0,1.2fr);gap:7px}
.money-product,.money-paid{min-height:112px;padding:8px 6px;gap:4px}
.money-product-icon{font-size:46px}
@ -1939,14 +1962,14 @@ body.dark .world-fact{background:#1d3b2a;color:#a9e5b7}body.dark .world-fact .wo
.lg,.mt,.dc,.np,.timer,.nlw,.sb{padding:6px}
.lg-cards{gap:3px}
.lg-cards button,.lg-snd,.lg-mode{width:32px;height:34px}
.lg .slot:not(.clock-digit-slot):not(.money-digit-slot),
.lg .blok:not(.clock-digit-block):not(.money-digit-block){width:52px;height:82px}
.clock-answer-slots,.money-answer-slots{gap:4px}
.clock-digit-slot.slot.sm,.money-digit-slot.slot.sm{width:39px;height:60px}
.clock-digit-block.blok,.money-digit-block.blok{width:34px;height:52px;min-height:52px}
.clock-block-tray.lg-tray,.money-digit-tray.lg-tray{
.lg .slot:not(.clock-digit-slot):not(.money-digit-slot):not(.place-digit-slot),
.lg .blok:not(.clock-digit-block):not(.money-digit-block):not(.place-digit-block){width:52px;height:82px}
.clock-answer-slots,.money-answer-slots,.place-answer-slots{gap:4px}
.clock-digit-slot.slot.sm,.money-digit-slot.slot.sm,.place-digit-slot.slot.sm{width:39px;height:60px}
.clock-digit-block.blok,.money-digit-block.blok,.place-digit-block.blok{width:34px;height:52px;min-height:52px}
.clock-block-tray.lg-tray,.money-digit-tray.lg-tray,.place-digit-tray.lg-tray{
grid-template-columns:repeat(5,34px);gap:4px;padding:7px}
.clock-block-tray .clock-drag-block{width:34px;height:52px;min-height:52px}
.clock-block-tray .clock-drag-block,.place-digit-tray .place-drag-block{width:34px;height:52px;min-height:52px}
.clock-type-display,.money-type-display{min-width:0;width:100%;font-size:30px}
.world-answers{grid-template-columns:1fr}
.world-visual.mixed{gap:5px}

View file

@ -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.15-beta";
const VERSION = "0.4.16-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
@ -147,7 +147,7 @@ const I18N = {
wg_mood:"Gevoelsmeter", wg_mood_d:"Doe een snelle, anonieme check-in met de klas.",
wg_poll:"Klaspoll", wg_poll_d:"Stem samen op maximaal zes antwoorden.",
clock24Hour:"24-uursklok",
widgetSettings:"Instellingen", clockWelcome:"Oefenen met de klok", clockWelcomeHint:"Kies bovenaan niveau 1, 2 of 3. Kies bij Instellingen hoe de opdracht wordt getoond en start daarna.", clockSetTitle:"Zet de wijzers op de goede tijd", clockReadTitle:"Bekijk de opdracht en maak de tijd", clockChooseTitle:"Kies het juiste tijdblok", clockYourTask:"Jouw opdracht", clockLinkHands:"Grote wijzer draait mee", clockTypeHint:"Voer de tijd in als UU:MM.", clockLevel1Hint:"Niveau 1: hele en halve uren", clockLevel2Hint:"Niveau 2: stappen van vijf minuten", clockLevel3Hint:"Niveau 3: alle minuten, met optioneel 24 uur en dagdelen", clockBuildHint:"Sleep vier cijfers naar de vakken en maak UU:MM.", clockDigitGhost:"?", clockDigit:"Cijfer {digit}", clockIncomplete:"Maak eerst de volledige tijd af.", clockCorrect:"Goed gedaan! Kies de volgende opdracht. 🌟", clockTryAgain:"Bijna! Probeer het nog een keer.", clockStartTask:"Start opdracht", clockNextTask:"Volgende opdracht", clockLevel:"Niveau", clockLevelHour:"Hele uren", clockLevelQuarter:"Kwartieren", clockLevelFive:"Vijf minuten", clockLevelMinute:"Iedere minuut", clockLearningStyle:"Opdracht tonen", clockStyleAnalogue:"Analoge klok", clockStyleDigital:"Grote digitale tijd", clockStyleWords:"In woorden", clockStyleBoth:"Klok, cijfers en woorden", clockDragHint:"Sleep de wijzers om de tijd te veranderen.", clockGame:"Spel", clockGameSet:"Klok zetten", clockGameRead:"Tijd aflezen", clockGameChoose:"Kies de tijd", clockStart:"Kies Nieuwe opdracht om te beginnen", clockRead:"Welke tijd zie je? Stel je antwoord hieronder in.", clockChoose:"Welke digitale tijd hoort bij deze klok?", clockTry:"Nog niet. Het goede antwoord is {time}.", clockScore:"Score: {score} · reeks: {streak}", clockWordsHour:"{hour} uur", clockWordsQuarterPast:"kwart over {hour}", clockWordsHalf:"half {hour}", clockWordsQuarterTo:"kwart voor {hour}", clockWordsPast:"{minutes} over {hour}", clockWordsTo:"{minutes} voor {hour}", clockMorning:"in de ochtend", clockAfternoon:"in de middag", clockEvening:"in de avond", clockNight:"in de nacht", fractionRows:"Rijen", fractionLabels:"Breuken tonen", fractionHint:"Tik in een rij tot waar je de breuk wilt kleuren.", fractionSelected:"Gekozen breuk: {fraction}", fractionClear:"Leegmaken", clockTask:"Oefenopdracht", clockSet:"Zet de klok op {time}", clockHour:"Uur", clockMinute:"Minuten", placeNumber:"Getal", moneyMake:"Maak € {amount}", wordtypeSentence:"Zin", wordtypeHint:"Tik: blauw zelfstandig naamwoord · groen werkwoord · geel bijvoeglijk naamwoord", scheduleActivity:"activiteit…", scheduleEmpty:"Voeg de eerste activiteit toe.", scheduleCount:"{count} momenten op de planning", pollReset:"Opnieuw", pollQuestion:"Vraag", pollOptions:"Antwoorden (één per regel)", pollTotal:"{count} stemmen", moodTotal:"{count} kinderen hebben gekozen", moodVote:"Kies {face}", galleryFavorites:"Favorieten", galleryFavorite:"Favoriet maken", galleryEmpty:"Geen widgets gevonden.", gallerySearch:"Zoek widgets…", templateMath:"Rekenles", templateLanguage:"Taalles", templateDay:"Dagstart", exportBoard:"Exporteren", importBoard:"Importeren", importError:"Dit is geen geldig Teach-bordbestand.", widgetDuplicate:"Dupliceren", widgetLock:"Vastzetten", widgetUnlock:"Losmaken", widgetPreview:"Voorbeeld zonder bediening", undo:"Ongedaan maken", redo:"Opnieuw",
widgetSettings:"Instellingen", clockWelcome:"Oefenen met de klok", clockWelcomeHint:"Kies bovenaan niveau 1, 2 of 3. Kies bij Instellingen hoe de opdracht wordt getoond en start daarna.", clockSetTitle:"Zet de wijzers op de goede tijd", clockReadTitle:"Bekijk de opdracht en maak de tijd", clockChooseTitle:"Kies het juiste tijdblok", clockYourTask:"Jouw opdracht", clockLinkHands:"Grote wijzer draait mee", clockTypeHint:"Voer de tijd in als UU:MM.", clockLevel1Hint:"Niveau 1: hele en halve uren", clockLevel2Hint:"Niveau 2: stappen van vijf minuten", clockLevel3Hint:"Niveau 3: alle minuten, met optioneel 24 uur en dagdelen", clockBuildHint:"Sleep vier cijfers naar de vakken en maak UU:MM.", clockDigitGhost:"?", clockDigit:"Cijfer {digit}", clockIncomplete:"Maak eerst de volledige tijd af.", clockCorrect:"Goed gedaan! Kies de volgende opdracht. 🌟", clockTryAgain:"Bijna! Probeer het nog een keer.", clockStartTask:"Start opdracht", clockNextTask:"Volgende opdracht", clockLevel:"Niveau", clockLevelHour:"Hele uren", clockLevelQuarter:"Kwartieren", clockLevelFive:"Vijf minuten", clockLevelMinute:"Iedere minuut", clockLearningStyle:"Opdracht tonen", clockStyleAnalogue:"Analoge klok", clockStyleDigital:"Grote digitale tijd", clockStyleWords:"In woorden", clockStyleBoth:"Klok, cijfers en woorden", clockDragHint:"Sleep de wijzers om de tijd te veranderen.", clockGame:"Spel", clockGameSet:"Klok zetten", clockGameRead:"Tijd aflezen", clockGameChoose:"Kies de tijd", clockStart:"Kies Nieuwe opdracht om te beginnen", clockRead:"Welke tijd zie je? Stel je antwoord hieronder in.", clockChoose:"Welke digitale tijd hoort bij deze klok?", clockTry:"Nog niet. Het goede antwoord is {time}.", clockScore:"Score: {score} · reeks: {streak}", clockWordsHour:"{hour} uur", clockWordsQuarterPast:"kwart over {hour}", clockWordsHalf:"half {hour}", clockWordsQuarterTo:"kwart voor {hour}", clockWordsPast:"{minutes} over {hour}", clockWordsTo:"{minutes} voor {hour}", clockMorning:"in de ochtend", clockAfternoon:"in de middag", clockEvening:"in de avond", clockNight:"in de nacht", fractionRows:"Rijen", fractionLabels:"Breuken tonen", fractionHint:"Tik in een rij tot waar je de breuk wilt kleuren.", fractionSelected:"Gekozen breuk: {fraction}", fractionClear:"Leegmaken", clockTask:"Oefenopdracht", clockSet:"Zet de klok op {time}", clockHour:"Uur", clockMinute:"Minuten", placeNumber:"Getal", placeGame:"Spel", placeGameExplore:"Verkennen", placeGameBuild:"Bouwen", placeLevel:"Niveau", placeLevel1Hint:"Niveau 1: getallen tot 100 (T en E)", placeLevel2Hint:"Niveau 2: getallen tot 1000 (H, T en E)", placeLevel3Hint:"Niveau 3: getallen tot 1.000.000 (HD, TD, D, H, T en E)", placeBuildTitle:"Bouw het getal", placeYourTask:"Jouw opdracht", placeBuildHint:"Sleep de cijfers naar de vakken en bouw het getal.", placeDigitGhost:"?", placeIncomplete:"Maak eerst het hele getal af.", placeCorrect:"Goed gedaan! Kies de volgende opdracht. 🌟", placeTryAgain:"Bijna! Probeer het nog een keer.", placeNextTask:"Volgende opdracht", placeScore:"Score: {score} · reeks: {streak}", moneyMake:"Maak € {amount}", wordtypeSentence:"Zin", wordtypeHint:"Tik: blauw zelfstandig naamwoord · groen werkwoord · geel bijvoeglijk naamwoord", scheduleActivity:"activiteit…", scheduleEmpty:"Voeg de eerste activiteit toe.", scheduleCount:"{count} momenten op de planning", pollReset:"Opnieuw", pollQuestion:"Vraag", pollOptions:"Antwoorden (één per regel)", pollTotal:"{count} stemmen", moodTotal:"{count} kinderen hebben gekozen", moodVote:"Kies {face}", galleryFavorites:"Favorieten", galleryFavorite:"Favoriet maken", galleryEmpty:"Geen widgets gevonden.", gallerySearch:"Zoek widgets…", templateMath:"Rekenles", templateLanguage:"Taalles", templateDay:"Dagstart", exportBoard:"Exporteren", importBoard:"Importeren", importError:"Dit is geen geldig Teach-bordbestand.", widgetDuplicate:"Dupliceren", widgetLock:"Vastzetten", widgetUnlock:"Losmaken", widgetPreview:"Voorbeeld zonder bediening", undo:"Ongedaan maken", redo:"Opnieuw",
wg_sentence:"Zinsbouwer", wg_sentence_d:"Bouw zinnen met kleurgecodeerde zinsdelen en voeg eigen woorden toe.",
sentence_who:"Wie?", sentence_does:"Doet wat?", sentence_what:"Wat of waar?", sentenceHint:"Kies woorden om een zin te maken…",
sentenceAdd:"eigen woord of zinsdeel…", sentenceClear:"Wissen", sentenceSpeak:"🔊 Lees voor", sentenceRemove:"Verwijder woord", sentenceSurprise:"✨ Verrassingszin",
@ -454,7 +454,7 @@ const I18N = {
wg_mood:"Mood check-in", wg_mood_d:"Run a quick anonymous class check-in.",
wg_poll:"Class poll", wg_poll_d:"Vote together on up to six answers.",
clock24Hour:"24-hour clock",
widgetSettings:"Settings", clockWelcome:"Practise telling time", clockWelcomeHint:"Choose level 1, 2 or 3 at the top. In Settings, choose how the task is shown, then start.", clockSetTitle:"Move the hands to the correct time", clockReadTitle:"View the task and make the time", clockChooseTitle:"Choose the correct time block", clockYourTask:"Your task", clockLinkHands:"Move the minute hand along", clockTypeHint:"Enter the time as HH:MM.", clockLevel1Hint:"Level 1: whole and half hours", clockLevel2Hint:"Level 2: five-minute steps", clockLevel3Hint:"Level 3: every minute, with optional 24-hour time and day parts", clockBuildHint:"Drag four digits into the slots to make HH:MM.", clockDigitGhost:"?", clockDigit:"Digit {digit}", clockIncomplete:"Complete the full time first.", clockCorrect:"Well done! Choose the next task. 🌟", clockTryAgain:"Almost! Try again.", clockStartTask:"Start task", clockNextTask:"Next task", clockLevel:"Level", clockLevelHour:"Whole hours", clockLevelQuarter:"Quarter hours", clockLevelFive:"Five minutes", clockLevelMinute:"Every minute", clockLearningStyle:"Show task as", clockStyleAnalogue:"Analogue clock", clockStyleDigital:"Large digital time", clockStyleWords:"In words", clockStyleBoth:"Clock, digits and words", clockDragHint:"Drag the hands to change the time.", clockGame:"Game", clockGameSet:"Set the clock", clockGameRead:"Read the time", clockGameChoose:"Choose the time", clockStart:"Choose New task to begin", clockRead:"What time is it? Set your answer below.", clockChoose:"Which digital time matches this clock?", clockTry:"Not yet. The correct answer is {time}.", clockScore:"Score: {score} · streak: {streak}", clockWordsHour:"{hour} o'clock", clockWordsQuarterPast:"quarter past {hour}", clockWordsHalf:"half past {hour}", clockWordsQuarterTo:"quarter to {hour}", clockWordsPast:"{minutes} past {hour}", clockWordsTo:"{minutes} to {hour}", clockMorning:"in the morning", clockAfternoon:"in the afternoon", clockEvening:"in the evening", clockNight:"at night", fractionRows:"Rows", fractionLabels:"Show fractions", fractionHint:"Tap a row up to the part you want to colour.", fractionSelected:"Selected fraction: {fraction}", fractionClear:"Clear", clockTask:"Practice task", clockSet:"Set the clock to {time}", clockHour:"Hour", clockMinute:"Minutes", placeNumber:"Number", moneyMake:"Make € {amount}", wordtypeSentence:"Sentence", wordtypeHint:"Tap: blue noun · green verb · yellow adjective", scheduleActivity:"activity…", scheduleEmpty:"Add the first activity.", scheduleCount:"{count} moments in the schedule", pollReset:"Reset", pollQuestion:"Question", pollOptions:"Answers (one per line)", pollTotal:"{count} votes", moodTotal:"{count} children have chosen", moodVote:"Choose {face}", galleryFavorites:"Favourites", galleryFavorite:"Add favourite", galleryEmpty:"No widgets found.", gallerySearch:"Search widgets…", templateMath:"Maths lesson", templateLanguage:"Language lesson", templateDay:"Day start", exportBoard:"Export", importBoard:"Import", importError:"This is not a valid Teach board file.", widgetDuplicate:"Duplicate", widgetLock:"Lock", widgetUnlock:"Unlock", widgetPreview:"Preview without controls", undo:"Undo", redo:"Redo",
widgetSettings:"Settings", clockWelcome:"Practise telling time", clockWelcomeHint:"Choose level 1, 2 or 3 at the top. In Settings, choose how the task is shown, then start.", clockSetTitle:"Move the hands to the correct time", clockReadTitle:"View the task and make the time", clockChooseTitle:"Choose the correct time block", clockYourTask:"Your task", clockLinkHands:"Move the minute hand along", clockTypeHint:"Enter the time as HH:MM.", clockLevel1Hint:"Level 1: whole and half hours", clockLevel2Hint:"Level 2: five-minute steps", clockLevel3Hint:"Level 3: every minute, with optional 24-hour time and day parts", clockBuildHint:"Drag four digits into the slots to make HH:MM.", clockDigitGhost:"?", clockDigit:"Digit {digit}", clockIncomplete:"Complete the full time first.", clockCorrect:"Well done! Choose the next task. 🌟", clockTryAgain:"Almost! Try again.", clockStartTask:"Start task", clockNextTask:"Next task", clockLevel:"Level", clockLevelHour:"Whole hours", clockLevelQuarter:"Quarter hours", clockLevelFive:"Five minutes", clockLevelMinute:"Every minute", clockLearningStyle:"Show task as", clockStyleAnalogue:"Analogue clock", clockStyleDigital:"Large digital time", clockStyleWords:"In words", clockStyleBoth:"Clock, digits and words", clockDragHint:"Drag the hands to change the time.", clockGame:"Game", clockGameSet:"Set the clock", clockGameRead:"Read the time", clockGameChoose:"Choose the time", clockStart:"Choose New task to begin", clockRead:"What time is it? Set your answer below.", clockChoose:"Which digital time matches this clock?", clockTry:"Not yet. The correct answer is {time}.", clockScore:"Score: {score} · streak: {streak}", clockWordsHour:"{hour} o'clock", clockWordsQuarterPast:"quarter past {hour}", clockWordsHalf:"half past {hour}", clockWordsQuarterTo:"quarter to {hour}", clockWordsPast:"{minutes} past {hour}", clockWordsTo:"{minutes} to {hour}", clockMorning:"in the morning", clockAfternoon:"in the afternoon", clockEvening:"in the evening", clockNight:"at night", fractionRows:"Rows", fractionLabels:"Show fractions", fractionHint:"Tap a row up to the part you want to colour.", fractionSelected:"Selected fraction: {fraction}", fractionClear:"Clear", clockTask:"Practice task", clockSet:"Set the clock to {time}", clockHour:"Hour", clockMinute:"Minutes", placeNumber:"Number", placeGame:"Game", placeGameExplore:"Explore", placeGameBuild:"Build", placeLevel:"Level", placeLevel1Hint:"Level 1: numbers up to 100 (T and E)", placeLevel2Hint:"Level 2: numbers up to 1000 (H, T and E)", placeLevel3Hint:"Level 3: numbers up to 1,000,000 (HD, TD, D, H, T and E)", placeBuildTitle:"Build the number", placeYourTask:"Your task", placeBuildHint:"Drag the digits into the slots to build the number.", placeDigitGhost:"?", placeIncomplete:"Complete the whole number first.", placeCorrect:"Well done! Choose the next task. 🌟", placeTryAgain:"Almost! Try again.", placeNextTask:"Next task", placeScore:"Score: {score} · streak: {streak}", moneyMake:"Make € {amount}", wordtypeSentence:"Sentence", wordtypeHint:"Tap: blue noun · green verb · yellow adjective", scheduleActivity:"activity…", scheduleEmpty:"Add the first activity.", scheduleCount:"{count} moments in the schedule", pollReset:"Reset", pollQuestion:"Question", pollOptions:"Answers (one per line)", pollTotal:"{count} votes", moodTotal:"{count} children have chosen", moodVote:"Choose {face}", galleryFavorites:"Favourites", galleryFavorite:"Add favourite", galleryEmpty:"No widgets found.", gallerySearch:"Search widgets…", templateMath:"Maths lesson", templateLanguage:"Language lesson", templateDay:"Day start", exportBoard:"Export", importBoard:"Import", importError:"This is not a valid Teach board file.", widgetDuplicate:"Duplicate", widgetLock:"Lock", widgetUnlock:"Unlock", widgetPreview:"Preview without controls", undo:"Undo", redo:"Redo",
wg_sentence:"Sentence builder", wg_sentence_d:"Build sentences with colour-coded parts and add your own words.",
sentence_who:"Who?", sentence_does:"Does what?", sentence_what:"What or where?", sentenceHint:"Choose words to build a sentence…",
sentenceAdd:"your own word or phrase…", sentenceClear:"Clear", sentenceSpeak:"🔊 Read aloud", sentenceRemove:"Remove word", sentenceSurprise:"✨ Surprise sentence",

View file

@ -6,14 +6,19 @@ const DOTCODE = {
a:[1], b:[1,2], c:[1,4], d:[1,4,5], e:[1,5], f:[1,2,4], g:[1,2,4,5], h:[1,2,5],
i:[2,4], j:[2,4,5], k:[1,3], l:[1,2,3], m:[1,3,4], n:[1,3,4,5], o:[1,3,5],
p:[1,2,3,4], q:[1,2,3,4,5], r:[1,2,3,5], s:[2,3,4], t:[2,3,4,5], u:[1,3,6],
v:[1,2,3,6], w:[2,4,5,6], x:[1,3,4,6], y:[1,3,4,5,6], z:[1,3,5,6],
/* digits use the braille number patterns (aj) */
"1":[1], "2":[1,2], "3":[1,4], "4":[1,4,5], "5":[1,5],
"6":[1,2,4], "7":[1,2,4,5], "8":[1,2,5], "9":[2,4], "0":[2,4,5]
v:[1,2,3,6], w:[2,4,5,6], x:[1,3,4,6], y:[1,3,4,5,6], z:[1,3,5,6]
};
/* cijferblokken gebruikten hiervoor de braille a-j-patronen, maar die
gebruiken nooit posities 3/6 (de onderste rij) - op een kind-oog leken
alle tien blokken daardoor op elkaar. Een eigen codetabel die alle zes
stippen inzet, geeft elk cijfer een echt eigen vorm. */
const DIGIT_DOTCODE = {
"0":[], "1":[2], "2":[2,5], "3":[1,2,3], "4":[1,3,4,6],
"5":[1,2,3,4,5], "6":[1,2,3,4,5,6], "7":[1,2,4,5], "8":[1,2,3,4,6], "9":[1,4,6]
};
const VOWELS = "aeiou";
function dotGrid(letter){
const code = DOTCODE[letter] || [];
const code = (Object.prototype.hasOwnProperty.call(DIGIT_DOTCODE, letter) ? DIGIT_DOTCODE[letter] : DOTCODE[letter]) || [];
const grid = document.createElement("div");
grid.className = "dots";
[1,4,2,5,3,6].forEach(p=>{

View file

@ -708,9 +708,56 @@ function mountClock(root,init){
const feedback=document.createElement('p');feedback.className='clock-feedback '+(S.feedback==='good'?'ok':S.feedback?'bad':'');feedback.setAttribute('role','status');if(S.feedback)feedback.textContent=T(S.feedback==='good'?'clockCorrect':S.feedback==='incomplete'?'clockIncomplete':'clockTryAgain');card.append(feedback);const actions=document.createElement('div');actions.className='clock-actions';if(S.task&&S.game!=='choose'&&!S.solved&&!(S.game==='read'&&S.amode==='type'))actions.append(btn(T('numberlineCheck'),check,''));actions.append(btn(T('clockNextTask'),newTask,'ghost'));card.append(actions);if(S.task&&S.game==='set'){const hint=document.createElement('small');hint.className='clock-drag-hint';hint.textContent=T('clockDragHint');card.append(hint)}stage.append(card)};
style.onchange=draw;game.onchange=()=>{syncSettings();newTask()};learningShell(root,[field(T('clockLearningStyle'),style),field(T('clockGame'),game),linkSetting,hour24Setting],stage);syncSettings();newTask();return{getState:()=>({...S,choices:S.choices.map(x=>({...x})),answerDigits:[...S.answerDigits]})};
}
const PLACE_COLUMNS=['HD','TD','D','H','T','E'];
const placeLevelDigits=level=>level===1?2:level===2?3:6;
function mountPlacevalue(root,init){
const S=Object.assign({number:1342},init||{}),input=num(S.number,0,999999),stage=document.createElement('div');
const draw=()=>{S.number=Math.max(0,Math.min(999999,+input.value||0));const digits=String(S.number).padStart(6,'0').split('');stage.className='place-grid';stage.innerHTML='';['HD','TD','D','H','T','E'].forEach((x,i)=>{const d=document.createElement('div');const b=document.createElement('b');b.textContent=x;const o=document.createElement('output');o.textContent=digits[i];d.append(b,o);stage.append(d)})};input.oninput=draw;learningShell(root,[field(T('placeNumber'),input),btn('🎲',()=>{input.value=Math.floor(Math.random()*999999);draw()})],stage);draw();return{getState:()=>({...S})};
const S=Object.assign({number:1342,game:'explore',level:1,target:null,answerDigits:null,score:0,streak:0,solved:false,feedback:''},init||{});
S.level=Math.max(1,Math.min(3,+S.level||1));S.game=S.game==='build'?'build':'explore';if(!Array.isArray(S.answerDigits))S.answerDigits=null;
const input=num(S.number,0,999999),game=document.createElement('select'),stage=document.createElement('div');
game.add(new Option(T('placeGameExplore'),'explore'));game.add(new Option(T('placeGameBuild'),'build'));game.value=S.game;
const gameSetting=field(T('placeGame'),game),numberSetting=field(T('placeNumber'),input),diceBtn=btn('🎲',()=>{input.value=Math.floor(Math.random()*999999);drawExplore()});
const syncSettings=()=>{numberSetting.hidden=S.game!=='explore';diceBtn.hidden=S.game!=='explore'};
const activeCols=()=>PLACE_COLUMNS.slice(6-placeLevelDigits(S.level));
const maxForLevel=()=>Math.pow(10,placeLevelDigits(S.level))-1;
const woodDigit=value=>{const block=document.createElement('span');block.className='blok sm place-digit-block';const label=document.createElement('span');label.className='bl';label.textContent=String(value);const dots=dotGrid(Number(value));dots.classList.add('blok-dots');block.append(label,dots);return block};
const drawExplore=()=>{S.number=Math.max(0,Math.min(999999,+input.value||0));const digits=String(S.number).padStart(6,'0').split('');stage.className='place-grid';stage.innerHTML='';PLACE_COLUMNS.forEach((x,i)=>{const d=document.createElement('div');const b=document.createElement('b');b.textContent=x;const o=document.createElement('output');o.textContent=digits[i];d.append(b,o);stage.append(d)})};
const setFeedback=ok=>{if(S.solved)return;if(ok){S.solved=true;S.score++;S.streak++;S.feedback='good';sndGood()}else{S.streak=0;S.feedback='bad';sndBad()}drawBuild()};
const check=()=>{if(S.answerDigits.some(d=>d===null)){S.feedback='incomplete';sndBad();drawBuild();return}setFeedback(Number(S.answerDigits.join(''))===S.target)};
const needsNewTask=()=>S.target===null||!Array.isArray(S.answerDigits)||S.answerDigits.length!==activeCols().length;
const newTask=()=>{S.target=Math.floor(Math.random()*(maxForLevel()+1));S.answerDigits=activeCols().map(()=>null);S.solved=false;S.feedback='';drawBuild()};
const chooseLevel=value=>{S.level=value;S.score=0;S.streak=0;newTask()};
const buildAnswer=()=>{
const answer=document.createElement('div');answer.className='place-block-answer';const instruction=document.createElement('p');instruction.textContent=T('placeBuildHint');
const slots=document.createElement('div');slots.className='place-answer-slots';const cols=activeCols();
const makeSlot=index=>{const wrap=document.createElement('div');wrap.className='place-slot-col';const colLabel=document.createElement('b');colLabel.textContent=cols[index];
const slot=document.createElement('div');slot.className='slot sm place-digit-slot'+(S.answerDigits[index]!==null?' filled':'');slot.dataset.index=index;
const ghost=document.createElement('div');ghost.className='ghost';ghost.textContent=T('placeDigitGhost');const dots=dotGrid(S.answerDigits[index]===null?0:S.answerDigits[index]);dots.classList.add('slot-dots');slot.append(ghost,dots);
if(S.answerDigits[index]!==null){const placed=woodDigit(S.answerDigits[index]);placed.classList.add('in-slot','place-drag-block');slot.append(placed);slot.onclick=()=>{if(!S.solved){S.answerDigits[index]=null;S.feedback='';drawBuild()}}}
wrap.append(colLabel,slot);return wrap};
cols.forEach((_,i)=>slots.append(makeSlot(i)));
const tray=document.createElement('div');tray.className='place-digit-tray lg-tray';
const attachDrag=block=>{block.onpointerdown=e=>{if(S.solved||(e.pointerType==='mouse'&&e.button!==0))return;e.preventDefault();try{block.releasePointerCapture(e.pointerId)}catch(_){}const clone=block.cloneNode(true);clone.id='dragClone';document.body.append(clone);block.classList.add('dragging');const moveTo=ev=>{const zoom=VZ();clone.style.left=ev.clientX/zoom+'px';clone.style.top=ev.clientY/zoom+'px'},hit=ev=>{let found=null;slots.querySelectorAll('.place-digit-slot').forEach(slot=>{const r=slot.getBoundingClientRect(),pad=8;if(ev.clientX>=r.left-pad&&ev.clientX<=r.right+pad&&ev.clientY>=r.top-pad&&ev.clientY<=r.bottom+pad)found=slot});return found};moveTo(e);let hovered=null;const move=ev=>{moveTo(ev);const target=hit(ev);if(hovered&&hovered!==target)hovered.classList.remove('droptarget');hovered=target;if(hovered)hovered.classList.add('droptarget')},clean=()=>{document.removeEventListener('pointermove',move);document.removeEventListener('pointerup',up);document.removeEventListener('pointercancel',cancel);clone.remove();block.classList.remove('dragging');if(hovered)hovered.classList.remove('droptarget')},up=ev=>{const target=hit(ev);clean();if(target){S.answerDigits[+target.dataset.index]=+block.dataset.digit;S.feedback='';drawBuild()}},cancel=()=>clean();document.addEventListener('pointermove',move);document.addEventListener('pointerup',up);document.addEventListener('pointercancel',cancel)}};
'0123456789'.split('').forEach(d=>{const block=woodDigit(+d);block.classList.add('place-drag-block');block.dataset.digit=d;block.setAttribute('role','button');block.setAttribute('aria-label',T('clockDigit').replace('{digit}',d));block.tabIndex=0;block.onkeydown=e=>{if((e.key==='Enter'||e.key===' ')&&!S.solved){e.preventDefault();const index=S.answerDigits.indexOf(null);if(index>=0){S.answerDigits[index]=+d;drawBuild()}}};attachDrag(block);tray.append(block)});
answer.append(instruction,slots,tray);return answer;
};
const drawBuild=()=>{
stage.className='place-app';stage.innerHTML='';const top=document.createElement('div');top.className='place-top';
const levelControl=document.createElement('div');levelControl.className='place-level-control';const label=document.createElement('span');label.textContent=T('placeLevel');
const levels=document.createElement('div');levels.className='lg-levels place-levels';
[1,2,3].forEach(value=>{const button=document.createElement('button');button.type='button';button.textContent=T('lvl'+value);button.title=T('placeLevel'+value+'Hint');button.classList.toggle('active',S.level===value);button.onclick=()=>chooseLevel(value);levels.append(button)});
levelControl.append(label,levels);top.append(levelControl);
const score=document.createElement('span');score.className='place-score';score.textContent=T('placeScore').replace('{score}',S.score).replace('{streak}',S.streak);top.append(score);stage.append(top);
const card=document.createElement('section');card.className='place-task-card';const title=document.createElement('h3');title.textContent=T('placeBuildTitle');card.append(title);
const target=document.createElement('div');target.className='place-target';const targetLabel=document.createElement('span');targetLabel.textContent=T('placeYourTask');
const targetValue=document.createElement('output');targetValue.className='place-task-digital';targetValue.textContent=String(S.target);target.append(targetLabel,targetValue);card.append(target,buildAnswer());
const feedback=document.createElement('p');feedback.className='place-feedback '+(S.feedback==='good'?'ok':S.feedback?'bad':'');feedback.setAttribute('role','status');
if(S.feedback)feedback.textContent=T(S.feedback==='good'?'placeCorrect':S.feedback==='incomplete'?'placeIncomplete':'placeTryAgain');card.append(feedback);
const actions=document.createElement('div');actions.className='place-actions';if(!S.solved)actions.append(btn(T('numberlineCheck'),check,''));actions.append(btn(T('placeNextTask'),newTask,'ghost'));card.append(actions);stage.append(card);
};
input.oninput=drawExplore;game.onchange=()=>{S.game=game.value;syncSettings();if(S.game!=='build')drawExplore();else if(needsNewTask())newTask();else drawBuild()};
learningShell(root,[gameSetting,numberSetting,diceBtn],stage);syncSettings();
if(S.game!=='build')drawExplore();else if(needsNewTask())newTask();else drawBuild();
return{getState:()=>({...S,answerDigits:S.answerDigits?[...S.answerDigits]:null})};
}
function mountMoney(root,init){
const legacy=init||{},S=Object.assign({level:1,style:'both',game:'make',category:'all',productId:'juice',amode:'blocks',targetCents:375,paidCents:500,selected:[],given:[],input:'',answerDigits:[0,null,0,0],score:0,streak:0,solved:false,feedback:''},legacy);