teach/public/index.html
Ramon 24d89b8bb5
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 36s
feat: eigen personage (avatar) voor leerlingen, ouders en staf (v0.4.23-beta)
- Nieuwe migratie db/018_user_avatar.sql: kolom avatar_data (jsonb) op users.
- publicUser() geeft avatar overal automatisch mee (login, /auth/me,
  /admin/users) - bestaande school/klas-rechten regelen vanzelf wie welke
  personages ziet, geen nieuwe permissie-sleutel nodig.
- Nieuwe route PUT /me/avatar (open voor elke ingelogde rol, zelfde patroon
  als /me/data): valideert soort/kleuren/haar/accessoires, slaat de eigen rij op.
- Nieuwe gedeelde module public/js/avatar.js: personage samenstellen uit
  soort (dino/monster/dier - elk met een vast eigen lichaamsdeel, ook de
  gevraagde dinostekels), lichaams- en haarkleur, haarstijl en hoofd-/
  lichaamsaccessoires; gedeelde editor-UI voor zowel de instellingen-sectie
  "Mijn personage" als de nieuwe widget.
- Instellingen-sectie "Mijn personage" (cms.js-patroon) - de leerling-toegang,
  want widgets van categorie "class" zijn niet toewijsbaar in het
  afgeschermde leerling-speelscherm.
- Widget 🦖 toegevoegd aan het bord (categorie "class") voor staf en
  volledige-whiteboard-gebruikers.
- School-navigator (admin.js): leerkracht/beheerder ziet nu het personage
  van elk klaslid in de personenlijst i.p.v. altijd de initialen-cirkel.
- Voorbereiding gerelateerd roadmappunt: math.js toont het eigen personage
  kort naast "Goed!" als aanmoediging (pilot in één widget, geen nieuw
  feedbacktype); zonder ingesteld personage blijft het gedrag ongewijzigd.
- Vertalingen (nl+en) voor de nieuwe widget, instellingen-sectie en editor.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014EPxzBVRXZZnBPvSAaPAbJ
2026-07-19 07:34:25 +02:00

