<!doctype html><html lang="en"> <head> <meta charset="UTF-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>DigitalLife - a civilization of AI humans</title> <link rel="icon" href="data:," /> <style> :root { --panel: rgba(14, 18, 26, 0.88); --panel-border: rgba(255, 255, 255, 0.09); --text: #e8ecf3; --dim: #9aa4b5; --gold: #ffd166; --blue: #74c0fc; --green: #57cc99; --red: #ef6461; --purple: #b892ff; } * { box-sizing: border-box; margin: 0; padding: 0; } html, body { height: 100%; overflow: hidden; } body { font-family: "Segoe UI", system-ui, sans-serif; background: #10151d; color: var(--text); font-size: 13px; } #game { position: absolute; inset: 0; } #game canvas { display: block; } #ui { position: absolute; inset: 0; pointer-events: none; } #ui > * { pointer-events: auto; } .panel { background: var(--panel); border: 1px solid var(--panel-border); border-radius: 12px; backdrop-filter: blur(10px); box-shadow: 0 8px 24px rgba(0, 0, 0, 0.35); } /* ---------- top bar ---------- */ #topbar { position: absolute; top: 10px; left: 10px; right: 10px; display: flex; align-items: center; gap: 10px; padding: 8px 14px; z-index: 10; } #brand { font-weight: 700; letter-spacing: 0.4px; color: var(--gold); white-space: nowrap; } #timeinfo { color: var(--text); white-space: nowrap; } #timeinfo b { color: var(--gold); } #seasoninfo { color: var(--green); white-space: nowrap; } #weatherinfo { white-space: nowrap; color: var(--blue); } #pop { color: var(--dim); white-space: nowrap; } .spacer { flex: 1; } #modelwrap { display: flex; align-items: center; gap: 6px; } #dot { width: 9px; height: 9px; border-radius: 50%; background: #777; flex: none; } #dot.up { background: var(--green); box-shadow: 0 0 6px var(--green); } #dot.down { background: var(--red); } select, button, input[type="text"] { font: inherit; color: var(--text); background: #1c2330; border: 1px solid var(--panel-border); border-radius: 8px; padding: 5px 10px; outline: none; } select:hover, button:hover { background: #263042; } button { cursor: pointer; white-space: nowrap; } button.danger:hover { background: #4a2330; } /* ---------- powers ---------- */ #powers { position: absolute; top: 58px; left: 10px; display: flex; align-items: center; gap: 6px; flex-wrap: wrap; max-width: 520px; padding: 8px 12px; z-index: 10; } #powers button { border-radius: 8px; font-size: 12px; padding: 4px 9px; } #powers button.active { background: var(--gold); color: #1b1b1b; font-weight: 700; border-color: var(--gold); } .pw-cat { color: var(--dim); font-size: 10px; letter-spacing: 1px; text-transform: uppercase; margin: 0 4px 0 6px; } #powerhint { color: var(--gold); font-size: 12px; margin-left: 4px; width: 100%; } /* ---------- log feed ---------- */ #logfeed { position: absolute; left: 10px; bottom: 10px; width: 340px; max-height: 200px; overflow: hidden; padding: 10px 12px; display: flex; flex-direction: column; gap: 4px; z-index: 5; mask-image: linear-gradient(to bottom, transparent, black 22%); } .logline { font-size: 12px; line-height: 1.35; color: var(--dim); } .logline.god { color: var(--gold); } .logline.faith { color: var(--purple); } .logline.death { color: var(--red); } .logline.info { color: var(--blue); } .logline.sim { color: var(--dim); } .logline.birth { color: var(--green); } .logline.marriage, .logline.love { color: #ff9ecb; } .logline.season { color: var(--green); } .logline.miracle { color: var(--gold); } .logline.job { color: #ffe9b3; } .logline.festival { color: #ffb4a2; } .logline .d { opacity: 0.55; margin-right: 5px; } /* ---------- chronicle panel ---------- */ #chronicle { position: absolute; top: 160px; left: 10px; width: 360px; max-height: 45vh; display: none; flex-direction: column; z-index: 9; overflow: hidden; } #chronicle.show { display: flex; } #chroniclehead { padding: 10px 14px 6px; display: flex; align-items: center; font-weight: 700; color: var(--gold); } #chroniclehead .x { margin-left: auto; cursor: pointer; color: var(--dim); } #statcanvas { padding: 4px 14px 8px; } #chroniclebody { overflow-y: auto; padding: 4px 14px 12px; display: flex; flex-direction: column; gap: 4px; } .chrono-entry { font-size: 12px; line-height: 1.4; color: var(--text); } .chrono-entry .k { font-size: 9px; text-transform: uppercase; letter-spacing: 1px; padding: 1px 6px; border-radius: 5px; margin-right: 6px; background: #232b3a; color: var(--dim); } .chrono-entry.birth .k { color: var(--green); } .chrono-entry.death .k { color: var(--red); } .chrono-entry.god .k, .chrono-entry.miracle .k { color: var(--gold); } .chrono-entry.marriage .k, .chrono-entry.love .k { color: #ff9ecb; } .chrono-entry.season .k { color: var(--green); } .chrono-entry.faith .k { color: var(--purple); } /* ---------- profile ---------- */ #profile { position: absolute; top: 58px; right: 10px; width: 310px; max-height: calc(100% - 330px); overflow-y: auto; padding: 14px; display: none; z-index: 8; } #profile.show { display: block; } #profile h2 { font-size: 16px; margin-bottom: 2px; } #profile .sub { color: var(--dim); font-size: 12px; margin-bottom: 8px; font-style: italic; } #profile .state { color: var(--blue); font-size: 12px; margin-bottom: 6px; } #profile .lifestage { color: var(--green); font-size: 12px; margin-bottom: 10px; } .barrow { display: flex; align-items: center; gap: 8px; margin: 5px 0; } .barrow label { width: 62px; font-size: 11px; color: var(--dim); flex: none; } .bar { flex: 1; height: 8px; border-radius: 5px; background: #232b3a; overflow: hidden; } .bar > i { display: block; height: 100%; border-radius: 5px; transition: width 0.3s; } .hunger > i { background: #ef6461; } .energy > i { background: #74c0fc; } .social > i { background: #b892ff; } .health > i { background: #57cc99; } .moodbar > i { background: #57cc99; } .stressbar > i { background: linear-gradient(90deg, #56657f, #ef6461); } .faithbar > i { background: linear-gradient(90deg, #56657f, #ffd166); } .traits { display: grid; grid-template-columns: 1fr 1fr; gap: 4px 12px; margin: 8px 0; } .traits span { font-size: 11px; color: var(--dim); } .traits span b { color: var(--text); float: right; font-weight: 500; } .section { margin-top: 12px; } .section h3 { font-size: 11px; text-transform: uppercase; letter-spacing: 1px; color: var(--dim); margin-bottom: 6px; } .beliefs li, .memories li { list-style: none; font-size: 12px; margin-bottom: 4px; line-height: 1.4; padding-left: 10px; border-left: 2px solid #33405a; } .beliefs li { border-color: var(--gold); color: var(--text); } .memories li { color: var(--dim); } .memories li b, .voicememories li b { color: var(--text); font-weight: 500; } .voicememories li { list-style: none; font-size: 12px; margin-bottom: 5px; line-height: 1.4; padding-left: 10px; border-left: 2px solid var(--gold); color: #ffe9b3; background: rgba(255, 209, 102, 0.05); padding: 5px 6px 5px 10px; border-radius: 0 5px 5px 0; } .kv { font-size: 12px; color: var(--dim); margin: 3px 0; } .kv b { color: var(--text); font-weight: 600; } .kv .pregnant { color: #ff9ecb; } .kv .prophet { color: var(--gold); } .deadnote { color: var(--red); font-weight: 600; } /* ---------- chat ---------- */ #chat { position: absolute; right: 10px; bottom: 10px; width: 400px; max-height: 320px; display: flex; flex-direction: column; z-index: 9; } #chathead { padding: 9px 12px 0; display: flex; align-items: center; gap: 8px; flex-wrap: wrap; } #chatmode { font-size: 12px; padding: 3px 6px; max-width: 150px; } #chattarget { font-weight: 600; } #chattarget .who { color: var(--gold); } #chatmsg { flex: 1; overflow-y: auto; padding: 8px 12px; display: flex; flex-direction: column; gap: 6px; max-height: 180px; min-height: 60px; } .msg { line-height: 1.4; font-size: 12.5px; } .msg .god { color: var(--gold); font-weight: 600; } .msg .who { color: var(--blue); font-weight: 600; } .msg .thought { color: var(--dim); font-style: italic; display: block; margin-top: 2px; } .msg .meta { color: var(--purple); font-size: 11px; display: block; margin-top: 2px; } .msg.err { color: var(--red); font-style: italic; } .msg.waiting { color: var(--dim); font-style: italic; } #chatform { display: flex; gap: 8px; padding: 10px 12px; } #chatinput { flex: 1; } #chatform button { background: var(--gold); color: #1b1b1b; font-weight: 700; border-color: var(--gold); } #chatform button:disabled { opacity: 0.4; cursor: not-allowed; } /* ---------- banner ---------- */ #banner { position: absolute; inset: 0; display: none; align-items: center; justify-content: center; background: rgba(10, 12, 18, 0.7); z-index: 50; } #banner.show { display: flex; } #banner .box { padding: 24px 28px; max-width: 460px; text-align: center; } #banner h2 { margin-bottom: 10px; } #banner p { color: var(--dim); margin-bottom: 14px; line-height: 1.5; } #banner code { background: #1c2330; padding: 2px 6px; border-radius: 5px; font-size: 12px; } #banner button { background: var(--gold); color: #1b1b1b; font-weight: 700; border-color: var(--gold); } /* scrollbars */ ::-webkit-scrollbar { width: 8px; } ::-webkit-scrollbar-thumb { background: #2c3547; border-radius: 4px; } ::-webkit-scrollbar-track { background: transparent; } #help { position: absolute; top: 130px; left: 50%; transform: translateX(-50%); color: var(--dim); font-size: 11px; padding: 5px 12px; z-index: 4; } </style> </head> <body> <div id="game"></div> <div id="ui"> <div id="topbar" class="panel"> <span id="brand">DigitalLife</span> <span id="timeinfo">Day <b id="day">1</b> · <span id="clock">06:00</span> <span id="phase">Morning</span></span> <span id="seasoninfo">Spring · Year 1</span> <span id="weatherinfo"></span> <span id="pop">People: <b id="popnum">0</b></span> <span class="spacer"></span> <button id="chroniclebtn" title="The village history book">Chronicle</button> <span id="modelwrap"> <span id="dot" title="Ollama status"></span> <select id="modelsel" title="Which local model plays the humans"></select> </span> <button id="savebtn" title="Save to browser storage">Save</button> <button id="loadbtn" title="Load from browser storage">Load</button> <button id="exportbtn" title="Download save as a file">Export</button> <button id="importbtn" title="Load a save file">Import</button> <button id="newbtn" class="danger" title="Abandon this world and start a fresh one">New World</button> <input type="file" id="importfile" accept="application/json" style="display: none" /> </div> <div id="powers" class="panel"> <span class="pw-cat">Hand</span> <button id="pow-give_food" title="Make berries appear">Give Food</button> <button id="pow-smite" title="Strike down with lightning">Smite</button> <button id="pow-bless" title="Heal and renew">Bless</button> <button id="pow-peace" title="Calm a troubled mind">Touch of Peace</button> <span class="pw-cat">Sky</span> <button id="pow-weather_rain" title="Rain makes crops grow fast">Rain</button> <button id="pow-weather_storm" title="A fearsome storm">Storm</button> <button id="pow-weather_sun" title="Golden, blessed light">Sun</button> <span class="pw-cat">Miracles</span> <button id="pow-resurrect" title="Click a grave - raise the dead">Resurrect</button> <button id="pow-plague" title="Sicken an area">Plague</button> <button id="pow-famine" title="Blight the land">Famine</button> <span class="pw-cat">Earth</span> <button id="pow-terraform_raise" title="Raise land from water">Raise</button> <button id="pow-terraform_dig" title="Dig a river">Dig</button> <button id="pow-terraform_plant" title="Plant a tree">Plant</button> <button id="pow-terraform_clear" title="Clear trees and rocks">Clear</button> <span class="pw-cat">Life</span> <button id="pow-spawn_chicken" title="Chickens lay eggs">Chicken</button> <button id="pow-spawn_sheep" title="Graze the meadows">Sheep</button> <button id="pow-spawn_fish" title="Fill water with fish">Fish</button> <span id="powerhint"></span> </div> <div id="help" class="panel">left-click a villager to select & talk · right-drag to pan · scroll to zoom · WASD to move</div> <div id="logfeed" class="panel"></div> <div id="chronicle" class="panel"> <div id="chroniclehead">The Chronicle <span class="x" id="chronicleclose">×</span></div> <canvas id="statcanvas" width="330" height="80"></canvas> <div id="chroniclebody"></div> </div> <div id="profile" class="panel"></div> <div id="chat" class="panel"> <div id="chathead"> <select id="chatmode" title="How your words reach them"> <option value="selected">Speak to selected</option> <option value="broadcast">Broadcast to ALL</option> <option value="prophet">As prophet (selected)</option> <option value="dream">Send dream (sleeper)</option> </select> <span id="chattarget"><span class="who">…select a villager</span></span> </div> <div id="chatmsg"></div> <form id="chatform"> <input type="text" id="chatinput" placeholder="Let there be light…" autocomplete="off" /> <button id="sendbtn" type="submit">Speak</button> </form> </div> <div id="banner"> <div class="box panel"> <h2 id="bannertitle">Ollama is not reachable</h2> <p id="bannertext"></p> <button id="bannerbtn">Retry</button> </div> </div> </div> <script type="module" src="/src/main.ts"></script> </body></html>