Spaces:
Running
Running
| <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 & 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>1×</option> | |
| <option value="450">2×</option> | |
| <option value="220">4×</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> | |