oracle / index.html
vivek gangadharan
docs updated
22555c9
Raw
History Blame Contribute Delete
24.6 kB
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>The Oracle</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Cinzel:wght@600;700;900&family=Spectral:ital@0;1&display=swap" rel="stylesheet" />
<style>
:root {
--gold:#f0c75e; --gold-deep:#c8902f; --violet:#b388ff; --ink:#efe7fb;
--panel:#3a2766cc; --panel-edge:#7b5fc7; --green:#46b34a; --red:#d8442f;
}
* { box-sizing: border-box; }
html, body { margin: 0; min-height: 100%; }
body {
font-family: 'Spectral', Georgia, serif; color: var(--ink); text-align: center;
background:
linear-gradient(rgba(20,11,42,.55), rgba(20,11,42,.78)),
url('https://huggingface.co/buckets/build-small-hackathon/oracle-storage/resolve/background.png') center top / cover no-repeat fixed,
radial-gradient(120% 100% at 50% -10%, #3a2a63 0%, #241546 45%, #140b2a 100%);
overflow-x: hidden; min-height: 100vh;
}
.stars { position: fixed; inset: 0; pointer-events: none; z-index: 0; }
.star { position: absolute; width: 3px; height: 3px; border-radius: 50%; background:#fff; opacity:0; animation: tw 3s ease-in-out infinite; }
@keyframes tw { 0%,100%{opacity:.1;} 50%{opacity:.85;} }
/* top bar */
.topbar { position: relative; z-index: 2; display: flex; justify-content: space-between;
align-items: center; padding: 14px 22px; }
.logo { font-family:'Cinzel',serif; font-weight:900; color: var(--gold); letter-spacing:1px;
font-size: 1.1rem; text-shadow: 0 2px 6px rgba(0,0,0,.6); }
.logo b { font-size: 1.3rem; }
.built-with { margin-top: 14px; font-size: .78rem; opacity: .65; letter-spacing: .3px; }
.top-controls { display: flex; gap: 10px; }
.chip { font-family:'Cinzel',serif; font-weight:700; cursor: pointer; color: var(--ink);
background: linear-gradient(180deg,#5a3fa0,#3a2766); border: 1px solid var(--panel-edge);
border-radius: 999px; padding: 7px 14px; font-size: .85rem; }
.chip:hover { filter: brightness(1.12); }
.iconbtn { background: none; border: none; padding: 0; cursor: pointer; line-height: 0; }
.iconbtn img { height: 40px; display: block; filter: drop-shadow(0 3px 6px rgba(0,0,0,.5)); }
.iconbtn:hover img { filter: brightness(1.12) drop-shadow(0 3px 6px rgba(0,0,0,.5)); }
#mute.muted img { opacity: .4; }
.imgchip { position: relative; background: none; border: none; padding: 0; cursor: pointer; line-height: 0; }
.imgchip img { height: 40px; display: block; filter: drop-shadow(0 3px 6px rgba(0,0,0,.5)); }
.imgchip span {
position: absolute;
inset: 0;
display: flex;
align-items: center;
justify-content: center;
font-family: sans-serif;
font-weight: 800;
font-size: 17px;
color: #c99dfe;
letter-spacing: .5px;
text-shadow: 0 1px 2px rgba(0, 0, 0, .5);
pointer-events: none;
}
.imgchip:hover img { filter: brightness(1.12) drop-shadow(0 3px 6px rgba(0,0,0,.5)); }
.howimg { display: block; width: 100%; height: auto; margin-bottom: 10px; }
/* layout */
.grid { position: relative; z-index: 1; display: grid; gap: 18px; align-items: start;
grid-template-columns: 230px 1fr 250px; max-width: 1040px; margin: 0 auto; padding: 4px 18px 50px; }
@media (max-width: 900px) { .grid { grid-template-columns: 1fr; } .side { max-width: 460px; margin: 0 auto; } }
.side { background: var(--panel); border: 1px solid var(--panel-edge); border-radius: 16px;
padding: 16px; text-align: left; box-shadow: 0 10px 30px rgba(0,0,0,.35); }
.side h3 { font-family:'Cinzel',serif; font-size: .8rem; letter-spacing: 2px;
text-transform: uppercase; color: var(--gold); margin: 0 0 10px; }
.howto p { margin: 0; line-height: 1.6; color: #d9cdf5; font-size: .98rem; }
/* answers panel framed by top/bottom images, stretchable middle */
.answers { width: 100%; max-width: 460px; margin: 0 auto; display: flex; flex-direction: column;
filter: drop-shadow(0 10px 30px rgba(0,0,0,.35)); }
.ans-top, .ans-bottom { display: block; width: 100%; height: auto; }
.ans-mid { flex: 1; text-align: left; margin: -1px 0; padding: 2px 18px 8px;
background: url('./images/answer_center.png') center / 100% 100% no-repeat; }
.center { min-width: 0; }
.title-img { display: block; width: min(560px, 92%); height: auto; margin: 6px auto 2px;
filter: drop-shadow(0 4px 10px rgba(0,0,0,.5)); }
.stage { margin: 10px auto 2px; width: 260px; max-width: 78%; position: relative; }
.globe { display: block; width: 100%; height: auto;
filter: drop-shadow(0 10px 18px rgba(0,0,0,.5)) drop-shadow(0 0 26px rgba(150,110,255,.45));
animation: float 4s ease-in-out infinite; }
@keyframes float { 0%,100%{transform:translateY(0);} 50%{transform:translateY(-7px);} }
.globe.think { animation: float 4s ease-in-out infinite, glow 1.1s ease-in-out infinite; }
@keyframes glow {
0%,100%{ filter: drop-shadow(0 10px 18px rgba(0,0,0,.5)) drop-shadow(0 0 26px rgba(150,110,255,.45)); }
50%{ filter: drop-shadow(0 10px 18px rgba(0,0,0,.5)) drop-shadow(0 0 46px rgba(190,150,255,.95)); } }
.panel { display: none; animation: fade .35s ease; }
.panel.active { display: block; }
@keyframes fade { from{opacity:0; transform: translateY(8px);} to{opacity:1; transform:none;} }
.stepper { display:flex; gap:8px; justify-content:center; align-items:center; margin: 4px 0 10px; }
.dot { width: 10px; height: 10px; border-radius: 50%; background: #5a4a86; box-shadow: inset 0 0 0 2px #7b5fc7; }
.dot.on { background: var(--gold); box-shadow: 0 0 10px rgba(240,199,94,.7); }
.status { font-size: .72rem; letter-spacing: 2px; text-transform: uppercase; color: var(--violet); margin-bottom: 4px; }
.question { font-family:'Cinzel',serif; font-weight:700; font-size: 1.5rem; line-height: 1.3;
margin: 4px auto 18px; min-height: 1.6em; max-width: 540px;
color: var(--gold); text-shadow: 0 2px 8px rgba(0,0,0,.5); }
.guess-big { font-family:'Cinzel',serif; font-size: 2.1rem; color: var(--gold); margin: 8px 0 22px; }
.teach-input { font-family:'Cinzel',serif; font-size: 1.1rem; text-align:center; width: 80%; max-width: 340px;
padding: 12px 16px; margin: 0 auto 18px; display:block; border-radius: 12px; border: 1px solid #6a4fa3;
background: rgba(20,10,45,.7); color: #f6f0ff; outline: none; }
.teach-input:focus { border-color: var(--gold); box-shadow: 0 0 0 2px rgba(212,175,55,.3); }
.reveal { font-style: italic; color: #e7d9ff; opacity: .9; max-width: 460px; margin: 6px auto 10px;
min-height: 1.2em; font-size: 1.05rem; text-shadow: 0 1px 6px rgba(0,0,0,.5); }
#teachCats button.sel { outline: 2px solid var(--gold); box-shadow: 0 0 0 3px rgba(212,175,55,.3); }
.reasons { margin: 18px auto 0; max-width: 480px; text-align: left; font-family: system-ui, sans-serif; }
.reasons-h { font-family:'Cinzel',serif; color: var(--gold); font-size: .95rem; margin-bottom: 8px; text-align:center; }
.reason-row { font-size: .9rem; line-height: 1.5; color: #e8def7; background: rgba(120,40,60,.22);
border: 1px solid rgba(200,80,100,.35); border-radius: 10px; padding: 8px 12px; margin: 6px 0; }
.reason-x { color: #ff7a90; font-weight: 700; margin-right: 6px; }
.btns { display: flex; gap: 12px; justify-content: center; flex-wrap: wrap; }
button { font-family:'Cinzel',serif; cursor: pointer; font-size: 1rem; font-weight: 700;
padding: 13px 22px; border-radius: 14px; border: 1px solid #6a4fa3; color: #f6f0ff;
background: linear-gradient(180deg, #5a3fa0, #38236a); box-shadow: 0 6px 0 #2a1850, 0 8px 16px rgba(0,0,0,.4);
transition: transform .1s, filter .12s; }
button:hover { filter: brightness(1.1); }
button:active { transform: translateY(3px); box-shadow: 0 3px 0 #2a1850, 0 4px 10px rgba(0,0,0,.4); }
.btn-yes { background: linear-gradient(180deg,#5ec763,#36953b); border-color:#2f7d33; box-shadow:0 6px 0 #235c26,0 8px 16px rgba(0,0,0,.4); color:#fff; }
.btn-no { background: linear-gradient(180deg,#e85b44,#be3422); border-color:#9c2a1b; box-shadow:0 6px 0 #7e2317,0 8px 16px rgba(0,0,0,.4); color:#fff; }
.btn-unsure { background: linear-gradient(180deg,#f0c75e,#cc922f); border-color:#a9741f; box-shadow:0 6px 0 #875b16,0 8px 16px rgba(0,0,0,.4); color:#3a2600; }
/* image buttons (yes / no / not sure) — full painted buttons with text baked in */
.imgbtn { background:none; border:none; box-shadow:none; padding:0; border-radius:0; }
.imgbtn img { height: 60px; width:auto; display:block; }
.imgbtn:hover { filter: brightness(1.08); }
.imgbtn:active { transform: translateY(2px); box-shadow:none; }
@media (max-width: 900px) { .imgbtn img { height: 52px; } }
/* blank button images with our own text on top */
.imgtext { background:none; border:none; box-shadow:none; border-radius:0;
background-repeat:no-repeat; background-position:center; background-size:100% 100%;
font-family:'Cinzel',serif; font-weight:700; font-size:1rem;
min-width:150px; height:56px; padding:0 26px; }
.imgtext.green { background-image:url(./images/blank_green_button.png); color:#eafff0; text-shadow:0 1px 2px rgba(0,0,0,.4); }
.imgtext.red { background-image:url(./images/blank_red_button.png); color:#fff; text-shadow:0 1px 2px rgba(0,0,0,.4); }
.imgtext.yellow { background-image:url(./images/empty_yellow_btn.png); color:#3a2600; }
.imgtext:hover { filter:brightness(1.08); }
.imgtext:active { transform:translateY(2px); }
@media (max-width: 900px) { .imgtext { height:50px; min-width:130px; font-size:.92rem; } }
.cat { font-size: 1.05rem; padding: 15px 22px; }
.start { background:none; border:none; box-shadow:none; color:#b9a7e6; font-weight:600;
font-size:.85rem; letter-spacing:1px; text-transform:uppercase; margin-top: 16px; }
.start:hover { color: var(--gold); filter:none; } .start:active{ transform:none; box-shadow:none; }
.loading { color:#cdbdf0; font-style: italic; margin-top: 12px; min-height: 1.2em; }
.end-msg { font-family:'Cinzel',serif; font-size: 1.5rem; line-height: 1.4; margin: 12px 0 22px; }
.confetti { font-size: 2.6rem; animation: pop .5s ease; }
@keyframes pop { 0%{transform:scale(0);} 70%{transform:scale(1.3);} 100%{transform:scale(1);} }
/* answers panel */
#log { display: none; }
.logrow { display: flex; justify-content: space-between; gap: 10px; padding: 7px 0;
border-top: 1px solid rgba(255,255,255,.1); font-size: 10px;
font-family: 'Lucida Sans', 'Lucida Sans Regular', 'Lucida Grande', 'Lucida Sans Unicode', Geneva, Verdana, sans-serif; }
.logrow:first-of-type { border-top: none; }
.logrow .lq { color: #ddd2f6; }
.logrow .la { font-weight: 700; white-space: nowrap; }
.la.yes { color: #6fe074; } .la.no { color: #ff7a63; } .la.maybe { color: var(--gold); }
.empty-hint { color:#9d8fc9; font-style:italic; font-size:.9rem; }
</style>
</head>
<body>
<div class="stars" id="stars"></div>
<div class="topbar">
<div class="logo">🔮 <b>THE ORACLE</b></div>
<div class="top-controls">
<button class="imgchip" id="learnBtn" title="Teach the Oracle a new thing"><img src="./images/demo_blank_btn.png" alt="" /><span>+ Teach</span></button>
<button class="iconbtn" id="mute" title="Sound on/off"><img src="./images/volume.png" alt="Sound" /></button>
</div>
</div>
<div class="grid">
<!-- left: how it works -->
<img class="howimg" src="https://huggingface.co/buckets/build-small-hackathon/oracle-storage/resolve/how_it_work.png" alt="How it works — answer the questions in your mind and I will reveal your answer." />
<!-- center stage -->
<main class="center">
<img class="title-img" src="./images/orcale-title.png" alt="The Oracle — think of something, I shall pluck it from your mind." />
<div class="stage">
<img class="globe" id="ball" src="https://huggingface.co/buckets/build-small-hackathon/oracle-storage/resolve/center_globe.png" alt="Crystal ball" />
</div>
<div class="panel active" id="intro">
<div class="question">What are you thinking of?</div>
<div class="btns">
<button class="imgtext green" data-cat="animal">🐾 Animal</button>
<button class="imgtext yellow" data-cat="fruit">🍎 Fruit</button>
<button class="imgtext green" data-cat="vegetable">🥕 Vegetable</button>
</div>
</div>
<div class="panel" id="game">
<div class="stepper" id="stepper"></div>
<div class="status" id="status"></div>
<div class="question" id="question"></div>
<div class="btns" id="options"></div>
<div class="loading" id="loading"></div>
<div><button class="start" id="restart1">✦ Start over</button></div>
</div>
<div class="panel" id="guessView">
<div class="status">The Oracle senses…</div>
<div class="reveal" id="revealLine"></div>
<div class="guess-big" id="guessText"></div>
<div class="btns">
<button class="imgbtn" id="guessYes"><img src="./images/yes_button.png" alt="Yes" /></button>
<button class="imgbtn" id="guessNo"><img src="./images/no_button.png" alt="No" /></button>
</div>
</div>
<div class="panel" id="teachView">
<div class="status" id="teachStatus">🌫️ The Oracle is stumped…</div>
<div class="guess-big" id="teachPrompt">What were you thinking of?</div>
<div class="btns" id="teachCats" style="display:none">
<button class="imgtext green" data-tcat="animal">🐾 Animal</button>
<button class="imgtext yellow" data-tcat="fruit">🍎 Fruit</button>
<button class="imgtext green" data-tcat="vegetable">🥕 Vegetable</button>
</div>
<input id="teachInput" class="teach-input" type="text" placeholder="Type its name…" autocomplete="off" />
<div class="btns">
<button class="imgtext yellow" id="teachBtn">✦ Teach the Oracle</button>
<button class="imgtext green" id="teachSkip">Skip</button>
</div>
<div class="loading" id="teachMsg"></div>
</div>
<div class="panel" id="endView">
<div class="confetti" id="endIcon">🔮</div>
<div class="end-msg" id="endMsg"></div>
<div class="btns"><button class="imgtext yellow" id="restart2">Play again</button></div>
</div>
</main>
<!-- right: your answers -->
<aside class="answers" id="logCard">
<img class="ans-top" src="./images/answer_top.png" alt="Your answers" />
<div class="ans-mid">
<div id="log"></div>
<div class="empty-hint" id="logEmpty">No answers yet…</div>
</div>
<img class="ans-bottom" src="./images/answer_bottom.png" alt="" />
</aside>
</div>
<script type="module">
import { Client } from "https://cdn.jsdelivr.net/npm/@gradio/client/dist/index.min.js";
const stars = document.getElementById("stars");
for (let i = 0; i < 70; i++) { const s = document.createElement("div"); s.className = "star";
s.style.left = Math.random()*100+"vw"; s.style.top = Math.random()*100+"vh";
s.style.animationDelay = (-Math.random()*3)+"s"; s.style.transform = `scale(${0.5+Math.random()})`; stars.appendChild(s); }
let _client = null;
const getClient = async () => (_client ??= await Client.connect(window.location.origin));
const byId = (id) => document.getElementById(id);
const ball = byId("ball");
const panels = { intro: byId("intro"), game: byId("game"), guess: byId("guessView"), teach: byId("teachView"), end: byId("endView") };
const show = (name) => { for (const k in panels) panels[k].classList.toggle("active", k === name); };
let muted = false;
let actx = null;
function chime(){ if (muted) return; try { actx ??= new (window.AudioContext||window.webkitAudioContext)();
[0,4,7,12].forEach((semi,i)=>{ const o=actx.createOscillator(),g=actx.createGain(); o.type="sine";
o.frequency.value=440*Math.pow(2,semi/12); g.gain.setValueAtTime(0.0001,actx.currentTime+i*0.08);
g.gain.exponentialRampToValueAtTime(0.2,actx.currentTime+i*0.08+0.02);
g.gain.exponentialRampToValueAtTime(0.0001,actx.currentTime+i*0.08+0.5);
o.connect(g); g.connect(actx.destination); o.start(actx.currentTime+i*0.08); o.stop(actx.currentTime+i*0.08+0.55); }); } catch(e){} }
// state
let category="animal", history=[], asked=0, currentQ="", currentAttr="", currentGuess="";
document.querySelectorAll("[data-cat]").forEach(b => b.onclick = () => {
category = b.dataset.cat; history = []; asked = 0;
askModel();
});
// map an option to one of the 3 button images (else a styled text button)
function optImage(text) {
const t = String(text).toLowerCase();
if (/^yes\b/.test(t)) return "./images/yes_button.png";
if (/^no\b/.test(t)) return "./images/no_button.png";
if (/not sure|don'?t know|can'?t say/.test(t)) return "./images/i_am_not_sure_btn.png";
return null;
}
function renderOptions(opts, onPick) {
const box = byId("options"); box.innerHTML = "";
(opts || []).forEach(o => {
const b = document.createElement("button");
const img = optImage(o);
if (img) { b.className = "imgbtn"; b.innerHTML = `<img src="${img}" alt="${esc(o)}" />`; }
else { b.className = "imgtext yellow"; b.textContent = o; } // colors/sizes etc. on a blank button
b.onclick = () => onPick(o); box.appendChild(b);
});
}
const esc = (s) => (s == null ? "" : String(s)).replace(/[&<>]/g, c => ({ "&":"&amp;","<":"&lt;",">":"&gt;" }[c]));
function ansClass(a){ const t=String(a).toLowerCase();
if(/^yes\b/.test(t)) return "yes"; if(/^no\b/.test(t)) return "no"; return "maybe"; }
const ansLabel = (a) => a === "yes" ? "Yes" : a === "no" ? "No" : "Not sure";
function renderLog() {
const log = byId("log");
byId("logEmpty").style.display = history.length ? "none" : "block";
log.style.display = history.length ? "block" : "none";
log.innerHTML = history.map((h, i) =>
`<div class="logrow"><span class="lq">${i + 1}. ${esc(h.question)}</span>` +
`<span class="la ${ansClass(h.answer)}">${esc(ansLabel(h.answer))}</span></div>`
).join("");
}
function renderStepper(total, remaining) {
const n = Math.min(8, asked + 1);
byId("stepper").innerHTML = Array.from({length: n}, (_, i) =>
`<span class="dot ${i <= asked ? "on" : ""}"></span>`).join("");
}
async function askModel() {
renderLog();
show("game");
byId("options").innerHTML = "";
byId("status").textContent = "";
byId("question").textContent = "";
ball.classList.add("think");
byId("loading").textContent = "The Oracle gazes into the glass…";
try {
const c = await getClient();
const res = await c.predict("/next", {
category, history_json: JSON.stringify(history), asked,
});
const step = res.data[0];
ball.classList.remove("think"); byId("loading").textContent = "";
if (step.action === "giveup") {
showTeach(step.text || "What were you thinking of?");
} else if (step.action === "guess") {
currentGuess = step.text;
byId("revealLine").textContent = step.reveal || "";
byId("guessText").textContent = "Is it… " + step.text + "?";
show("guess");
} else {
currentQ = step.text;
currentAttr = step.attribute || "";
renderStepper(step.max, step.remaining);
byId("status").textContent = `Question ${asked + 1}` + (step.remaining ? ` · ${step.remaining} left` : "");
byId("question").textContent = step.text;
let opts = (step.options && step.options.length ? step.options : ["Yes", "No"]).slice(0, 3);
if (!opts.some(o => /not sure|can'?t say|don'?t know/i.test(o))) opts.push("I am not sure");
renderOptions(opts, (opt) => {
const answer = /^yes/i.test(opt) ? "yes" : /^no/i.test(opt) ? "no" : "unknown";
history.push({ attribute: currentAttr, question: currentQ, answer });
if (answer !== "unknown") asked++;
askModel();
});
show("game");
}
} catch (e) {
ball.classList.remove("think");
byId("loading").textContent = "The spirits are unreachable…";
console.error(e);
}
}
byId("guessYes").onclick = () => {
chime(); byId("endIcon").textContent = "🎉";
byId("endMsg").innerHTML = `The Oracle has read your mind.<br><span style="color:var(--gold)">${currentGuess}</span>!`;
show("end");
};
byId("guessNo").onclick = () => {
showTeach("I was wrong! What were you really thinking of?");
};
// --- discovery / teach mode ---
function renderReasons(list, name) {
if (!list || !list.length) return "";
const rows = list.map(c =>
`<div class="reason-row"><span class="reason-x">✗</span> You answered ` +
`<b>${esc(c.your_answer)}</b> to “${esc(c.question)}”, but ${esc(c.reason)} ` +
`— it should have been <b style="color:var(--gold)">${esc(c.correct_answer)}</b>.</div>`
).join("");
return `<div class="reasons"><div class="reasons-h">That's where you threw me off:</div>${rows}</div>`;
}
let teachMode = "game"; // "game" (after a wrong/giveup) or "learn" (standalone)
let teachCategory = "animal"; // chosen category in standalone mode
function showTeach(prompt) { // called after a wrong guess / giveup
teachMode = "game";
byId("teachStatus").textContent = "🌫️ The Oracle is stumped…";
byId("teachCats").style.display = "none";
byId("teachPrompt").textContent = prompt;
byId("teachInput").value = "";
byId("teachMsg").textContent = "";
show("teach");
setTimeout(() => byId("teachInput").focus(), 50);
}
function openLearn() { // standalone "+ Teach" button
teachMode = "learn";
teachCategory = "animal";
byId("teachStatus").textContent = "📖 Teach the Oracle";
byId("teachPrompt").textContent = "Pick a type, then name something new for me to learn.";
byId("teachCats").style.display = "flex";
document.querySelectorAll("[data-tcat]").forEach(b =>
b.classList.toggle("sel", b.dataset.tcat === teachCategory));
byId("teachInput").value = "";
byId("teachMsg").textContent = "";
show("teach");
setTimeout(() => byId("teachInput").focus(), 50);
}
document.querySelectorAll("[data-tcat]").forEach(b => b.onclick = () => {
teachCategory = b.dataset.tcat;
document.querySelectorAll("[data-tcat]").forEach(x => x.classList.toggle("sel", x === b));
byId("teachInput").focus();
});
async function teach() {
const name = byId("teachInput").value.trim();
if (!name) { byId("teachInput").focus(); return; }
const cat = teachMode === "learn" ? teachCategory : category;
const hist = teachMode === "learn" ? [] : history;
byId("teachMsg").textContent = "The Oracle studies it closely…";
try {
const c = await getClient();
const res = await c.predict("/learn", { category: cat, name, history_json: JSON.stringify(hist) });
const r = res.data[0] || {};
chime();
byId("endIcon").textContent = "📖";
let msg;
if (r.status === "known") {
msg = `The Oracle already knew <span style="color:var(--gold)">${esc(r.name)}</span>.`;
} else if (r.status === "learned") {
msg = `The Oracle has learned <span style="color:var(--gold)">${esc(r.name)}</span>!<br>It now knows ${r.learned} thing${r.learned === 1 ? "" : "s"} about it.`;
} else {
msg = `The Oracle could not grasp it this time.`;
}
msg += renderReasons(r.contradictions, r.name);
byId("endMsg").innerHTML = msg;
show("end");
} catch (e) {
console.error(e);
byId("teachMsg").textContent = "The spirits are unreachable…";
}
}
byId("teachBtn").onclick = teach;
byId("teachInput").addEventListener("keydown", (e) => { if (e.key === "Enter") teach(); });
byId("teachSkip").onclick = () => {
if (teachMode === "learn") { show("intro"); return; }
byId("endIcon").textContent = "🙇";
byId("endMsg").innerHTML = `You have bested the Oracle!<br>Its vision was too cloudy this time.`;
show("end");
};
byId("learnBtn").onclick = openLearn;
const restart = () => { history = []; asked = 0; currentAttr = ""; renderLog(); show("intro"); };
byId("restart1").onclick = restart;
byId("restart2").onclick = restart;
byId("mute").onclick = () => { muted = !muted; byId("mute").classList.toggle("muted", muted); };
renderLog();
</script>
</body>
</html>