← back to Main Stage
index.html
178 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="The Main Stage — an animated exhibition of interactive generative rooms by Agent Abrams. Enter the Scarlet Riverboat Masquerade or Playa Ignition.">
<meta name="color-scheme" content="dark">
<title>The Main Stage — An Animated Exhibition</title>
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16'><rect width='16' height='16' fill='%230a0812'/><circle cx='8' cy='8' r='4' fill='none' stroke='%23f2b64a' stroke-width='1.2'/><circle cx='8' cy='8' r='1.6' fill='%23ff5aa0'/></svg>">
<style>
:root{
--ink:#efe8f2; --dim:#a99fc0; --gold:#f2b64a; --line:rgba(150,120,220,.22);
--serif:"Iowan Old Style","Palatino Linotype",Palatino,Georgia,serif;
--sans:"Avenir Next","Segoe UI",system-ui,-apple-system,sans-serif;
}
*{box-sizing:border-box}
html{scroll-behavior:smooth}
body{margin:0;min-height:100%;background:#070510;color:var(--ink);font-family:var(--sans);overflow-x:hidden}
#bg{position:fixed;inset:0;z-index:0;display:block}
.wrap{position:relative;z-index:1;max-width:1180px;margin:0 auto;padding:clamp(40px,7vw,90px) clamp(20px,5vw,56px) 60px}
.grain{position:fixed;inset:0;pointer-events:none;z-index:2;opacity:.06;mix-blend-mode:overlay;
background-image:url("data:image/svg+xml,%3Csvg viewBox='0 0 220 220' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E")}
header.top{display:flex;flex-direction:column;gap:14px;margin-bottom:clamp(38px,6vw,64px)}
.kicker{font-size:11px;letter-spacing:.42em;text-transform:uppercase;color:var(--gold);opacity:.95;margin:0}
h1{font-family:var(--serif);font-weight:600;font-style:italic;font-size:clamp(40px,8vw,96px);line-height:.92;margin:0;letter-spacing:-.02em}
.lead{max-width:640px;font-family:var(--serif);font-size:clamp(16px,2.2vw,22px);line-height:1.5;color:var(--dim);margin:6px 0 0}
.pieces{display:grid;grid-template-columns:repeat(auto-fit,minmax(340px,1fr));gap:clamp(20px,3vw,32px)}
.piece{position:relative;display:flex;flex-direction:column;text-decoration:none;color:inherit;
border:1px solid var(--line);border-radius:18px;overflow:hidden;background:rgba(16,12,28,.55);
backdrop-filter:blur(6px);transition:transform .28s,border-color .28s,box-shadow .28s}
.piece:hover,.piece:focus-visible{transform:translateY(-6px);border-color:var(--accent);box-shadow:0 24px 60px rgba(0,0,0,.5),0 0 0 1px var(--accent) inset}
.thumb{height:220px;position:relative;overflow:hidden}
.thumb canvas{position:absolute;inset:0;width:100%;height:100%}
.thumb .veil{position:absolute;inset:0;background:linear-gradient(180deg,rgba(0,0,0,0) 40%,rgba(10,8,18,.85))}
.body{padding:22px 24px 26px;display:flex;flex-direction:column;gap:10px;flex:1}
.body .n{font:11px/1 var(--sans);letter-spacing:.24em;color:var(--accent);text-transform:uppercase}
.body h2{font-family:var(--serif);font-weight:600;font-style:italic;font-size:clamp(24px,3vw,32px);line-height:1.02;margin:2px 0}
.body p{margin:0;font-size:14px;line-height:1.55;color:var(--dim)}
.body .tags{display:flex;gap:8px;flex-wrap:wrap;margin-top:4px}
.tag{font-size:10px;letter-spacing:.1em;text-transform:uppercase;color:var(--dim);border:1px solid var(--line);border-radius:999px;padding:4px 10px}
.go{margin-top:auto;padding-top:16px;font:11px/1 var(--sans);letter-spacing:.14em;text-transform:uppercase;color:var(--accent);display:inline-flex;gap:8px;align-items:center}
.piece:hover .go span{transform:translateX(4px)}
.go span{transition:transform .2s}
footer{margin-top:clamp(46px,7vw,80px);padding-top:26px;border-top:1px solid var(--line);display:flex;gap:18px;flex-wrap:wrap;justify-content:space-between;font:11px/1.6 var(--sans);letter-spacing:.06em;color:var(--dim)}
footer a{color:var(--gold);text-decoration:none}
:focus-visible{outline:3px solid #8fe0ff;outline-offset:3px;border-radius:8px}
@media(prefers-reduced-motion:reduce){.piece{transition:none}}
</style>
</head>
<body>
<canvas id="bg" aria-hidden="true"></canvas>
<div class="grain" aria-hidden="true"></div>
<div class="wrap">
<header class="top">
<p class="kicker">An Animated Exhibition · Agent Abrams</p>
<h1>The Main Stage</h1>
<p class="lead">A small gallery of interactive, generative rooms — each an original animated world you can steer. Choose a room and step in.</p>
</header>
<main class="pieces" id="pieces"></main>
<footer>
<span>The Main Stage · agentabrams.com</span>
<span>Original generative works · listen-only music streamed from the Internet Archive</span>
</footer>
</div>
<script>
(() => {
"use strict";
// ---- pieces (extensible) ----
const PIECES=[
{ n:'Room 01', title:'Scarlet Riverboat Masquerade', href:'https://dead.agentabrams.com/room/', accent:'#e0663c',
theme:'river', tags:['Grateful Dead','Dead & Company','Live Music Archive','Browse by year'],
desc:'A paddlewheel riverboat drifts a hand-painted crimson canyon while masked dancers turn on three decks — to a live Grateful Dead or Dead & Company show you stream from the archive, browsing 1965–1995 (and the 2015–2023 reunion) by year. Cutout troupe, beat-synced bears, a 3D orb overhead.' },
{ n:'Room 02', title:'Playa Ignition', href:'https://playa.agentabrams.com/', accent:'#2fe6ff',
theme:'playa', tags:['Burning Man','Synth beat','Ignite the Man'],
desc:'A night on the open playa: the neon Man effigy you can set alight, fire-poi dancers, an art car and a spinning 3D orb — all pulsing to a synthesized techno beat with a live spectrum graph.' },
{ n:'Room 03', title:'Aurora Drift', href:'https://aurora.agentabrams.com/', accent:'#2fff9a',
theme:'aurora', tags:['Northern Lights','Frozen lake','Reflection'],
desc:'Northern lights drift over a still, frozen lake at night — aurora curtains folding across the sky, a lone glowing cabin, falling snow, and the whole sky mirrored in the ice.' }
];
const host=document.getElementById('pieces');
const thumbs=[];
PIECES.forEach(p=>{
const a=document.createElement('a'); a.className='piece'; a.href=p.href; a.style.setProperty('--accent',p.accent);
a.setAttribute('aria-label',`Enter ${p.title}`);
a.innerHTML=`<div class="thumb"><canvas></canvas><div class="veil"></div></div>
<div class="body"><span class="n">${p.n}</span><h2>${p.title}</h2>
<div class="tags">${p.tags.map(t=>`<span class="tag">${t}</span>`).join('')}</div>
<p>${p.desc}</p><span class="go">Enter the room <span>→</span></span></div>`;
host.appendChild(a);
thumbs.push({cv:a.querySelector('canvas'), theme:p.theme, accent:p.accent});
});
// ---- animated thumbs + background starfield ----
const bg=document.getElementById('bg'), bx=bg.getContext('2d');
let W,H,DPR,stars=[];
function resize(){
DPR=Math.min(2,devicePixelRatio||1); W=innerWidth; H=innerHeight;
bg.width=W*DPR; bg.height=H*DPR; bx.setTransform(DPR,0,0,DPR,0,0);
stars=[]; const n=Math.round(W*H/6000);
for(let i=0;i<n;i++) stars.push({x:Math.random()*W,y:Math.random()*H,r:Math.random()*1.3+.2,tw:Math.random()*6.28,vy:.05+Math.random()*.2});
thumbs.forEach(t=>{ const c=t.cv, r=c.getBoundingClientRect(); c.width=Math.max(1,r.width*DPR); c.height=Math.max(1,r.height*DPR); c.getContext('2d').setTransform(DPR,0,0,DPR,0,0); c._w=r.width; c._h=r.height; });
}
addEventListener('resize',resize);
const reduce=matchMedia('(prefers-reduced-motion: reduce)').matches;
function drawThumb(t,time){
const c=t.cv, g=c.getContext('2d'), w=c._w||300, h=c._h||220;
g.clearRect(0,0,w,h);
if(t.theme==='river'){
const sky=g.createLinearGradient(0,0,0,h); sky.addColorStop(0,'#2a0a12'); sky.addColorStop(.6,'#7c1522'); sky.addColorStop(1,'#c0432b');
g.fillStyle=sky; g.fillRect(0,0,w,h);
// moon
g.fillStyle='rgba(255,207,110,.85)'; g.beginPath(); g.arc(w*.8,h*.3,16,0,7); g.fill();
// canyon
g.fillStyle='#3a0d16'; g.beginPath(); g.moveTo(0,h); g.lineTo(0,h*.6);
for(let x=0;x<=w;x+=30) g.lineTo(x,h*.55+Math.sin(x*.03)*10); g.lineTo(w,h); g.fill();
// river shimmer
g.strokeStyle='rgba(201,90,58,.5)';
for(let i=0;i<4;i++){ g.beginPath(); const y=h*.78+i*8; for(let x=0;x<=w;x+=14) g.lineTo(x,y+Math.sin(x*.05+time*2+i)*2); g.stroke(); }
// little boat
g.fillStyle='#1e0810'; g.beginPath(); g.ellipse(w*.5,h*.72,34,7,0,0,7); g.fill();
g.fillStyle='#f2b64a'; for(let i=0;i<5;i++){ g.beginPath(); g.arc(w*.5-24+i*12,h*.68,1.6,0,7); g.fill(); }
} else if(t.theme==='playa'){
const sky=g.createLinearGradient(0,0,0,h); sky.addColorStop(0,'#07061a'); sky.addColorStop(.7,'#2a1836'); sky.addColorStop(1,'#5a4436');
g.fillStyle=sky; g.fillRect(0,0,w,h);
for(let i=0;i<40;i++){ g.globalAlpha=.5+.5*Math.abs(Math.sin(i+time)); g.fillStyle='#eef0ff'; g.fillRect((i*53)%w,(i*31)%(h*.6),1.4,1.4); }
g.globalAlpha=1;
// playa
g.fillStyle='#6a5040'; g.fillRect(0,h*.72,w,h*.28);
// neon man
const cx=w*.5, by=h*.72; g.strokeStyle=`hsl(${(time*40)%360},90%,62%)`; g.shadowColor=g.strokeStyle; g.shadowBlur=10; g.lineWidth=3; g.lineCap='round';
g.beginPath(); g.moveTo(cx,by-40); g.lineTo(cx-14,by); g.moveTo(cx,by-40); g.lineTo(cx+14,by);
g.moveTo(cx-8,by-40); g.lineTo(cx-8,by-70); g.moveTo(cx+8,by-40); g.lineTo(cx+8,by-70);
g.moveTo(cx-8,by-68); g.lineTo(cx-22,by-88); g.moveTo(cx+8,by-68); g.lineTo(cx+22,by-88); g.stroke();
g.beginPath(); g.arc(cx,by-78,7,0,7); g.stroke(); g.shadowBlur=0;
} else { // aurora
const sky=g.createLinearGradient(0,0,0,h); sky.addColorStop(0,'#04070f'); sky.addColorStop(.62,'#0c2436'); sky.addColorStop(1,'#030a12');
g.fillStyle=sky; g.fillRect(0,0,w,h);
for(let i=0;i<28;i++){ g.globalAlpha=.55; g.fillStyle='#eaf2ff'; g.fillRect((i*47)%w,(i*23)%(h*.5),1.2,1.2); } g.globalAlpha=1;
g.save(); g.globalCompositeOperation='lighter';
const cols=['47,255,154','47,230,255','140,120,255'];
for(let b=0;b<3;b++){ const yTop=h*(0.16+b*0.10), col=cols[b];
const grad=g.createLinearGradient(0,yTop-10,0,yTop+h*0.24); grad.addColorStop(0,`rgba(${col},0)`); grad.addColorStop(.3,`rgba(${col},.28)`); grad.addColorStop(1,`rgba(${col},0)`);
g.fillStyle=grad; g.beginPath(); g.moveTo(0,yTop);
for(let x=0;x<=w;x+=16) g.lineTo(x, yTop+Math.sin(x*0.03+b+time)*10);
for(let x=w;x>=0;x-=16) g.lineTo(x, yTop+Math.sin(x*0.03+b+time)*10+h*0.24); g.closePath(); g.fill(); }
g.restore();
g.fillStyle='rgba(6,20,32,.92)'; g.fillRect(0,h*0.62,w,h*0.38);
g.save(); g.globalCompositeOperation='lighter';
for(let b=0;b<3;b++){ g.fillStyle=`rgba(${cols[b]},.22)`; g.fillRect(0,h*0.65+b*7,w,3); } g.restore();
}
}
let t0=performance.now();
function loop(now){
const time=(now-t0)/1000;
// background
bx.clearRect(0,0,W,H);
const bgGrad=bx.createLinearGradient(0,0,0,H); bgGrad.addColorStop(0,'#07040f'); bgGrad.addColorStop(1,'#120a1e');
bx.fillStyle=bgGrad; bx.fillRect(0,0,W,H);
for(const s of stars){ if(!reduce){ s.y+=s.vy; if(s.y>H)s.y=0; } const tw=.4+.6*Math.abs(Math.sin(s.tw+time)); bx.globalAlpha=tw; bx.fillStyle='#dfe4ff'; bx.beginPath(); bx.arc(s.x,s.y,s.r,0,7); bx.fill(); }
bx.globalAlpha=1;
thumbs.forEach(t=>drawThumb(t,time));
requestAnimationFrame(loop);
}
resize(); requestAnimationFrame(loop);
})();
</script>
</body>
</html>