← Commit history
README.md+159−2
@@ -1,3 +1,160 @@-# Adom Screensaver β€” the vibe-coding screensaver+# adom-screensaver β€” the vibe-coding screensaver -Turn your laptop into an always-on vibe coding server: branded screensaver + native keep-awake (no PowerToys) + honest, hardware-aware power management.+> πŸͺŸ **Windows only.** This runs on your **Windows laptop**, not a Linux/WSL2 container.++Turn your Windows laptop into an always-on home for your Claude Code threads. A branded Adom+screensaver that rests your screen, **keeps your machine awake** (network + Remote Control stay+alive), and β€” while it rests β€” runs a **live billboard of the latest Adom Wiki drops**, rendered in a+real Edge/Chromium engine so it plays **video (silently)** and renders **SVG**, not just static images.++Launch it any time with **Win + Shift + L**.++![Adom Screensaver β€” the branded splash that explains what it's doing while it rests your screen and keeps your machine awake](/blob/app/adom-screensaver/screenshots/hero.png)++Built for the people who fire up their Claude threads before they head to dinner and need them+still running when they get back.++## Install++```bash+adompkg install adom-screensaver+```++**What that does:** on your Adom container (Linux/WSL2) it installs the **skills** and stages the+**prebuilt, signed** Windows binaries β€” it does **not** run anything there. The screensaver is a+Windows app. To actually put it on your machine, then ask Claude:++```+set up the Adom screensaver on my Windows laptop+```++Claude deploys the **prebuilt, code-signed** binaries to your laptop over the `adom-desktop` relay,+wires keep-awake + autostart, registers it as your screensaver, and sets the display-off timing β€” no+compiler needed, and Windows Defender won't quarantine it (it's signed by "Adom Industries, Inc.").++## What it does++- **Keep-awake, no PowerToys.** A ~20-line helper (`adom-stayawake.exe`) holds+  `SetThreadExecutionState(ES_CONTINUOUS | ES_SYSTEM_REQUIRED)` on a permanently-alive thread, so+  the system stays awake (network + Remote Control survive) while the screen is free to turn off.+- **Opens with a gorgeous splash, then the billboards.** A dark cover lifts over ~800ms to reveal+  a power-aware **nerd-stats panel** β€” live rows for *Power* (plugged / on battery %), *Screen*+  (powers-off countdown), *Sleep* (never while plugged, or the on-battery countdown), *Keep-awake*+  (`ES_SYSTEM_REQUIRED` held β†’ NIC up, `/remote-control` reachable), and *Billboards* (how many are+  loaded + cache size, climbing live) β€” plus the animated "fetching the latest drops" counter.+- **A live Wiki billboard (WebView2).** It then cross-fades through recent apps/skills from the+  Adom Wiki β€” each with its hero, title, brief, and a **"just ask Claude"** prompt. Because it's a+  real browser tile it **plays webm video muted + looped** and renders **SVG**. Hero images/SVGs are+  **truly cached to disk** so they paint instantly and survive offline (video streams β€” too large to+  cache). Order is **re-shuffled every run**. Footer shows power + the display-off countdown.+- **Rests the panel β€” no burn-in.** When your display-off time arrives, the saver **pauses all+  video** (releasing the browser's display wake-lock that would otherwise keep the screen lit),+  **blanks to pure black**, and **powers the panel off** β€” while keep-awake holds the machine ++  network alive. It does *not* keep cycling bright billboards on a panel that's meant to be off.+  A 60-second heartbeat to `wv.log` records exactly what it did overnight.+- **Both wikis, with a migration nudge.** It pulls **Wiki v2** (canonical) *and* **Wiki v1**+  (legacy). If a page is on both, it shows the v2 version; v1-only cards carry an *"On Wiki v1 Β·+  nudge the team to migrate to v2"* kicker β€” so the screensaver doubles as a gentle prod to+  finish the v1 β†’ v2 migration.+- **Honest dimming.** On laptops where software can't dim the backlight (e.g. ASUS ROG dGPU/MUX),+  it doesn't pretend to β€” the on-screen dim is cosmetic (the billboard rests at ~70% brightness); the **real backlight-off is+  Windows' display-off timeout**, which genuinely powers the LEDs down with a clean keypress wake.+- **Instant wake.** Move the mouse and the billboards are gone **immediately** β€” the window hides+  and the browser tears down in the background (fading a layered window over a live WebView2/video is+  slow and would lag the dismiss, so it doesn't). The lightweight opening phase still fades out+  gracefully. **Never plays audio.**+- **Branded fade-to-black open.** The screensaver fades up from your desktop with a small dark-grey+  Adom mark centered, which then fades itself to black before the splash takes over.+- **Launch on demand β€” Win + Shift + L.** A global hotkey starts the screensaver instantly (the+  manual counterpart to the idle timer, the way `Win + L` is for lock). Contributed by Drew as+  `adom-screensaver-hotkey` and folded in as a first-class feature β€” toggleable from the+  preferences screen if it ever collides with another shortcut.+- **Lid-close rides through; no needless lock.** Closing the lid no longer slams the machine to+  sleep β€” on AC it does nothing, on battery Windows' own idle timer (e.g. 30 min) is the give-up+  clock β€” so a quick close-and-carry doesn't kill your threads. And because the screensaver only+  *rests the screen* (it doesn't sleep), it **doesn't throw a lock screen**; a real sleep-then-wake+  still asks for sign-in as usual.+- **Public-only, curatable rotation.** Only **public** wiki pages are advertised β€” explicitly+  org-private/internal pages are filtered out β€” and you can drop anything else (legacy v1 pages have+  no visibility field) by adding its slug to `exclude.txt`, no rebuild.+- **Signed.** Both binaries are code-signed with Adom's Azure Trusted Signing cert ("Adom+  Industries, Inc.", the same one HD and AD use), so Windows Defender doesn't quarantine the `.scr`.+- **Driveable by other apps.** Hydrogen Desktop / adom-desktop can ask it to get off the screen+  via a named event, a flag file, or a localhost control port (see the `adom-screensaver-wake` skill).++## Screenshots++### The preferences screen β€” read & fix your live power settings+Reachable from Windows' Screen Saver **"Settings…"** button, it reads your *actual* Windows+power settings (lid, sleep, screen-off, lock) and shows them against the recommended+"vibe-coding" profile, with one-click **Apply** for anything that's drifted. A **Re-check**+button re-reads the live settings, and a toggle turns the **Win + Shift + L** hotkey on or off.+Windows' defaults predate the AI age β€” they assume an idle laptop should sleep; this profile keeps+it working instead.++![Adom Screensaver preferences screen β€” live power-settings drift table](/blob/app/adom-screensaver/screenshots/preferences.png)++### A live billboard slide β€” advertising the latest Wiki drops+While the screen rests, it cycles recent Adom Wiki apps/skills β€” each hero, title, and a+**"just ask Claude"** prompt β€” over a live power + display-off-countdown footer.++![A live billboard slide from the Adom Wiki](/blob/app/adom-screensaver/screenshots/billboard-concur.png)++## Dependencies++| Dependency | Required? | Why |+|---|---|---|+| `adom-desktop` relay | Yes (runtime) | The cloud container drives your Windows laptop through it β€” pushing files, compiling, shipping the WebView2 SDK, configuring power/registry. Ships with your Adom desktop bridge. |+| WebView2 Runtime | Yes (on the laptop) | Renders the billboard (video + SVG). Ships with Windows 11 / Edge. |+| .NET Framework (`csc.exe`) | Yes (on the laptop) | Ships with every Windows. Compiles the screensaver + keep-awake helper natively. |+| PowerToys | **No** | Explicitly replaced β€” native keep-awake without it. |++No `adompkg` package dependencies.++## Features++- WebView2 billboard of recent Wiki v2 + v1 drops; **webm video (muted/looped)** + **SVG** + raster, all streamed.+- Native keep-awake via `SetThreadExecutionState` (PowerToys-free), autostarting at login.+- Real backlight-off via Windows display-off timeout (true LED power-down) while staying awake.+- Power-state + live display-off countdown footer; v1 "migrate to v2" kicker; randomized order; ~70%-brightness cosmetic dim.+- Adaptive wake (instant on-screen dissolve / panel warm-up when off); no lock unless the machine truly slept.+- **Win + Shift + L** launch hotkey (Drew's contribution), toggleable from preferences; lid-close rides through instead of sleeping.+- Runtime curation via `exclude.txt`; wake IPC (named event + flag + localhost control port) for other Adom apps.+- Honest by design: cosmetic dim, real display-off, never audio.++## Talk to the AI++**Set up / deploy**+- "set up the Adom screensaver on my laptop"+- "install the keep-awake helper and replace PowerToys"+- "turn off the Win+Shift+L screensaver hotkey" (if it collides with another app)++**Configure**+- "change the screensaver idle timeout to 2 minutes"+- "set the display to turn off after 3 minutes"+- "stop showing <slug> in the billboard"++**Run / troubleshoot**+- "trigger the Adom screensaver"+- "is the screensaver running right now?"+- "the billboard is blank" (WebView2 runtime / DLLs)++## Skills++| Skill | Purpose |+|---|---|+| `adom-screensaver` | Deploy, configure, run, troubleshoot via the `adom-desktop` relay. |+| `adom-screensaver-billboard` | Dual-wiki streaming discovery, video/SVG, `exclude.txt` curation, the manifest. |+| `adom-screensaver-wake` | Wake IPC for HD/AD/any app that needs the screen. |+| `adom-screensaver-build` | Compile + ship the WebView2 `.scr` (+ SDK DLLs) and verify renders. |+| `adom-screensaver-rationale` | The *why* β€” origin story, the staged cascade, and the design reasoning behind every behavior. |++The main skill is [SKILL.md](SKILL.md); the sub-skills live in [skills/](skills/).++## The honest hardware story++Built on a real ASUS ROG Zephyrus, where software can't touch the backlight (`WmiSetBrightness`+ignored on the dGPU/MUX path; NVIDIA blocks `SetDeviceGammaRamp`). So the app doesn't fake it: the+cosmetic dim is a CSS brightness veil (~70%) over the billboard, and the only real software backlight-off is+letting Windows turn the display off β€” which this app sets up, with keep-awake holding the system+so the network never drops.
SKILL.mdadded+85
@@ -0,0 +1,85 @@+---+name: adom-screensaver+description: Deploy (prebuilt + signed), configure, run, and troubleshoot the Adom vibe-coding screensaver + native keep-awake on a user's Windows laptop via the adom-desktop relay. A WebView2 (Edge/Chromium) billboard of recent Adom Wiki drops β€” plays webm silently, renders SVG, truly caches heroes β€” that rests the panel (no burn-in) while keeping the machine awake so Claude Code / Remote Control threads survive. WINDOWS ONLY. Use to install/deploy, change idle or display-off timing, disable lock-on-wake, trigger it, or troubleshoot. Trigger words β€” adom screensaver, adom-screensaver, vibe-coding screensaver, keep laptop awake, keep awake without powertoys, stay awake, screen off but stay awake, adom-stayawake, display off timeout, screensaver lock on wake, set up screensaver, trigger screensaver, Win+Shift+L screensaver, screensaver burn-in.+---++# Adom Screensaver β€” deploy (prebuilt) + operate++Turns a **Windows** laptop into an always-on home for the user's Claude Code threads: a branded+**WebView2 billboard** of recent Adom Wiki drops + native keep-awake (no PowerToys). Drive it from+the Adom container through the **`adom-desktop`** relay. See also:+- **[[adom-screensaver-billboard]]** β€” the viewer, dual-wiki discovery, true caching, `exclude.txt` curation.+- **[[adom-screensaver-wake]]** β€” how HD/AD or any app dismisses it + holds the display on (IPC).+- **[[adom-screensaver-rationale]]** β€” the *why*: origin story, the cascade, power/lock/rest reasoning.++> ⚠️ **WINDOWS ONLY.** `adompkg install adom-screensaver` on a Linux/WSL2 container only installs+> these skills + stages the deploy payload β€” it does **not** run anything there. The screensaver is+> deployed to the user's **Windows laptop** over the relay. (The private source + build live on the+> org-only `adom-screensaver-dev` page; this public package ships only the prebuilt, **signed**+> binaries β€” no compiler needed.)++## What it is+A fullscreen WebView2 rendering `billboard.html`: opens with a branded fade-to-black, then a+power-aware **nerd-stats splash** (cover-fades up ~800ms over the fully-rendered content), then+cross-fades through Wiki v2 + v1 hero cards. Plays **webm muted/looped**, renders **SVG**/raster;+hero images/SVGs are **truly cached to disk** (video streams). When the **display-off time** arrives+it **rests** β€” pauses video (releasing the browser's display wake-lock), blanks to black, and powers+the panel off β€” so it never burns a bright billboard into a panel that's meant to be off. Keep-awake+holds the system/network the whole time. **Win+Shift+L** launches it on demand. Only **public** wiki+pages are advertised (org-private filtered; curate more via `exclude.txt`).++## Relay: base64 everything+```bash+b64=$(base64 -w0 script.ps1); adom-desktop run_script "{\"interpreter\":\"powershell\",\"scriptB64\":\"$b64\"}"+```+**Check `adom-desktop status` hostname first** (it can flip between the laptop and winvm). Calls time+out ~60s; launch long things detached + poll. A winexe (`.scr`/`.exe`) ignores `&` β€” use+`Start-Process`. If `run_script` returns empty, re-arm `shell_auto_approve`.++## Deploy = PREBUILT signed binaries (no compile)+Install dir: `C:\Users\<user>\adom-screensaver`. The package's `dist/win/` already holds the+**signed** `.scr` + helper + DLLs + html + assets (subject `Adom Industries, Inc.`, Azure Trusted+Signing β€” so Defender won't quarantine them). To deploy:+1. **Push `dist/win/*`** to the install dir (`send_files` for the big DLLs; base64 for the small+   files via the Defender-safe `.new` + `Move-Item -Force` pattern). Files: `adom-screensaver.scr`,+   `adom-stayawake.exe`, `Microsoft.Web.WebView2.Core.dll`, `Microsoft.Web.WebView2.WinForms.dll`,+   `WebView2Loader.dll`, `billboard.html`, `config.html`, `adom.ico`, `adom-wordmark.png`,+   `bundle.json`. **Do not compile** β€” ship the signed binaries as-is.+2. **Keep-awake + autostart:** launch `adom-stayawake.exe`; set BOTH the HKCU `…\Run` value+   `adom-stayawake` AND a Startup-folder shortcut (one alone got wiped on reboot). It's power-aware+   (AC = hold `ES_SYSTEM_REQUIRED`; battery = passive) and registers the Win+Shift+L hotkey.+3. **Register** (merge, never clobber): `HKCU\Control Panel\Desktop` β†’ `SCRNSAVE.EXE`=`<dir>\adom-screensaver.scr`,+   `ScreenSaveActive`=1, `ScreenSaveTimeOut`=**120** (2 min), `ScreenSaverIsSecure`=0. Apply live:+   `SystemParametersInfo(0x0011,1,..)` + `SystemParametersInfo(0x000F,<secs>,..)`.+4. **Real backlight-off:** `powercfg /change monitor-timeout-ac 5` (+ `-dc 5`) β€” the saver rests ++   powers the panel off at this mark.+5. **No lock on wake (optional, ADMIN):** `CONSOLELOCK`=0 needs elevation β€” warn the user, trigger one+   UAC prompt, verify via the per-scheme registry index. (A real sleep-then-wake still asks for sign-in.)+6. **Verify the deployed `.scr` is signed:** `(Get-AuthenticodeSignature <dir>\adom-screensaver.scr).Status` = `Valid`.++`bundle.json` in `dist/win/` lists the canonical file set + registry keys for an automated deploy.++## Configure (common asks)+- **Idle timeout** ("come on after N min"): `ScreenSaveTimeOut`=N*60 + live `SystemParametersInfo(0x000F,..)`.+- **Display-off / rest** ("rest the screen after N min"): `powercfg /change monitor-timeout-ac N`.+- **Win+Shift+L hotkey:** toggle from the preferences screen (writes `hotkey.disabled` in the install dir).+- **Curate the rotation:** append a slug to `exclude.txt` β€” NO redeploy ([[adom-screensaver-billboard]]).+- **Preferences screen:** Windows Screen Saver "Settings…" opens the live power-settings drift table.++## Run on demand+**Win+Shift+L**, or `Start-Process '<dir>\adom-screensaver.scr' -ArgumentList '/s'`. Grace ~1s, then any+key/mouse wakes β€” **instant** during the billboards (the window hides immediately; no slow fade).++## Troubleshooting+- **Blank/black billboard:** WebView2 Runtime missing, or the 3 DLLs / `WebView2Loader.dll` not beside+  the `.scr`; check `%LOCALAPPDATA%\adom-screensaver\wv.log` (also has a 60s heartbeat + REST lines).+- **Defender quarantined the .scr:** shouldn't happen β€” it's signed. If a *modified* unsigned build was+  deployed, re-deploy the signed `dist/win` binaries (signing is a `-dev`/build concern).+- **Screen on all night / burn-in:** the rest path failed β€” confirm the deployed `.scr` is the current+  signed build; `wv.log` should show `REST: … powering panel off` at the display-off mark.+- **Keep-awake gone after reboot:** Run key + SCRNSAVE.EXE can get wiped β€” that's why we also set the+  Startup shortcut. Re-launch + re-apply.++## Honesty constraints+Don't claim a hardware dim (the on-screen dim is cosmetic CSS; real backlight-off = the display-off+timeout). Don't claim a `/remote-control` thread count (no programmatic enumeration). adom-rc is deprecated.
dist/win/Microsoft.Web.WebView2.Core.dlladded
⋯ 1 unchanged line ⋯
dist/win/Microsoft.Web.WebView2.WinForms.dlladded
⋯ 1 unchanged line ⋯
dist/win/WebView2Loader.dlladded
⋯ 1 unchanged line ⋯
dist/win/adom-screensaver.scradded
⋯ 1 unchanged line ⋯
dist/win/adom-stayawake.exeadded
⋯ 1 unchanged line ⋯
dist/win/adom-wordmark.pngadded
⋯ 1 unchanged line ⋯
dist/win/adom.icoadded
⋯ 1 unchanged line ⋯
dist/win/billboard.htmladded+306
@@ -0,0 +1,306 @@+<!doctype html>+<!-- Adom Screensaver billboard, rendered in a fullscreen WebView2 hosted by AdomSaver.scr.+     Plays webm (muted, looped), renders SVG + raster, cross-fades, shows the power + display-off+     countdown footer and the Wiki v1 "migrate to v2" kicker. Data comes from manifest.json+     (written by the C# host) served from the virtual host https://adom-screensaver.local/. -->+<html lang="en">+<head>+<meta charset="utf-8">+<style>+  :root { --teal:#00b8b0; --tealbri:#00e6dc; --hi:#e6edf3; --lo:#8b949e; --dim:#6b7280; --line:#1e2733; --bg:#0d1117; }+  * { margin:0; padding:0; box-sizing:border-box; }+  html,body { width:100%; height:100%; overflow:hidden; background:var(--bg);+    font-family:"Segoe UI","Segoe UI Semibold",system-ui,sans-serif; cursor:none; user-select:none; }+  /* The whole billboard rests at 50% (configurable). The host fades the WINDOW out on wake. */+  #stage { position:fixed; inset:0; background:var(--bg); filter:brightness(var(--dim,0.5));+    opacity:1; transition:opacity .45s ease; }+  .slide { position:absolute; inset:0; display:flex; flex-direction:column; align-items:center; }+  .kicker { margin-top:5.2vh; font-size:1.55vh; font-weight:600; letter-spacing:.55em;+    text-transform:uppercase; color:var(--teal); text-align:center; }+  .kicker.v1 { color:var(--lo); letter-spacing:.42em; }+  .herobox { margin-top:1.2vh; width:58vw; height:40vh; display:flex; align-items:center; justify-content:center; }+  .herobox > * { max-width:100%; max-height:100%; object-fit:contain;+    border:1.5px solid rgba(0,184,176,.35); border-radius:6px; background:#000; }+  .title { margin-top:2.4vh; font-size:3.6vh; font-weight:600; color:var(--hi); text-align:center; }+  .brief { margin-top:.6vh; max-width:62vw; font-size:1.7vh; line-height:1.4; color:var(--lo); text-align:center; }+  .label { margin-top:2.4vh; font-size:1.25vh; font-weight:600; letter-spacing:.45em;+    text-transform:uppercase; color:var(--lo); }+  .promptbox { margin-top:.9vh; padding:1vh 1.6vw; border:1.4px solid rgba(0,184,176,.5);+    border-radius:10px; background:rgba(0,184,176,.16); color:var(--tealbri); font-size:1.9vh; max-width:72vw;+    white-space:nowrap; overflow:hidden; text-overflow:ellipsis; }+  .foot { margin-top:2vh; font-size:1.45vh; color:var(--lo); text-align:center; }+  /* Intro splash β€” a LEFT-ALIGNED editorial layout (logo β†’ eyebrow β†’ headline β†’ sub β†’ live status,+     on a teal accent rail) over a soft off-center glow, with a big faded brand mark anchoring the+     right and a bottom control bar of real keycaps. Held until data is ready, then cross-faded. */+  #intro { position:fixed; inset:0; z-index:5; opacity:1; overflow:hidden;+    background:radial-gradient(70% 90% at 26% 42%, #17212f 0%, #0d1117 58%); }+  /* A solid full-screen cover sits OVER the splash while its text + logo render underneath, then+     fades off (800ms) to reveal them already-drawn β€” a simple rectangle composites perfectly smooth. */+  /* Plain dark cover β€” the branded grey logo lives ONLY on the GDI+ fade-to-black phase (showing it+     here too made it visibly jump, since GDI+ and the browser viewport scale it differently). */+  #blackcover { position:fixed; inset:0; z-index:30; background:#0d1117; opacity:1;+    transition:opacity .8s ease; pointer-events:none; will-change:opacity; }+  #blackcover.gone { opacity:0; }+  /* Rest layer: pure black covering everything once the display-off time arrives, so nothing burns in. */+  #restlayer { position:fixed; inset:0; z-index:50; background:#000; display:none; }+  .intro-mark { position:absolute; right:-7vw; top:50%; transform:translateY(-50%);+    width:74vh; height:74vh; opacity:.05; z-index:0; pointer-events:none; }+  .intro-grid { position:absolute; left:0; right:0; top:0; bottom:8.8vh; display:flex; align-items:center; z-index:1; }+  .intro-content { margin-left:9vw; max-width:60vw; padding-left:2.7vw;+    border-left:2px solid rgba(0,184,176,.5); }+  #intrologo { width:10.5vw; max-width:220px; display:block; filter:drop-shadow(0 0 28px rgba(0,184,176,.15)); }+  .intro-eyebrow { margin-top:3vh; color:var(--teal); letter-spacing:.44em; font-size:1.2vh;+    font-weight:600; text-transform:uppercase; }+  .intro-head { margin-top:1.4vh; max-width:17ch; color:var(--hi); font-size:4.3vh; font-weight:600;+    letter-spacing:-.015em; line-height:1.12; }+  .intro-head #introdev { color:var(--tealbri); }+  .intro-sub { margin-top:1.8vh; max-width:46ch; color:var(--lo); font-size:1.65vh; line-height:1.6; }+  .intro-sub b { color:var(--hi); font-weight:600; }+  /* Live "nerd stats" β€” power, screen-off + sleep countdowns, keep-awake internals, cache growth. */+  /* A real 2-column grid so every label and every value lines up in rigid columns (a table). */+  .intro-stats { margin-top:2.7vh; display:grid; grid-template-columns:max-content 1fr;+    column-gap:1.9vw; row-gap:1.15vh; align-items:baseline; max-width:68ch; font-size:1.6vh; line-height:1.34; }+  .stat { display:contents; }+  .stat-k { white-space:nowrap; color:var(--lo); letter-spacing:.06em; text-transform:uppercase;+    font-size:1.18vh; font-weight:600; }+  .stat-k::before { content:""; display:inline-block; width:.62vh; height:.62vh; border-radius:50%;+    background:var(--teal); margin-right:.8vw; vertical-align:middle; box-shadow:0 0 .7vh rgba(0,184,176,.55); }+  .stat-v { color:var(--hi); font-weight:500; font-variant-numeric:tabular-nums; }+  .stat-v b { color:var(--tealbri); font-weight:700; }+  .stat-v .mono { font-family:"Cascadia Code","Consolas","Courier New",monospace; font-size:.94em; color:var(--tealbri); letter-spacing:-.01em; }+  .intro-status { margin-top:3.1vh; display:inline-flex; align-items:center; gap:.7vw;+    padding:.85vh 1.5vw; border:1px solid rgba(0,184,176,.28); border-radius:999px;+    background:rgba(0,184,176,.07); color:var(--tealbri); font-size:1.4vh; }+  .pulse { width:.9vh; height:.9vh; border-radius:50%; background:var(--tealbri); flex:none;+    animation:pulse 1.7s ease-out infinite; }+  @keyframes pulse { 0%{box-shadow:0 0 0 0 rgba(0,230,220,.45)} 70%{box-shadow:0 0 0 1.1vh rgba(0,230,220,0)} 100%{box-shadow:0 0 0 0 rgba(0,230,220,0)} }+  .intro-bar { position:absolute; left:0; right:0; bottom:0; display:flex; align-items:center;+    justify-content:space-between; gap:3vw; padding:2.3vh 4vw; border-top:1px solid var(--line);+    background:linear-gradient(0deg, rgba(8,11,16,.72), transparent); font-size:1.32vh; color:var(--lo); }+  .bar-left, .bar-right { display:flex; align-items:center; gap:1vw; flex-wrap:wrap; }+  .kgrp { display:inline-flex; align-items:center; gap:.35vw; }+  .intro-bar kbd { font-family:inherit; font-size:1.28vh; font-weight:600; color:var(--hi);+    background:#1b2330; border:1px solid #2c3645; border-bottom-width:2px; border-radius:6px;+    padding:.4vh .85vw; box-shadow:0 1px 0 rgba(0,0,0,.45); }+  .intro-bar kbd.soft { background:transparent; border-style:dashed; border-bottom-width:1px;+    color:var(--lo); font-weight:500; }+  .kgrp i { color:var(--dim); font-style:normal; font-size:1.1vh; }+  .cap { color:var(--lo); }+  .sep { color:var(--line); }+  .bar-right b { color:var(--teal); font-weight:600; }+</style>+</head>+<body>+<div id="intro">+  <img class="intro-mark" src="data:image/png;base64,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" alt="">+  <div class="intro-grid">+    <div class="intro-content">+      <img id="intrologo" src="adom-wordmark.png" alt="Adom">+      <div class="intro-eyebrow">Vibe-coding screensaver</div>+      <h1 class="intro-head">Keeps your <span id="introdev">laptop</span> working while you step away.</h1>+      <p class="intro-sub">Your Claude Code threads under <b>/remote-control</b> keep running β€” here's exactly what your machine is doing right now:</p>+      <div class="intro-stats" id="introstats">+        <div class="stat"><span class="stat-k">Power</span><span class="stat-v" id="st-power">β€”</span></div>+        <div class="stat"><span class="stat-k">Screen</span><span class="stat-v" id="st-screen">β€”</span></div>+        <div class="stat"><span class="stat-k">Sleep</span><span class="stat-v" id="st-sleep">β€”</span></div>+        <div class="stat"><span class="stat-k">Keep-awake</span><span class="stat-v" id="st-awake">β€”</span></div>+        <div class="stat"><span class="stat-k">Billboards</span><span class="stat-v" id="st-cache">loading from the Adom Wiki…</span></div>+      </div>+      <div class="intro-status"><span class="pulse"></span><span id="introstatus">Fetching the latest drops from the Adom Wiki…</span></div>+    </div>+  </div>+  <div class="intro-bar">+    <div class="bar-left">+      <span class="kgrp"><kbd>Win</kbd><i>+</i><kbd>Shift</kbd><i>+</i><kbd>L</kbd></span><span class="cap">launch anytime</span>+      <span class="sep">Β·</span>+      <kbd class="soft">move&nbsp;mouse&nbsp;/&nbsp;any&nbsp;key</kbd><span class="cap">dismiss</span>+    </div>+    <div class="bar-right">+      <span class="cap">turn it off β€” ask Claude <b>β€œturn off the Adom screensaver”</b></span>+    </div>+  </div>+</div>+<div id="stage"></div>+<div id="blackcover"></div>+<div id="restlayer"></div>+<script>+const HOST = "https://adom-screensaver.local/";+const HOLD = 4200, FADE = 650, PER = HOLD + FADE; // ms per slide+const INTRO_MS = 15000; // hold the gorgeous WebView2 splash this long after it appears, THEN billboards+let slides = [], idx = -1, started = 0, status = { power:"", off:-1 }, dimSet = false;+let revealedAt = 0;+// Fade the splash up from black. WebView2 defers rendering AND image decode while it's hidden, so if+// we fade the instant it appears, the logo/text aren't painted yet and pop in late (the "blip"). So:+// once the page is actually visible, hold the dark (opacity:0) frame ~420ms to let it render + decode+// the logo/text/watermark, THEN flip to .in so everything rises out of black together, already drawn.+var introFadeScheduled = false;+function startIntroFade(){+  if (introFadeScheduled) return; introFadeScheduled = true;+  // Hold the black cover while the now-visible page renders + decodes the logo/text underneath,+  // THEN fade the cover off over 800ms to reveal the already-drawn splash (no content pop).+  setTimeout(function(){+    var cov = document.getElementById("blackcover");+    if (!cov) return;+    requestAnimationFrame(function(){ requestAnimationFrame(function(){+      cov.classList.add("gone");+      setTimeout(function(){ cov.style.display = "none"; }, 850); // remove after the 800ms fade so it can't cover the billboards+    }); });+  }, 420);+}+// visibilitychange is the reliable "rendering has resumed" signal when the host un-hides the WebView2.+document.addEventListener("visibilitychange", function(){ if (document.visibilityState === "visible") startIntroFade(); });++// The host calls this when it reveals the WebView2 (live) or when a capture nav completes β€” a backup+// trigger in case this WebView2 config doesn't emit visibility events.+window.adomRevealed = function(){+  if (revealedAt) return;+  revealedAt = Date.now();+  startIntroFade();+};++// The host calls this every second with live power + seconds-to-display-off.+window.adomStatus = function(power, off){ status.power = power || ""; status.off = (off==null?-1:off); paintFoot(); };+// The host streams what the loader is doing right now (so the intro always explains itself).+window.adomLoading = function(text, device){+  const dv = document.getElementById("introdev"); if(dv && device) dv.textContent = device;+};+function mmss(s){ if(s<0) return ""; const m=Math.floor(s/60), x=s%60; return m+":"+(x<10?"0":"")+x; }+function fmtBytes(b){ b=b||0; if(b<1024) return b+" B"; if(b<1048576) return (b/1024).toFixed(0)+" KB"; return (b/1048576).toFixed(1)+" MB"; }+// The host pushes a full live snapshot every second; we render the splash's nerd-stat panel from it.+window.adomStats = function(s){+  try {+    var set = function(id,html){ var el=document.getElementById(id); if(el) el.innerHTML=html; };+    set("st-power", s.ac ? "<b>Plugged in</b>" : ("On battery Β· <b>"+(s.pct<=100?s.pct+"%":"?")+"</b>"));+    set("st-screen", s.off>0 ? ("powers off in <span class='mono'>"+mmss(s.off)+"</span> to rest the panel")+                  : (s.off===0 ? "resting now β€” panel powered off" : "powers off shortly to rest the panel"));+    if (s.ac) set("st-sleep", "<b>Never</b> while plugged in β€” keep vibe-coding as long as you like");+    else if (s.sleepLeft>0) set("st-sleep", "in <span class='mono'>"+mmss(s.sleepLeft)+"</span> Β· Windows idle-sleep (on battery)");+    else if (s.sleepLeft===0) set("st-sleep", "idle limit reached β€” sleeping");+    else set("st-sleep", "won't idle-sleep on battery");+    if (s.ka==="off") set("st-awake", "helper isn't running");+    else if (s.ac) set("st-awake", "holding <span class='mono'>ES_SYSTEM_REQUIRED</span> β€” the machine never idles, so the NIC stays up and <b>/remote-control</b> stays reachable");+    else set("st-awake", "passive on battery β€” NIC stays up while awake; Windows' idle-sleep timer is the give-up clock");+    // Report what's ACTUALLY in the rotation (cached + freshly fetched), not just the host's+    // fresh-fetch counter β€” otherwise it reads "0 loaded" while cached billboards are already showing.+    set("st-cache", "<b>"+slides.length+"</b> loaded Β· <span class='mono'>"+fmtBytes(s.cacheBytes)+"</span> on disk");+  } catch(e){}+};+function footText(s, i, n){+  // Power-aware: keep-awake actively holds the machine only when plugged in; on+  // battery it stays passive and Windows' own idle-sleep timer is the give-up clock.+  const onAc = /plug/i.test(status.power||"");+  const ka = status.power ? (onAc ? "  Β·  keep-awake on" : "  Β·  sleeps when idle") : "";+  const right = (status.power||"") + (status.off>=0 ? "  Β·  screen powers off in "+mmss(status.off) : "") + ka;+  const by = s.author ? "  Β·  by "+s.author : "";+  return s.type + by + "      " + (i+1) + " / " + n + "      " + right;+}+function paintFoot(){+  const cur = document.querySelector("#stage .foot");+  if (cur && cur._slide) cur.textContent = footText(cur._slide, cur._i, slides.length);+}+// Drop a slide whose hero failed to load (e.g. a stale cached pointer that now 404s) so it+// never shows a broken icon.+function dropSlide(slug){ const i = slides.findIndex(s=>s.slug===slug); if(i>=0) slides.splice(i,1); }++function mediaEl(s){+  const src = /^https?:/i.test(s.media) ? s.media : HOST + encodeURIComponent(s.media);+  if (s.kind === "video"){+    const v = document.createElement("video");+    v.src = src; v.muted = true; v.defaultMuted = true; v.volume = 0; v.loop = true;+    v.autoplay = true; v.setAttribute("playsinline",""); v.preload = "auto";+    v.addEventListener("canplay", ()=>{ try{ v.play(); }catch(e){} });+    v.onerror = ()=>{ dropSlide(s.slug); };+    return v;+  }+  const img = document.createElement("img"); img.src = src; // <img> renders svg + raster+  img.onerror = ()=>{ img.style.display="none"; dropSlide(s.slug); };+  return img;+}+function buildSlide(s, i){+  const d = document.createElement("div"); d.className = "slide";+  const v1 = s.source === "v1";+  const k = document.createElement("div"); k.className = "kicker" + (v1?" v1":"");+  k.textContent = v1 ? "On Wiki v1 Β· nudge the team to migrate to v2" : "New on the Adom Wiki";+  const hb = document.createElement("div"); hb.className = "herobox"; hb.appendChild(mediaEl(s));+  const t = document.createElement("div"); t.className = "title"; t.textContent = s.title || s.slug;+  const b = document.createElement("div"); b.className = "brief"; b.textContent = s.brief || "";+  const lab = document.createElement("div"); lab.className = "label"; lab.textContent = "Just ask Claude";+  const pb = document.createElement("div"); pb.className = "promptbox"; pb.textContent = "β€Ί  " + (s.prompt || ("install "+s.slug));+  const f = document.createElement("div"); f.className = "foot"; f._slide = s; f._i = i; f.textContent = footText(s, i, slides.length);+  d.append(k, hb, t, b, lab, pb, f);+  return d;+}+function showSlide(i){+  const stage = document.getElementById("stage");+  const s = slides[i]; if(!s) return;+  // fade the stage OUT, swap content, fade IN β€” so text never overlaps mid-transition.+  stage.style.opacity = "0";+  setTimeout(()=>{+    stage.innerHTML = "";+    const el = buildSlide(s, i);+    stage.appendChild(el);+    const vid = el.querySelector("video"); if(vid){ try{ vid.currentTime=0; vid.play(); }catch(e){} }+    stage.style.opacity = "1";+  }, 460);+}+let introHidden = false;+function hideIntro(){+  if (location.hash.indexOf("holdintro") >= 0) return; // capture mode: keep the explanation splash up+  if (introHidden) return; introHidden = true;+  const el = document.getElementById("intro");+  // The 3s transition is the intro fade-IN; for the hand-off to billboards use a quick fade-OUT.+  el.style.transition = "opacity .7s ease";+  el.style.opacity = "0"; setTimeout(()=>{ el.style.display = "none"; }, 760);+}+// Fisher-Yates shuffle so the rotation order is randomized EACH run (the cached manifest otherwise+// preserves last run's order). New slides that stream in afterwards just append to the end.+function shuffleSlides(){+  for (var i = slides.length - 1; i > 0; i--){ var j = Math.floor(Math.random()*(i+1)); var t = slides[i]; slides[i] = slides[j]; slides[j] = t; }+}+// The host calls this when the display-off time arrives: pause ALL video (releases Chromium's+// display wake-lock so the panel can finally power off) and blank to pure black so nothing burns in.+// The rotation stops (tick() bails on `resting`), so no new slide ever restarts a video.+var resting = false;+window.adomRest = function(){+  if (resting) return; resting = true;+  try { var vs = document.querySelectorAll("video"); for (var i=0;i<vs.length;i++){ try{ vs[i].pause(); }catch(e){} } } catch(e){}+  var r = document.getElementById("restlayer"); if (r) r.style.display = "block";+};+function tick(){+  if (resting) return;                        // display is resting β€” do nothing (no video, no repaint)+  if (!revealedAt) return;                    // wait until the WebView2 is actually on screen+  const now = Date.now();+  if (now - revealedAt < INTRO_MS) return;    // hold the WebView2 splash its full ~5s+  if (slides.length === 0) return;            // not ready yet β€” keep the splash up until slides land+  if (started === 0){ shuffleSlides(); started = now; idx = 0; showSlide(0); hideIntro(); return; }+  const want = Math.floor((now - started)/PER) % slides.length;+  if (want !== idx){ idx = want; showSlide(idx); hideIntro(); }+}++async function loadManifest(){+  try{+    const r = await fetch(HOST + "manifest.json?_=" + Date.now(), {cache:"no-store"});+    const j = await r.json();+    if (j && typeof j.dimPct === "number" && !dimSet){ document.documentElement.style.setProperty("--dim", (j.dimPct/100).toFixed(2)); dimSet = true; }+    let incoming = j && j.slides ? j.slides : [];+    if (location.hash.indexOf("onlyvideo") >= 0) incoming = incoming.filter(s=>s.kind==="video"); // verification mode+    // append only NEW slugs (don't reorder/restart what's already cycling)+    const have = new Set(slides.map(s=>s.slug));+    incoming.forEach(s=>{ if(!have.has(s.slug)){ slides.push(s); have.add(s.slug); } });+  }catch(e){}+}++(async function(){+  await loadManifest();+  setInterval(loadManifest, 3000); // pick up slides as the host downloads more+  setInterval(tick, 200);+  setTimeout(function(){ window.adomRevealed(); }, 9000); // safety: never hang on the fallback splash+  // Tell the host about input so it can run the adaptive wake (it fades the window out + exits).+  const wake = ()=>{ try{ window.chrome.webview.postMessage("input"); }catch(e){} };+  let armed = false; setTimeout(()=>{ armed = true; }, 1200); // grace so launch doesn't self-dismiss+  ["mousemove","mousedown","keydown","wheel","touchstart"].forEach(ev=>+    window.addEventListener(ev, ()=>{ if(armed) wake(); }, {passive:true}));+})();+</script>+</body>+</html>
dist/win/bundle.jsonadded+32
@@ -0,0 +1,32 @@+{+  "name": "adom-screensaver",+  "version": "0.6.0",+  "platform": "windows",+  "install_dir_env": "LOCALAPPDATA",+  "install_subdir": "adom-screensaver",+  "screensaver_exe": "adom-screensaver.scr",+  "keepawake_exe": "adom-stayawake.exe",+  "config_modes": {+    "config_dialog": "/c",+    "read_json": "/cjson <outfile>",+    "apply_profile": "/capply"+  },+  "run_note": "Run /c, /cjson, /capply only from an .exe COPY of the .scr (a bare .scr runs the shell default verb).",+  "files": [+    "adom-screensaver.scr",+    "adom-stayawake.exe",+    "billboard.html",+    "config.html",+    "adom.ico",+    "adom-wordmark.png",+    "Microsoft.Web.WebView2.Core.dll",+    "Microsoft.Web.WebView2.WinForms.dll",+    "WebView2Loader.dll"+  ],+  "registry_HKCU_Control_Panel_Desktop": {+    "SCRNSAVE.EXE": "<install_dir>\\adom-screensaver.scr",+    "ScreenSaveActive": "1",+    "ScreenSaveTimeOut": "120",+    "ScreenSaverIsSecure": "0"+  }+}\ No newline at end of file
dist/win/config.htmladded+227
@@ -0,0 +1,227 @@+<!doctype html>+<!-- Adom Screensaver β€” Preferences. Rendered windowed in a WebView2 by adom-screensaver.scr /c+     (and, later, by the Hydrogen Desktop dialog). The C# host injects LIVE Windows settings via+     window.adomConfig(data, applied) every time it opens, and applies the recommended profile when+     the user clicks Apply (the host re-reads + re-injects so the table always shows real state). -->+<html lang="en">+<head>+<meta charset="utf-8">+<style>+  :root { --teal:#00b8b0; --tealbri:#00e6dc; --hi:#e6edf3; --lo:#8b949e; --dim:#6b7280;+          --bg:#0d1117; --panel:#161b22; --line:#21262d; --warn:#e3a008; --ok:#2ea043; }+  * { margin:0; padding:0; box-sizing:border-box; }+  html,body { height:100%; background:var(--bg); color:var(--hi);+    font-family:"Segoe UI",system-ui,sans-serif; user-select:none; }+  body { padding:26px 30px 84px; overflow-y:auto; }+  .head { display:flex; align-items:center; gap:14px; }+  .head img { height:30px; }+  .head .t { font-size:17px; font-weight:600; letter-spacing:.02em; }+  .head .t small { display:block; color:var(--teal); font-size:11px; letter-spacing:.42em;+    text-transform:uppercase; font-weight:600; margin-top:2px; }++  .why { margin-top:18px; background:var(--panel); border:1px solid var(--line);+    border-left:3px solid var(--teal); border-radius:8px; padding:15px 18px; }+  .why h2 { font-size:13.5px; color:var(--tealbri); font-weight:600; margin-bottom:8px; }+  .why p { font-size:12.5px; line-height:1.55; color:var(--lo); margin-bottom:8px; }+  .why p:last-child { margin-bottom:0; }+  .why b { color:var(--hi); font-weight:600; }+  .why .code { color:var(--tealbri); font-weight:600; }++  .status { margin-top:16px; display:flex; flex-wrap:wrap; gap:10px 22px; font-size:12.5px;+    padding:0 2px; }+  .status .k { color:var(--dim); }+  .status .v { color:var(--hi); font-weight:600; }+  .status .note { color:var(--lo); font-size:11.5px; }++  table { margin-top:14px; width:100%; border-collapse:collapse; }+  th { text-align:left; font-size:10px; letter-spacing:.16em; text-transform:uppercase;+    color:var(--dim); font-weight:600; padding:0 8px 7px; }+  th.r, td.r { text-align:right; }+  td { padding:9px 8px; border-top:1px solid var(--line); vertical-align:top; font-size:12.5px; }+  td .lab { color:var(--hi); font-weight:600; }+  td .why2 { color:var(--lo); font-size:11px; line-height:1.35; margin-top:2px; max-width:330px; }+  td.cur { color:var(--hi); white-space:nowrap; }+  td.rec { color:var(--lo); white-space:nowrap; }+  tr.bad td.cur { color:var(--warn); font-weight:600; }+  .badge { display:inline-block; min-width:20px; text-align:center; font-size:11px; font-weight:700; }+  .badge.ok { color:var(--ok); }+  .badge.fix { color:var(--warn); }++  .hkrow { margin-top:16px; display:flex; align-items:center; justify-content:space-between; gap:18px;+    padding:13px 15px; border:1px solid var(--line); border-radius:8px; background:var(--panel); }+  .hklab { font-size:12.5px; color:var(--hi); font-weight:600; }+  .hklab b { color:var(--tealbri); }+  .hkwhy { font-size:11px; color:var(--lo); line-height:1.35; margin-top:3px; max-width:440px; }+  .switch { position:relative; display:inline-block; width:42px; height:24px; flex:none; cursor:pointer; }+  .switch input { opacity:0; width:0; height:0; }+  .track { position:absolute; inset:0; background:#2a3340; border-radius:999px; transition:.2s; }+  .track:before { content:""; position:absolute; height:18px; width:18px; left:3px; top:3px; background:var(--lo); border-radius:50%; transition:.2s; }+  .switch input:checked + .track { background:var(--teal); }+  .switch input:checked + .track:before { transform:translateX(18px); background:#04211f; }++  .banner { margin-top:16px; border-radius:8px; padding:11px 15px; font-size:12.5px; font-weight:600; }+  .banner.ok  { background:rgba(46,160,67,.12);  border:1px solid rgba(46,160,67,.4);  color:#5dd879; }+  .banner.bad { background:rgba(227,160,8,.12);  border:1px solid rgba(227,160,8,.4);  color:#f0c548; }++  .actions { margin-top:18px; display:flex; align-items:center; gap:12px; }+  button { font-family:inherit; font-size:13px; font-weight:600; border-radius:8px; cursor:pointer;+    padding:10px 18px; border:1px solid var(--line); background:var(--panel); color:var(--hi); }+  button:hover { border-color:var(--teal); }+  button.primary { background:var(--teal); border-color:var(--teal); color:#04211f; }+  button.primary:hover { background:var(--tealbri); }+  button.primary[disabled] { background:#16332f; border-color:#16332f; color:#4b7a76; cursor:default; }+  .spacer { flex:1; }+  .foot { margin-top:14px; color:var(--dim); font-size:11px; line-height:1.5; }+  .toast { color:var(--ok); font-size:12.5px; font-weight:600; opacity:0; transition:opacity .3s; }+  .toast.show { opacity:1; }+  .spin { display:inline-block; width:11px; height:11px; margin-right:7px; vertical-align:-1px;+    border:2px solid rgba(255,255,255,.22); border-top-color:var(--teal); border-radius:50%;+    animation:spin .6s linear infinite; }+  @keyframes spin { to { transform:rotate(360deg); } }+  button:disabled { opacity:.8; cursor:default; }+</style>+</head>+<body>+  <div class="head">+    <img src="adom-wordmark.png" alt="Adom">+    <div class="t">Screensaver<small>Preferences</small></div>+  </div>++  <div class="why">+    <h2>Why your laptop needs different rules in the AI age</h2>+    <p>Windows laptops ship set to <b>aggressively sleep and power down</b> to save energy β€” defaults+      Microsoft chose long before AI agents ran for hours on your behalf. The moment the screen idles+      or the lid shuts, the machine sleeps, and anything still working dies with it.</p>+    <p>But when you're working with <b>Claude</b> and <b>Adom Hydrogen Desktop</b>, you want the AI to+      <b>keep chugging</b>: long sessions that finish, <span class="code">/remote-control</span> threads+      that don't drop while you step away, and β€” left plugged in β€” your laptop quietly becoming a+      <b>vibe-coding server</b>. The Adom model has the AI driving your frontend too β€” taking screenshots,+      rendering examples, building <b>shotlogs you can watch from your phone</b> β€” and none of that+      happens on a sleeping machine.</p>+    <p>So we tune a <b>fine balance</b>: plugged in, run indefinitely; on battery, ride through brief lid+      closings (walk to the next room) but still <b>sleep on a sensible schedule</b> once you've truly+      stepped away. The screensaver manages these settings so your laptop behaves a little differently+      than Microsoft expects β€” on purpose. Here's what we recommend, and what you've got right now:</p>+  </div>++  <div class="status" id="status"></div>++  <table>+    <thead><tr>+      <th>Setting</th><th class="r">Your setting</th><th class="r">Adom recommends</th><th class="r"></th>+    </tr></thead>+    <tbody id="rows"></tbody>+  </table>++  <div class="hkrow">+    <div>+      <div class="hklab">Global hotkey β€” launch with <b>Win + Shift + L</b></div>+      <div class="hkwhy">Press it anytime to show the screensaver on demand. Turn off if it collides with another shortcut you use.</div>+    </div>+    <label class="switch"><input type="checkbox" id="hkToggle"><span class="track"></span></label>+  </div>++  <div class="banner" id="banner"></div>++  <div class="actions">+    <button class="primary" id="apply">Apply Adom Recommended</button>+    <button id="recheck">Re-check</button>+    <span class="toast" id="toast">Applied βœ“</span>+    <span class="spacer"></span>+    <button id="close">Close</button>+  </div>++  <div class="foot">Reads your <b>live</b> Windows settings every time it opens β€” never a saved copy.+    Change anything in Control Panel and re-open (or hit Re-check) to see it here. Most settings apply+    without admin; the screen still powers off on schedule to rest the panel.</div>++<script>+  // Host-agnostic: in the .scr it's WebView2 (chrome.webview); embedded in the Hydrogen Desktop+  // dialog it's an iframe, so fall back to posting up to the parent window.+  function send(m){+    try{ if(window.chrome && window.chrome.webview){ window.chrome.webview.postMessage(m); return; } }catch(e){}+    try{ window.parent && window.parent.postMessage({ adomScreensaver: m }, "*"); }catch(e){}+  }+  var recheckAt = 0;  // timestamp of an in-flight Re-check, so adomConfig can finish the spinner+  // HD pushes config data via postMessage instead of ExecuteScriptAsync. Accept either shape.+  window.addEventListener("message", function(ev){+    var d = ev && ev.data; if(!d) return;+    if (d.adomConfigData) { try{ window.adomConfig(d.adomConfigData, !!d.applied); }catch(e){} }+  });+  function esc(s){ return (s==null?"":String(s)).replace(/&/g,"&amp;").replace(/</g,"&lt;"); }++  function kaNote(ka, onAc){+    if (ka === "ac") return "holding your machine awake, so sessions run as long as you like";+    if (ka === "dc") return "Windows' sleep timer is the give-up clock; plug in to run indefinitely";+    return "the helper isn't running β€” click Apply to start it";+  }++  // The host calls this on open (applied=false) and after Apply (applied=true).+  window.adomConfig = function(data, applied){+    if (!data) return;+    var ka = data.keepAwake || "off";+    document.getElementById("status").innerHTML =+      '<span><span class="k">Power right now:</span> <span class="v">' + esc(data.power) + '</span></span>' ++      '<span><span class="k">Keep-awake:</span> <span class="v">' ++        (ka==="ac"?"on (plugged)":ka==="dc"?"passive (battery)":"off") ++        '</span> <span class="note">β€” ' + kaNote(ka, data.onAc) + '</span></span>';++    var rows = data.rows || [], html = "", bad = 0;+    for (var i=0;i<rows.length;i++){+      var r = rows[i]; if(!r.ok) bad++;+      html += '<tr class="'+(r.ok?"":"bad")+'">' ++        '<td><div class="lab">'+esc(r.label)+'</div><div class="why2">'+esc(r.why)+'</div></td>' ++        '<td class="cur r">'+esc(r.cur)+'</td>' ++        '<td class="rec r">'+esc(r.rec)+'</td>' ++        '<td class="r"><span class="badge '+(r.ok?"ok":"fix")+'">'+(r.ok?"βœ“":"⚠")+'</span></td>' ++      '</tr>';+    }+    document.getElementById("rows").innerHTML = html;++    var banner = document.getElementById("banner");+    if (bad === 0){+      banner.className = "banner ok";+      banner.textContent = "βœ“ Your laptop matches the Adom vibe-coding profile.";+    } else {+      banner.className = "banner bad";+      banner.textContent = "⚠ " + bad + (bad===1?" setting differs":" settings differ") ++        " from the recommended profile β€” click Apply to bring them in line.";+    }++    var apply = document.getElementById("apply");+    apply.disabled = (bad === 0);+    apply.textContent = (bad === 0) ? "Already applied" : "Apply Adom Recommended";++    var hk = document.getElementById("hkToggle");+    if (hk) hk.checked = (data.hotkeyEnabled !== false);++    if (applied){+      var t = document.getElementById("toast"); t.textContent = "Applied βœ“"; t.classList.add("show");+      setTimeout(function(){ t.classList.remove("show"); }, 2600);+    }+    // finish a Re-check: drop the spinner and flash a confirmation (keep it visible >=550ms)+    if (recheckAt){+      var rb = document.getElementById("recheck");+      var done = function(){+        rb.disabled = false; rb.textContent = "Re-check";+        var tt = document.getElementById("toast"); tt.textContent = "βœ“ Re-checked just now"; tt.classList.add("show");+        setTimeout(function(){ tt.classList.remove("show"); }, 1700);+        recheckAt = 0;+      };+      var el = Date.now() - recheckAt;+      if (el < 550) setTimeout(done, 550 - el); else done();+    }+  };++  document.getElementById("apply").addEventListener("click", function(){ if(!this.disabled) send("apply"); });+  document.getElementById("recheck").addEventListener("click", function(){+    var rb = this; if (rb.disabled) return;+    rb.disabled = true; rb.innerHTML = '<span class="spin"></span>Re-checking…';+    recheckAt = Date.now();+    send("refresh");+  });+  document.getElementById("close").addEventListener("click", function(){ send("close"); });+  document.getElementById("hkToggle").addEventListener("change", function(){ send(this.checked ? "hotkey:on" : "hotkey:off"); });+</script>+</body>+</html>
install.shadded+32
@@ -0,0 +1,32 @@+#!/usr/bin/env bash+# adom-screensaver installer (runs in the Adom container β€” Linux/WSL2).+# Installs the operate skills + stages the PREBUILT, SIGNED Windows binaries so the skill can deploy+# them to the user's Windows laptop over the adom-desktop relay. Does NOT touch Windows, and does NOT+# run anything here β€” this is a Windows-only app. (Source + build live on the private adom-screensaver-dev page.)+set -euo pipefail+HERE="$(cd "$(dirname "$0")" && pwd)"++# Main operate skill (root SKILL.md).+mkdir -p "$HOME/.claude/skills/adom-screensaver"+cp -f "$HERE/SKILL.md" "$HOME/.claude/skills/adom-screensaver/SKILL.md"+echo "[adom-screensaver] skill -> adom-screensaver"++# Operate sub-skills: adom-screensaver-billboard, -wake, -rationale (NOT -build; that's on -dev).+if [ -d "$HERE/skills" ]; then+  for d in "$HERE/skills"/*/; do+    [ -f "$d/SKILL.md" ] || continue+    name="$(basename "$d")"+    mkdir -p "$HOME/.claude/skills/$name"+    cp -f "$d/SKILL.md" "$HOME/.claude/skills/$name/SKILL.md"+    echo "[adom-screensaver] skill -> $name"+  done+fi++# Stage the PREBUILT signed Windows binaries (the .scr + helper + WebView2 DLLs + html + assets) so+# the skill can push them straight to the laptop β€” no compiler involved.+ASSETS="$HOME/.adom-screensaver"+mkdir -p "$ASSETS"+cp -rf "$HERE/dist/win/"* "$ASSETS/" 2>/dev/null || true+echo "[adom-screensaver] prebuilt signed binaries -> $ASSETS"+echo "[adom-screensaver] This is a WINDOWS app β€” nothing was installed on this container."+echo "[adom-screensaver] next: ask Claude β€” \"set up the Adom screensaver on my Windows laptop\""
package.jsonadded+31
@@ -0,0 +1,31 @@+{+  "slug": "adom-screensaver",+  "version": "0.6.1",+  "description": "Vibe-coding screensaver \u2014 fades up a live billboard of the latest Adom Wiki drops (truly cached to disk; plays webm silently, renders SVG) with a power-aware nerd-stats splash, while signed native keep-awake holds the machine so Claude Code + /remote-control threads survive. Win+Shift+L to launch.",+  "type": "app",+  "keywords": [+    "screensaver",+    "keep-awake",+    "vibe-coding",+    "webview2",+    "billboard",+    "wiki",+    "remote-control",+    "windows"+  ],+  "scripts": {+    "install": "./install.sh",+    "uninstall": "./uninstall.sh"+  },+  "dependencies": {},+  "files": [+    "SKILL.md",+    "dist/win/**",+    "skills/**",+    "install.sh",+    "uninstall.sh",+    "page.json",+    "screenshots/**"+  ],+  "org_name": "adom"+}
page.json+51−14
@@ -2,12 +2,9 @@   "schema_version": 1,   "type": "app",   "slug": "adom-screensaver",-  "title": "Adom Screensaver β€” the vibe-coding screensaver",+  "title": "Adom Screensaver \u2014 the vibe-coding screensaver",   "brief": "Turn your laptop into an always-on vibe coding server: branded screensaver + native keep-awake (no PowerToys) + honest, hardware-aware power management.",   "version": "0.6.1",-  "visibility": {-    "public": true-  },   "install": {     "binary_name": "adom-screensaver",     "install_dir": "",@@ -19,14 +16,54 @@     "name": "John Lauer",     "email": "[email protected]"   },-  "tags": [],+  "tags": [+    "screensaver",+    "keep-awake",+    "vibe-coding-server",+    "powertoys-free",+    "remote-control",+    "power",+    "windows",+    "asus-rog"+  ],   "license": "proprietary",-  "hero": null,-  "sample_prompts": [],-  "discovery_triggers": [],-  "discovery_pitch": null,-  "metadata": {},-  "created_at": "2026-06-12T13:08:03.797Z",-  "updated_at": "2026-06-12T13:08:03.797Z",-  "skills": []-}\ No newline at end of file+  "visibility": {+    "public": true+  },+  "hero": {+    "path": "docs/hero.png",+    "type": "image"+  },+  "sample_prompts": [+    "set up the Adom vibe coding server screensaver on my laptop",+    "keep my laptop awake without PowerToys",+    "make my screen turn off but keep the machine awake",+    "change the screensaver idle timeout to 1 minute",+    "show me the Adom screensaver"+  ],+  "discovery_triggers": [+    "vibe coding server",+    "keep my laptop awake",+    "keep laptop awake",+    "stay awake without powertoys",+    "replace powertoys",+    "keep awake no powertoys",+    "screen off but stay awake",+    "turn off display but keep running",+    "keep claude threads running while away",+    "keep remote control alive",+    "adom screensaver",+    "laptop screensaver",+    "screensaver that keeps machine awake",+    "screensaver status card"+  ],+  "discovery_pitch": "Turn your laptop into an always-on vibe coding server: a branded Adom screensaver, native keep-awake without PowerToys, and honest hardware-aware backlight-off that keeps your Claude/Remote Control threads alive while you're away.",+  "metadata": {+    "platform": "windows"+  },+  "created_at": "2026-06-04T21:55:44.401Z",+  "updated_at": "2026-06-10T15:22:04.850Z",+  "skills": [],+  "org_name": "adom",+  "org_id": null+}
screenshots/battery-and-warning.pngadded
⋯ 1 unchanged line ⋯
screenshots/billboard-concur.pngadded
⋯ 1 unchanged line ⋯
screenshots/captured-on-rog.jpgadded
⋯ 1 unchanged line ⋯
screenshots/hero.pngadded
⋯ 1 unchanged line ⋯
screenshots/preferences.pngadded
⋯ 1 unchanged line ⋯
skills/adom-screensaver-billboard/SKILL.mdadded+69
@@ -0,0 +1,69 @@+---+name: adom-screensaver-billboard+description: How the Adom screensaver's billboard works internally β€” the WebView2 (Edge/Chromium) viewer, dual-wiki hero discovery (Wiki v2 + v1), media STREAMING (webm video / svg / raster, nothing cached to disk), dedupe-prefer-v2, the "migrate to v2" kicker, randomized order, the exclude.txt curation, and the manifest contract. Use when changing what the billboard advertises, debugging why a page does/doesn't appear, curating the rotation, or touching the renderer. Trigger words β€” billboard, screensaver billboard, wiki rotation, hero discovery, screensaver exclude, exclude.txt, curate rotation, screensaver manifest, billboard video, billboard svg, why isn't my app in the billboard, screensaver slides, webview2 billboard.+---++# adom-screensaver-billboard β€” what the saver shows & how++Parent skill: **adom-screensaver**++While the screen rests, the saver is a **WebView2 (Edge/Chromium) billboard** for recent Adom+Wiki drops. It cross-fades app/skill cards (~5s each), each with the page's title, brief, a+**"just ask Claude"** prompt, and a footer with **power state + a "screen powers off in M:SS"+countdown**. Because it's a real browser, it plays **webm video** (muted, looped) and renders+**SVG** + raster β€” not just bitmaps.++## Architecture (WebView2, not GDI+)+- `adom-screensaver.scr` (C# host) runs a fullscreen `Microsoft.Web.WebView2.WinForms.WebView2`.+- The renderer is **`billboard.html`** (HTML/CSS/JS). The host maps a virtual host+  `https://adom-screensaver.local/` β†’ the cache dir (`SetVirtualHostNameToFolderMapping`) and serves+  `billboard.html` + `manifest.json` from there.+- The host's background loader writes **`manifest.json`**; the page polls it every 3s and+  appends new slides without restarting the current one.+- Live **power + countdown** are pushed each second from C# via+  `ExecuteScriptAsync("window.adomStatus(power, secondsLeft)")`.++## Manifest contract (`cache/manifest.json`)+```json+{ "dimPct": 50,+  "slides": [ {"slug","title","brief","prompt","source":"v2|v1","type":"app|skill",+               "media":"<URL or local file>","kind":"video|image|svg"} ] }+```++## Media is STREAMED, never downloaded+`media` is the **remote wiki URL** for every kind (image/svg/video) β€” the WebView2 streams it+(cross-origin `<img>`/`<video>` is fine for display). Nothing is cached to disk (a hero webm can+be 50MB+; streaming avoids a giant cache and shows instantly). `kind=video` β†’+`<video autoplay muted loop>` (NEVER audio). `kind=svg`/`image` β†’ `<img>` (Chromium renders SVG++ webp + raster natively).++## Dual-wiki hero discovery+Query **per type** (`?type=app&limit=100` + `?type=skill`) on BOTH wikis β€” a flat `?limit=N` is+swamped by ~185 component pages. **Wiki v2 first so it WINS dedup**, then Wiki v1 (legacy).+- **v2 hero:** registered `hero_path` β†’ repo root images (`/files` API) β†’ nested+  `screenshots/hero.png`,`docs/hero.png` (tiny ranged-GET existence check). Media = `/blob/...` URL.+- **v1 hero:** `metadata.photo_url` (β†’ `/static/<type>s/<slug>/...`). v1 prompt = `metadata.sample_prompts[0]`.+- **video (preferred over static):** a `.webm`/`.mp4` from the v2 repo files OR a `.webm` URL+  found in the v1 page `content`. Deduped by URL so a shared tour shows once.+- **v1 slides get a grey kicker** `ON WIKI v1 Β· NUDGE THE TEAM TO MIGRATE TO v2` (the saver is a+  nudge to migrate v1 β†’ v2). v2 slides get the teal `NEW ON THE ADOM WIKI`.+- Heroes fetched by a **6-thread pool**; order **shuffled** each run (the first slide varies).++## Exclusions & curation+Structural (code): components & bootstraps (app/skill only), **HD/AD** (`adom-desktop*`,`hd-*`),+**auth** (`auth-*`,`*-auth`), the saver's own page, `test-/smoke-/shared-`.+Per-slug, no rebuild: **`exclude.txt`** in the saver folder (`C:\Users\<user>\adom-screensaver\exclude.txt`,+one slug per line, `#` comments) β€” read at runtime. To drop a page:+`Add-Content exclude.txt "some-slug"`. Currently seeded: `dhl-tracker`, `carbon-preferences`,+`gallia-bundle`, `adom-desktop-blender-bridge`.++## Fast first paint+On a warm launch the previous `manifest.json` + the page render instantly while the loader+re-queries in the background. Cold first run shows the intro card until the first slide resolves.++## Debugging "why isn't page X showing?"+1. type app/skill (not component/bootstrap)? not HD/AD/auth/excluded?+2. Does it have a resolvable hero? v2: `hero_path`/root file/`screenshots|docs/hero.png`; v1:+   `metadata.photo_url`. No hero β†’ correctly skipped (that's what `adom-wiki-publish` PUB-09/14 nudge).+3. In `exclude.txt`?+4. On both wikis β†’ only the **v2** version shows (by design).
skills/adom-screensaver-rationale/SKILL.mdadded+107
@@ -0,0 +1,107 @@+---+name: adom-screensaver-rationale+description: The WHY behind adom-screensaver β€” the necessity-is-the-mother-of-invention origin story (keeping /remote-control alive without burning in the screen), the justification for every behavior, the staged 2-min-billboard β†’ 5-min-display-off cascade, power-aware keep-awake, the lock-on-wake rule, the wiki-billboard "advertise apps" concept, and the org-aware personalized vision. Read this when reasoning about WHY the screensaver behaves a certain way, defending or changing a behavior, explaining its purpose to someone, or deciding whether a proposed change respects the original intent. Triggers β€” why does the screensaver, screensaver reasoning, screensaver rationale, why was adom-screensaver built, screensaver justification, why keep-awake, why does the screen stay on, screensaver cascade, why billboard, screensaver design, screensaver purpose.+metadata:+  node_type: skill+---++Parent skill: **adom-screensaver**++This is the **design rationale** β€” the *why*. For *how* to operate/build/extend, see the+sibling skills (`adom-screensaver` operate, `-billboard`, `-wake`, `-build`). When a change is+proposed, check it against the reasoning here first: most "obvious simplifications" regress a+behavior that exists for a hard-won reason.++## Necessity is the mother of invention β€” the origin++John kept getting burned by a specific failure: he'd start a **`/remote-control`** thread in+Claude Code, then walk away from the laptop β€” go to dinner with the family β€” and come back to a+**dead session**. The laptop had gone to **sleep and killed the session**, *even while plugged+in*. The AI had stopped working the moment he left.++The naive fix made it worse: force the laptop to never sleep β†’ now the **screen never turns off**+either β†’ he was **burning in his display** just to keep a vibe-coding session alive. Lose-lose:+either the session dies, or the panel cooks.++adom-screensaver exists to dissolve that trade-off. The core insight is that **"machine awake"+and "screen on" are two different things**, and Windows' defaults conflate them. The screensaver+splits them: hold the **machine** awake (network + `/remote-control` survive) while letting the+**screen** turn off to rest the panel. Best of all worlds β€” the thing Windows wouldn't give you+out of the box, because its power model predates AI agents that run for hours on your behalf.++## What each behavior is FOR (don't regress these)++- **Power-aware keep-awake.** Plugged in β†’ hold the machine awake indefinitely (`/remote-control`+  runs as long as you like β€” your laptop becomes a *vibe-coding server*). On **battery** β†’ stay+  passive and let Windows' own idle-sleep timer be the give-up clock (default 30 min), so a+  forgotten laptop in a bag eventually sleeps instead of draining flat. The keep-awake holds+  `ES_SYSTEM_REQUIRED` only on AC; it deliberately does **not** assert it on battery. See the+  power decisions in the project memory before changing this β€” it regressed twice.+- **Lid-close = "Do nothing".** Closing the lid is a hardware *button action*, separate from idle+  sleep β€” keep-awake can't influence it. Set the lid action to "Do nothing" so closing the lid to+  walk to the next room doesn't instantly sleep; the idle-sleep timer (30 min on battery) is the+  real give-up. Walk around freely; come back within 30 min and nothing was interrupted.+- **Lock rule: "no sleep β†’ no lock; real sleep β†’ normal sign-in."** `ScreenSaverIsSecure=0` so+  dismissing the billboard / reopening the lid (no sleep occurred) drops straight to the desktop,+  no password. `CONSOLELOCK` is left at Windows' default so waking from an *actual* sleep still+  asks to sign in. (CONSOLELOCK is admin-only to change; do not try to disable it.)++## The staged cascade (the 2-min β†’ 5-min design)++The saver runs in **stages**, on purpose β€” a gentle cascade rather than a hard cut to black:++1. **0–2 min idle:** nothing. (`ScreenSaveTimeOut = 120s`.)+2. **2–5 min idle:** the **billboard** plays β€” a *dim, mostly-black* full screen cycling recent+   wiki drops. This phase is **lightweight to dismiss**: the panel is still powered, so a single+   mouse flick or keypress expires it instantly.+3. **5 min idle:** the **display powers off** entirely (`monitor timeout = 300s`) to actually+   rest the panel. The machine stays awake (on AC) the whole time.++**Why this ordering matters:** the saver often kicks in when you didn't really want it (you paused+to read, took a call). If the *first* thing it did was fully power the screen off, every accidental+trigger would cost you the heaviness of the panel going dark and then warming back up. By showing a+dim billboard first and only powering the screen off 3 minutes later, an early/accidental trigger+is trivially undone β€” you barely notice it. The full display-off is reserved for when you've truly+stepped away. Don't collapse the two stages into one.++## The screensaver became a canvas β€” the billboard++Once you have a full screen that's "resting," it's the **perfect canvas**. Instead of black, the+billboard surfaces the **coolest new stuff in the Adom Wiki** β€” each app/skill's hero image, title,+and a "just ask Claude: `install <x>`" prompt. So the screensaver does double duty: it keeps the+machine working *and* **educates the user (and John) about new wiki apps while they're away**.++This reframes wiki hero images as **marketing surfaces**: your wiki assets advertise apps on+people's lock screens and nudge adoption β€” value far beyond the wiki page itself.++- **Media:** a **video hero** (`.webm`) is **streamed silently** (muted, looped) straight from the+  wiki β€” motion grabs the eye; nothing is downloaded. No video β†’ fall back to the **hero image**.+- **Dual-wiki:** pulls Wiki v2 (canonical) + Wiki v1 (legacy, with a "migrate to v2" nudge); v2+  wins on overlap. Order is shuffled each run so the first slide varies.+- **Curation:** a runtime `exclude.txt` drops baseline/uninteresting slugs without a rebuild.++## The vision this is part of++- **Personalized, org-aware billboards.** Today the billboard advertises *public* wiki pages.+  Next: HD hands the saver the user's Adom login, passed through on wiki queries, so the billboard+  shows **public + the user's org-private apps**. An NTX Embedded user sees NTX's own apps; someone+  in multiple orgs sees the union. The wiki becomes a personalized, org-aware app store, and the+  screensaver is its ambient discovery surface.+- **Distributed via the wiki, installed by HD.** The screensaver is a Windows-*host* app, so it's+  not `adompkg install`-into-the-workspace. It's published to Wiki v2 with a `dist/win/` bundle in+  the tarball; HD's setup steps fetch + install it on the host (opt-in, reversible). Publish a new+  version β†’ every HD picks it up, no HD rebuild. (macOS/Ubuntu HD must gracefully skip it β€” it's+  Windows-only.)++## Use cases to keep in mind++- **Vibe-coding server:** laptop plugged in on a desk, lid open or closed, running `/remote-control`+  threads for hours while John is elsewhere. Never sleeps; screen rests.+- **Mobile / on battery:** moving between rooms or out the door β€” rides short lid-closings, then+  sleeps on the 30-min give-up so it doesn't drain.+- **Ambient discovery:** any idle moment becomes a chance to learn what's new in the wiki.++**Guiding principle:** the screensaver bends the laptop's behavior away from Microsoft's+pre-AI defaults, *on purpose*, to protect long-running AI work β€” while being honest about power+(rest the screen, respect the battery, keep real-sleep security). Any change should preserve that+balance.
skills/adom-screensaver-wake/SKILL.mdadded+88
@@ -0,0 +1,88 @@+---+name: adom-screensaver-wake+description: How any Adom app (Hydrogen Desktop, adom-desktop, or any tool that needs the laptop screen visible) asks the running Adom screensaver to get out of the way AND keeps the display powered on. Use when an app needs to screenshot, drive UI automation, show an OAuth window, or otherwise SEE the user's screen while the Adom screensaver/billboard might be up. Trigger words β€” wake the screensaver, dismiss screensaver, adom-screensaver-wake, screensaver wake event, screensaver control port, turn off screensaver from code, screensaver IPC, screensaver is covering the screen, need the screen, ES_DISPLAY_REQUIRED, force display on, screensaver wake.flag, adom-screensaver port.txt.+---++# adom-screensaver-wake β€” ask the saver to get off the screen++Parent skill: **adom-screensaver**++When the Adom screensaver / Wiki billboard is up (the user went idle), it is a **topmost+fullscreen window** and, after the display-off timeout, the **panel is physically off**. Any+Adom app that needs to *see* the screen (screenshot, pup/UI automation, OAuth approval window)+must (1) dismiss the saver and (2) make sure the display is on. This skill is that contract.++## The two responsibilities (don't skip #2)++1. **Dismiss the saver** β€” signal it (below). It exits immediately.+2. **Hold the display ON yourself** β€” call `SetThreadExecutionState(ES_CONTINUOUS | ES_DISPLAY_REQUIRED)`+   in *your* (the requester's) process and keep it held while you need the screen. The saver+   pulses the display on as it exits, but **that pulse clears when the saver process dies** β€”+   only a live holder keeps the panel on. Release with `SetThreadExecutionState(ES_CONTINUOUS)`.++If the panel was physically off, give it **~1.5–2s** to power on after you hold ES_DISPLAY_REQUIRED+before you screenshot.++## Three ways to dismiss (all handled by the saver, pick what fits your language)++### 1. Named event `adom-screensaver-wake` β€” guaranteed, instant+Works even after the panel powered off (the saver listens on a blocking thread, not its paint+loop). This is the **primary** path.++PowerShell / adom-desktop relay:+```powershell+[System.Threading.EventWaitHandle]::OpenExisting('adom-screensaver-wake').Set()+# throws if no saver is running -> nothing to dismiss+```+Rust (Hydrogen Desktop), via the `windows` crate:+```rust+use windows::Win32::System::Threading::{OpenEventW, SetEvent};+use windows::Win32::System::Threading::EVENT_MODIFY_STATE;+let h = OpenEventW(EVENT_MODIFY_STATE, false, w!("adom-screensaver-wake"))?; // Err = not running+SetEvent(h)?;+```++### 2. Flag file β€” no event API needed (picked up within ~0.75s)+```+create:  %LOCALAPPDATA%\adom-screensaver\wake.flag    (the saver deletes it on read)+```+```powershell+New-Item -ItemType File "$env:LOCALAPPDATA\adom-screensaver\wake.flag" -Force | Out-Null+```++### 3. Localhost control port β€” when you also want STATUS+The saver runs a loopback HTTP server. **The port is dynamic** (Hyper-V/WSL phantom-reserve+fixed ports), so read it from `%LOCALAPPDATA%\adom-screensaver\port.txt` β€” never hardcode.+```powershell+$port = (Get-Content "$env:LOCALAPPDATA\adom-screensaver\port.txt" -Raw).Trim()+# is the saver up, and what is it showing / how long until display-off?+Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:$port/status"+#   -> {"running":true,"slides":16,"currentSlug":"shotlog","currentTitle":"Shotlog",+#       "secondsUntilDisplayOff":47,"power":"ac","battery":98,"waking":false}+# dismiss:+Invoke-WebRequest -UseBasicParsing "http://127.0.0.1:$port/wake" -Method POST+```+`port.txt` absent / connection refused == the saver isn't running (nothing to dismiss).++## Recommended recipe (e.g. adom-desktop about to screenshot)++```powershell+# 1) hold the display on in THIS process for the whole capture+Add-Type 'using System;using System.Runtime.InteropServices;public class P{[DllImport("kernel32.dll")]public static extern uint SetThreadExecutionState(uint f);}'+[P]::SetThreadExecutionState(0x80000000 -bor 0x00000002) | Out-Null   # ES_CONTINUOUS|ES_DISPLAY_REQUIRED+# 2) dismiss the saver+try { [System.Threading.EventWaitHandle]::OpenExisting('adom-screensaver-wake').Set() } catch {}+# 3) let the panel power on if it was off+Start-Sleep -Milliseconds 1800+# 4) ... capture / automate ...+# 5) release when done+[P]::SetThreadExecutionState(0x80000000) | Out-Null                   # ES_CONTINUOUS+```++## Notes+- **Keep-awake is separate.** Dismissing the saver never affects `adom-stayawake.exe`; the+  machine stays awake regardless.+- The saver is single-instance (`adom-screensaver-singleton` mutex) and re-arms on the next idle, so+  after you're done it simply comes back when the user goes idle again β€” you don't restart it.+- For a headless test of the channel without the fullscreen window, run `adom-screensaver.scr /serve`+  (control port + wake listener only).
uninstall.shadded+12
@@ -0,0 +1,12 @@+#!/usr/bin/env bash+# Removes the container-side skill bundle + assets. Does NOT remove the screensaver/keep-awake from+# your Windows laptop β€” ask Claude to "remove the Adom screensaver from my laptop" for that.+set -euo pipefail+rm -rf "$HOME/.claude/skills/adom-screensaver" \+       "$HOME/.claude/skills/adom-screensaver-billboard" \+       "$HOME/.claude/skills/adom-screensaver-wake" \+       "$HOME/.claude/skills/adom-screensaver-build" \+       "$HOME/.claude/skills/adom-screensaver-rationale" \+       "$HOME/.adom-screensaver"+echo "[adom-screensaver] removed container-side skill bundle + assets."+echo "[adom-screensaver] to remove the Windows install, ask Claude to uninstall it from your laptop."