310 lines
15 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<html lang="nl">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>teachus.today</title>
<link rel="icon" type="image/svg+xml" href="img/logo.svg?v=__V__">
<link rel="preload" href="fonts/quicksand.woff2" as="font" type="font/woff2" crossorigin>
<link rel="stylesheet" href="css/teach.css?v=__V__">
<link rel="stylesheet" href="css/image-catalog.css?v=__V__">
<!-- alle app-scripts: defer = parallel downloaden, uitvoeren in dézelfde
volgorde na het parsen (de volgorde is dwingend: core levert T()/api(),
de widgets leveren mount-functies aan REGISTRY in board.js, app.js start).
?v=__V__ wordt server-side vervangen door de app-versie (cache-busting;
zie src/frontend.js) zodat js/css een jaar immutable gecachet mag worden. -->
<script src="js/core.js?v=__V__" defer></script>
<script src="js/permissions.js?v=__V__" defer></script>
<script src="js/settings.js?v=__V__" defer></script>
<script src="js/cms.js?v=__V__" defer></script>
<script src="js/data.js?v=__V__" defer></script>
<script src="js/avatar.js?v=__V__" defer></script>
<script src="js/explorer.js?v=__V__" defer></script>
<script src="js/picker.js?v=__V__" defer></script>
<script src="js/widgets/letters.js?v=__V__" defer></script>
<script src="js/widgets/write.js?v=__V__" defer></script>
<script src="js/widgets/math.js?v=__V__" defer></script>
<script src="js/widgets/media.js?v=__V__" defer></script>
<script src="js/widgets/notes.js?v=__V__" defer></script>
<script src="js/widgets/timer.js?v=__V__" defer></script>
<script src="js/widgets/flits.js?v=__V__" defer></script>
<script src="js/widgets/hak.js?v=__V__" defer></script>
<script src="js/widgets/anchor.js?v=__V__" defer></script>
<script src="js/widgets/ball.js?v=__V__" defer></script>
<script src="js/widgets/dice.js?v=__V__" defer></script>
<script src="js/widgets/light.js?v=__V__" defer></script>
<script src="js/widgets/names.js?v=__V__" defer></script>
<script src="js/widgets/mind.js?v=__V__" defer></script>
<script src="js/widgets/birthday.js?v=__V__" defer></script>
<script src="js/widgets/noise.js?v=__V__" defer></script>
<script src="js/widgets/feelings.js?v=__V__" defer></script>
<script src="js/widgets/breathe.js?v=__V__" defer></script>
<script src="js/widgets/compliments.js?v=__V__" defer></script>
<script src="js/widgets/numberline.js?v=__V__" defer></script>
<script src="js/widgets/sentence.js?v=__V__" defer></script>
<script src="js/widgets/learning.js?v=__V__" defer></script>
<script src="js/widgets/world.js?v=__V__" defer></script>
<script src="js/board.js?v=__V__" defer></script>
<script src="js/pupil.js?v=__V__" defer></script>
<script src="js/parent.js?v=__V__" defer></script>
<script src="js/admin.js?v=__V__" defer></script>
<script src="js/cast.js?v=__V__" defer></script>
<script src="js/image-catalog.js?v=__V__" defer></script>
<script src="js/app.js?v=__V__" defer></script>
</head>
<body>
<div id="toolbar">
<div class="logo" title="teachus.today">
<img class="logo-owl" src="img/logo.svg" alt="teachus.today">
<span class="logo-word"><span class="lw-teach">teach</span><span class="lw-us">us</span><span class="lw-dot">.</span><span class="lw-today">today</span></span>
</div>
<button class="tbtn ghost" id="btnMute">🔊</button>
<button class="tbtn ghost" id="btnZoomOut">A</button>
<button class="tbtn ghost" id="zoomPct">100%</button>
<button class="tbtn ghost" id="btnZoomIn">A+</button>
<div class="spacer"></div>
<button class="tbtn ghost" id="btnImages">🖼️</button>
<button class="tbtn ghost" id="btnFolders">📁</button>
<button class="tbtn ghost" id="btnSave">💾 <span data-i18n="saveBoard"></span></button>
<button class="tbtn ghost" id="btnCast" title="Cast">📡</button>
<button class="tbtn ghost" id="btnUser">⚙️ <span id="userLbl"></span></button>
<button class="tbtn ghost" id="btnFs" title="Fullscreen"></button>
</div>
<div id="board"></div>
<div id="pupilView"></div>
<div id="parentView"></div>
<button id="fab"></button>
<button id="fabBoardsBtn">🗂</button>
<div id="boardStrip"></div>
<div class="modalwrap" id="explorerWrap" role="dialog" aria-modal="true" aria-labelledby="explorerTitle">
<div class="modal explorer">
<div class="gallery-head"><h2 id="explorerTitle"></h2><button class="wclose explorer-close" type="button"></button></div>
<div class="ex-bar">
<div class="ex-crumbs" id="exCrumbs"></div>
<div class="ex-tools" id="exTools"></div>
</div>
<div class="ex-msg" id="exMsg"></div>
<div class="ex-grid" id="exGrid"></div>
</div>
</div>
<div id="fabmenu">
<button class="fab-item" data-f="widgets"><span class="fi">🧩</span><span class="fl"></span></button>
<button class="fab-item" data-f="anchor"><span class="fi"></span><span class="fl"></span></button>
<button class="fab-item" data-f="draw"><span class="fi">✏️</span><span class="fl"></span></button>
<button class="fab-item" data-f="mind"><span class="fi">🕸️</span><span class="fl"></span></button>
<button class="fab-item" data-f="img"><span class="fi">🖼️</span><span class="fl"></span></button>
</div>
<div id="hud">
<div class="pen-grip"></div>
<button class="hud-btn hud-orient"></button>
<button class="hud-btn hud-min"></button>
<div id="penbar">
<div class="pen-picker" data-picker="tools">
<button class="pen-current" type="button" aria-expanded="false"><span class="pen-current-value">✏️</span><span class="pen-caret"></span></button>
<div class="wp-group pen-tools pen-popover"></div>
</div>
<div class="pen-picker" data-picker="colors">
<button class="pen-current" type="button" aria-expanded="false"><i class="pen-current-color"></i><span class="pen-caret"></span></button>
<div class="pen-popover pen-color-popover">
<div class="pen-pop-label pen-recent-label"></div>
<div class="wp-group pen-recent"></div>
<div class="pen-pop-label pen-palette-label"></div>
<div class="wp-group pen-colors"></div>
<label class="pen-custom"><span class="pen-custom-label"></span><input type="color" value="#26344a"></label>
</div>
</div>
<div class="pen-picker" data-picker="sizes">
<button class="pen-current" type="button" aria-expanded="false"><i class="pen-current-size"></i><span class="pen-caret"></span></button>
<div class="wp-group pen-sizes pen-popover"></div>
</div>
<div class="pen-picker" data-picker="backgrounds">
<button class="pen-current" type="button" aria-expanded="false"><i class="pen-current-bg"></i><span class="pen-caret"></span></button>
<div class="wp-group pen-bgs pen-popover"></div>
</div>
<div class="pen-picker" data-picker="extras">
<button class="pen-current" type="button" aria-expanded="false"><span class="pen-current-value">•••</span><span class="pen-caret"></span></button>
<div class="wp-group pen-extras pen-popover"></div>
</div>
<button class="wp-clear pen-clear">🗑</button>
<button class="wp-clear pen-close"></button>
</div>
<div class="hud-div"></div>
<div id="dock"></div>
</div>
<div class="modalwrap" id="galleryWrap" role="dialog" aria-modal="true" aria-labelledby="galleryTitle">
<div class="modal">
<div class="gallery-head"><h2 id="galleryTitle" data-i18n="chooseWidget"></h2><button class="wclose gallery-close" type="button"></button></div>
<div class="gallery-tools"><input id="gallerySearch" type="search" autocomplete="off"><button id="galleryFavOnly" class="tbtn ghost" type="button"></button></div>
<div id="galleryTemplates"></div>
<div class="gallery-data"><button id="boardUndo" class="tbtn ghost" type="button"></button><button id="boardRedo" class="tbtn ghost" type="button"></button><button id="boardExport" class="tbtn ghost" type="button"></button><button id="boardImport" class="tbtn ghost" type="button"></button></div>
<div id="gallery"></div>
</div>
</div>
<div class="modalwrap" id="loginWrap">
<div class="modal" id="loginModal">
<div class="login-logo">
<img class="logo-owl" src="img/logo.svg" alt="">
<span class="logo-word"><span class="lw-teach">teach</span><span class="lw-us">us</span><span class="lw-dot">.</span><span class="lw-today">today</span></span>
</div>
<div class="ed-tabs">
<button type="button" class="active" data-tab="login" data-i18n="tabLogin"></button>
<button type="button" data-tab="link" data-i18n="tabLinkCode"></button>
<button type="button" data-tab="parentreg" data-i18n="tabParentReg"></button>
</div>
<div id="loginBox">
<select id="liSchool"></select>
<input type="text" id="liName" maxlength="30" autocomplete="username">
<input type="password" id="liPass" maxlength="64" autocomplete="current-password">
<div class="lrow">
<button class="tbtn" id="btnLogin" data-i18n="loginBtn"></button>
</div>
</div>
<div id="linkBox" style="display:none;">
<div class="lisub" data-i18n="linkIntro"></div>
<input type="text" id="liCode" maxlength="9" style="text-transform:uppercase;">
<div class="lrow">
<input type="password" id="liPassNew" maxlength="64" autocomplete="new-password">
<button class="tbtn ghost pwtoggle" type="button" id="btnLiPassToggle" data-i18n="pwShow"></button>
</div>
<input type="password" id="liPassNew2" maxlength="64" autocomplete="new-password">
<div class="lrow">
<button class="tbtn ghost" id="btnLink" data-i18n="linkBtn"></button>
</div>
</div>
<div id="parentRegBox" style="display:none;">
<div class="lisub" data-i18n="parentRegIntro"></div>
<input type="text" id="prName" maxlength="30" autocomplete="username">
<div class="lrow">
<input type="password" id="prPass" maxlength="64" autocomplete="new-password">
<button class="tbtn ghost pwtoggle" type="button" id="btnPrPassToggle" data-i18n="pwShow"></button>
</div>
<input type="password" id="prPass2" maxlength="64" autocomplete="new-password">
<div class="lrow">
<button class="tbtn ghost" id="btnParentReg" data-i18n="parentRegBtn"></button>
</div>
</div>
<div id="loginMsg" class="formMsg"></div>
<div class="guestnote" data-i18n="guestNote"></div>
<button class="tbtn ghost" type="button" id="btnGuestView" style="margin-top:10px;">🎨 <span data-i18n="stView"></span></button>
</div>
</div>
<div class="modalwrap" id="settingsWrap">
<div class="modal st-modal" id="settingsModal">
<div class="st-head">
<h2 data-i18n="settingsTitle"></h2>
<input type="search" id="stSearch" maxlength="60" autocomplete="off">
<button class="wclose" id="btnSettingsClose"></button>
</div>
<div class="st-body">
<nav id="stNav"></nav>
<div id="stContent">
<div id="stSecHead"></div>
<div id="stSearchResults" style="display:none;"></div>
<div class="st-section" data-section="account" style="display:none;">
<div class="am-reset">
<div class="am-name" id="helloName"></div>
<div class="am-role" id="helloRole"></div>
</div>
<div id="pwSection">
<div class="am-h3 am-h3-toggle" id="pwToggleHead" data-i18n="stPassword"></div>
<div class="am-reset" id="ownPwBlock">
<div id="settingsMsg" class="formMsg"></div>
<div class="lrow">
<input type="password" id="ownPwCurrent" maxlength="64" autocomplete="current-password">
</div>
<div class="lrow">
<input type="password" id="ownPw" maxlength="64" autocomplete="new-password">
<button class="tbtn ghost pwtoggle" type="button" id="btnOwnPwToggle" data-i18n="pwShow"></button>
</div>
<div class="lrow">
<input type="password" id="ownPw2" maxlength="64" autocomplete="new-password">
<button class="tbtn ghost" id="btnOwnPw" data-i18n="pwChange"></button>
</div>
</div>
</div>
<div class="am-h3" id="backupsHead" data-i18n="stBackups"></div>
<div class="am-reset" id="backupsBlock">
<div id="backupsList"></div>
<div class="guestnote" data-i18n="backupsNote"></div>
</div>
<div class="am-h3" data-i18n="stSession"></div>
<div class="am-reset">
<div class="lrow">
<button class="tbtn ghost" id="btnLogout" data-i18n="logout"></button>
</div>
<div class="guestnote" data-i18n="storedNote"></div>
</div>
</div>
<div class="st-section" data-section="view" style="display:none;">
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwTheme"></span>
<div class="seg" id="segTheme">
<button type="button" data-v="light" data-i18n="vwLight"></button>
<button type="button" data-v="dark" data-i18n="vwDark"></button>
<button type="button" data-v="auto" data-i18n="vwAuto"></button>
</div>
</div>
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwContrast"></span>
<div class="seg" id="segHC">
<button type="button" data-v="off" data-i18n="vwOff"></button>
<button type="button" data-v="on" data-i18n="vwOn"></button>
</div>
</div>
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwLang"></span>
<div class="seg" id="langToggle">
<button type="button" data-lang="nl">NL</button>
<button type="button" data-lang="en">EN</button>
</div>
</div>
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwTbar"></span>
<div class="seg" id="segTbar">
<button type="button" data-v="top" data-i18n="vwTop"></button>
<button type="button" data-v="left" data-i18n="vwLeft"></button>
<button type="button" data-v="right" data-i18n="vwRight"></button>
</div>
</div>
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwMotion"></span>
<div class="seg" id="segMotion">
<button type="button" data-v="on" data-i18n="vwOn"></button>
<button type="button" data-v="off" data-i18n="vwOff"></button>
</div>
</div>
<div class="vw-row">
<span class="vw-lbl" data-i18n="vwGlass"></span>
<div class="seg" id="segGlass">
<button type="button" data-v="on" data-i18n="vwOn"></button>
<button type="button" data-v="off" data-i18n="vwOff"></button>
</div>
</div>
<div class="guestnote" data-i18n="vwNote"></div>
</div>
<div class="st-section" data-section="admin" style="display:none;">
<div id="adminModal"></div>
</div>
</div>
</div>
<div class="st-footer">
<span id="verTag"></span>
<span id="copyright"></span>
</div>
</div>
</div>
</body>
</html>