diff --git a/VERSION b/VERSION
index 2359ad6..e43e170 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.3.01-beta
+0.3.02-beta
diff --git a/public/css/teach.css b/public/css/teach.css
index 86b9ef8..ccd6e36 100644
--- a/public/css/teach.css
+++ b/public/css/teach.css
@@ -1267,3 +1267,46 @@ body.dark .dock-item:hover{background:var(--surface-3);}
body.dark .wp-group button.on{background:var(--surface-3); box-shadow:none;}
body.dark .lg-kbd button,body.dark .blok{filter:saturate(.9) brightness(.96);}
body.dark .bs-card canvas{background:#fff;} /* bord-preview blijft de echte (witte) bordkleur tonen */
+
+
+/* ---------- compacte tekenwerkbalk met selectie-popovers ---------- */
+#penbar{flex-wrap:nowrap; gap:4px; overflow:visible;}
+.pen-picker{position:relative; flex:none;}
+.pen-current{
+ width:42px; height:32px; padding:0 5px; border:1px solid transparent; border-radius:9px;
+ background:var(--surface-2); color:var(--ink); cursor:pointer; display:flex;
+ align-items:center; justify-content:center; gap:3px; font:inherit;
+}
+.pen-current:hover,.pen-picker.open .pen-current{background:var(--surface-3); border-color:var(--line);}
+.pen-current-value{font-size:16px; line-height:1; min-width:18px; text-align:center;}
+.pen-caret{font-size:8px; color:var(--muted); transform:rotate(0); transition:transform .15s;}
+.pen-picker.open .pen-caret{transform:rotate(180deg);}
+.pen-current-color{width:18px; height:18px; border-radius:50%; box-shadow:inset 0 0 0 1px rgba(0,0,0,.16);}
+.pen-current-size{display:block; border-radius:50%; background:var(--ink);}
+.pen-current-bg{width:19px; height:19px; display:block; border:1.5px solid #9db6d8; border-radius:4px; background:#fff;}
+.pen-current-bg.bg-dots{background-image:radial-gradient(circle,#9db6d8 1px,transparent 1px); background-size:5px 5px;}
+.pen-current-bg.bg-lines{background-image:repeating-linear-gradient(#0000 0 4px,#9db6d8 4px 5px);}
+.pen-current-bg.bg-write{background-image:repeating-linear-gradient(#0000 0 2px,#c3d5ec 2px 3px,#0000 3px 5px,#6d92c4 5px 6px);}
+.pen-current-bg.bg-grid-s{background-image:repeating-linear-gradient(#0000 0 4px,#9db6d8 4px 5px),repeating-linear-gradient(90deg,#0000 0 4px,#9db6d8 4px 5px);}
+.pen-current-bg.bg-grid-b{background-image:repeating-linear-gradient(#0000 0 8px,#9db6d8 8px 9px),repeating-linear-gradient(90deg,#0000 0 8px,#9db6d8 8px 9px);}
+#penbar .pen-popover{
+ display:none; position:absolute; left:50%; bottom:calc(100% + 10px); transform:translateX(-50%);
+ z-index:20; width:max-content; max-width:min(300px,calc(100vw - 24px)); padding:7px;
+ background:var(--surface); border:1px solid var(--line); border-radius:12px;
+ box-shadow:var(--shadow-2); gap:5px;
+}
+#penbar .pen-picker.open>.pen-popover{display:flex;}
+#penbar .pen-color-popover{width:210px; flex-direction:column; align-items:stretch;}
+#penbar .pen-color-popover .wp-group{display:grid; grid-template-columns:repeat(6,28px); background:transparent; padding:0;}
+.pen-pop-label{font-size:10px; font-weight:800; color:var(--muted); text-transform:uppercase; letter-spacing:.45px; margin:2px 2px 0;}
+.pen-custom{display:flex; align-items:center; justify-content:space-between; gap:10px; margin-top:3px; padding-top:7px; border-top:1px solid var(--line); font-size:12px; font-weight:700;}
+.pen-custom input{width:34px; height:26px; padding:0; border:0; border-radius:7px; background:transparent; cursor:pointer;}
+#hud.vert #penbar .pen-popover{left:calc(100% + 10px); bottom:auto; top:50%; transform:translateY(-50%);}
+#hud.vert #penbar .pen-color-popover{display:none;}
+#hud.vert #penbar .pen-picker.open>.pen-color-popover{display:flex;}
+body.hc #penbar .pen-current,body.hc #penbar .pen-popover{border:2px solid #000;}
+@media (max-width:600px),(pointer:coarse){
+ #penbar{gap:3px;}
+ .pen-current{width:40px; height:36px;}
+ #penbar .pen-popover{position:fixed; left:50%; bottom:70px; transform:translateX(-50%);}
+}
diff --git a/public/index.html b/public/index.html
index 249501b..887b49f 100644
--- a/public/index.html
+++ b/public/index.html
@@ -43,11 +43,32 @@
-
-
-
-
-
+
+
+
+
+
+
+
+
diff --git a/public/js/board.js b/public/js/board.js
index 1577edf..fb18c83 100644
--- a/public/js/board.js
+++ b/public/js/board.js
@@ -277,8 +277,51 @@ new ResizeObserver(bResize).observe(board);
bResize();
const PEN = { tool:"pen", color:"#26344a", size:4, nib:4 };
+const DEFAULT_PEN_COLORS = ["#26344a","#3b7dd8","#e0554d","#3fae6a","#9b59b6","#f7c948"];
+const RECENT_COLOR_KEY = "teachRecentPenColors";
+let recentPenColors = [];
+try{ recentPenColors = JSON.parse(localStorage.getItem(RECENT_COLOR_KEY) || "[]").filter(c=>/^#[0-9a-f]{6}$/i.test(c)).slice(0,6); }catch(e){}
const BOARD = { bg:"blank" };
const penbar = document.getElementById("penbar");
+function closePenPickers(except){
+ penbar.querySelectorAll(".pen-picker.open").forEach(p=>{
+ if(p===except) return;
+ p.classList.remove("open");
+ p.querySelector(".pen-current").setAttribute("aria-expanded","false");
+ });
+}
+penbar.querySelectorAll(".pen-picker").forEach(p=>{
+ p.querySelector(".pen-current").addEventListener("click", e=>{
+ e.stopPropagation();
+ const open = !p.classList.contains("open");
+ closePenPickers();
+ p.classList.toggle("open", open);
+ p.querySelector(".pen-current").setAttribute("aria-expanded", String(open));
+ });
+});
+document.addEventListener("pointerdown", e=>{ if(!e.target.closest(".pen-picker")) closePenPickers(); });
+function makeColorButton(c, host){
+ const b = document.createElement("button");
+ b.className = "wp-color"; b.dataset.c = c; b.type = "button";
+ const dot = document.createElement("i"); dot.style.background = c;
+ b.appendChild(dot);
+ b.addEventListener("click", ()=>selectPenColor(c));
+ host.appendChild(b);
+}
+function renderRecentPenColors(){
+ const host = penbar.querySelector(".pen-recent");
+ host.innerHTML = "";
+ recentPenColors.forEach(c=>makeColorButton(c, host));
+ penbar.querySelector(".pen-recent-label").hidden = recentPenColors.length===0;
+ host.hidden = recentPenColors.length===0;
+}
+function selectPenColor(c){
+ PEN.color = c.toLowerCase();
+ if(PEN.tool==="erase") PEN.tool = "pen";
+ recentPenColors = [PEN.color, ...recentPenColors.filter(x=>x!==PEN.color)].slice(0,6);
+ try{ localStorage.setItem(RECENT_COLOR_KEY, JSON.stringify(recentPenColors)); }catch(e){}
+ renderRecentPenColors(); closePenPickers(); refreshPenbar();
+}
function setPen(on){
document.body.classList.toggle("pen", on);
document.body.classList.toggle("pen-text", on && PEN.tool==="text");
@@ -295,24 +338,18 @@ const PEN_TOOLS = [["hand","✋","toolHand"],["pen","✏️","toolPen"],["vulp",
PEN_TOOLS.forEach(([id,icon])=>{
const b = document.createElement("button");
b.dataset.tool = id; b.textContent = icon;
- b.addEventListener("click", ()=>{ PEN.tool = id; setPen(true); refreshPenbar(); });
+ b.addEventListener("click", ()=>{ PEN.tool = id; setPen(true); closePenPickers(); refreshPenbar(); });
penbar.querySelector(".pen-tools").appendChild(b);
});
-["#26344a","#3b7dd8","#e0554d","#3fae6a","#9b59b6","#f7c948"].forEach(c=>{
- const b = document.createElement("button");
- b.className = "wp-color"; b.dataset.c = c;
- const dot = document.createElement("i"); dot.style.background = c;
- b.appendChild(dot);
- b.addEventListener("click", ()=>{ PEN.color = c; if(PEN.tool==="erase") PEN.tool = "pen"; refreshPenbar(); });
- penbar.querySelector(".pen-colors").appendChild(b);
-});
+DEFAULT_PEN_COLORS.forEach(c=>makeColorButton(c, penbar.querySelector(".pen-colors")));
+renderRecentPenColors();
[3,5,8].forEach(s=>{
const b = document.createElement("button");
b.className = "wp-size"; b.dataset.s = s;
const i = document.createElement("i");
i.style.width = i.style.height = (s+4)+"px";
b.appendChild(i);
- b.addEventListener("click", ()=>{ PEN.size = s; refreshPenbar(); });
+ b.addEventListener("click", ()=>{ PEN.size = s; closePenPickers(); refreshPenbar(); });
penbar.querySelector(".pen-sizes").appendChild(b);
});
const PEN_BGS = [["blank","bgBlank"],["dots","bgDots"],["lines","bgLines"],["write","bgWrite"],["grid-s","bgGridS"],["grid-b","bgGridB"]];
@@ -320,16 +357,19 @@ PEN_BGS.forEach(([id])=>{
const b = document.createElement("button");
b.className = "wp-bg"; b.dataset.bg = id;
b.appendChild(document.createElement("i"));
- b.addEventListener("click", ()=>setBoardBg(id));
+ b.addEventListener("click", ()=>{ setBoardBg(id); closePenPickers(); });
penbar.querySelector(".pen-bgs").appendChild(b);
});
const PEN_EXTRAS = [["ruler","📏","toolRuler"],["geo","📐","toolGeo"],["lens","🔍","toolLens"],["light","🚦","wg_light"]];
PEN_EXTRAS.forEach(([id,icon])=>{
const b = document.createElement("button");
b.dataset.extra = id; b.textContent = icon;
- b.addEventListener("click", ()=>toggleDTool(id));
+ b.addEventListener("click", ()=>{ toggleDTool(id); closePenPickers(); });
penbar.querySelector(".pen-extras").appendChild(b);
});
+const customPenColor = penbar.querySelector(".pen-custom input");
+customPenColor.addEventListener("input", ()=>selectPenColor(customPenColor.value));
+customPenColor.addEventListener("click", e=>e.stopPropagation());
penbar.querySelector(".pen-clear").addEventListener("click", ()=>bCtx.clearRect(0,0,bInk.width,bInk.height));
/* the ✍-button toggles drawing mode on/off (palette itself stays visible) */
penbar.querySelector(".pen-close").addEventListener("click", ()=>setPen(!document.body.classList.contains("pen")));
@@ -367,6 +407,23 @@ function updateHud(){ hud.classList.toggle("has-dock", dock.children.length > 0)
new MutationObserver(updateHud).observe(dock, {childList:true});
updateHud();
function refreshPenbar(){
+ const toolDef = PEN_TOOLS.find(x=>x[0]===PEN.tool);
+ penbar.querySelector('[data-picker="tools"] .pen-current-value').textContent = toolDef[1];
+ penbar.querySelector('[data-picker="tools"] .pen-current').title = T(toolDef[2]);
+ const currentColor = penbar.querySelector(".pen-current-color");
+ currentColor.style.background = PEN.color;
+ penbar.querySelector('[data-picker="colors"] .pen-current').title = T("penColor");
+ const currentSize = penbar.querySelector(".pen-current-size");
+ currentSize.style.width = currentSize.style.height = (PEN.size+4)+"px";
+ penbar.querySelector('[data-picker="sizes"] .pen-current').title = T("penSize");
+ const currentBg = penbar.querySelector(".pen-current-bg");
+ currentBg.className = "pen-current-bg bg-"+BOARD.bg;
+ penbar.querySelector('[data-picker="backgrounds"] .pen-current').title = T(PEN_BGS.find(x=>x[0]===BOARD.bg)[1]);
+ penbar.querySelector('[data-picker="extras"] .pen-current').title = T("penExtras");
+ penbar.querySelector(".pen-recent-label").textContent = T("recentColors");
+ penbar.querySelector(".pen-palette-label").textContent = T("paletteColors");
+ penbar.querySelector(".pen-custom-label").textContent = T("customColor");
+ customPenColor.value = PEN.color;
penbar.querySelectorAll(".pen-tools button").forEach(b=>{
b.classList.toggle("on", b.dataset.tool===PEN.tool);
b.title = T(PEN_TOOLS.find(x=>x[0]===b.dataset.tool)[2]);
diff --git a/public/js/core.js b/public/js/core.js
index 1d0d373..89d00fa 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.3.01-beta";
+const VERSION = "0.3.02-beta";
(function(){
const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION;
@@ -38,7 +38,7 @@ const I18N = {
wg_write:"Schrijfblad", wg_write_d:"Schrijven en rekenen: pen, gum en papier met schrijflijnen of rekenblokjes.",
bgBlank:"Leeg blad", bgLines:"Lijntjes", bgWrite:"Schrijflijnen",
bgGridS:"Kleine rekenblokjes", bgGridB:"Grote rekenblokjes",
- penSize:"Dikte", eraser:"Gum", clearAll:"Alles wissen",
+ penSize:"Dikte", penColor:"Kleur", penExtras:"Hulpmiddelen", recentColors:"Recent gebruikt", paletteColors:"Kleuren", customColor:"Eigen kleur", eraser:"Gum", clearAll:"Alles wissen",
toolPen:"Pen", toolFountain:"Vulpen (dun en dik, reageert op druk of snelheid)",
toolMark:"Markeerstift", toolText:"Tekst typen (klik op het blad)",
wMin:"Minimaliseren", wMax:"Herstellen",
@@ -178,7 +178,7 @@ const I18N = {
wg_write:"Writing pad", wg_write_d:"Writing and maths: pen, eraser and paper with handwriting lines or maths squares.",
bgBlank:"Blank sheet", bgLines:"Lines", bgWrite:"Handwriting lines",
bgGridS:"Small maths squares", bgGridB:"Big maths squares",
- penSize:"Thickness", eraser:"Eraser", clearAll:"Clear all",
+ penSize:"Thickness", penColor:"Colour", penExtras:"Tools", recentColors:"Recently used", paletteColors:"Colours", customColor:"Custom colour", eraser:"Eraser", clearAll:"Clear all",
toolPen:"Pen", toolFountain:"Fountain pen (thin and thick, follows pressure or speed)",
toolMark:"Highlighter", toolText:"Type text (click the sheet)",
wMin:"Minimize", wMax:"Restore",