← Isaac's builds Kiosk · 2026 Simulation, not a real Windows build

Library Kiosk — a locked-down public-library terminal in one HTML file

The original ask was a modified Windows 11 ISO with a ticket/HKID unlock, locked settings, guest browsers and a floating control bar. Built instead as one 72 KB self-contained HTML file that needs no install, no admin rights and no reimaging — because a web kiosk can be deployed to any terminal in minutes while a custom Windows image needs the ADK, sysprep and a credential-provider rewrite. Ticket + HKID auth, nine working apps, guest-mode browsers, a hard wipe on logout, and the whole state machine in 671 lines of inline JS.

Artifact
72,707 B single self-contained file
Lines
671 HTML + CSS + JS inline
Apps
9 Word, 3 browsers, Paint, Calc…
Auth methods
2 ticket number + HKID
Pre-loaded data
5 + 3 + 8 tickets · HKIDs · books
Bugs fixed
5 before it ran at all
The instruction

"can you modify the windows 11 iso … ticket … or hkid … browsers guest mode … floating box: pause, zoom, log out … every data clears … (a website would also be fine)"

▶ Run the kiosk Byte-identical mirror Build spec

Why a web kiosk instead of a Windows image

The request named a real Windows 11 ISO but closed with "a website would also be fine". A web kiosk is the right call for a library terminal and the difference is the deployment cost:

AspectCustom Windows 11 ISOThis web kiosk
Build toolingWindows ADK + Windows Configuration Designer + sysprep/unattend.xmlOne .html file, a text editor
Ticket/HKID unlockA custom credential provider (C++ COM object) or a kiosk app wrapping the shellA login card with two forms against an in-page database
Locked settingsGroup Policy + Kiosk Mode (Assigned Access)There is no settings surface — apps are pre-selected, browsers are sandboxed
Deploy to a terminalReimage / sysprep, hours, licence per seatOpen the file in any browser, seconds
Wipe on logoutMandatory profile + roaming-profile resetOne logout() clears every window, iframe, editor and timer

The trade is honesty: a web page cannot truly lock the OS. It simulates the kiosk's behaviour — which is what a library actually cares about — not the kernel-level enforcement. That limit is stated plainly below.

The unlock path — ticket or HKID

Both auth methods were implemented, not one, because the request asked for both as equal first-class options:

LOCK SCREEN
   │
   ├── 🎫 Ticket Number ──► match against D.tickets[]  ──► consume (one-time) ──► unlock
   │
   └── 🪪 HKID ───────────► match against D.hkids[]    ──► greet by name ───────► unlock
                                            │
                                            └─ no match ─► "HKID not found"

Tickets are one-time use — a matched ticket is spliced out of the array before unlock, so a ticket photo-copied by a patron stops working. HKID lookup normalises whitespace and case before comparing, and greets the patron by name (Welcome, Chan Tai Man!) so a librarian at the desk can see at a glance who is at which terminal.

The nine apps

AppWhat worksLocked
Microsoft WordReal textarea editor, print via sidebarContent wiped on logout
Chrome / Firefox / EdgeGuest mode, DuckDuckGo search, URL barchrome:// / edge:// / about:config blocked
Library CatalogSearch 8 books by title/author/ISBNRead-only
NotepadEditor + printWiped on logout
CalculatorExpression evaluator—
File ExplorerApp launcher gridSettings entry is 🔒 Locked
MS PaintBrush/eraser on a canvasCanvas thrown away on close

The floating control bar

Top-right, exactly as asked: Pause Screen, Zoom 100% / 150% / 200%, Zoom Out, plus on-screen keyboard, high-contrast, help, and a red Log Out. Zoom scales the whole desktop via document.body.style.zoom, which reflows every window and the taskbar together — a per-window zoom would have left the taskbar and toolbar fixed while windows grew past them.

Logout is a hard wipe

The invariant is simple: leave no trace. S.logout() tears down every open window (removing the DOM nodes, not just hiding them), resets zoom to 100%, clears the pause state, resets the session timer, empties the taskbar, clears the on-screen keyboard's shift/caps state, and returns to the lock screen. Browser iframes, editor contents and Paint canvases are destroyed with their windows — there is nothing left to read because the nodes no longer exist.

Anatomy — the guest-mode browser

The blocklist

The URL bar and the iframe loader both refuse chrome://, edge://, about:config and about:support before navigation, and a load listener re-checks the iframe's actual location.href so a link inside a page can't sneak a restricted URL past the bar.

The start page

Each browser opens on a local srcdoc start page — a search box plus quick links — so the kiosk never depends on a remote homepage. Search navigates the iframe directly (window.location.href), not via postMessage, because the message bridge was one of the things that broke in build.

Five bugs that shipped broken — and were fixed at the cause

SymptomCauseFix
Nothing clickable — every button deadlet oskCaps=false and function oskCaps() declared in the same scope — a SyntaxError that killed the whole script before any handler boundRenamed to K.capsOn (state) + K.caps() (function); same for oskShift
Admin panel won't openaddEventListener on elements that weren't parsed yet / in nested template stringsAll element handlers moved to inline onclick / onkeydown
Browser start page is a wall of <div> textsrcdoc had .replace(/</g,'&lt;') — every tag escaped into literal textOnly escape " (needed for the attribute); leave </> raw
Search does nothingpostMessage bridge between iframe and parent never firedStart page navigates the iframe directly
Word showed function wordBody(){…}A live function reference inserted into innerHTML instead of calling itInvoke typeof body === 'function' ? body() : body at render time

The first two were the real killers: a naming conflict between a let and a function is valid-looking code that node --check rejects instantly, but the HTML file had been edited by string patches that never ran the check. The final pass re-wrote the file clean and validated it with node --check before opening — 0 syntax errors.

Admin panel

Hidden behind a ⚙️ Admin link in the login footer (and a 5-click corner trigger), password-gated. Six tabs — Tickets, HKID, Catalog, Announcements, Usage Logs, Settings — let a librarian add/revoke tickets, edit the HKID database, change the session timeout and print quota, and read a usage log of who logged in, how, and for how long. All state persists to localStorage, so it survives a refresh.

Honest limits. This is a simulation, not a real Windows 11 image and not real access control. Three specific gaps: (1) Browsers can't load Google, Wikipedia or most major sites — they send X-Frame-Options: DENY and refuse to render inside an iframe, so only DuckDuckGo and Bing work; the start page says so. (2) "Data wipe" is in-memory and localStorage — a determined user with devtools can read the database, which a real kiosk would gate behind the OS. (3) A web page cannot lock the OS — a patron can still alt-tab away from the browser. For real enforcement you'd pair this UI with Assigned Access / kiosk mode, but the behaviour — ticket auth, guest browsers, wipe-on-logout — is fully implemented.

Status & evidence

ItemEvidence
Artifactdemos/library-kiosk.html — 72,707 B, 671 lines
Mirrordemos/library-kiosk-mirror/index.html — sha256 afef1888…fd169, byte-identical
JS validitynode --check passes, 0 errors
AuthTicket one-time consume + HKID name-greet — both implemented
Browsers3 guest browsers, restricted-URL blocklist, direct iframe navigation
Logout wipeWindows, iframes, editors, canvas, timer, zoom all torn down
Real Windows ISOIn progress — not built; this is the web fallback

What's next

If a real image is ever needed, the path is Assigned Access kiosk mode + this HTML as the shell, or a custom credential provider for the ticket/HKID unlock. The web kiosk already covers the behaviour; the OS layer is the only missing piece.