Wijzigen voor document Tekenveld

Laatst gewijzigd door Jeeves de Butler op 23-10-2026 02:42

Van versie 2.1
gewijzigd door Jeeves de Butler
op 16-10-2026 12:29
Opmerking bij wijziging: Er is geen opmerking bij deze versie
Naar versie 10.1
gewijzigd door Jeeves de Butler
op 20-10-2026 10:41
Opmerking bij wijziging: Er is geen opmerking bij deze versie

Samenvatting

Details

Paginaeigenschappen
Inhoud
... ... @@ -1,4 +1,4 @@
1 -Tekenveld op de startpagina van de Kennisbank: een bijna onzichtbaar veld van letters en cijfers achter de inhoud, met een muisspoor, hartslagringen, af en toe een ECG-lijn en een smiley bij een klik op de lege achtergrond. Het is hetzelfde veld als in Ember, in een rustige variant: het vervaagt naar het midden en ligt stil na een minuut zonder muis.
1 +Tekenveld op de startpagina van de Kennisbank: een bijna onzichtbaar veld van letters en cijfers achter de inhoud, met een muisspoor, hartslagringen, af en toe een rode ECG-lijn en een smiley bij een klik op de lege achtergrond. Vijf snelle klikken op de lege achtergrond geven een explosie; daarna komt het veld in een minuut terug. Rond feestdagen en herdenkingen (Sint-Maarten, Sinterklaas, Kerst, Nieuwjaar, Pasen, Koningsdag, 4 en 5 mei) neemt een seizoensthema het over, met de kalender uit Ember; ?tekenveld=kerst in de adresbalk toont een thema meteen. Het is hetzelfde veld als in Ember, in een rustige variant: het vervaagt naar het midden en ligt stil na een minuut zonder muis.
2 2  
3 3  Het object //JavaScriptExtension// op deze pagina is de engine van Ember plus een korte opstart; het object //StyleSheetExtension// zet het canvas achter de inhoud. Beide staan op //onDemand// en worden alleen geladen door [[Startpagina>>doc:Wardenburg.Code.Startpagina]].
4 4  
XWiki.JavaScriptExtension[0]
code
... ... @@ -1,5 +1,5 @@
1 1  /* Tekenveld voor de Kennisbank. Gemaakt met maak-tekenveld.js uit de skill xwiki-wardenburg-admin.
2 - Engine: Ember src/lib/glyphField.js, commit 2af047d. Niet hier wijzigen: pas de bron aan en maak opnieuw. */
2 + Engine: Ember src/lib/glyphField.js, commit 83ea536. Niet hier wijzigen: pas de bron aan en maak opnieuw. */
3 3  (function () {
4 4  "use strict";
5 5  // /src/lib/glyphField.js
... ... @@ -258,9 +258,11 @@
258 258   const styles = getComputedStyle(root);
259 259   const text = styles.getPropertyValue("--text").trim() || "#111827";
260 260   const accent = styles.getPropertyValue("--brand-red").trim() || "#e62b27";
261 + // Een eigen kleur voor de hartslaglijn (ECG); zonder --glyph-line is dat het accent, zoals in Ember.
262 + const line = styles.getPropertyValue("--glyph-line").trim() || accent;
261 261   // :root zonder attribuut is in Ember donker; zie layout.css.
262 262   const mode = root.getAttribute("data-appearance") === "light" ? "light" : "dark";
263 - return { text, accent, mode };
265 + return { text, accent, line, mode };
264 264  }
265 265  
266 266  /* De tekens van een thema: een niet-lege lijst van hooguit 256 tekens (de index staat in een Uint8Array). */
... ... @@ -305,9 +305,12 @@
305 305   let trail = new Float32Array(0);
306 306   let pulse = new Float32Array(0);
307 307   let streak = new Float32Array(0);
310 + // Het spoor van de hartslaglijn apart van dat van de smileys, zodat de lijn een eigen kleur kan hebben.
311 + let lineTrail = new Float32Array(0);
308 308   let quiet = new Float32Array(0);
309 309   let neutralAtlas = null;
310 310   let accentAtlas = null;
315 + let lineAtlas = null;
311 311   let yellowAtlas = null;
312 312   let orangeAtlas = null;
313 313   let smokeAtlas = null;
... ... @@ -356,6 +356,7 @@
356 356   function buildAtlases() {
357 357   neutralAtlas = buildAtlas(theme.text);
358 358   accentAtlas = buildAtlas(theme.accent);
364 + lineAtlas = theme.line === theme.accent ? accentAtlas : buildAtlas(theme.line);
359 359   // Vuurkleuren voor de explosie.
360 360   yellowAtlas = buildAtlas(theme.mode === "light" ? "#eab308" : "#facc15");
361 361   orangeAtlas = buildAtlas("#f97316");
... ... @@ -387,6 +387,7 @@
387 387   trail = new Float32Array(cols * rows);
388 388   pulse = new Float32Array(cols * rows);
389 389   streak = new Float32Array(cols * rows);
396 + lineTrail = new Float32Array(cols * rows);
390 390   quiet = new Float32Array(cols * rows);
391 391   minC = minR = 1;
392 392   maxC = maxR = 0;
... ... @@ -398,7 +398,7 @@
398 398  
399 399   function applyTheme() {
400 400   const next = readTheme();
401 - if (next.text === theme.text && next.accent === theme.accent && next.mode === theme.mode) return;
408 + if (next.text === theme.text && next.accent === theme.accent && next.line === theme.line && next.mode === theme.mode) return;
402 402   theme = next;
403 403   buildAtlases();
404 404   lastAmbient = -Infinity;
... ... @@ -625,7 +625,7 @@
625 625   const phase = x / ecg.beatWidth + ecg.offset;
626 626   const y = ecg.baseY - (ecgShape(phase % 1) * beatScale(ecg, Math.floor(phase)) + wander) * ecg.amplitude;
627 627   if (Math.hypot(x - ecg.stampX, y - ecg.stampY) >= options.ecgRadius * 0.3) {
628 - stamp(x, y, options.ecgRadius, options.ecgStrength, true, streak);
635 + stamp(x, y, options.ecgRadius, options.ecgStrength, true, lineTrail);
629 629   ecg.stampX = x;
630 630   ecg.stampY = y;
631 631   }
... ... @@ -801,8 +801,9 @@
801 801   let heat = trail[index];
802 802   let beat = pulse[index];
803 803   let flash = streak[index];
811 + let pulseLine = lineTrail[index];
804 804   let hush = quiet[index];
805 - if (heat <= 0 && beat <= 0 && flash <= 0 && hush <= 0) continue;
813 + if (heat <= 0 && beat <= 0 && flash <= 0 && pulseLine <= 0 && hush <= 0) continue;
806 806  
807 807   if (heat > 0) {
808 808   heat *= decay;
... ... @@ -819,12 +819,17 @@
819 819   if (flash < 0.03) flash = 0;
820 820   streak[index] = flash;
821 821   }
830 + if (pulseLine > 0) {
831 + pulseLine *= streakDecay;
832 + if (pulseLine < 0.03) pulseLine = 0;
833 + lineTrail[index] = pulseLine;
834 + }
822 822   if (hush > 0) {
823 823   hush *= streakDecay;
824 824   if (hush < 0.03) hush = 0;
825 825   quiet[index] = hush;
826 826   }
827 - if (heat === 0 && beat === 0 && flash === 0 && hush === 0) continue;
840 + if (heat === 0 && beat === 0 && flash === 0 && pulseLine === 0 && hush === 0) continue;
828 828  
829 829   if (c < nextMinC) nextMinC = c;
830 830   if (c > nextMaxC) nextMaxC = c;
... ... @@ -832,7 +832,7 @@
832 832   if (r > nextMaxR) nextMaxR = r;
833 833  
834 834   // Onder het spoor en de hartslag wisselen de tekens snel.
835 - if (dt > 0 && Math.random() < (heat * 9 + beat * 5 + flash * 6) * dt) glyphs[index] = randomGlyph();
848 + if (dt > 0 && Math.random() < (heat * 9 + beat * 5 + (flash + pulseLine) * 6) * dt) glyphs[index] = randomGlyph();
836 836  
837 837   // Een verse ring is roder dan het spoor; verder weg is hij al zwakker en daarmee
838 838   // vanzelf grijzer.
... ... @@ -846,9 +846,10 @@
846 846   const x = cellX(c);
847 847   const sx = glyphs[index] * tileW;
848 848   // Achter een smiley wordt het rustende veld stilgezet (hush), zodat het gezicht
849 - // niet in de wolken verdwijnt. De lijn en de smileys (flash) zijn vooral rood.
850 - const neutral = ambient[index] * calm * (1 - lift) * (1 - hush) + neutralMax * lift + streakNeutral * flash;
862 + // niet in de wolken verdwijnt. De smileys (flash) en de hartslaglijn (pulseLine) zijn vooral rood; de lijn in --glyph-line.
863 + const neutral = ambient[index] * calm * (1 - lift) * (1 - hush) + neutralMax * lift + streakNeutral * Math.max(flash, pulseLine);
851 851   const red = Math.max(glow * glow * accentMax, flash * streakAccent);
865 + const lineRed = pulseLine * streakAccent;
852 852   ctx.clearRect(x, y, tileW, tileH);
853 853   if (neutral >= MIN_ALPHA) {
854 854   ctx.globalAlpha = neutral;
... ... @@ -858,6 +858,10 @@
858 858   ctx.globalAlpha = Math.min(1, red);
859 859   ctx.drawImage(accentAtlas, sx, 0, tileW, tileH, x, y, tileW, tileH);
860 860   }
875 + if (lineRed > 0.01) {
876 + ctx.globalAlpha = Math.min(1, lineRed);
877 + ctx.drawImage(lineAtlas, sx, 0, tileW, tileH, x, y, tileW, tileH);
878 + }
861 861   }
862 862   }
863 863  
... ... @@ -1186,6 +1186,7 @@
1186 1186   fallers = null;
1187 1187   canvas.style.transform = "";
1188 1188   streak.fill(0);
1207 + lineTrail.fill(0);
1189 1189   quiet.fill(0);
1190 1190   minC = minR = 1;
1191 1191   maxC = maxR = 0;
... ... @@ -1287,6 +1287,13 @@
1287 1287  // beeldverkeer op de terminalserver); bij de eerste beweging loopt het weer.
1288 1288  const KB_STILSTAND_MS = 60000;
1289 1289  
1309 +// De explosie van de engine: vijf klikken op de lege achtergrond binnen 1,6 seconde, met een
1310 +// nasleep van een minuut. Dezelfde getallen als de standaard van de engine; hier alleen om
1311 +// tijdens de explosie het vervagen naar het midden weg te halen en het veld wakker te houden.
1312 +const KB_BOEM_KLIKKEN = 5;
1313 +const KB_BOEM_VENSTER_MS = 1600;
1314 +const KB_BOEM_DUUR_MS = 62000;
1315 +
1290 1290  function kbTekenveld() {
1291 1291   // Alleen op de startpagina; de extensie wordt ook alleen daar geladen.
1292 1292   if (!document.querySelector(".wb-home")) return;
... ... @@ -1299,29 +1299,59 @@
1299 1299   canvas.setAttribute("aria-hidden", "true");
1300 1300   document.body.prepend(canvas);
1301 1301  
1328 + const isAchtergrond = (event) => event.target instanceof Element && !event.target.closest(KB_NIET_ACHTERGROND);
1302 1302   const opties = {
1303 1303   ...glyphFieldSettings({ intensity: "lijst" }),
1304 - // Geen atoomexplosie op de Kennisbank.
1305 - nukeClicks: Infinity,
1306 - isBackground: (event) => event.target instanceof Element && !event.target.closest(KB_NIET_ACHTERGROND),
1331 + isBackground: isAchtergrond,
1307 1307   obstacles: () => Array.from(document.querySelectorAll(KB_BLOKKEN), (element) => element.getBoundingClientRect()),
1308 1308   };
1309 1309  
1310 1310   let veld = null;
1311 1311   let rustTimer = 0;
1337 + let klikken = [];
1338 + let boemTot = 0;
1312 1312  
1313 - function wakker() {
1314 - if (!veld) veld = createGlyphField(canvas, opties);
1340 + function slaap() {
1341 + // Tijdens een explosie niet: de nasleep duurt een minuut, ook zonder muis.
1342 + if (Date.now() < boemTot) {
1343 + rustTimer = window.setTimeout(slaap, boemTot - Date.now());
1344 + return;
1345 + }
1346 + // Het laatste beeld blijft staan; alleen de animatie stopt.
1347 + veld.destroy();
1348 + veld = null;
1349 + }
1350 +
1351 + // De klik die een slapend veld wekt, ziet de engine zelf niet (hij luistert pas daarna);
1352 + // die telt hier dus ook niet mee, anders lopen de twee tellers uiteen.
1353 + let wekklik = null;
1354 +
1355 + function wakker(event) {
1356 + if (!veld) {
1357 + veld = createGlyphField(canvas, opties);
1358 + wekklik = event || null;
1359 + }
1315 1315   window.clearTimeout(rustTimer);
1316 - rustTimer = window.setTimeout(() => {
1317 - // Het laatste beeld blijft staan; alleen de animatie stopt.
1318 - veld.destroy();
1319 - veld = null;
1320 - }, KB_STILSTAND_MS);
1361 + rustTimer = window.setTimeout(slaap, KB_STILSTAND_MS);
1321 1321   }
1322 1322  
1364 + function telKlik(event) {
1365 + const nu = Date.now();
1366 + if (event === wekklik || event.button !== 0 || nu < boemTot || !isAchtergrond(event)) return;
1367 + klikken = klikken.filter((tijd) => nu - tijd < KB_BOEM_VENSTER_MS);
1368 + klikken.push(nu);
1369 + if (klikken.length < KB_BOEM_KLIKKEN) return;
1370 + klikken = [];
1371 + boemTot = nu + KB_BOEM_DUUR_MS;
1372 + // Tijdens de explosie het hele scherm, niet alleen de randen.
1373 + canvas.classList.add("kb-tekenveld--boem");
1374 + window.setTimeout(() => canvas.classList.remove("kb-tekenveld--boem"), KB_BOEM_DUUR_MS);
1375 + }
1376 +
1323 1323   window.addEventListener("pointermove", wakker, { passive: true });
1378 + // Eerst wakker maken, zodat de engine de klik ook krijgt als het veld sliep.
1324 1324   window.addEventListener("pointerdown", wakker, { passive: true });
1380 + window.addEventListener("pointerdown", telKlik, { passive: true });
1325 1325   wakker();
1326 1326  }
1327 1327  
XWiki.StyleSheetExtension[0]
Caching policy
... ... @@ -1,0 +1,1 @@
1 +long
Code
... ... @@ -1,0 +1,54 @@
1 +/* Tekenveld op de startpagina van de Kennisbank (Wardenburg.Code.Tekenveld); alleen geladen op
2 + de startpagina. Het canvas ligt achter de inhoud; de vlakken eromheen worden doorzichtig. */
3 +
4 +/* Kleuren voor de engine: de tekstkleur en het donkere oranje (brand-warning) uit het kleurthema;
5 + de hartslaglijn in het rood van het thema (brand-danger). */
6 +html {
7 + --text: #202124;
8 + --brand-red: #e8920c;
9 + --glyph-line: #d0021b;
10 + background: #ffffff;
11 +}
12 +
13 +body,
14 +#body,
15 +#contentcontainer,
16 +#contentcolumn,
17 +#mainContentArea,
18 +.main,
19 +#xwikicontent,
20 +.wb-home {
21 + background: transparent !important;
22 +}
23 +
24 +canvas.kb-tekenveld {
25 + position: fixed;
26 + inset: 0;
27 + z-index: -1;
28 + pointer-events: none;
29 + /* Naar het midden, waar het zoekveld en de tegels staan, vervaagt het veld tot 40%: het zwakke
30 + grijze veld is daar vrijwel weg, de rode hartslaglijn komt er nog doorheen. */
31 + -webkit-mask-image: radial-gradient(ellipse 48% 58% at 50% 55%, rgba(0, 0, 0, .4) 45%, #000 100%);
32 + mask-image: radial-gradient(ellipse 48% 58% at 50% 55%, rgba(0, 0, 0, .4) 45%, #000 100%);
33 +}
34 +
35 +/* Tijdens de explosie (vijf snelle klikken) het hele scherm. */
36 +canvas.kb-tekenveld.kb-tekenveld--boem {
37 + -webkit-mask-image: none;
38 + mask-image: none;
39 +}
40 +
41 +/* Leesbaarheid: een zachte witte gloed achter de kop, het zoekveld en elke tegel. */
42 +.wb-zoek > h1,
43 +.wb-zoek > p,
44 +.wb-zoek > form,
45 +.wb-tegels > li > a {
46 + background: radial-gradient(closest-side, rgba(255, 255, 255, .97) 62%, rgba(255, 255, 255, 0) 100%);
47 +}
48 +
49 +.wb-home-onder {
50 + background: #ffffff;
51 + border-radius: 16px;
52 + box-shadow: 0 0 36px 28px #ffffff;
53 +}
54 +
Content Type
... ... @@ -1,0 +1,1 @@
1 +CSS
Naam
... ... @@ -1,0 +1,1 @@
1 +Wardenburg tekenveld
Inhoud verwerken
... ... @@ -1,0 +1,1 @@
1 +Nee
Gebruik deze uitbreiding
... ... @@ -1,0 +1,1 @@
1 +onDemand
XWiki.XWikiRights[0]
allow
... ... @@ -1,0 +1,1 @@
1 +Sta toe
levels
... ... @@ -1,0 +1,1 @@
1 +view
users
... ... @@ -1,0 +1,1 @@
1 +XWiki.XWikiGuest
XWiki.XWikiRights[1]
allow
... ... @@ -1,0 +1,1 @@
1 +Sta toe
groups
... ... @@ -1,0 +1,1 @@
1 +XWiki.XWikiAllGroup
levels
... ... @@ -1,0 +1,1 @@
1 +view
XWiki.XWikiRights[2]
allow
... ... @@ -1,0 +1,1 @@
1 +Sta toe
groups
... ... @@ -1,0 +1,1 @@
1 +XWiki.XWikiAdminGroup
levels
... ... @@ -1,0 +1,1 @@
1 +view,edit,comment,script