1// What every Jev site's pages share: the copy buttons, and the dot fields 2// behind the page. Nothing here talks to the server. 3 4// The copy buttons: `data-copy` names the element whose link or text is 5// copied. 6(() => { 7 document.addEventListener("click", (event) => { 8 const button = event.target.closest("[data-copy]"); 9 if (!button) return; 10 const link = document.getElementById(button.dataset.copy); 11 if (!link) return; 12 navigator.clipboard.writeText(link.href ?? link.textContent).then( 13 () => { button.textContent = "Copied"; }, 14 () => { getSelection().selectAllChildren(link); button.textContent = "Selected, press Ctrl+C"; } 15 ); 16 }); 17})(); 18 19// The dot fields behind the page, after typesafe.ai: soft blobs put through an 20// 8x8 ordered dither, one image pixel per dot, tiled. Drawn once into CSS 21// variables rather than into elements, because a page may replace whole 22// sections of itself and a canvas inside one would be wiped with it. 23(() => { 24 const BAYER = [ 25 [0, 32, 8, 40, 2, 34, 10, 42], [48, 16, 56, 24, 50, 18, 58, 26], 26 [12, 44, 4, 36, 14, 46, 6, 38], [60, 28, 52, 20, 62, 30, 54, 22], 27 [3, 35, 11, 43, 1, 33, 9, 41], [51, 19, 59, 27, 49, 17, 57, 25], 28 [15, 47, 7, 39, 13, 45, 5, 37], [63, 31, 55, 23, 61, 29, 53, 21], 29 ]; 30 const W = 400, H = 300; 31 // A fixed seed: the same field on every load. 32 const random = (seed) => () => ((seed = (seed * 1664525 + 1013904223) >>> 0) / 4294967296); 33 34 function field(seed, count, strength, [r, g, b]) { 35 const next = random(seed); 36 const blobs = Array.from({ length: count }, () => ({ 37 x: next() * W, y: next() * H, size: 16 + next() * 30, weight: (0.5 + next() * 0.5) * strength, 38 })); 39 const canvas = document.createElement("canvas"); 40 canvas.width = W; canvas.height = H; 41 const context = canvas.getContext("2d"); 42 const image = context.createImageData(W, H); 43 for (let y = 0; y < H; y++) { 44 for (let x = 0; x < W; x++) { 45 let value = 0; 46 for (const blob of blobs) { 47 // Distances wrap, so the tile has no seam. 48 const dx = Math.min(Math.abs(x - blob.x), W - Math.abs(x - blob.x)); 49 const dy = Math.min(Math.abs(y - blob.y), H - Math.abs(y - blob.y)); 50 value += blob.weight * Math.exp(-(dx * dx + dy * dy) / (2 * blob.size * blob.size)); 51 } 52 if (value > (BAYER[y % 8][x % 8] + 0.5) / 64) { 53 const at = (y * W + x) * 4; 54 image.data[at] = r; image.data[at + 1] = g; image.data[at + 2] = b; image.data[at + 3] = 255; 55 } 56 } 57 } 58 context.putImageData(image, 0, 0); 59 return `url(${canvas.toDataURL()})`; 60 } 61 62 const root = document.documentElement.style; 63 root.setProperty("--dither-paper", field(7, 4, 0.55, [196, 196, 196])); 64 root.setProperty("--dither-pink", field(23, 5, 0.9, [30, 30, 30])); 65})();