From d864299ea6f3776a4307b93b8c679d38c3c800da Mon Sep 17 00:00:00 2001 From: Ramon Date: Tue, 14 Jul 2026 10:57:56 +0200 Subject: [PATCH] v0.3.02-beta: maak tekenwerkbalk compact --- VERSION | 2 +- public/css/teach.css | 43 +++++++++++++++++++++++ public/index.html | 31 ++++++++++++++--- public/js/board.js | 81 +++++++++++++++++++++++++++++++++++++------- public/js/core.js | 6 ++-- 5 files changed, 142 insertions(+), 21 deletions(-) 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",