ui.jsannotatedui.jssource65 lines · 2.8 KB · raw

What every Jev site's pages share: the copy buttons, and the dot fields behind the page. Nothing here talks to the server.

The copy buttons: data-copy names the element whose link or text is 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})();

The dot fields behind the page, after typesafe.ai: soft blobs put through an 8x8 ordered dither, one image pixel per dot, tiled. Drawn once into CSS variables rather than into elements, because a page may replace whole 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})();