0x26cda2b3…87d9sent to0xb8952959…ccf4·#23,924,208·view on Etherscan
<!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>