PageSourceSearch

https://www.screen-ruler.com/effects.js

js screen-ruler.com collected 2026-09-24 13:30:47 UTC 4,851 bytes, 139 lines download raw bytes

1"use strict";
2
3const DOT_SIZE = 2;
4const GRID_GAP = 12;
5const HEAT_LEVELS = 24;
6const GLOW_ALPHA = 0.8;
7const FRAME_MS = 33;
8const WAVE_LENGTH = 420;
9const WAVE_SPEED = 0.00025;
10const WAVE_SHARPNESS = 4;
11
12let pixelFields = [];
13
14document.addEventListener("DOMContentLoaded", onEffectsLoad);
15window.addEventListener("resize", onEffectsResize);
16
17function onEffectsLoad() {
18    const canvases = document.querySelectorAll("canvas[data-pixels]");
19    if (canvases.length === 0) return;
20    window.matchMedia("(prefers-color-scheme: dark)").addEventListener("change", onSchemeChange);
21    for (const canvas of canvases) {
22        const field = createField(canvas);
23        pixelFields.push(field);
24        observeField(field);
25    }
26    if (window.matchMedia("(prefers-reduced-motion: reduce)").matches) {
27        for (const field of pixelFields) drawField(field, 0);
28        return;
29    }
30    requestAnimationFrame(onFrame);
31}
32
33function onEffectsResize() {
34    for (const field of pixelFields) {
35        sizeField(field);
36        drawField(field, 0);
37    }
38}
39
40function onSchemeChange() {
41    for (const field of pixelFields) {
42        field.dotStyles = buildDotStyles(field.canvas);
43        drawField(field, 0);
44    }
45}
46
47function buildDotStyles(element) {
48    const style = getComputedStyle(element);
49    const accentHex = style.getPropertyValue("--accent").trim();
50    const strokeParts = (style.getPropertyValue("--stroke").match(/[\d.]+/g) ?? ["0", "0", "0", "0.1"]).map(Number);
51    const accentValue = parseInt(accentHex.slice(1), 16);
52    const accent = { r: (accentValue >> 16) & 255, g: (accentValue >> 8) & 255, b: accentValue & 255 };
53    const base = { r: strokeParts[0], g: strokeParts[1], b: strokeParts[2], a: strokeParts[3] ?? 1 };
54    const styles = [];
55    for (let i = 0; i < HEAT_LEVELS; i++) {
56        const t = i / (HEAT_LEVELS - 1);
57        const r = Math.round(base.r + (accent.r - base.r) * t);
58        const g = Math.round(base.g + (accent.g - base.g) * t);
59        const b = Math.round(base.b + (accent.b - base.b) * t);
60        const a = base.a + (GLOW_ALPHA - base.a) * t;
61        styles.push(`rgba(${r}, ${g}, ${b}, ${a.toFixed(3)})`);
62    }
63    return styles;
64}
65
66function createField(canvas) {
67    const field = {
68        canvas,
69        ctx: canvas.getContext("2d"),
70        width: 0,
71        height: 0,
72        cols: 0,
73        rows: 0,
74        offsetX: 0,
75        offsetY: 0,
76        visible: false,
77        lastFrameAt: 0,
78        dotStyles: buildDotStyles(canvas)
79    };
80    sizeField(field);
81    return field;
82}
83
84function sizeField(field) {
85    const rect = field.canvas.getBoundingClientRect();
86    if (rect.width < 10 || rect.height < 10) return;
87    const dpr = window.devicePixelRatio || 1;
88    field.width = rect.width;
89    field.height = rect.height;
90    field.canvas.width = Math.max(1, Math.round(rect.width * dpr));
91    field.canvas.height = Math.max(1, Math.round(rect.height * dpr));
92    field.ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
93    field.cols = Math.max(1, Math.floor((rect.width - DOT_SIZE) / GRID_GAP) + 1);
94    field.rows = Math.max(1, Math.floor((rect.height - DOT_SIZE) / GRID_GAP) + 1);
95    field.offsetX = (rect.width - ((field.cols - 1) * GRID_GAP + DOT_SIZE)) / 2;
96    field.offsetY = (rect.height - ((field.rows - 1) * GRID_GAP + DOT_SIZE)) / 2;
97}
98
99function observeField(field) {
100    const observer = new IntersectionObserver((entries) => {
101        for (const entry of entries) {
102            field.visible = entry.isIntersecting;
103            if (!entry.isIntersecting) continue;
104            const rect = field.canvas.getBoundingClientRect();
105            if (Math.abs(rect.width - field.width) > 1 || Math.abs(rect.height - field.height) > 1) {
106                sizeField(field);
107                drawField(field, 0);
108            }
109        }
110    });
111    observer.observe(field.canvas);
112}
113
114function drawField(field, time) {
115    const { ctx, cols, rows } = field;
116    const phase = time * WAVE_SPEED;
117    ctx.clearRect(0, 0, field.width, field.height);
118    for (let row = 0; row < rows; row++) {
119        const y = field.offsetY + row * GRID_GAP;
120        for (let col = 0; col < cols; col++) {
121            const x = field.offsetX + col * GRID_GAP;
122            const wave = 0.5 + 0.5 * Math.sin(((x + y) / WAVE_LENGTH - phase) * Math.PI * 2);
123            const value = Math.pow(wave, WAVE_SHARPNESS);
124            const level = Math.min(HEAT_LEVELS - 1, Math.floor(value * HEAT_LEVELS));
125            ctx.fillStyle = field.dotStyles[level];
126            ctx.fillRect(x, y, DOT_SIZE, DOT_SIZE);
127        }
128    }
129}
130
131function onFrame(timestamp) {
132    for (const field of pixelFields) {
133        if (!field.visible) continue;
134        if (timestamp - field.lastFrameAt < FRAME_MS) continue;
135        field.lastFrameAt = timestamp;
136        drawField(field, timestamp);
137    }
138    requestAnimationFrame(onFrame);
139}

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.