/messiah/public/book/index.html
Published 2026-04-16T19:23:14Z UTC by Jacques / SPRAXXX
<!doctype html> <html lang="en"> <head> <meta charset="utf-8"> <title>Book Reader — SPRAXXX</title> <meta name="viewport" content="width=device-width,initial-scale=1"> <style> body{ margin:0; background:#0f1115; color:#e8e8e8; font:16px/1.5 system-ui,sans-serif; } main{ max-width:1100px; margin:0 auto; padding:20px; display:grid; gap:18px; } .panel{ background:#151922; border:1px solid #2d3440; border-radius:14px; padding:16px; } video, canvas, textarea{ width:100%; border-radius:10px; background:#000; } textarea{ min-height:260px; color:#e8e8e8; background:#0d1117; border:1px solid #2d3440; padding:12px; resize:vertical; } .row{ display:flex; flex-wrap:wrap; gap:10px; } button, select, input{ background:#1c2230; color:#e8e8e8; border:1px solid #3b4352; border-radius:10px; padding:10px 12px; } .muted{ color:#a9b0bb; } .status{ font-family:monospace; } </style> </head> <body> <main> <div class="panel"> <h1>BOOK READER</h1> <p class="muted">Private ingest first. Public release only when rights are clear.</p>
<div class="row"> <button id="startCam">Start Camera</button> <button id="snap">Capture Frame</button> <button id="ocrBtn">Run OCR</button> <button id="appendBtn">Append OCR</button> <button id="downloadTxt">Download TXT</button> <button id="downloadJson">Download JSON</button>
<select id="pageMode"> <option value="cover">cover</option> <option value="title">title page</option> <option value="copyright">copyright page</option> <option value="left">left page</option> <option value="right">right page</option> <option value="spread">spread</option> </select>
<select id="rightsMode"> <option value="private">private</option> <option value="metadata-only">metadata-only</option> <option value="public-domain">public-domain</option> <option value="licensed">licensed</option> </select> </div>
<p class="status" id="status">idle</p> </div>
<div class="panel"> <video id="video" autoplay playsinline></video> </div>
<div class="panel"> <canvas id="canvas"></canvas> </div>
<div class="panel"> <h2>OCR Output</h2> <textarea id="ocrText" placeholder="recognized text appears here"></textarea> </div>
<div class="panel"> <h2>Session Text</h2> <textarea id="sessionText" placeholder="appended book text builds here"></textarea> </div> </main>
<script type="module"> import { createWorker } from "https://cdn.jsdelivr.net/npm/tesseract.js@6/dist/tesseract.esm.min.js";
const $ = (id) => document.getElementById(id); const video = $("video"); const canvas = $("canvas"); const ctx = canvas.getContext("2d"); const statusEl = $("status"); const ocrText = $("ocrText"); const sessionText = $("sessionText");
let stream = null; let lastImage = null; const session = { created_at: new Date().toISOString(), page_mode: null, rights_mode: null, entries: [] };
function setStatus(msg) { statusEl.textContent = msg; }
$("startCam").onclick = async () => { try { stream = await navigator.mediaDevices.getUserMedia({ video: { facingMode: { ideal: "environment" } }, audio: false }); video.srcObject = stream; setStatus("camera live"); } catch (err) { setStatus("camera error: " + err.message); } };
$("snap").onclick = () => { if (!video.videoWidth) { setStatus("camera not ready"); return; } canvas.width = video.videoWidth; canvas.height = video.videoHeight; ctx.drawImage(video, 0, 0, canvas.width, canvas.height); lastImage = canvas.toDataURL("image/jpeg", 0.92); setStatus("frame captured"); };
$("ocrBtn").onclick = async () => { if (!lastImage) { setStatus("capture a frame first"); return; } setStatus("ocr running..."); const worker = await createWorker("eng"); const result = await worker.recognize(lastImage); ocrText.value = result.data.text || ""; await worker.terminate(); setStatus("ocr complete"); };
$("appendBtn").onclick = () => { const text = ocrText.value.trim(); if (!text) { setStatus("no ocr text to append"); return; } const entry = { ts: new Date().toISOString(), page_mode: $("pageMode").value, rights_mode: $("rightsMode").value, text }; session.entries.push(entry); session.page_mode = $("pageMode").value; session.rights_mode = $("rightsMode").value; sessionText.value += (sessionText.value ? "\n\n" : "") + text; setStatus("ocr appended to session"); };
$("downloadTxt").onclick = () => { const blob = new Blob([sessionText.value], { type: "text/plain" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "book-session.txt"; a.click(); };
$("downloadJson").onclick = () => { const blob = new Blob([JSON.stringify(session, null, 2)], { type: "application/json" }); const a = document.createElement("a"); a.href = URL.createObjectURL(blob); a.download = "book-session.json"; a.click(); }; </script> </body> </html>