Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Windows Pipes Screensaver</title> | |
| <script src="https://cdn.tailwindcss.com"></script> | |
| <style> | |
| body { | |
| margin: 0; | |
| overflow: hidden; | |
| background-color: #000; | |
| } | |
| canvas { | |
| display: block; | |
| } | |
| .controls { | |
| position: absolute; | |
| bottom: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| background-color: rgba(0, 0, 0, 0.7); | |
| padding: 10px 20px; | |
| border-radius: 8px; | |
| color: white; | |
| font-family: Arial, sans-serif; | |
| display: flex; | |
| gap: 15px; | |
| align-items: center; | |
| } | |
| .controls button { | |
| background-color: #0078d7; | |
| color: white; | |
| border: none; | |
| padding: 5px 10px; | |
| border-radius: 4px; | |
| cursor: pointer; | |
| transition: background-color 0.3s; | |
| } | |
| .controls button:hover { | |
| background-color: #005a9e; | |
| } | |
| .title { | |
| position: absolute; | |
| top: 20px; | |
| left: 50%; | |
| transform: translateX(-50%); | |
| color: white; | |
| font-family: 'Arial', sans-serif; | |
| font-size: 24px; | |
| text-shadow: 0 0 10px rgba(0, 120, 215, 0.7); | |
| background-color: rgba(0, 0, 0, 0.5); | |
| padding: 10px 20px; | |
| border-radius: 8px; | |
| } | |
| .pipe-count { | |
| display: flex; | |
| align-items: center; | |
| gap: 5px; | |
| } | |
| input[type="range"] { | |
| width: 100px; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="title">Windows Pipes Screensaver</div> | |
| <canvas id="pipesCanvas"></canvas> | |
| <div class="controls"> | |
| <div class="pipe-count"> | |
| <span>Pipe Count:</span> | |
| <input type="range" id="pipeCount" min="5" max="50" value="15"> | |
| <span id="pipeCountValue">15</span> | |
| </div> | |
| <div class="pipe-count"> | |
| <span>Speed:</span> | |
| <input type="range" id="speedControl" min="1" max="10" value="5"> | |
| <span id="speedValue">5</span> | |
| </div> | |
| <button id="colorBtn">Random Colors</button> | |
| <button id="resetBtn">Reset</button> | |
| </div> | |
| <script> | |
| document.addEventListener('DOMContentLoaded', () => { | |
| const canvas = document.getElementById('pipesCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const pipeCountInput = document.getElementById('pipeCount'); | |
| const pipeCountValue = document.getElementById('pipeCountValue'); | |
| const speedControl = document.getElementById('speedControl'); | |
| const speedValue = document.getElementById('speedValue'); | |
| const colorBtn = document.getElementById('colorBtn'); | |
| const resetBtn = document.getElementById('resetBtn'); | |
| // Set canvas size | |
| function resizeCanvas() { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| } | |
| resizeCanvas(); | |
| window.addEventListener('resize', resizeCanvas); | |
| // Pipe settings | |
| let pipeCount = parseInt(pipeCountInput.value); | |
| let speed = parseInt(speedControl.value); | |
| let useRandomColors = false; | |
| let pipes = []; | |
| const colors = [ | |
| '#0078d7', '#107c10', '#e81123', '#ffb900', | |
| '#881798', '#00b7c3', '#ff8c00', '#5c2d91' | |
| ]; | |
| // Pipe class | |
| class Pipe { | |
| constructor() { | |
| this.reset(); | |
| this.color = colors[Math.floor(Math.random() * colors.length)]; | |
| } | |
| reset() { | |
| this.x = Math.random() * canvas.width; | |
| this.y = Math.random() * canvas.height; | |
| this.direction = Math.floor(Math.random() * 4); // 0: right, 1: down, 2: left, 3: up | |
| this.length = 0; | |
| this.maxLength = 50 + Math.random() * 100; | |
| this.speed = 2 + Math.random() * 3; | |
| this.bendCount = 0; | |
| this.maxBends = 2 + Math.floor(Math.random() * 3); | |
| } | |
| update() { | |
| const moveAmount = this.speed * (speed / 5); | |
| switch(this.direction) { | |
| case 0: // right | |
| this.x += moveAmount; | |
| break; | |
| case 1: // down | |
| this.y += moveAmount; | |
| break; | |
| case 2: // left | |
| this.x -= moveAmount; | |
| break; | |
| case 3: // up | |
| this.y -= moveAmount; | |
| break; | |
| } | |
| this.length += moveAmount; | |
| // Check if we should bend | |
| if (this.length >= this.maxLength && this.bendCount < this.maxBends) { | |
| this.length = 0; | |
| this.bendCount++; | |
| this.maxLength = 50 + Math.random() * 100; | |
| // Randomly choose to turn left or right relative to current direction | |
| if (Math.random() > 0.5) { | |
| this.direction = (this.direction + 1) % 4; | |
| } else { | |
| this.direction = (this.direction - 1 + 4) % 4; | |
| } | |
| } | |
| // Check if we should reset | |
| if (this.x < 0 || this.x > canvas.width || this.y < 0 || this.y > canvas.height || | |
| (this.length >= this.maxLength && this.bendCount >= this.maxBends)) { | |
| this.reset(); | |
| } | |
| } | |
| draw(ctx) { | |
| ctx.strokeStyle = useRandomColors ? | |
| `hsl(${Math.random() * 360}, 100%, 50%)` : this.color; | |
| ctx.lineWidth = 8; | |
| ctx.lineCap = 'round'; | |
| ctx.beginPath(); | |
| // Draw the pipe segment based on direction | |
| switch(this.direction) { | |
| case 0: // right | |
| ctx.moveTo(this.x - this.length, this.y); | |
| ctx.lineTo(this.x, this.y); | |
| break; | |
| case 1: // down | |
| ctx.moveTo(this.x, this.y - this.length); | |
| ctx.lineTo(this.x, this.y); | |
| break; | |
| case 2: // left | |
| ctx.moveTo(this.x + this.length, this.y); | |
| ctx.lineTo(this.x, this.y); | |
| break; | |
| case 3: // up | |
| ctx.moveTo(this.x, this.y + this.length); | |
| ctx.lineTo(this.x, this.y); | |
| break; | |
| } | |
| ctx.stroke(); | |
| // Draw the pipe joint | |
| ctx.fillStyle = useRandomColors ? | |
| `hsl(${Math.random() * 360}, 100%, 50%)` : this.color; | |
| ctx.beginPath(); | |
| ctx.arc(this.x, this.y, 5, 0, Math.PI * 2); | |
| ctx.fill(); | |
| } | |
| } | |
| // Initialize pipes | |
| function initPipes() { | |
| pipes = []; | |
| for (let i = 0; i < pipeCount; i++) { | |
| pipes.push(new Pipe()); | |
| } | |
| } | |
| initPipes(); | |
| // Animation loop | |
| function animate() { | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.1)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| for (let pipe of pipes) { | |
| pipe.update(); | |
| pipe.draw(ctx); | |
| } | |
| requestAnimationFrame(animate); | |
| } | |
| animate(); | |
| // Event listeners | |
| pipeCountInput.addEventListener('input', () => { | |
| pipeCount = parseInt(pipeCountInput.value); | |
| pipeCountValue.textContent = pipeCount; | |
| initPipes(); | |
| }); | |
| speedControl.addEventListener('input', () => { | |
| speed = parseInt(speedControl.value); | |
| speedValue.textContent = speed; | |
| }); | |
| colorBtn.addEventListener('click', () => { | |
| useRandomColors = !useRandomColors; | |
| colorBtn.textContent = useRandomColors ? 'Solid Colors' : 'Random Colors'; | |
| }); | |
| resetBtn.addEventListener('click', () => { | |
| initPipes(); | |
| }); | |
| // Mouse movement to reset position (like a real screensaver) | |
| let mouseX = 0; | |
| let mouseY = 0; | |
| let lastMouseMove = Date.now(); | |
| document.addEventListener('mousemove', (e) => { | |
| mouseX = e.clientX; | |
| mouseY = e.clientY; | |
| lastMouseMove = Date.now(); | |
| // If mouse is moved, reset all pipes | |
| if (Date.now() - lastMouseMove < 100) { | |
| for (let pipe of pipes) { | |
| pipe.reset(); | |
| } | |
| } | |
| }); | |
| }); | |
| </script> | |
| </body> | |
| </html> | |