Day 20,831 Webs doodler update – 570012094830

Web drawing now has wind and a few color mods

https://svonberg.org/wp-content/uploads/2026/02/webs2.html


<!DOCTYPE html>
<html lang=”en”>
<head>
    <meta charset=”UTF-8″>
    <meta name=”viewport” content=”width=device-width, initial-scale=1.0″>
    <title>Zen Spiderweb Generator</title>
    <script src=”https://cdn.tailwindcss.com”></script>
    <style>
        body, html {
            margin: 0;
            padding: 0;
            width: 100%;
            height: 100%;
            overflow: hidden;
            background-color: #111827;
            font-family: ‘Segoe UI’, Tahoma, Geneva, Verdana, sans-serif;
            transition: background 1s ease;
        }

        #canvas-container {
            position: absolute;
            top: 0;
            left: 0;
            width: 100%;
            height: 100%;
            z-index: 0;
        }

        canvas {
            display: block;
        }

        /* Custom Scrollbar */
        .custom-scroll::-webkit-scrollbar {
            width: 6px;
        }
        .custom-scroll::-webkit-scrollbar-track {
            background: rgba(255, 255, 255, 0.05);
        }
        .custom-scroll::-webkit-scrollbar-thumb {
            background: rgba(255, 255, 255, 0.2);
            border-radius: 3px;
        }
        .custom-scroll::-webkit-scrollbar-thumb:hover {
            background: rgba(255, 255, 255, 0.4);
        }

        /* Range Slider Styling */
        input[type=range] {
            -webkit-appearance: none;
            width: 100%;
            background: transparent;
        }
        input[type=range]::-webkit-slider-thumb {
            -webkit-appearance: none;
            height: 16px;
            width: 16px;
            border-radius: 50%;
            background: #e5e7eb;
            cursor: pointer;
            margin-top: -6px;
            box-shadow: 0 0 5px rgba(0,0,0,0.5);
            transition: transform 0.1s;
        }
        input[type=range]::-webkit-slider-thumb:hover {
            transform: scale(1.1);
        }
        input[type=range]::-webkit-slider-runnable-track {
            width: 100%;
            height: 4px;
            cursor: pointer;
            background: rgba(255, 255, 255, 0.2);
            border-radius: 2px;
        }
       
        /* Section dividers */
        .control-group {
            border-top: 1px solid rgba(255,255,255,0.1);
            padding-top: 1rem;
            margin-top: 1rem;
        }

        /* Zen Button Specifics */
        #open-btn {
            z-index: 50; /* Ensure it is above everything */
            transition: all 0.3s ease;
            box-shadow: 0 0 15px rgba(0,0,0,0.5);
        }
        #open-btn:hover {
            transform: scale(1.1) rotate(90deg);
        }
    </style>
