Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Sequence Generator V4 - Dual Color</title> | |
| <style> | |
| /* General Reset */ | |
| body { | |
| margin: 0; | |
| padding: 0; | |
| background-color: black; | |
| overflow: hidden; | |
| font-family: 'Courier New', Courier, monospace; | |
| } | |
| /* --- SCREEN 1: INPUT INTERFACE --- */ | |
| #input-screen { | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| width: 100%; | |
| height: 100%; | |
| background-color: black; | |
| display: flex; | |
| flex-direction: column; | |
| justify-content: center; | |
| align-items: center; | |
| z-index: 10; | |
| transition: opacity 0.8s ease-in-out; | |
| } | |
| .input-container { | |
| width: 100%; | |
| max-width: 800px; | |
| text-align: center; | |
| display: flex; | |
| flex-direction: column; | |
| gap: 25px; | |
| } | |
| /* Text Input */ | |
| input[type="text"] { | |
| background: transparent; | |
| border: none; | |
| border-bottom: 2px solid #003300; | |
| color: #0F0; | |
| font-family: 'Courier New', monospace; | |
| font-size: 1.5rem; | |
| width: 90%; | |
| padding: 10px; | |
| text-align: center; | |
| outline: none; | |
| margin: 0 auto; | |
| transition: border-color 0.3s; | |
| } | |
| input[type="text"]:focus { | |
| border-bottom: 2px solid #0F0; | |
| } | |
| input[type="text"]::placeholder { | |
| color: #004400; | |
| font-size: 1rem; | |
| text-transform: uppercase; | |
| } | |
| /* Settings Row */ | |
| .settings-row { | |
| display: flex; | |
| justify-content: center; | |
| flex-wrap: wrap; | |
| gap: 20px; | |
| margin-bottom: 10px; | |
| align-items: flex-end; /* Align bottom */ | |
| } | |
| .setting-group { | |
| display: flex; | |
| flex-direction: column; | |
| align-items: flex-start; | |
| } | |
| label { | |
| color: #004400; | |
| font-size: 0.7rem; | |
| margin-bottom: 5px; | |
| text-transform: uppercase; | |
| } | |
| select, input[type="color"] { | |
| background: black; | |
| color: #0F0; | |
| border: 1px solid #003300; | |
| font-family: 'Courier New', monospace; | |
| font-size: 0.9rem; | |
| outline: none; | |
| cursor: pointer; | |
| height: 35px; | |
| box-sizing: border-box; | |
| } | |
| select { | |
| padding: 5px; | |
| min-width: 140px; | |
| } | |
| /* Custom Style for Color Picker */ | |
| input[type="color"] { | |
| -webkit-appearance: none; | |
| width: 60px; | |
| padding: 0; | |
| border: 1px solid #003300; | |
| transition: all 0.3s; | |
| } | |
| input[type="color"]::-webkit-color-swatch-wrapper { padding: 2px; } | |
| input[type="color"]::-webkit-color-swatch { border: none; } | |
| select:hover, select:focus, input[type="color"]:hover { | |
| border-color: #0F0; | |
| } | |
| /* Hidden class for 2nd color picker */ | |
| .invisible { | |
| display: none; | |
| } | |
| /* Action Button */ | |
| button { | |
| background: transparent; | |
| color: #0F0; | |
| border: 1px solid #0F0; | |
| padding: 15px 30px; | |
| font-family: 'Courier New', monospace; | |
| font-size: 1rem; | |
| cursor: pointer; | |
| text-transform: uppercase; | |
| letter-spacing: 2px; | |
| transition: all 0.2s; | |
| width: fit-content; | |
| align-self: center; | |
| } | |
| button:hover { | |
| background: #0F0; | |
| color: black; | |
| box-shadow: 0 0 15px #0F0; | |
| } | |
| /* --- SCREEN 2: CANVAS --- */ | |
| canvas { | |
| display: block; | |
| position: absolute; | |
| top: 0; | |
| left: 0; | |
| z-index: 1; | |
| } | |
| #hint { | |
| position: absolute; | |
| bottom: 20px; | |
| right: 20px; | |
| color: #004400; | |
| z-index: 5; | |
| font-size: 0.8rem; | |
| opacity: 0; | |
| transition: opacity 2s; | |
| pointer-events: none; | |
| } | |
| .hidden { | |
| opacity: 0; | |
| pointer-events: none; | |
| } | |
| </style> | |
| </head> | |
| <body> | |
| <!-- SCREEN 1: User Input --> | |
| <div id="input-screen"> | |
| <div class="input-container"> | |
| <!-- Text Input --> | |
| <input type="text" id="userWords" placeholder="> Enter dataset keywords..." autocomplete="off"> | |
| <!-- Controls Row --> | |
| <div class="settings-row"> | |
| <!-- 1. Orientation --> | |
| <div class="setting-group"> | |
| <label>Orientation</label> | |
| <select id="orientation"> | |
| <option value="vertical">Vertical Rain</option> | |
| <option value="horizontal">Horizontal Data</option> | |
| </select> | |
| </div> | |
| <!-- 2. Flow --> | |
| <div class="setting-group"> | |
| <label>Flow Direction</label> | |
| <select id="flow"> | |
| <option value="one">One Way</option> | |
| <option value="both">Bidirectional</option> | |
| </select> | |
| </div> | |
| <!-- 3. Color Mode --> | |
| <div class="setting-group"> | |
| <label>Color Mode</label> | |
| <select id="colorMode" onchange="toggleColorInputs()"> | |
| <option value="single">Single Color</option> | |
| <option value="dual">Dual Colors</option> | |
| </select> | |
| </div> | |
| <!-- 4. Pickers --> | |
| <div class="setting-group"> | |
| <label>Colors</label> | |
| <div style="display: flex; gap: 5px;"> | |
| <input type="color" id="color1" value="#00FF00" title="Primary Color"> | |
| <input type="color" id="color2" value="#FF0000" title="Secondary Color" class="invisible"> | |
| </div> | |
| </div> | |
| </div> | |
| <button onclick="startSequence()">[ GENERATE SEQUENCE ]</button> | |
| </div> | |
| </div> | |
| <!-- SCREEN 2: Matrix Rain --> | |
| <canvas id="matrixCanvas"></canvas> | |
| <div id="hint">Press ESC to reset</div> | |
| <script> | |
| const canvas = document.getElementById('matrixCanvas'); | |
| const ctx = canvas.getContext('2d'); | |
| const inputScreen = document.getElementById('input-screen'); | |
| const hint = document.getElementById('hint'); | |
| // Input Elements | |
| const inputField = document.getElementById('userWords'); | |
| const orientationSelect = document.getElementById('orientation'); | |
| const flowSelect = document.getElementById('flow'); | |
| const colorModeSelect = document.getElementById('colorMode'); | |
| const color1Input = document.getElementById('color1'); | |
| const color2Input = document.getElementById('color2'); | |
| // Setup Canvas | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| // State Variables | |
| const fontSize = 16; | |
| let animationId = null; | |
| let characters = []; | |
| let drops = []; | |
| let isVertical = true; | |
| // Color State | |
| let useDualColor = false; | |
| let col1 = '#00FF00'; | |
| let col2 = '#FF0000'; | |
| // --- UI LOGIC --- | |
| function toggleColorInputs() { | |
| if (colorModeSelect.value === 'dual') { | |
| color2Input.classList.remove('invisible'); | |
| } else { | |
| color2Input.classList.add('invisible'); | |
| } | |
| } | |
| // --- CORE LOGIC --- | |
| function initDrops(orientation, flow) { | |
| drops = []; | |
| isVertical = (orientation === 'vertical'); | |
| const limit = isVertical ? canvas.width / fontSize : canvas.height / fontSize; | |
| for (let i = 0; i < limit; i++) { | |
| let speed = 1; | |
| if (flow === 'both') { | |
| speed = Math.random() > 0.5 ? 1 : -1; | |
| } | |
| // Initial Position | |
| let startPos = Math.random() * (isVertical ? canvas.height : canvas.width); | |
| drops.push({ | |
| val: startPos, | |
| speed: speed | |
| }); | |
| } | |
| } | |
| function draw() { | |
| // Fade effect | |
| ctx.fillStyle = 'rgba(0, 0, 0, 0.05)'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| ctx.font = fontSize + 'px monospace'; | |
| const maxLimit = isVertical ? canvas.height : canvas.width; | |
| for (let i = 0; i < drops.length; i++) { | |
| const text = characters[Math.floor(Math.random() * characters.length)]; | |
| const drop = drops[i]; | |
| // COLOR LOGIC: Per Character | |
| if (useDualColor) { | |
| // Randomly pick between color 1 and 2 for a "noisy" mix | |
| ctx.fillStyle = Math.random() > 0.5 ? col1 : col2; | |
| } else { | |
| ctx.fillStyle = col1; | |
| } | |
| // COORDINATES | |
| let x, y; | |
| if (isVertical) { | |
| x = i * fontSize; | |
| y = drop.val; | |
| } else { | |
| x = drop.val; | |
| y = i * fontSize; | |
| } | |
| ctx.fillText(text, x, y); | |
| // MOVEMENT | |
| drop.val += drop.speed * fontSize; | |
| // LOOPING | |
| if (drop.speed > 0) { | |
| if (drop.val > maxLimit && Math.random() > 0.975) drop.val = -fontSize; | |
| } else { | |
| if (drop.val < -fontSize && Math.random() > 0.975) drop.val = maxLimit; | |
| } | |
| } | |
| } | |
| // --- ACTIONS --- | |
| function startSequence() { | |
| const textVal = inputField.value.trim(); | |
| // Capture Settings | |
| col1 = color1Input.value; | |
| col2 = color2Input.value; | |
| useDualColor = (colorModeSelect.value === 'dual'); | |
| // Set Characters | |
| if (textVal.length > 0) { | |
| characters = (textVal + " ").split(''); | |
| } else { | |
| characters = "01".split(''); | |
| } | |
| // Init Logic | |
| initDrops(orientationSelect.value, flowSelect.value); | |
| // Hide UI | |
| inputScreen.classList.add('hidden'); | |
| setTimeout(() => { hint.style.opacity = 1; }, 2000); | |
| // Start Animation | |
| if (animationId) clearInterval(animationId); | |
| animationId = setInterval(draw, 33); | |
| } | |
| function stopSequence() { | |
| clearInterval(animationId); | |
| ctx.fillStyle = 'black'; | |
| ctx.fillRect(0, 0, canvas.width, canvas.height); | |
| inputScreen.classList.remove('hidden'); | |
| hint.style.opacity = 0; | |
| inputField.focus(); | |
| } | |
| // --- LISTENERS --- | |
| window.addEventListener('resize', () => { | |
| canvas.width = window.innerWidth; | |
| canvas.height = window.innerHeight; | |
| if (inputScreen.classList.contains('hidden')) { | |
| initDrops(orientationSelect.value, flowSelect.value); | |
| } | |
| }); | |
| inputField.addEventListener('keypress', (e) => { | |
| if (e.key === 'Enter') startSequence(); | |
| }); | |
| document.addEventListener('keydown', (e) => { | |
| if (e.key === "Escape") stopSequence(); | |
| }); | |
| // Initialize | |
| toggleColorInputs(); // Set correct initial state | |
| inputField.focus(); | |
| </script> | |
| </body> | |
| </html> |