feat: add kiosk frontend for guided intake flow

This commit is contained in:
pyr0ball 2026-07-13 11:03:45 -07:00
parent 7d0bebf16d
commit 7807e32133
4 changed files with 144 additions and 2 deletions

42
bookmark/static/app.js Normal file
View file

@ -0,0 +1,42 @@
// bookmark/bookmark/static/app.js
const statusEl = document.getElementById("status");
const hintEl = document.getElementById("hint");
const previewEl = document.getElementById("preview");
const triggerButton = document.getElementById("trigger-button");
const STATE_COPY = {
waiting_front: { status: "Waiting for book...", hint: "Center the front cover in the guide box." },
waiting_back: { status: "Front captured — flip it over", hint: "Center the back cover in the guide box." },
book_complete: { status: "Book complete!", hint: "Tap Next Book to continue." },
};
async function refreshStatus() {
const response = await fetch("/api/session-state");
const body = await response.json();
const copy = STATE_COPY[body.state] || { status: body.state, hint: "" };
statusEl.textContent = copy.status;
hintEl.textContent = copy.hint;
triggerButton.textContent = body.state === "book_complete" ? "Next Book" : "Capture";
}
function refreshPreview() {
previewEl.src = `/api/preview.jpg?ts=${Date.now()}`;
}
async function observe() {
await fetch("/api/observe", { method: "POST" });
await refreshStatus();
}
triggerButton.addEventListener("click", async () => {
if (statusEl.textContent === "Book complete!") {
await fetch("/api/next-book", { method: "POST" });
} else {
await fetch("/api/manual-trigger", { method: "POST" });
}
await refreshStatus();
});
setInterval(observe, 500);
setInterval(refreshPreview, 500);
refreshStatus();

View file

@ -1,5 +1,22 @@
<!-- bookmark/bookmark/static/index.html -->
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8" /><title>Books in Hand — Intake</title></head>
<body><h1>Books in Hand — Intake</h1></body>
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Books in Hand — Intake</title>
<link rel="stylesheet" href="/theme.css" />
<link rel="stylesheet" href="/style.css" />
</head>
<body>
<main class="kiosk">
<div class="kiosk__preview">
<img id="preview" alt="Camera preview" />
</div>
<div class="kiosk__status" id="status">Waiting for book...</div>
<div class="kiosk__hint" id="hint">Center the front cover in the guide box.</div>
<button class="kiosk__button" id="trigger-button">Capture</button>
</main>
<script src="/app.js"></script>
</body>
</html>

48
bookmark/static/style.css Normal file
View file

@ -0,0 +1,48 @@
/* bookmark/bookmark/static/style.css */
.kiosk {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
min-height: 100vh;
padding: var(--space-lg);
gap: var(--space-md);
text-align: center;
}
.kiosk__preview {
width: min(90vw, 640px);
aspect-ratio: 4 / 3;
background: var(--color-surface);
border-radius: var(--radius);
overflow: hidden;
}
.kiosk__preview img {
width: 100%;
height: 100%;
object-fit: cover;
}
.kiosk__status {
font-size: 1.5rem;
font-weight: 600;
}
.kiosk__hint {
color: var(--color-muted);
}
.kiosk__button {
padding: var(--space-md) var(--space-lg);
font-size: 1.25rem;
border: none;
border-radius: var(--radius);
background: var(--color-accent);
color: var(--color-bg);
cursor: pointer;
}
.kiosk__button:active {
opacity: 0.8;
}

35
bookmark/static/theme.css Normal file
View file

@ -0,0 +1,35 @@
/* bookmark/bookmark/static/theme.css */
:root {
--color-bg: #101418;
--color-surface: #1b2128;
--color-text: #f2f4f6;
--color-muted: #9aa5b1;
--color-accent: #4fb0ae;
--color-success: #5cb85c;
--color-warning: #e0a13a;
--space-sm: 0.5rem;
--space-md: 1rem;
--space-lg: 2rem;
--radius: 0.5rem;
--font-base: system-ui, -apple-system, "Segoe UI", sans-serif;
}
@media (prefers-color-scheme: light) {
:root {
--color-bg: #f2f4f6;
--color-surface: #ffffff;
--color-text: #101418;
--color-muted: #5a6472;
}
}
* {
box-sizing: border-box;
}
body {
margin: 0;
background: var(--color-bg);
color: var(--color-text);
font-family: var(--font-base);
}