diff --git a/VERSION b/VERSION index f900021..d692748 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.4.20-beta +0.4.21-beta diff --git a/public/css/teach.css b/public/css/teach.css index 282673a..199b49d 100644 --- a/public/css/teach.css +++ b/public/css/teach.css @@ -1722,6 +1722,61 @@ body.dark .bd-stage{background:radial-gradient(circle at 50% 20%,var(--surface-2 .wordtypes{display:flex;gap:9px;flex-wrap:wrap;justify-content:center}.wordtypes button{border:0;border-radius:10px;padding:10px 13px;font:inherit;font-weight:900;background:var(--surface-2)}.wordtypes button[data-type=noun]{background:#dce9ff;color:#174c9b}.wordtypes button[data-type=verb]{background:#dcf7e7;color:#176b3a}.wordtypes button[data-type=adjective]{background:#fff0cf;color:#855b00}.wordtypes p{flex-basis:100%;text-align:center;color:var(--muted);font-size:12px} .schedule{width:min(100%,450px);display:flex;flex-direction:column;gap:7px}.schedule>div{display:flex;align-items:center;gap:12px;padding:9px;background:var(--surface-2);border-radius:12px;border-left:4px solid var(--widget-accent)}.schedule-icon{width:33px;height:33px;display:grid;place-items:center;flex:none;border-radius:10px;background:var(--surface);font-size:20px;font-style:normal}.schedule time{font-weight:900;color:var(--blue)}.schedule span{flex:1;font-weight:800}.schedule .tbtn{height:30px;min-height:30px;padding:0 9px}.class-total{flex-basis:100%;width:100%;margin:0 0 3px;color:var(--muted);font-size:12px;font-weight:900;text-align:center}.mood{display:flex;gap:8px;flex-wrap:wrap}.mood .tbtn{flex:1;min-width:62px;font-size:36px;height:auto;padding:12px;background:var(--surface);border:2px solid var(--line);color:var(--ink)}.mood span{display:block;font-size:14px}.poll{width:min(100%,440px);display:flex;flex-direction:column;gap:8px}.poll h3{margin-bottom:0;text-align:center}.poll .tbtn{position:relative;overflow:hidden;text-align:left}.poll .tbtn i{position:absolute;inset:0 auto 0 0;background:color-mix(in srgb,var(--widget-accent) 22%,var(--surface));z-index:0;transition:width .25s}.poll .tbtn span{position:relative;float:right;font-weight:900}.poll .tbtn{isolation:isolate} @media(max-width:600px){.gallery{grid-template-columns:repeat(2,minmax(0,1fr))}.gcard{min-height:120px;padding:13px 8px}.gcard .gd{display:none}.widget .wdup,.widget .wpreview{display:none}.edu-stage{padding:10px}.place-grid b{font-size:9px}.place-grid output{padding:12px 2px}} +/* =========== Geluidsmeter =========== */ +.nm{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:14px} +.nm-stage{display:flex;align-items:flex-end;gap:14px;flex:1;min-height:0} +.nm-gauge{position:relative;width:70px;height:min(280px,100%);min-height:120px;border-radius:35px;background:var(--surface-2);border:1px solid var(--line);overflow:hidden;box-shadow:inset 0 2px 8px rgba(20,36,52,.12)} +.nm-fill{position:absolute;left:0;right:0;bottom:0;height:0%;border-radius:35px 35px 0 0;transition:height .25s ease-out,background-color .25s;background:var(--surface-2)} +.nm-fill.green{background:var(--green)} +.nm-fill.yellow{background:var(--yellow)} +.nm-fill.red{background:var(--red)} +.nm-mark{position:absolute;left:0;right:0;height:2px;background:rgba(255,255,255,.8);box-shadow:0 0 0 1px rgba(20,36,52,.25)} +.nm-face{font-size:52px;line-height:1} +.nm-gauge.warn{animation:nmwarn 1s infinite} +@keyframes nmwarn{0%,100%{box-shadow:inset 0 2px 8px rgba(20,36,52,.12)}50%{box-shadow:inset 0 2px 8px rgba(20,36,52,.12),0 0 0 4px color-mix(in srgb,var(--red) 45%,transparent)}} +.nm-status{min-height:26px;font-size:16px;font-weight:900;text-align:center} +.nm-zones{width:100%;max-width:320px;display:flex;flex-direction:column;gap:6px} +.nm-zone-row{display:flex;align-items:center;gap:8px;font-size:12.5px;font-weight:800;color:var(--muted)} +.nm-zone-row input{flex:1} +.nm-zone-lbl{flex:none;min-width:96px} +.nm-privacy{margin:0;max-width:320px;color:var(--muted);font-size:11px;text-align:center;line-height:1.4} +/* =========== Gevoelszones =========== */ +.fz{flex:1;min-height:0;display:flex;flex-direction:column;gap:14px;padding:14px;overflow:auto} +.fz-zones{display:grid;grid-template-columns:repeat(2,1fr);gap:10px} +.fz-zone{display:flex;flex-direction:column;align-items:center;gap:6px;padding:14px 10px;border-radius:18px;border:2px solid var(--line);background:var(--surface);cursor:pointer;font:inherit;color:var(--ink)} +.fz-zone.blue{border-color:color-mix(in srgb,var(--blue) 40%,var(--line))} +.fz-zone.green{border-color:color-mix(in srgb,var(--green) 40%,var(--line))} +.fz-zone.yellow{border-color:color-mix(in srgb,var(--yellow) 55%,var(--line))} +.fz-zone.red{border-color:color-mix(in srgb,var(--red) 40%,var(--line))} +.fz-zone.active.blue{background:color-mix(in srgb,var(--blue) 16%,var(--surface))} +.fz-zone.active.green{background:color-mix(in srgb,var(--green) 16%,var(--surface))} +.fz-zone.active.yellow{background:color-mix(in srgb,var(--yellow) 22%,var(--surface))} +.fz-zone.active.red{background:color-mix(in srgb,var(--red) 16%,var(--surface))} +.fz-emoji{font-size:32px} +.fz-label{font-weight:900;font-size:14px} +.fz-detail{padding:12px 16px;border-radius:16px;border:1px solid var(--line);background:var(--surface-2);text-align:center} +.fz-detail-title{margin:0 0 4px;font-size:16px} +.fz-detail-desc{margin:0 0 6px;font-weight:800;color:var(--ink)} +.fz-detail-tip{margin:0;color:var(--muted);font-size:13px;line-height:1.4} +.fz-detail.blue{border-color:color-mix(in srgb,var(--blue) 35%,var(--line))} +.fz-detail.green{border-color:color-mix(in srgb,var(--green) 35%,var(--line))} +.fz-detail.yellow{border-color:color-mix(in srgb,var(--yellow) 45%,var(--line))} +.fz-detail.red{border-color:color-mix(in srgb,var(--red) 35%,var(--line))} +/* =========== Ademhalingsoefening =========== */ +.bre{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:14px} +.bre-stage{flex:1;min-height:0;width:100%;display:flex;align-items:center;justify-content:center} +.bre-circle{width:120px;height:120px;border-radius:50%;background:radial-gradient(circle at 35% 30%,color-mix(in srgb,var(--blue) 55%,#fff),var(--blue));display:flex;flex-direction:column;align-items:center;justify-content:center;gap:2px;color:#fff;font-weight:900;transform:scale(1);transition:transform 1s ease-in-out;box-shadow:0 12px 30px rgba(20,54,97,.28)} +.bre-circle.hold{filter:brightness(.92)} +.bre-word{font-size:15px} +.bre-count{font-size:26px;font-variant-numeric:tabular-nums} +.bre-rounds{color:var(--muted);font-size:13px;font-weight:800} +.bre-row{display:flex;gap:10px;align-items:center} +/* =========== Complimentenmachine =========== */ +.cpl{flex:1;min-height:0;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:16px;padding:14px} +.cpl-card{width:min(100%,420px);min-height:140px;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:10px;padding:20px;border-radius:22px;background:linear-gradient(135deg,var(--pink),var(--purple));color:#fff;text-align:center;box-shadow:0 14px 30px rgba(139,92,246,.28);opacity:.55;transform:scale(.96);transition:opacity .2s,transform .2s} +.cpl-card.show{opacity:1;transform:scale(1)} +.cpl-icon{font-size:34px} +.cpl-text{margin:0;font-size:17px;font-weight:800;line-height:1.4;text-shadow:0 2px 6px rgba(0,0,0,.2)} /* Wereldoriëntatie: natuurherkenning en topografie */ .world-app{width:100%;min-height:100%;align-self:flex-start;display:flex;flex-direction:column;justify-content:flex-start;color:var(--ink)} diff --git a/public/index.html b/public/index.html index be8443c..b9f1d0a 100644 --- a/public/index.html +++ b/public/index.html @@ -35,6 +35,10 @@ + + + + diff --git a/public/js/board.js b/public/js/board.js index 2afe24b..f124323 100644 --- a/public/js/board.js +++ b/public/js/board.js @@ -61,6 +61,10 @@ const REGISTRY = [ { id:"light", cat:"class", icon:"🚦", w:480, h:440, minW:320, minH:320, mount:mountLight }, { id:"mood", cat:"class", icon:"🌡️", w:530, h:360, minW:350, minH:260, mount:mountMood }, { id:"poll", cat:"class", icon:"📊", w:540, h:500, minW:360, minH:330, mount:mountPoll }, + { id:"noise", cat:"class", icon:"🎙️", w:420, h:600, minW:320, minH:460, mount:mountNoise }, + { id:"feelings", cat:"class", icon:"🎭", w:560, h:520, minW:380, minH:400, mount:mountFeelings }, + { id:"breathe", cat:"class", icon:"🌬️", w:440, h:520, minW:320, minH:420, mount:mountBreathe }, + { id:"compliments", cat:"class", icon:"💝", w:460, h:400, minW:340, minH:300, mount:mountCompliments }, { id:"media", cat:"media", icon:"🎬", w:560, h:420, minW:320, minH:240, mount:mountMedia } ]; const WIDGET_CATS = [["favorites","galleryFavorites"],["taal","catTaal"],["rekenen","catRekenen"],["world","catWorld"],["class","catClass"],["tools","catTools"],["media","catMedia"]]; diff --git a/public/js/core.js b/public/js/core.js index f8446f5..216ffb3 100644 --- a/public/js/core.js +++ b/public/js/core.js @@ -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.20-beta"; +const VERSION = "0.4.21-beta"; (function(){ const tag = document.getElementById("verTag"); tag.textContent = "v"+VERSION; @@ -148,6 +148,20 @@ const I18N = { wg_schedule:"Dagplanning", wg_schedule_d:"Maak een duidelijke visuele planning voor de klas.", 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.", + wg_noise:"Geluidsmeter", wg_noise_d:"Meet het geluidsniveau in de klas met de microfoon - alles blijft lokaal, er wordt niets opgenomen of verstuurd.", + noiseOff:"Zet de microfoon aan om te starten.", noiseStatus_green:"Lekker rustig!", noiseStatus_yellow:"Een beetje luidruchtig…", noiseStatus_red:"Te luid! Even rustig aan.", + noiseDenied:"Kan niet bij de microfoon. Geef toestemming in de browser en probeer opnieuw.", + noiseStart:"Microfoon aan", noiseStop:"Stop", noiseZoneGreen:"Rustig tot", noiseZoneYellow:"Te luid vanaf", + noisePrivacy:"Er wordt niets opgenomen of verstuurd: het geluidsniveau blijft alleen in deze browser.", + wg_feelings:"Gevoelszones", wg_feelings_d:"Individuele check-in: kies je zone en krijg een passende tip om jezelf te helpen.", + fzZone_blue:"Blauw", fzZone_green:"Groen", fzZone_yellow:"Geel", fzZone_red:"Rood", + fzDesc_blue:"Ik voel me moe of verdrietig.", fzDesc_green:"Ik voel me kalm en klaar om te leren.", fzDesc_yellow:"Ik voel me druk, gestrest of opgewonden.", fzDesc_red:"Ik voel me boos of overprikkeld.", + fzTip_blue:"Even rustig zitten, wat water drinken of een boek pakken kan helpen.", fzTip_green:"Fijn zo! Dit is een goed moment om aan de slag te gaan.", fzTip_yellow:"Diep ademhalen of een korte pauze kan helpen om weer rustig te worden.", fzTip_red:"Ga naar de rustige plek, haal diep adem of vraag de juf of meester om hulp.", + wg_breathe:"Ademhalingsoefening", wg_breathe_d:"Adem rustig mee met de groeiende en krimpende cirkel, in drie tempo's.", + bre_in:"Adem in", bre_hold:"Houd vast", bre_out:"Adem uit", breStart:"Start", breStop:"Stop", breRounds:"{n} rondjes gedaan", + "brePattern_4-4":"4 tellen in, 4 uit", "brePattern_4-7-8":"4 in, 7 vast, 8 uit", "brePattern_4-4-4-4":"4 in, 4 vast, 4 uit, 4 vast", + wg_compliments:"Complimentenmachine", wg_compliments_d:"Trek een willekeurig compliment om iemand blij te maken.", + complimentsDraw:"Trek een compliment", 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", 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.", @@ -457,6 +471,20 @@ const I18N = { wg_schedule:"Day schedule", wg_schedule_d:"Create a clear visual class schedule.", 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.", + wg_noise:"Noise meter", wg_noise_d:"Measure the classroom noise level with the microphone - everything stays local, nothing is recorded or sent anywhere.", + noiseOff:"Turn on the microphone to start.", noiseStatus_green:"Nice and quiet!", noiseStatus_yellow:"A bit noisy…", noiseStatus_red:"Too loud! Let's settle down.", + noiseDenied:"Can't reach the microphone. Allow access in your browser and try again.", + noiseStart:"Microphone on", noiseStop:"Stop", noiseZoneGreen:"Quiet up to", noiseZoneYellow:"Too loud from", + noisePrivacy:"Nothing is recorded or sent anywhere: the noise level stays in this browser only.", + wg_feelings:"Feelings zones", wg_feelings_d:"Individual check-in: pick your zone and get a matching tip to help yourself.", + fzZone_blue:"Blue", fzZone_green:"Green", fzZone_yellow:"Yellow", fzZone_red:"Red", + fzDesc_blue:"I feel tired or sad.", fzDesc_green:"I feel calm and ready to learn.", fzDesc_yellow:"I feel restless, stressed or excited.", fzDesc_red:"I feel angry or overwhelmed.", + fzTip_blue:"Sitting quietly, drinking some water, or grabbing a book can help.", fzTip_green:"Great! This is a good moment to get started.", fzTip_yellow:"Taking a deep breath or a short break can help you feel calm again.", fzTip_red:"Go to the quiet spot, take a deep breath, or ask your teacher for help.", + wg_breathe:"Breathing exercise", wg_breathe_d:"Breathe along with the growing and shrinking circle, in three paces.", + bre_in:"Breathe in", bre_hold:"Hold", bre_out:"Breathe out", breStart:"Start", breStop:"Stop", breRounds:"{n} rounds done", + "brePattern_4-4":"4 counts in, 4 out", "brePattern_4-7-8":"4 in, 7 hold, 8 out", "brePattern_4-4-4-4":"4 in, 4 hold, 4 out, 4 hold", + wg_compliments:"Compliment machine", wg_compliments_d:"Draw a random compliment to make someone's day.", + complimentsDraw:"Draw a compliment", 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", 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.", diff --git a/public/js/widgets/breathe.js b/public/js/widgets/breathe.js new file mode 100644 index 0000000..95cc8d7 --- /dev/null +++ b/public/js/widgets/breathe.js @@ -0,0 +1,81 @@ +/* widget: Ademhalingsoefening */ +/* ========================================================= + Rustgevende ademhaling met een groeiende/krimpende cirkel, drie tempo's. + De cirkelgrootte wordt via inline style/transition-duration gestuurd + (niet via CSS-klassen) zodat elke fase precies aansluit op de vorige + zonder terugspringen. +==========================================================*/ +const BREATHE_PATTERNS = { "4-4": [["in",4],["out",4]], "4-7-8": [["in",4],["hold",7],["out",8]], "4-4-4-4": [["in",4],["hold",4],["out",4],["hold",4]] }; +const BREATHE_SCALE = { in: 1.4, hold: null, out: .7 }; +function mountBreathe(root, initState){ + let pattern = (initState && BREATHE_PATTERNS[initState.pattern]) ? initState.pattern : "4-4"; + let running = false, phase = -1, phaseStart = 0, rafId = null, rounds = 0; + root.innerHTML = ` +