DigitalLife

DigitalLife index.html

15.0 KB 348 lines Raw ↗ GitHub ↗

<!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 &amp; 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>