memoscan
← all memos

Memo 0x4d4ead79…fc0364 on Ethereum

<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Fever Dream Grid</title> <style> * { margin: 0; padding: 0; box-sizing: border-box; } body { background: #000; overflow: hidden; } #grid { display: grid; grid-template-columns: 2fr 1fr; grid-template-rows: 1fr 1fr; width: 100vw; height: 100vh; gap: 0; } .large { grid-row: 1 / 3; } .cell { width: 100%; height: 100%; overflow: hidden; background: #000; } .cell video { width: 100%; height: 100%; object-fit: cover; display: block; } #loading { position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; display: flex; flex-direction: column; align-items: center; justify-content: center; background: #000; z-index: 1000; } #loading.hidden { display: none; } .progress-bar { width: 300px; height: 4px; background: #222; border-radius: 2px; overflow: hidden; margin-bottom: 10px; } .progress-fill { height: 100%; background: #CEFF00; width: 0%; transition: width 0.3s; } .progress-text { color: #888; font-family: monospace; font-size: 14px; } </style> </head> <body> <div id="loading"> <div class="progress-bar"> <div class="progress-fill" id="progressFill"></div> </div> <div class="progress-text" id="progressText">Loading videos 0/10...</div> </div> <div id="grid"> <div class="cell large"></div> <div class="cell"></div> <div class="cell"></div> </div> <script> let mintedVideos = []; // Generate a random pattern with increased variety function generateRandomPattern() { const videoCount = mintedVideos.length; const pattern = []; // Pick 3 different random videos (no repeats in pattern) const usedIndices = new Set(); while (pattern.length < 3 && usedIndices.size < Math.min(3, videoCount)) { const randomIndex = Math.floor(Math.random() * videoCount); if (!usedIndices.has(randomIndex)) { pattern.push(randomIndex); usedIndices.add(randomIndex); } } // If we have less than 3 videos total, fill remaining slots while (pattern.length < 3) { pattern.push(Math.floor(Math.random() * videoCount)); } return pattern; } async function decodeVideoFromTx(txHash) { try { const response = await fetch(`https://eth-mainnet.g.alchemy.com/v2/T1Y8E3Z8hUkVqyXz-I67Qh_Un2wHsfIZ`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ jsonrpc: '2.0', id: 1, method: 'eth_getTransactionByHash', params: [txHash] }) }); const data = await response.json(); if (!data.result || !data.result.input) throw new Error('No transaction data'); const hex = data.result.input.slice(2); const bytes = new Uint8Array(hex.match(/.{1,2}/g).map(byte => parseInt(byte, 16))); const decoder = new TextDecoder(); return decoder.decode(bytes); } catch (e) { console.error('Failed to decode:', txHash, e); return null; } } async function fetchAllMinted() { try { console.log('Fetching from API...'); const response = await fetch('https://chainfeed.online/api/projects/fever-dream?page=0&limit=450'); const data = await response.json(); console.log('API response:', data); const minted = data.items.filter(item => item.minted && item.mintTxHash); console.log('Minted items:', minted.length); return minted; } catch (e) { console.error('Fetch error:', e); return []; } } function renderGrid(stagger = false) { const grid = document.getElementById('grid'); const cells = grid.querySelectorAll('.cell'); if (mintedVideos.length === 0) return; const pattern = generateRandomPattern(); cells.forEach((cell, i) => { const delay = stagger ? Math.random() * 2000 : Math.random() * 1000; setTimeout(() => { const videoDataUri = mintedVideos[pattern[i]]; if (!videoDataUri) return; cell.innerHTML = `<video src="${videoDataUri}" loop muted playsinline></video>`; const video = cell.querySelector('video'); video.currentTime = Math.random() * 1; video.play().catch(() => {}); }, delay); }); } function cycleVideos() { if (mintedVideos.length === 0) return; renderGrid(true); } function updateProgress(current, target) { const progressFill = document.getElementById('progressFill'); const progressText = document.getElementById('progressText'); const percentage = (current / target) * 100; progressFill.style.width = `${percentage}%`; progressText.textContent = `Loading videos ${current}/${target}...`; } async function init() { try { console.log('Init started...'); console.log('Loading all minted videos...'); const mintedItems = await fetchAllMinted(); console.log(`Found ${mintedItems.length} minted items`); if (mintedItems.length === 0) { document.getElementById('progressText').textContent = 'No minted videos found'; return; } const target = Math.min(10, mintedItems.length); updateProgress(0, target); // Load first 10 videos (or all if less than 10) for (let i = 0; i < target; i++) { console.log(`Decoding video ${i + 1}/${target}...`); const video = await decodeVideoFromTx(mintedItems[i].mintTxHash); if (video) { mintedVideos.push(video); updateProgress(mintedVideos.length, target); console.log(`Decoded ${mintedVideos.length}/${target}`); } } } catch (e) { console.error('Init error:', e); document.getElementById('progressText').textContent = 'Error loading videos'; } // Hide loading screen document.getElementById('loading').classList.add('hidden'); console.log(`Loaded ${mintedVideos.length} videos`); renderGrid(); setInterval(cycleVideos, 3000); // Continue loading remaining videos in background if (mintedItems.length > 10) { for (let i = 10; i < mintedItems.length; i++) { const video = await decodeVideoFromTx(mintedItems[i].mintTxHash); if (video) { mintedVideos.push(video); console.log(`Background loaded: ${mintedVideos.length}/${mintedItems.length}`); } } } } init(); </script> </body> </html>