polis / static /index.html
AK
fix: serve scripts from /static (were 404) + bundle three.js locally — 3D now renders
4c218db
Raw
History Blame Contribute Delete
13.1 kB
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Polis — A Living Society of AI Agents</title>
<meta name="description" content="20+ generative AI agents that form memories, relationships and an emergent society. Watch it evolve in 3D." />
<style>
:root{
--bg:#05060a; --bg2:#0a0e18; --ink:#e9edf7; --muted:#8b93a7;
--accent:#7cc5ff; --accent2:#c792ea; --gold:#f6c453; --green:#8bd17c;
--line:rgba(255,255,255,.08); --panel:rgba(15,20,34,.72);
--font:'Inter',system-ui,-apple-system,Segoe UI,Roboto,sans-serif;
--mono:'JetBrains Mono',ui-monospace,Menlo,Consolas,monospace;
}
*{box-sizing:border-box}
html,body{margin:0;padding:0;background:var(--bg);color:var(--ink);font-family:var(--font);
-webkit-font-smoothing:antialiased;overflow-x:hidden}
a{color:var(--accent);text-decoration:none}
#bg-canvas{position:fixed;inset:0;width:100vw;height:100vh;z-index:0;display:block}
/* ---- floating HUD ---- */
.hud{position:fixed;top:16px;left:16px;z-index:30;display:flex;gap:10px;align-items:center;
font-family:var(--mono);font-size:12px;color:var(--muted)}
.hud .dot{width:8px;height:8px;border-radius:50%;background:var(--green);
box-shadow:0 0 12px var(--green);animation:pulse 2s infinite}
@keyframes pulse{50%{opacity:.4}}
.brand{position:fixed;top:14px;right:18px;z-index:30;font-weight:700;letter-spacing:.14em;
font-size:13px;color:var(--ink);opacity:.9}
.brand small{color:var(--muted);font-weight:400;letter-spacing:.02em}
/* ---- scroll story sections ---- */
.story{position:relative;z-index:10}
section.panel{min-height:100vh;display:flex;align-items:center;padding:0 8vw;
position:relative}
section.panel .card{max-width:540px;background:linear-gradient(180deg,rgba(10,14,24,.55),rgba(10,14,24,.25));
backdrop-filter:blur(6px);border:1px solid var(--line);border-radius:18px;padding:30px 32px;
box-shadow:0 30px 80px -40px rgba(0,0,0,.9)}
section.panel.right{justify-content:flex-end}
.kicker{font-family:var(--mono);font-size:12px;letter-spacing:.22em;text-transform:uppercase;
color:var(--accent);margin:0 0 14px}
.kicker.g{color:var(--gold)} .kicker.v{color:var(--accent2)} .kicker.e{color:var(--green)}
h1{font-size:clamp(40px,7vw,86px);line-height:.98;margin:.1em 0 .3em;font-weight:800;
letter-spacing:-.02em}
h1 .grad{background:linear-gradient(120deg,var(--accent),var(--accent2) 60%,var(--gold));
-webkit-background-clip:text;background-clip:text;color:transparent}
h2{font-size:clamp(26px,3.4vw,42px);margin:0 0 14px;font-weight:750;letter-spacing:-.01em}
p.lead{font-size:18px;line-height:1.6;color:#cfd6e6;margin:0 0 10px}
p.sub{color:var(--muted);font-size:15px;line-height:1.6}
.tags{display:flex;flex-wrap:wrap;gap:8px;margin-top:18px}
.tag{font-family:var(--mono);font-size:11px;color:var(--muted);border:1px solid var(--line);
border-radius:999px;padding:5px 11px}
.scroll-hint{position:fixed;bottom:22px;left:50%;transform:translateX(-50%);z-index:30;
font-family:var(--mono);font-size:11px;color:var(--muted);letter-spacing:.2em;opacity:.8;
animation:bob 2.4s infinite}
@keyframes bob{50%{transform:translate(-50%,7px)}}
.stepnum{font-family:var(--mono);font-size:13px;color:var(--muted);margin-bottom:6px}
.btn{display:inline-flex;align-items:center;gap:9px;margin-top:22px;cursor:pointer;
background:linear-gradient(120deg,var(--accent),var(--accent2));color:#06121f;font-weight:700;
border:none;border-radius:12px;padding:14px 22px;font-size:15px;font-family:var(--font);
box-shadow:0 14px 40px -14px var(--accent);transition:transform .15s ease}
.btn:hover{transform:translateY(-2px)}
.btn.ghost{background:transparent;color:var(--ink);border:1px solid var(--line);box-shadow:none}
/* ---- live simulation overlay ---- */
#live{position:fixed;inset:0;z-index:20;display:none;pointer-events:none}
#live.on{display:block}
.labels{position:absolute;inset:0;pointer-events:none;overflow:hidden}
.bubble{position:absolute;transform:translate(-50%,-120%);max-width:230px;
background:var(--panel);border:1px solid var(--line);border-radius:12px;padding:8px 11px;
font-size:12.5px;line-height:1.4;color:var(--ink);backdrop-filter:blur(8px);
box-shadow:0 12px 30px -14px #000;transition:opacity .4s;pointer-events:none}
.bubble .who{font-family:var(--mono);font-size:10px;color:var(--accent);margin-bottom:3px;
text-transform:uppercase;letter-spacing:.1em}
.bubble.reflect{border-color:rgba(199,146,234,.5)}
.bubble.reflect .who{color:var(--accent2)}
.nametag{position:absolute;transform:translate(-50%,-50%);font-family:var(--mono);font-size:10px;
color:var(--muted);pointer-events:none;white-space:nowrap;text-shadow:0 1px 6px #000}
.controls{position:fixed;bottom:20px;left:50%;transform:translateX(-50%);z-index:31;
display:flex;gap:8px;align-items:center;background:var(--panel);border:1px solid var(--line);
border-radius:16px;padding:10px 12px;backdrop-filter:blur(14px);pointer-events:auto;
box-shadow:0 20px 60px -30px #000}
.controls button{background:rgba(255,255,255,.06);border:1px solid var(--line);color:var(--ink);
border-radius:10px;padding:9px 13px;font-size:13px;cursor:pointer;font-family:var(--font);
display:flex;align-items:center;gap:6px;transition:background .15s}
.controls button:hover{background:rgba(255,255,255,.13)}
.controls .tickinfo{font-family:var(--mono);font-size:12px;color:var(--muted);padding:0 8px}
.controls select,.controls input{background:rgba(255,255,255,.06);border:1px solid var(--line);
color:var(--ink);border-radius:9px;padding:8px;font-size:12px;font-family:var(--font)}
.ev-row{display:flex;gap:6px;align-items:center}
.ev-row input{width:230px}
/* inspector */
#inspector{position:fixed;top:0;right:0;height:100vh;width:340px;z-index:32;
background:linear-gradient(180deg,rgba(8,11,20,.96),rgba(8,11,20,.9));
border-left:1px solid var(--line);transform:translateX(100%);transition:transform .3s ease;
pointer-events:auto;overflow-y:auto;padding:24px 22px}
#inspector.on{transform:translateX(0)}
#inspector h3{margin:0;font-size:22px}
#inspector .role{font-family:var(--mono);font-size:12px;color:var(--accent);margin:2px 0 14px}
#inspector .close{position:absolute;top:16px;right:16px;cursor:pointer;color:var(--muted);
background:none;border:none;font-size:20px}
.field{margin:12px 0}
.field .k{font-family:var(--mono);font-size:10px;letter-spacing:.12em;text-transform:uppercase;
color:var(--muted);margin-bottom:5px}
.field .v{font-size:14px;color:#dfe5f2;line-height:1.5}
.mem{border-left:2px solid var(--line);padding:6px 0 6px 10px;margin:7px 0;font-size:12.5px;
color:#c7cede;line-height:1.45}
.mem .meta{font-family:var(--mono);font-size:9px;color:var(--muted);text-transform:uppercase;
letter-spacing:.08em}
.mem.reflection{border-color:var(--accent2)} .mem.dialogue{border-color:var(--accent)}
.rel{display:flex;justify-content:space-between;font-size:12.5px;padding:3px 0}
.rel .bar{height:5px;border-radius:3px;flex:1;margin:0 10px;align-self:center;
background:rgba(255,255,255,.08);position:relative;overflow:hidden}
.rel .bar i{position:absolute;top:0;bottom:0;left:50%;background:var(--green)}
.statbar{position:fixed;top:44px;left:16px;z-index:30;font-family:var(--mono);font-size:11px;
color:var(--muted);display:none;gap:16px}
.statbar.on{display:flex}
.statbar b{color:var(--ink);font-weight:600}
.backbtn{position:fixed;top:14px;left:16px;z-index:33;display:none;pointer-events:auto}
.backbtn.on{display:inline-flex}
.toast{position:fixed;top:70px;left:50%;transform:translateX(-50%);z-index:40;background:var(--panel);
border:1px solid var(--line);border-radius:10px;padding:10px 16px;font-size:13px;
backdrop-filter:blur(10px);opacity:0;transition:opacity .3s;pointer-events:none}
.toast.on{opacity:1}
@media (max-width:640px){#inspector{width:100vw}.ev-row input{width:150px}}
</style>
</head>
<body>
<canvas id="bg-canvas"></canvas>
<div class="hud"><span class="dot" id="live-dot"></span><span id="mode-label">MOCK ENGINE · replay</span></div>
<div class="statbar" id="statbar"></div>
<div class="brand">POLIS <small>· emergent AI society</small></div>
<!-- ============ SCROLL STORY ============ -->
<div class="story" id="story">
<section class="panel">
<div class="card">
<p class="kicker">Generative agents · Stanford-style</p>
<h1>A society that <span class="grad">writes itself</span>.</h1>
<p class="lead">Polis is a town of AI agents. No scripts, no dialogue trees — each agent
remembers, reflects, forms relationships and makes its own choices. Watch a culture
emerge, then drop an event into the world and see it ripple.</p>
<div class="tags">
<span class="tag">memory stream + retrieval</span>
<span class="tag">reflection loop</span>
<span class="tag">emergent behavior</span>
<span class="tag">OpenAI · embeddings</span>
<span class="tag">Three.js · WebGL</span>
</div>
</div>
</section>
<section class="panel right">
<div class="card">
<div class="stepnum">01 · PERCEIVE</div>
<h2>Each agent sees its world.</h2>
<p class="sub">On every tick an agent observes its location and who is nearby. Those raw
observations become timestamped memories — the atoms everything else is built from.</p>
</div>
</section>
<section class="panel">
<div class="card">
<div class="stepnum">02 · REMEMBER</div>
<h2>A memory stream, scored and retrieved.</h2>
<p class="sub">When an agent needs to act it doesn't dump its whole history into the prompt.
It retrieves the handful of memories that matter, ranked by
<b>recency × importance × relevance</b> — the exact scoring function from the
Generative Agents paper.</p>
</div>
</section>
<section class="panel right">
<div class="card">
<div class="stepnum">03 · PLAN &amp; ACT</div>
<h2>Retrieved memory becomes behavior.</h2>
<p class="sub">The agent conditions on its persona + retrieved memories to choose an action
and, when someone is close, a line of dialogue. Warm words strengthen a bond; sharp ones
fray it. Relationships are never hard-coded — they accumulate.</p>
</div>
</section>
<section class="panel">
<div class="card">
<div class="stepnum">04 · REFLECT</div>
<h2>Low-level memories fold into insight.</h2>
<p class="sub">Once enough has happened, the agent pauses to synthesize its recent life into a
higher-level reflection — "I value the people who show up for me" — which is written back
into memory and shapes future choices. That feedback loop is where personality comes from.</p>
</div>
</section>
<section class="panel right">
<div class="card">
<p class="kicker e">05 · The world is yours</p>
<h2>Step in and change history.</h2>
<p class="sub">Launch the live town, click any agent to read its mind, and inject an event —
a stranger with gold, a storm, a festival — then watch the society metabolize it.</p>
<button class="btn" id="enter-btn">▶ Launch live simulation</button>
<button class="btn ghost" id="enter-btn2">Read the architecture</button>
</div>
</section>
</div>
<div class="scroll-hint" id="scroll-hint">SCROLL ↓</div>
<!-- ============ LIVE MODE ============ -->
<div id="live">
<div class="labels" id="labels"></div>
</div>
<button class="btn ghost backbtn" id="backbtn">← Back to story</button>
<div class="controls" id="controls" style="display:none">
<button id="play">⏸ Pause</button>
<button id="restart">⟲ Restart</button>
<select id="speed">
<option value="1600">0.5×</option>
<option value="900" selected></option>
<option value="450"></option>
<option value="220"></option>
</select>
<span class="tickinfo" id="tickinfo">tick 0</span>
<div class="ev-row">
<input id="event-input" placeholder="Inject a world event…" />
<button id="event-send">Inject</button>
</div>
</div>
<!-- inspector -->
<div id="inspector">
<button class="close" id="insp-close"></button>
<div id="insp-body"></div>
</div>
<div class="toast" id="toast"></div>
<!-- Three.js is bundled LOCALLY and served from /static so no CDN/CSP can block it. -->
<script src="/static/three.min.js"></script>
<script>
// Load the app only once THREE is guaranteed. Local first; CDN only as a last resort.
(function(){
function loadApp(){ var s=document.createElement('script'); s.src='/static/app.js?v=3'; document.body.appendChild(s); }
if(window.THREE){ loadApp(); return; }
var f=document.createElement('script');
f.src='https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js';
f.onload=loadApp; f.onerror=loadApp;
document.body.appendChild(f);
})();
</script>
</body>
</html>