</head>
<body>

    <!– Canvas Layer –>
    <div id=”canvas-container”>
        <canvas id=”webCanvas”></canvas>
    </div>

    <!– UI Overlay –>
    <div id=”ui-layer” class=”absolute top-0 right-0 p-4 md:p-6 z-10 w-full md:w-[400px] h-full pointer-events-none transition-transform duration-500 ease-in-out transform translate-x-0″>
       
        <!– Main Controls Panel –>
        <div id=”controls-panel” class=”bg-gray-900/90 backdrop-blur-md border border-gray-700/50 rounded-2xl p-5 shadow-2xl text-gray-200 custom-scroll h-full max-h-full overflow-y-auto pointer-events-auto flex flex-col”>
           
            <div class=”flex justify-between items-center mb-4 flex-shrink-0″>
                <div>
                    <h1 class=”text-xl font-light tracking-wider text-white”>Silk Weaver</h1>
                    <p class=”text-xs text-gray-400″>Procedural Generator v2.1</p>
                </div>
                <button id=”close-btn” class=”p-2 text-gray-300 hover:text-white transition-colors bg-white/10 hover:bg-white/20 rounded-lg flex items-center gap-2″ title=”Enter Zen Mode”>
                    <span class=”text-xs font-medium uppercase tracking-wider”>Zen Mode</span>
                    <svg xmlns=”http://www.w3.org/2000/svg” width=”18″ height=”18″ viewBox=”0 0 24 24″ fill=”none” stroke=”currentColor” stroke-width=”2″ stroke-linecap=”round” stroke-linejoin=”round”><path d=”M15 3h6v6M14 10l6.1-6.1M9 21H3v-6M10 14l-6.1 6.1″/></svg>
                </button>
            </div>

            <div class=”space-y-4 flex-grow”>
               
                <!– THEME SELECTOR –>
                <div>
                    <label class=”text-xs uppercase tracking-widest text-indigo-400 font-semibold mb-2 block”>Atmosphere</label>
                    <div class=”grid grid-cols-4 gap-2″>
                        <button class=”theme-btn bg-gray-800 border-2 border-indigo-500 rounded h-8 w-full hover:brightness-110 transition-all” data-theme=”midnight” title=”Midnight”></button>
                        <button class=”theme-btn bg-green-900 border-2 border-transparent hover:border-gray-400 rounded h-8 w-full transition-all” data-theme=”forest” title=”Forest”></button>
                        <button class=”theme-btn bg-purple-900 border-2 border-transparent hover:border-gray-400 rounded h-8 w-full transition-all” data-theme=”sunset” title=”Sunset”></button>
                        <button class=”theme-btn bg-black border-2 border-transparent hover:border-gray-400 rounded h-8 w-full transition-all” data-theme=”cyber” title=”Cyber”></button>
                    </div>
                </div>

                <!– CORE SHAPE –>
                <div class=”control-group”>
                    <label class=”text-xs uppercase tracking-widest text-indigo-400 font-semibold mb-3 block”>Structure</label>
                   
                    <div class=”space-y-4″>
                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Radial Spokes</span>
                                <span id=”val-density”>12</span>
                            </div>
                            <input type=”range” id=”density” min=”5″ max=”30″ value=”12″ step=”1″>
                        </div>

                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Spiral Spacing</span>
                                <span id=”val-spacing”>20</span>
                            </div>
                            <input type=”range” id=”spacing” min=”10″ max=”60″ value=”20″ step=”5″>
                        </div>
                       
                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Center Size</span>
                                <span id=”val-centerSize”>50</span>
                            </div>
                            <input type=”range” id=”centerSize” min=”0″ max=”200″ value=”50″ step=”10″>
                        </div>
                    </div>
                </div>

                <!– PHYSICS & CHAOS –>
                <div class=”control-group”>
                    <label class=”text-xs uppercase tracking-widest text-indigo-400 font-semibold mb-3 block”>Physics & Chaos</label>
                   
                    <div class=”space-y-4″>
                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Thread Slack (Gravity)</span>
                                <span id=”val-slack”>0.5</span>
                            </div>
                            <input type=”range” id=”slack” min=”0″ max=”1″ value=”0.5″ step=”0.1″>
                        </div>

                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Tear Probability</span>
                                <span id=”val-tears”>0%</span>
                            </div>
                            <input type=”range” id=”tears” min=”0″ max=”0.4″ value=”0″ step=”0.05″>
                        </div>

                         <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Irregularity</span>
                                <span id=”val-chaos”>0.3</span>
                            </div>
                            <input type=”range” id=”chaos” min=”0″ max=”1″ value=”0.3″ step=”0.1″>
                        </div>
                    </div>
                </div>

                <!– VISUALS –>
                <div class=”control-group”>
                    <label class=”text-xs uppercase tracking-widest text-indigo-400 font-semibold mb-3 block”>Visual FX</label>
                   
                    <div class=”space-y-4″>
                         <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Glow Strength</span>
                                <span id=”val-glow”>Low</span>
                            </div>
                            <input type=”range” id=”glow” min=”0″ max=”20″ value=”0″ step=”1″>
                        </div>

                        <div>
                            <div class=”flex justify-between text-xs text-gray-400 mb-1″>
                                <span>Wind Speed</span>
                                <span id=”val-wind”>0</span>
                            </div>
                            <input type=”range” id=”wind” min=”0″ max=”5″ value=”0″ step=”0.5″>
                        </div>

                        <div class=”flex items-center justify-between”>
                            <span class=”text-sm text-gray-300″>Morning Dew</span>
                            <label class=”relative inline-flex items-center cursor-pointer”>
                                <input type=”checkbox” id=”dew-toggle” class=”sr-only peer”>
                                <div class=”w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[”] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-500″></div>
                            </label>
                        </div>
                    </div>
                </div>

                <!– SYSTEM –>
                <div class=”control-group”>
                    <div class=”flex items-center justify-between”>
                        <span class=”text-sm text-gray-300″>Instant Draw</span>
                        <label class=”relative inline-flex items-center cursor-pointer”>
                            <input type=”checkbox” id=”instant-toggle” class=”sr-only peer”>
                            <div class=”w-9 h-5 bg-gray-700 peer-focus:outline-none rounded-full peer peer-checked:after:translate-x-full peer-checked:after:border-white after:content-[”] after:absolute after:top-[2px] after:left-[2px] after:bg-white after:border-gray-300 after:border after:rounded-full after:h-4 after:w-4 after:transition-all peer-checked:bg-indigo-500″></div>
                        </label>
                    </div>
                </div>

            </div>

            <!– Actions –>
            <div class=”mt-6 grid grid-cols-2 gap-3 flex-shrink-0″>
                <button id=”generate-btn” class=”col-span-2 bg-indigo-600 hover:bg-indigo-500 text-white font-medium py-3 px-4 rounded-xl transition-all shadow-lg shadow-indigo-500/20 active:scale-95 flex justify-center items-center gap-2″>
                    <svg xmlns=”http://www.w3.org/2000/svg” width=”16″ height=”16″ viewBox=”0 0 24 24″ fill=”none” stroke=”currentColor” stroke-width=”2″ stroke-linecap=”round” stroke-linejoin=”round”><path d=”M21 12a9 9 0 1 1-6.219-8.56″/></svg>
                    Re-Weave
                </button>
                <button id=”download-btn” class=”bg-gray-800 hover:bg-gray-700 text-gray-300 text-sm py-2 px-3 rounded-lg transition-colors border border-gray-700″>
                    Save Img
                </button>
                 <button id=”clear-btn” class=”bg-gray-800 hover:bg-red-900/30 text-gray-300 hover:text-red-200 text-sm py-2 px-3 rounded-lg transition-colors border border-gray-700″>
                    Clear
                </button>
            </div>
        </div>
    </div>

    <!– Zen Mode Restore Button (Fixed Visibility) –>
    <button id=”open-btn” class=”fixed bottom-6 right-6 bg-white/10 hover:bg-white/20 text-white p-4 rounded-full backdrop-blur-md border border-white/30 hidden transition-all” title=”Exit Zen Mode”>
        <svg xmlns=”http://www.w3.org/2000/svg” width=”28″ height=”28″ viewBox=”0 0 24 24″ fill=”none” stroke=”currentColor” stroke-width=”2″ stroke-linecap=”round” stroke-linejoin=”round”><circle cx=”12″ cy=”12″ r=”3″></circle><path d=”M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06.06a2 2 0 0 1 0 2.83 2 2 0 0 1-2.83 0l-.06-.06a1.65 1.65 0 0 0-1.82-.33 1.65 1.65 0 0 0-1 1.51V21a2 2 0 0 1-2 2 2 2 0 0 1-2-2v-.09A1.65 1.65 0 0 0 9 19.4a1.65 1.65 0 0 0-1.82.33l-.06.06a2 2 0 0 1-2.83 0 2 2 0 0 1 0-2.83l.06-.06a1.65 1.65 0 0 0 .33-1.82 1.65 1.65 0 0 0-1.51-1H3a2 2 0 0 1-2-2 2 2 0 0 1 2-2h.09A1.65 1.65 0 0 0 4.6 9a1.65 1.65 0 0 0-.33-1.82l-.06-.06a2 2 0 0 1 0-2.83 2 2 0 0 1 2.83 0l.06.06a1.65 1.65 0 0 0 1.82.33H9a1.65 1.65 0 0 0 1-1.51V3a2 2 0 0 1 2-2 2 2 0 0 1 2 2v.09a1.65 1.65 0 0 0 1 1.51 1.65 1.65 0 0 0 1.82-.33l.06-.06a2 2 0 0 1 2.83 0 2 2 0 0 1 0 2.83l-.06.06a1.65 1.65 0 0 0-.33 1.82V9a1.65 1.65 0 0 0 1.51 1H21a2 2 0 0 1 2 2 2 2 0 0 1-2 2h-.09a1.65 1.65 0 0 0-1.51 1z”></path></svg>
    </button>

    <script>
        /**
         * Core Logic for Spiderweb Generator
         */
        const canvas = document.getElementById(‘webCanvas’);
        const ctx = canvas.getContext(‘2d’);
       
        // State
        let width, height;
        let centerX, centerY;
        let animationFrameId;
        let time = 0; // For wind animation
       
        // Web Data
        let spokes = [];
        let spirals = [];
        let currentDrawIndex = 0;
        let isConstructing = false; // Is the initial build animation running?

        // Theme Configuration
        const themes = {
            midnight: { bg: ‘#111827’, web: ‘rgba(255, 255, 255, 0.2)’, dew: ‘rgba(255, 255, 255, 0.6)’ },
            forest:   { bg: ‘#064e3b’, web: ‘rgba(209, 250, 229, 0.2)’, dew: ‘rgba(167, 243, 208, 0.6)’ },
            sunset:   { bg: ‘linear-gradient(to bottom, #4c1d95, #c2410c)’, web: ‘rgba(254, 215, 170, 0.3)’, dew: ‘rgba(255, 237, 213, 0.7)’ },
            cyber:    { bg: ‘#000000’, web: ‘rgba(50, 255, 100, 0.3)’, dew: ‘rgba(50, 255, 100, 0.8)’ }
        };

        // Settings
        const settings = {
            theme: ‘midnight’,
            spokeCount: 12,
            spacing: 20, // Distance between spiral loops
            centerSize: 50,
            slack: 0.5,
            chaos: 0.3,
            tears: 0.0,
            glow: 0,
            wind: 0.0,
            speed: 15,
            dew: false,
            instant: false
        };

        // UI Elements
        const uiLayer = document.getElementById(‘ui-layer’);
        const openBtn = document.getElementById(‘open-btn’);
        const themeBtns = document.querySelectorAll(‘.theme-btn’);

        // Helper: Random Range
        const random = (min, max) => Math.random() * (max – min) + min;

        // Helper: Apply Wind to a Point
        // Returns a new object so we don’t mutate original geometry permanently
        function applyWind(point) {
            if (settings.wind <= 0.1) return point;
           
            // Simple vertex displacement based on time and Y position
            // Stronger effect further from center?
            const dist = Math.sqrt((point.x – centerX)**2 + (point.y – centerY)**2);
            const distFactor = Math.min(dist / 500, 1);

            const offsetX = Math.sin(time * 0.002 + point.y * 0.01) * (settings.wind * 10) * distFactor;
            const offsetY = Math.cos(time * 0.003 + point.x * 0.01) * (settings.wind * 5) * distFactor;

            return {
                x: point.x + offsetX,
                y: point.y + offsetY
            };
        }

        // Initialization
        function resize() {
            width = window.innerWidth;
            height = window.innerHeight;
            canvas.width = width;
            canvas.height = height;
            centerX = width / 2;
            centerY = height / 2;
            generateWebData();
            startDrawing();
        }

        window.addEventListener(‘resize’, resize);

        // Generate the mathematical model of the web
        function generateWebData() {
            spokes = [];
            spirals = [];
           
            // 1. Generate Spokes (Radials)
            const baseAngle = (Math.PI * 2) / settings.spokeCount;
            const maxRadius = Math.max(width, height) * 0.8;

            for (let i = 0; i < settings.spokeCount; i++) {
                let angleOffset = (Math.random() – 0.5) * settings.chaos;
                let angle = (i * baseAngle) + angleOffset;
                let length = maxRadius * random(0.8, 1.2);

                spokes.push({
                    angle: angle,
                    length: length,
                    endX: centerX + Math.cos(angle) * length,
                    endY: centerY + Math.sin(angle) * length
                });
            }

            // 2. Generate Spiral Segments
            let currentRadius = settings.centerSize;
            let spokeIndex = 0;
           
            // Prevent infinite loops
            let safetyCount = 0;
           
            while (safetyCount < 5000) {
                safetyCount++;
               
                let s1 = spokes[spokeIndex];
                let nextIndex = (spokeIndex + 1) % spokes.length;
                let s2 = spokes[nextIndex];

                // R1 and R2 allow the spiral to spiral outwards
                let r1 = currentRadius + random(-5, 5) * (settings.chaos * 5);
                // Look ahead: The connection point on the next spoke is slightly further out
                let spiralStep = (settings.spacing / settings.spokeCount);
                let r2 = r1 + spiralStep + random(-2, 2) * (settings.chaos * 5);
               
                // Stop if off screen
                if (r1 > s1.length || r2 > s2.length) break;

                // Probability to skip a segment (Tear)
                if (Math.random() > settings.tears) {

                    let p1 = {
                        x: centerX + Math.cos(s1.angle) * r1,
                        y: centerY + Math.sin(s1.angle) * r1
                    };

                    let p2 = {
                        x: centerX + Math.cos(s2.angle) * r2,
                        y: centerY + Math.sin(s2.angle) * r2
                    };

                    // Control Point for Catenary (gravity sag)
                    let midX = (p1.x + p2.x) / 2;
                    let midY = (p1.y + p2.y) / 2;
                   
                    // Pull vector towards center
                    let dirX = midX – centerX;
                    let dirY = midY – centerY;
                    let pullFactor = settings.slack * 0.4;
                   
                    let cpX = midX – (dirX * pullFactor);
                    let cpY = midY – (dirY * pullFactor);

                    spirals.push({
                        p1: p1,
                        p2: p2,
                        cp: {x: cpX, y: cpY},
                        dew: Math.random() > 0.7
                    });
                }

                // Advance
                spokeIndex = nextIndex;
                currentRadius += spiralStep; // Increment radius constantly around the spiral
               
                if (currentRadius > Math.max(width, height) * 0.7) break;
            }
        }

        function getThemeColors() {
            return themes[settings.theme] || themes[‘midnight’];
        }

        function drawDew(p1, p2, count) {
            const theme = getThemeColors();
            ctx.fillStyle = theme.dew;
           
            for(let i=1; i<count; i++) {
                const t = i/count;
                const x = p1.x + (p2.x – p1.x) * t;
                const y = p1.y + (p2.y – p1.y) * t;
               
                ctx.beginPath();
                ctx.arc(x, y, random(0.5, 1.5), 0, Math.PI * 2);
                ctx.fill();
            }
        }

        function drawDewOnCurve(p1, cp, p2) {
             const theme = getThemeColors();
             ctx.fillStyle = theme.dew;

             const drops = Math.floor(random(1, 4));
             for (let i = 0; i < drops; i++) {
                const t = random(0.2, 0.8);
                // Bezier Point
                const x = (1 – t) * (1 – t) * p1.x + 2 * (1 – t) * t * cp.x + t * t * p2.x;
                const y = (1 – t) * (1 – t) * p1.y + 2 * (1 – t) * t * cp.y + t * t * p2.y;

                ctx.beginPath();
                ctx.arc(x, y, random(0.5, 1.8), 0, Math.PI * 2);
                ctx.fill();
             }
        }

        function startDrawing() {
            if (isConstructing) isConstructing = false; // Reset current build
            currentDrawIndex = 0;
           
            // Set Background based on theme
            const theme = getThemeColors();
            if (theme.bg.includes(‘gradient’)) {
                document.body.style.background = theme.bg;
            } else {
                document.body.style.backgroundColor = theme.bg;
                document.body.style.backgroundImage = ‘none’;
            }

            if (settings.instant) {
                currentDrawIndex = spirals.length;
            } else {
                isConstructing = true;
            }
           
            if (!animationFrameId) animate();
        }

        function renderFrame() {
            ctx.clearRect(0, 0, width, height);
            const theme = getThemeColors();
           
            // Setup Glow
            if (settings.glow > 0) {
                ctx.shadowBlur = settings.glow;
                ctx.shadowColor = theme.web;
            } else {
                ctx.shadowBlur = 0;
            }

            // 1. Draw Spokes
            ctx.strokeStyle = theme.web;
            ctx.lineWidth = 1;
            ctx.lineCap = “round”;
           
            // Only draw spokes if we are generating or they are always visible
            spokes.forEach(spoke => {
                const start = applyWind({x: centerX, y: centerY});
                const end = applyWind({x: spoke.endX, y: spoke.endY});

                ctx.beginPath();
                ctx.moveTo(start.x, start.y);
                ctx.lineTo(end.x, end.y);
                ctx.stroke();
            });

            // 2. Draw Spirals
            // If constructing, limit by currentDrawIndex. If done, draw all.
            const limit = isConstructing ? currentDrawIndex : spirals.length;
           
            // Batch drawing for performance? Canvas handles single paths better
            // But we need individual segments for wind
            ctx.strokeStyle = theme.web; // Re-apply incase changed
            ctx.lineWidth = Math.max(0.5, 0.8 – (settings.spokeCount * 0.01)); // Thinner webs for high density

            ctx.beginPath();
            for(let i=0; i < limit; i++) {
                let seg = spirals[i];
               
                // Apply wind to all control points
                let p1 = applyWind(seg.p1);
                let p2 = applyWind(seg.p2);
                let cp = applyWind(seg.cp);

                ctx.moveTo(p1.x, p1.y);
                ctx.quadraticCurveTo(cp.x, cp.y, p2.x, p2.y);
            }
            ctx.stroke();

            // 3. Draw Dew (Separate pass for color change)
            if (settings.dew) {
                ctx.shadowBlur = 0; // No glow on dew for crispness
                for(let i=0; i < limit; i++) {
                    let seg = spirals[i];
                    if (seg.dew) {
                        // Recalculate positions for dew to match wind
                        let p1 = applyWind(seg.p1);
                        let p2 = applyWind(seg.p2);
                        let cp = applyWind(seg.cp);
                        drawDewOnCurve(p1, cp, p2);
                    }
                }
            }

            // Logic for Construction Animation
            if (isConstructing) {
                let speed = Math.floor(settings.speed + (currentDrawIndex / 50));
                currentDrawIndex += speed;
                if (currentDrawIndex >= spirals.length) {
                    currentDrawIndex = spirals.length;
                    isConstructing = false;
                }
            }
        }

        function animate(timestamp) {
            time = timestamp || 0;
            renderFrame();
            animationFrameId = requestAnimationFrame(animate);
        }

        // UI Interactions
        function updateDisplay(id, val) {
            const el = document.getElementById(`val-${id}`);
            if (el) el.innerText = val;
        }

        // Attach listeners to all range inputs
        [‘density’, ‘spacing’, ‘centerSize’, ‘slack’, ‘chaos’, ‘tears’, ‘glow’, ‘wind’].forEach(id => {
            const input = document.getElementById(id);
            input.addEventListener(‘input’, (e) => {
                let val = parseFloat(e.target.value);
               
                // Special formatting
                if (id === ‘tears’) updateDisplay(id, Math.round(val * 100) + ‘%’);
                else if (id === ‘glow’) updateDisplay(id, val === 0 ? ‘Off’ : (val > 15 ? ‘High’ : ‘Low’));
                else updateDisplay(id, val);

                // Map to settings
                if (id === ‘density’) settings.spokeCount = parseInt(val);
                else if (id === ‘spacing’) settings.spacing = parseInt(val);
                else if (id === ‘centerSize’) settings.centerSize = parseInt(val);
                else settings[id] = val;

                // Regen logic
                if ([‘density’, ‘spacing’, ‘centerSize’, ‘chaos’, ‘tears’, ‘slack’].includes(id)) {
                    generateWebData();
                    if (!settings.instant && !isConstructing) startDrawing(); // Restart anim if structural change
                }
            });
        });

        // Theme Buttons
        themeBtns.forEach(btn => {
            btn.addEventListener(‘click’, (e) => {
                themeBtns.forEach(b => b.classList.remove(‘border-indigo-500’));
                themeBtns.forEach(b => b.classList.add(‘border-transparent’));
               
                e.target.classList.remove(‘border-transparent’);
                e.target.classList.add(‘border-indigo-500’);

                settings.theme = e.target.getAttribute(‘data-theme’);
                startDrawing();
            });
        });

        document.getElementById(‘dew-toggle’).addEventListener(‘change’, (e) => {
            settings.dew = e.target.checked;
        });

        document.getElementById(‘instant-toggle’).addEventListener(‘change’, (e) => {
            settings.instant = e.target.checked;
        });

        document.getElementById(‘generate-btn’).addEventListener(‘click’, () => {
            generateWebData();
            startDrawing();
        });
       
        document.getElementById(‘clear-btn’).addEventListener(‘click’, () => {
             isConstructing = false;
             currentDrawIndex = 0;
             spokes = [];
             spirals = [];
             ctx.clearRect(0,0,width,height);
        });

        document.getElementById(‘download-btn’).addEventListener(‘click’, () => {
            const link = document.createElement(‘a’);
            link.download = `spiderweb_${settings.theme}.png`;
            link.href = canvas.toDataURL();
            link.click();
        });

        // Zen Mode Logic
        const closeBtn = document.getElementById(‘close-btn’);
        let uiVisible = true;

        function toggleUI() {
            uiVisible = !uiVisible;
            if (uiVisible) {
                uiLayer.classList.remove(‘translate-x-full’);
                openBtn.classList.add(‘hidden’);
            } else {
                uiLayer.classList.add(‘translate-x-full’);
                // Remove hidden immediately, CSS transitions handle the rest if you want opacity,
                // but for now simple display toggling ensures it’s clickable.
                openBtn.classList.remove(‘hidden’);
            }
        }

        closeBtn.addEventListener(‘click’, toggleUI);
        openBtn.addEventListener(‘click’, toggleUI);
       
        document.addEventListener(‘click’, (e) => {
            // If Zen mode is active (UI hidden) and we aren’t clicking the restore button
            if (!uiVisible && !openBtn.contains(e.target)) {
                // Gentle regen in Zen mode
                generateWebData();
                startDrawing();
            }
        });

        // Start
        resize();
        animate();

    </script>
</body>
</html>

I messaged myself someone else’s Epstein story and IG put me on restriction. I can still post and even share stories, but I’m not allowed to message! Yeah, *I’M* the problem! So if you messaged me, I can’t respond for 3 days. Thanks IG for protecting PDFs!

“The Hangman” from 1964 based on the poem by Maurice Ogden. Film made by Les Goldman and Paul Julian. “The 1964 animated short film Hangman, directed by Paul Julian and Les Goldman, is a 16.4K cautionary tale about apathy and complicity, based on Maurice Ogden’s 1951 poem. It depicts a mysterious figure erecting a gallows in a town square, systematically executing citizens while the fearful populace: remains silent, only for the narrator to realize he is next.”

The ending of "The Hangman" from 1964 based on the poem by Maurice Ogden. Film made by Les Goldman and Paul Julian.

Scottobear (@scottobear.bsky.social) 2026-02-12T09:48:05.228Z

Putting the wait in perspective

Thereโ€™s a specific kind of internal hum that starts the moment you hit “Checkout” on a piece of niche tech. Itโ€™s been a minute since Iโ€™ve been this genuinely keyed up for a mail call, but the wait for the x4 has me checking tracking tabs like itโ€™s a competitive sport.

To keep my sanity, I did what any self-respecting nerd does: I opened a spreadsheet.

Putting the wait into perspective helps dampen the “where is it?” anxiety. My device left Shenzhen nine days ago. Since then, itโ€™s been navigating a gauntlet of weather patterns, logistics hubs, and handoff hops.

Here is the breakdown of its 7,842-mile trek:

* Distance Covered: 7,105 miles (roughly 90% of the trip).
* Average Speed: A steady 36 mph.
* Touchpoints: 8 distinct scans across the globe.

Itโ€™s currently cleared the customs hurdle, and with the bulk of the journey in the rearview mirror, Iโ€™m optimistic for a delivery before the 14th.

Some people have that “buy it and forget it” zen mindset. I am not those people. I canโ€™t just let it arrive “whenever.” To bridge the gap, Iโ€™ve been curate-stacking my digital environment:

* Customizing the Vibe: Designing a fresh set of wallpapers.
* Community Building: Getting the account settled over at readme.club.
* The Library: Hoarding choice EPUB files like a digital dragon.

Iโ€™m also diving back into Calibre. I haven’t touched the software in nearly a decade, but seeing that itโ€™s still the gold standard for library management is a testament to its staying power. Getting a Calibre server spun up is next on the weekend warrior list.

The SD card is “ready-ish,” and the Android apps are already staged on my phone and tablet. Software-wise, Iโ€™m leaning toward Crosspoint. If I can swing a multi-boot setup to test the waters of every OS flavor available, thatโ€™s the dream.

Day 20826 – 570007063317

Woolgathering: What most people call their “self” is, for the most part, a bundle of different moods, impulses, and roles fighting for control. “I decided” usually means “one impulse temporarily won.”

If this sounds preposterous, spend some time in honest “self-observation”. I’m betting you find a constellation of “selves” rather a self.

The “self” you experience when hearing a nostalgic song, for example, can be very different from the “self” that you experience after stubbing your toe or getting cut off in traffic. Often dramatically different.

In the episode “Troubled Waters,” the good Lieutenant seems to pass some gas, and it can clearly be heard. After Columbo first examines the crime scene, he heads to the doctor’s office to get help for his seasickness. Just as he reaches the top of the steps, just before he reaches the doctor’s door, you can clearly hear two farts. He even sighs after the first one. Now, I thought it might have been the floor creaking or the ship settling, but that particular sound is never heard again in the episode. So, say what you will, but I’m convinced it’s a fart, and the sound guy missed it… or perhaps they left it in. After all, he was experiencing problems with his tummy.

X4 wallpaper generator

Link to self-contained html

Latest ver:

https://svonberg.org/wp-content/uploads/2026/02/x4paper2.html

Added a dungeon and a pollack, and variant aliens

https://svonberg.org/wp-content/uploads/2026/02/x4paper3.html

Original:

https://svonberg.org/wp-content/uploads/2026/02/x4paper.html

# Animated 1-Bit Widget

A single-file, canvas-based generative art widget. 
Animated procedural backgrounds, pixel-style drawing, and BMP export โ€” all in vanilla JavaScript.

## Features

– Animated procedural patterns
– Pixel-style drawing (mouse + touch)
– Text stamping tool
– Speed control and pause
– Export to BMP (client-side, no server)
– Mobile-friendly UI

## Usage

1. Download or clone the repo
2. Open the HTML file in a modern browser
3. Tap the menu button to change modes or export

No build steps. No dependencies.

## Tech

– HTML5 Canvas
– Vanilla JavaScript
– Tailwind CSS (CDN)

## Export

Snapshots combine background + drawing layers and are saved as uncompressed BMP files for crisp pixel output.

## License

Free to use, modify, and remix.

Welcome to my wall scrawls.