Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ on:
push:
pull_request:

permissions:
contents: read

jobs:
static-checks:
runs-on: ubuntu-latest
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,11 @@ The project explores a practical question: can a small always-on-top visual make

[Open the GitHub Pages experiment](https://simplecaci.github.io/Stop-Time-Blindness/)

<img src="docs/media/project-visual.png" align="right" width="240" alt="Always-on-top focus timer over a desktop workspace">
![Current browser timer controls and on-page display](docs/media/timer-controls.jpg)

*Local browser screenshot with the default sample task. The floating PiP window is not shown.*

[Run locally](#run-locally) · [Browser support](#browser-support) · [Limitations](#known-limitations)

## Capabilities

Expand Down Expand Up @@ -62,7 +66,7 @@ GitHub Actions syntax-checks the application and worker JavaScript on every push
- timer accuracy after sleep/background throttling needs review
- destructive log clearing needs stronger confirmation
- accessibility, keyboard shortcuts, and reduced-motion behavior need testing
- `browswerPIP.html` has a spelling error and appears to be an earlier experiment
- `browser-pip-prototype.html` is a separate Document Picture-in-Picture experiment; `index.html` is the main application
- root `main.js`, `worker.js`, and the inline implementation need consolidation

## Roadmap
Expand Down
314 changes: 157 additions & 157 deletions browswerPIP.html → browser-pip-prototype.html
Original file line number Diff line number Diff line change
@@ -1,157 +1,157 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Document Picture-in-Picture Test (No Escaping Needed)</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; padding: 20px; }
button { padding: 10px 14px; font-size: 16px; cursor: pointer; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
#status { margin-top: 12px; white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
</style>
</head>
<body>
<h2>Document Picture-in-Picture (Test)</h2>

<div class="row">
<button id="open">Open Document PiP</button>
<button id="close" disabled>Close PiP</button>
</div>

<div id="status"></div>

<script>
const statusEl = document.getElementById("status");
const openBtn = document.getElementById("open");
const closeBtn = document.getElementById("close");

let pipWin = null;
let tickInterval = null;

const supported = () =>
"documentPictureInPicture" in window &&
window.documentPictureInPicture &&
typeof window.documentPictureInPicture.requestWindow === "function";

const log = (msg) => (statusEl.textContent = msg);

const stopTicker = () => {
if (tickInterval) clearInterval(tickInterval);
tickInterval = null;
};

window.addEventListener("message", (e) => {
if (e?.data?.type === "pipPing") {
log("Received ping from PiP at " + new Date(e.data.at).toLocaleTimeString());
}
});

function renderPiPUI(win) {
const doc = win.document;

// No nested <script> tags, so nothing to escape.
doc.head.innerHTML = `
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PiP Window</title>
<style>
body {
margin: 0;
padding: 12px;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: #111;
color: #fff;
}
.card {
border: 1px solid rgba(255,255,255,0.15);
border-radius: 12px;
padding: 12px;
}
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
button {
padding: 8px 10px;
font-size: 14px;
cursor: pointer;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.25);
background: rgba(255,255,255,0.06);
color: white;
}
#time { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
p { margin: 10px 0 12px 0; opacity: 0.9; }
</style>
`;

doc.body.innerHTML = `
<div class="card">
<div class="row" style="justify-content: space-between;">
<strong>Document PiP</strong>
<span id="time"></span>
</div>

<p>If you can see this floating window, Document PiP is working.</p>

<div class="row">
<button id="ping">Ping main page</button>
<button id="closeBtn">Close</button>
</div>
</div>
`;

// Wire events from the main page (no PiP scripts).
doc.getElementById("ping").addEventListener("click", () => {
window.postMessage({ type: "pipPing", at: Date.now() }, "*");
});

doc.getElementById("closeBtn").addEventListener("click", () => win.close());

// Start time ticker
const timeEl = doc.getElementById("time");
stopTicker();
tickInterval = setInterval(() => {
if (!pipWin || pipWin.closed) { stopTicker(); return; }
timeEl.textContent = new Date().toLocaleTimeString();
}, 1000);
}

openBtn.addEventListener("click", async () => {
if (!supported()) {
log("Not supported. Use Chrome/Edge (Chromium). Safari/Firefox do not support Document PiP.");
return;
}

if (pipWin && !pipWin.closed) {
pipWin.focus?.();
return;
}

try {
pipWin = await documentPictureInPicture.requestWindow({ width: 360, height: 220 });

renderPiPUI(pipWin);

pipWin.addEventListener("pagehide", () => {
stopTicker();
closeBtn.disabled = true;
log("PiP closed.");
});

closeBtn.disabled = false;
log("PiP opened successfully.");
} catch (err) {
log("Failed to open PiP:\n" + (err?.message || String(err)));
}
});

closeBtn.addEventListener("click", () => {
if (pipWin && !pipWin.closed) pipWin.close();
});

log(supported()
? "Supported. Click 'Open Document PiP'."
: "Not supported here. Use Chrome/Edge (Chromium)."
);
</script>
</body>
</html>
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Document Picture-in-Picture Test (No Escaping Needed)</title>
<style>
body { font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif; padding: 20px; }
button { padding: 10px 14px; font-size: 16px; cursor: pointer; }
.row { display: flex; gap: 10px; flex-wrap: wrap; align-items: center; }
#status { margin-top: 12px; white-space: pre-wrap; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
</style>
</head>
<body>
<h2>Document Picture-in-Picture (Test)</h2>
<div class="row">
<button id="open">Open Document PiP</button>
<button id="close" disabled>Close PiP</button>
</div>
<div id="status"></div>
<script>
const statusEl = document.getElementById("status");
const openBtn = document.getElementById("open");
const closeBtn = document.getElementById("close");
let pipWin = null;
let tickInterval = null;
const supported = () =>
"documentPictureInPicture" in window &&
window.documentPictureInPicture &&
typeof window.documentPictureInPicture.requestWindow === "function";
const log = (msg) => (statusEl.textContent = msg);
const stopTicker = () => {
if (tickInterval) clearInterval(tickInterval);
tickInterval = null;
};
window.addEventListener("message", (e) => {
if (e?.data?.type === "pipPing") {
log("Received ping from PiP at " + new Date(e.data.at).toLocaleTimeString());
}
});
function renderPiPUI(win) {
const doc = win.document;
// No nested <script> tags, so nothing to escape.
doc.head.innerHTML = `
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>PiP Window</title>
<style>
body {
margin: 0;
padding: 12px;
font-family: system-ui, -apple-system, Segoe UI, Roboto, Arial, sans-serif;
background: #111;
color: #fff;
}
.card {
border: 1px solid rgba(255,255,255,0.15);
border-radius: 12px;
padding: 12px;
}
.row { display: flex; gap: 10px; align-items: center; flex-wrap: wrap; }
button {
padding: 8px 10px;
font-size: 14px;
cursor: pointer;
border-radius: 10px;
border: 1px solid rgba(255,255,255,0.25);
background: rgba(255,255,255,0.06);
color: white;
}
#time { font-family: ui-monospace, SFMono-Regular, Menlo, monospace; }
p { margin: 10px 0 12px 0; opacity: 0.9; }
</style>
`;
doc.body.innerHTML = `
<div class="card">
<div class="row" style="justify-content: space-between;">
<strong>Document PiP</strong>
<span id="time"></span>
</div>
<p>If you can see this floating window, Document PiP is working.</p>
<div class="row">
<button id="ping">Ping main page</button>
<button id="closeBtn">Close</button>
</div>
</div>
`;
// Wire events from the main page (no PiP scripts).
doc.getElementById("ping").addEventListener("click", () => {
window.postMessage({ type: "pipPing", at: Date.now() }, "*");
});
doc.getElementById("closeBtn").addEventListener("click", () => win.close());
// Start time ticker
const timeEl = doc.getElementById("time");
stopTicker();
tickInterval = setInterval(() => {
if (!pipWin || pipWin.closed) { stopTicker(); return; }
timeEl.textContent = new Date().toLocaleTimeString();
}, 1000);
}
openBtn.addEventListener("click", async () => {
if (!supported()) {
log("Not supported. Use Chrome/Edge (Chromium). Safari/Firefox do not support Document PiP.");
return;
}
if (pipWin && !pipWin.closed) {
pipWin.focus?.();
return;
}
try {
pipWin = await documentPictureInPicture.requestWindow({ width: 360, height: 220 });
renderPiPUI(pipWin);
pipWin.addEventListener("pagehide", () => {
stopTicker();
closeBtn.disabled = true;
log("PiP closed.");
});
closeBtn.disabled = false;
log("PiP opened successfully.");
} catch (err) {
log("Failed to open PiP:\n" + (err?.message || String(err)));
}
});
closeBtn.addEventListener("click", () => {
if (pipWin && !pipWin.closed) pipWin.close();
});
log(supported()
? "Supported. Click 'Open Document PiP'."
: "Not supported here. Use Chrome/Edge (Chromium)."
);
</script>
</body>
</html>
Binary file added docs/media/timer-controls.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading