v0.3.02-beta: maak tekenwerkbalk compact
All checks were successful
dev - build & deploy naar test / build-and-deploy (push) Successful in 9s

This commit is contained in:
Ramon 2026-07-14 10:57:56 +02:00
parent 09a5d84bba
commit d864299ea6
5 changed files with 142 additions and 21 deletions

View file

@ -1 +1 @@
0.3.01-beta 0.3.02-beta

View file

@ -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 .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 .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 */ 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%);}
}

View file

@ -43,11 +43,32 @@
<button class="hud-btn hud-orient"></button> <button class="hud-btn hud-orient"></button>
<button class="hud-btn hud-min"></button> <button class="hud-btn hud-min"></button>
<div id="penbar"> <div id="penbar">
<div class="wp-group pen-tools"></div> <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> <div class="wp-group pen-colors"></div>
<div class="wp-group pen-sizes"></div> <label class="pen-custom"><span class="pen-custom-label"></span><input type="color" value="#26344a"></label>
<div class="wp-group pen-bgs"></div> </div>
<div class="wp-group pen-extras"></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-clear">🗑</button>
<button class="wp-clear pen-close"></button> <button class="wp-clear pen-close"></button>
</div> </div>

View file

@ -277,8 +277,51 @@ new ResizeObserver(bResize).observe(board);
bResize(); bResize();
const PEN = { tool:"pen", color:"#26344a", size:4, nib:4 }; 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 BOARD = { bg:"blank" };
const penbar = document.getElementById("penbar"); 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){ function setPen(on){
document.body.classList.toggle("pen", on); document.body.classList.toggle("pen", on);
document.body.classList.toggle("pen-text", on && PEN.tool==="text"); 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])=>{ PEN_TOOLS.forEach(([id,icon])=>{
const b = document.createElement("button"); const b = document.createElement("button");
b.dataset.tool = id; b.textContent = icon; 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); penbar.querySelector(".pen-tools").appendChild(b);
}); });
["#26344a","#3b7dd8","#e0554d","#3fae6a","#9b59b6","#f7c948"].forEach(c=>{ DEFAULT_PEN_COLORS.forEach(c=>makeColorButton(c, penbar.querySelector(".pen-colors")));
const b = document.createElement("button"); renderRecentPenColors();
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);
});
[3,5,8].forEach(s=>{ [3,5,8].forEach(s=>{
const b = document.createElement("button"); const b = document.createElement("button");
b.className = "wp-size"; b.dataset.s = s; b.className = "wp-size"; b.dataset.s = s;
const i = document.createElement("i"); const i = document.createElement("i");
i.style.width = i.style.height = (s+4)+"px"; i.style.width = i.style.height = (s+4)+"px";
b.appendChild(i); b.appendChild(i);
b.addEventListener("click", ()=>{ PEN.size = s; refreshPenbar(); }); b.addEventListener("click", ()=>{ PEN.size = s; closePenPickers(); refreshPenbar(); });
penbar.querySelector(".pen-sizes").appendChild(b); penbar.querySelector(".pen-sizes").appendChild(b);
}); });
const PEN_BGS = [["blank","bgBlank"],["dots","bgDots"],["lines","bgLines"],["write","bgWrite"],["grid-s","bgGridS"],["grid-b","bgGridB"]]; 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"); const b = document.createElement("button");
b.className = "wp-bg"; b.dataset.bg = id; b.className = "wp-bg"; b.dataset.bg = id;
b.appendChild(document.createElement("i")); b.appendChild(document.createElement("i"));
b.addEventListener("click", ()=>setBoardBg(id)); b.addEventListener("click", ()=>{ setBoardBg(id); closePenPickers(); });
penbar.querySelector(".pen-bgs").appendChild(b); penbar.querySelector(".pen-bgs").appendChild(b);
}); });
const PEN_EXTRAS = [["ruler","📏","toolRuler"],["geo","📐","toolGeo"],["lens","🔍","toolLens"],["light","🚦","wg_light"]]; const PEN_EXTRAS = [["ruler","📏","toolRuler"],["geo","📐","toolGeo"],["lens","🔍","toolLens"],["light","🚦","wg_light"]];
PEN_EXTRAS.forEach(([id,icon])=>{ PEN_EXTRAS.forEach(([id,icon])=>{
const b = document.createElement("button"); const b = document.createElement("button");
b.dataset.extra = id; b.textContent = icon; b.dataset.extra = id; b.textContent = icon;
b.addEventListener("click", ()=>toggleDTool(id)); b.addEventListener("click", ()=>{ toggleDTool(id); closePenPickers(); });
penbar.querySelector(".pen-extras").appendChild(b); 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)); 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) */ /* the ✍-button toggles drawing mode on/off (palette itself stays visible) */
penbar.querySelector(".pen-close").addEventListener("click", ()=>setPen(!document.body.classList.contains("pen"))); 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}); new MutationObserver(updateHud).observe(dock, {childList:true});
updateHud(); updateHud();
function refreshPenbar(){ 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=>{ penbar.querySelectorAll(".pen-tools button").forEach(b=>{
b.classList.toggle("on", b.dataset.tool===PEN.tool); b.classList.toggle("on", b.dataset.tool===PEN.tool);
b.title = T(PEN_TOOLS.find(x=>x[0]===b.dataset.tool)[2]); b.title = T(PEN_TOOLS.find(x=>x[0]===b.dataset.tool)[2]);

View file

@ -2,7 +2,7 @@
"use strict"; "use strict";
/* version shown until /api/version resolves (or if the fetch fails, e.g. offline). /* 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. */ 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(){ (function(){
const tag = document.getElementById("verTag"); const tag = document.getElementById("verTag");
tag.textContent = "v"+VERSION; 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.", wg_write:"Schrijfblad", wg_write_d:"Schrijven en rekenen: pen, gum en papier met schrijflijnen of rekenblokjes.",
bgBlank:"Leeg blad", bgLines:"Lijntjes", bgWrite:"Schrijflijnen", bgBlank:"Leeg blad", bgLines:"Lijntjes", bgWrite:"Schrijflijnen",
bgGridS:"Kleine rekenblokjes", bgGridB:"Grote rekenblokjes", 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)", toolPen:"Pen", toolFountain:"Vulpen (dun en dik, reageert op druk of snelheid)",
toolMark:"Markeerstift", toolText:"Tekst typen (klik op het blad)", toolMark:"Markeerstift", toolText:"Tekst typen (klik op het blad)",
wMin:"Minimaliseren", wMax:"Herstellen", 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.", 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", bgBlank:"Blank sheet", bgLines:"Lines", bgWrite:"Handwriting lines",
bgGridS:"Small maths squares", bgGridB:"Big maths squares", 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)", toolPen:"Pen", toolFountain:"Fountain pen (thin and thick, follows pressure or speed)",
toolMark:"Highlighter", toolText:"Type text (click the sheet)", toolMark:"Highlighter", toolText:"Type text (click the sheet)",
wMin:"Minimize", wMax:"Restore", wMin:"Minimize", wMax:"Restore",