app
5-Minute Timer
Public UnreviewedA countdown timer that goes green to red, then flashes red and counts negative when time's up.
← Commit history
Publish 5-Minute Timer source
11 files changed
+601−27
README.md+58−1@@ -1,3 +1,60 @@ # 5-Minute Timer -A countdown timer that goes green to red, then flashes red and counts negative when time's up.+A countdown timer that starts green, sweeps to red as it runs down, and once it passes zero keeps counting negative while the whole screen flashes red at 1 Hz. Play/pause, reset, and on-the-fly time adjustments. One self-contained HTML file — no build, no server.++++## Live Demo++<iframe src="/blob/app/timer/demo/index.html" width="100%" height="560" frameborder="0" style="border-radius: 10px; border: 1px solid #30363d;"></iframe>++Press **Start**, let it run past zero, and watch it flash. Everything is baked in — the demo above is the real app.++## Install++```+adompkg install timer+```++## Quick Start++```bash+timer # serve it locally → http://localhost:8741/+TIMER_PORT=9000 timer # pick a different port+```++Or just open `~/.local/share/adom-timer/index.html` in any browser — it needs no server.++## Screenshots++### Ready — full green at 5:00+++### Time up — flashing red, counting negative+++## Features++- 5-minute countdown with a depleting progress ring and large tabular digits.+- Color sweeps green → yellow → red as the remaining time shrinks.+- Past zero it counts negative (`-0:01`, `-0:02`, …) and the whole background strobes red at **1 Hz** while running.+- Pause stops the strobe (digits stay red) for a calm, readable "time's up" state.+- **Play / Pause** and **Reset** buttons (Space and R keyboard shortcuts).+- **−60s / −30s / −10s / +10s / +30s / +60s** adjust buttons that work whether running or paused — add time to climb back into the green.+- Wall-clock-accurate timing (`performance.now()`), so it stays correct even when the tab is backgrounded.+- Fully self-contained: a single static HTML file, zero dependencies.++## Dependencies++This app has **zero** adompkg dependencies. It is one self-contained HTML file with no external libraries, fonts, or services.++## Talk to the AI++- **Open / run:** "open the timer", "start a 5 minute timer", "serve the countdown timer"+- **Use:** "reset the timer", "add 30 seconds", "pause it"++## Skills++| Skill | Purpose |+|---|---|+| `timer` | Describes the app, how to run it (`timer` launcher or open the HTML directly), the controls, and the green→red→flash behavior. |
SKILL.mdadded+36@@ -0,0 +1,36 @@+---+name: timer+description: "A 5-minute countdown timer web app. Shifts green to red as it expires, then flashes red at 1 Hz and counts negative once time is up. Play/pause, reset, and +/- 10/30/60s adjust buttons. Use when the user wants a visual countdown timer, a meeting/speaking timer, a stand-up timer, or asks to open/serve the timer. Trigger words: timer, countdown, 5 minute timer, start a timer, stopwatch countdown, meeting timer, open the timer."+---++# 5-Minute Timer++A self-contained single-file web app: a 5-minute countdown that is green at the+start, sweeps through yellow to red as it runs down, and once it passes zero it+keeps counting negative while the whole background flashes red at 1 Hz.++## Run it++```bash+timer # serves the app on http://localhost:8741/+TIMER_PORT=9000 timer # use a different port+```++Or open `~/.local/share/adom-timer/index.html` directly in any browser — there+is no server requirement; it is pure HTML/CSS/JS.++## Controls++- **Start / Pause** — toggle the countdown (keyboard: Space).+- **Reset** — back to 5:00 (keyboard: R).+- **−60s / −30s / −10s / +10s / +30s / +60s** — adjust the remaining time on+ the fly, whether running or paused. You can add time after it's gone negative+ to bring it back into the green.++## Behavior++- Color sweeps green (5:00) → yellow → red (0:00) following the remaining time.+- Past zero it shows `-0:01`, `-0:02`, … with the page background strobing red+ at 1 Hz **while running**; pausing stops the strobe (digits stay red).+- Timing is wall-clock accurate (uses `performance.now()`), so it stays correct+ even if the browser tab is backgrounded.
demo/index.htmladded+219@@ -0,0 +1,219 @@+<!DOCTYPE html>+<html lang="en">+<head>+<meta charset="UTF-8">+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">+<title>5-Minute Timer</title>+<style>+ :root {+ --bg: #0d1117;+ --panel: #161b22;+ --text: #e6edf3;+ --muted: #8b949e;+ --border: #30363d;+ }+ * { box-sizing: border-box; margin: 0; padding: 0; -webkit-user-select: none; user-select: none; }+ html, body { height: 100%; }+ body {+ background: radial-gradient(ellipse at center, #11161f 0%, var(--bg) 70%);+ color: var(--text);+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;+ display: flex; flex-direction: column; align-items: center; justify-content: center;+ min-height: 100%; gap: 40px; padding: 24px;+ }++ .timer { position: relative; width: min(78vw, 420px); aspect-ratio: 1; display: grid; place-items: center; }+ .ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }+ .ring .track { fill: none; stroke: var(--border); stroke-width: 8; }+ .ring .progress {+ fill: none; stroke-width: 12; stroke-linecap: round;+ transition: stroke 0.25s linear;+ filter: drop-shadow(0 0 10px currentColor);+ }++ .readout { display: flex; flex-direction: column; align-items: center; gap: 6px; }+ .time {+ font-variant-numeric: tabular-nums;+ font-family: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;+ font-size: clamp(48px, 17vw, 96px); font-weight: 700; line-height: 1;+ letter-spacing: 1px; transition: color 0.25s linear;+ }+ .label { font-size: 13px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); }++ /* Flash the WHOLE background red when expired (past zero) */+ body.expired { animation: bgFlash 1s ease-in-out infinite; } /* 1 Hz */+ @keyframes bgFlash {+ 0%, 100% { background: var(--bg); }+ 50% { background: #c01f28; }+ }++ .controls { display: flex; gap: 14px; }+ button {+ font-family: inherit; font-size: 15px; font-weight: 600;+ color: var(--text); background: var(--panel);+ border: 1px solid var(--border); border-radius: 10px;+ padding: 12px 22px; min-width: 110px; cursor: pointer;+ transition: background 0.15s, border-color 0.15s, transform 0.05s;+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;+ }+ button:hover { background: #1f2630; border-color: #484f58; }+ button:active { transform: translateY(1px); }+ button.primary { background: #1f6feb22; border-color: #1f6feb88; color: #cfe2ff; }+ button.primary:hover { background: #1f6feb33; }+ button svg { width: 16px; height: 16px; fill: currentColor; }++ .adjust { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; max-width: 430px; }+ .adj { min-width: 58px; padding: 9px 12px; font-size: 14px; }+ .adj[data-delta^="-"] { color: #ffc2c2; border-color: #5a2a2a; }+ .adj[data-delta^="-"]:hover { background: #261313; border-color: #7a3a3a; }+ .adj:not([data-delta^="-"]) { color: #bdf0cd; border-color: #2c5a3e; }+ .adj:not([data-delta^="-"]):hover { background: #16271c; border-color: #3c7a52; }+</style>+</head>+<body>+ <div class="timer" id="timer">+ <svg class="ring" viewBox="0 0 240 240">+ <circle class="track" cx="120" cy="120" r="110"></circle>+ <circle class="progress" id="progress" cx="120" cy="120" r="110"></circle>+ </svg>+ <div class="readout">+ <div class="time" id="time">5:00</div>+ <div class="label" id="label">ready</div>+ </div>+ </div>++ <div class="controls">+ <button class="primary" id="playBtn" aria-label="Play or pause">+ <svg id="playIcon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>+ <span id="playLabel">Start</span>+ </button>+ <button id="resetBtn" aria-label="Reset">+ <svg viewBox="0 0 24 24"><path d="M12 5V1L7 6l5 5V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7z"/></svg>+ <span>Reset</span>+ </button>+ </div>++ <div class="adjust" id="adjust">+ <button class="adj" data-delta="-60">−60s</button>+ <button class="adj" data-delta="-30">−30s</button>+ <button class="adj" data-delta="-10">−10s</button>+ <button class="adj" data-delta="10">+10s</button>+ <button class="adj" data-delta="30">+30s</button>+ <button class="adj" data-delta="60">+60s</button>+ </div>++<script>+(function () {+ const TOTAL = 5 * 60; // 5 minutes, seconds+ const R = 110;+ const CIRC = 2 * Math.PI * R;++ const $timer = document.getElementById('timer');+ const $time = document.getElementById('time');+ const $label = document.getElementById('label');+ const $progress = document.getElementById('progress');+ const $playBtn = document.getElementById('playBtn');+ const $playIcon = document.getElementById('playIcon');+ const $playLabel = document.getElementById('playLabel');+ const $resetBtn = document.getElementById('resetBtn');++ $progress.style.strokeDasharray = CIRC;++ const ICON_PLAY = 'M8 5v14l11-7z';+ const ICON_PAUSE = 'M6 5h4v14H6zM14 5h4v14h-4z';++ let remaining = TOTAL; // seconds, may go negative+ let running = false;+ let endAt = 0; // performance.now()/1000 + remaining, while running++ function now() { return performance.now() / 1000; }++ // green (120°) at full time -> red (0°) at zero+ function colorFor(r) {+ if (r > 0) {+ const hue = 120 * Math.max(0, Math.min(1, r / TOTAL));+ return `hsl(${hue.toFixed(1)}, 85%, 55%)`;+ }+ return 'hsl(0, 90%, 58%)';+ }++ function fmt(r) {+ let neg = false, secs;+ if (r > 0) { secs = Math.ceil(r - 1e-9); }+ else { secs = Math.ceil(-r - 1e-9); neg = secs > 0; }+ const m = Math.floor(secs / 60);+ const s = secs % 60;+ return (neg ? '-' : '') + m + ':' + String(s).padStart(2, '0');+ }++ function render() {+ const expired = remaining <= 0;+ const flashing = expired && running; // only strobe while actually counting past 0+ const color = colorFor(remaining);++ $time.textContent = fmt(remaining);+ $time.style.color = flashing ? '#ffffff' : color; // white over the strobe; red when paused-expired+ $progress.style.stroke = color;++ // ring: full at TOTAL, empty at <=0+ const frac = Math.max(0, Math.min(1, remaining / TOTAL));+ $progress.style.strokeDashoffset = CIRC * (1 - frac);++ $timer.classList.toggle('expired', expired);+ document.body.classList.toggle('expired', flashing); // strobe stops when paused+ $label.textContent = expired ? 'time up' : (running ? 'running' : (remaining === TOTAL ? 'ready' : 'paused'));+ }++ function tick() {+ if (running) remaining = endAt - now();+ render();+ requestAnimationFrame(tick);+ }++ function setPlayingUI(isRunning) {+ $playIcon.querySelector('path').setAttribute('d', isRunning ? ICON_PAUSE : ICON_PLAY);+ $playLabel.textContent = isRunning ? 'Pause' : (remaining === TOTAL ? 'Start' : 'Resume');+ }++ function play() {+ if (running) return;+ endAt = now() + remaining;+ running = true;+ setPlayingUI(true);+ }+ function pause() {+ if (!running) return;+ remaining = endAt - now();+ running = false;+ setPlayingUI(false);+ }+ function toggle() { running ? pause() : play(); }+ function reset() {+ running = false;+ remaining = TOTAL;+ setPlayingUI(false);+ render();+ }+ function adjust(delta) {+ if (running) { endAt += delta; remaining = endAt - now(); }+ else { remaining += delta; }+ setPlayingUI(running); // refresh Start/Resume label+ render();+ }++ $playBtn.addEventListener('click', toggle);+ $resetBtn.addEventListener('click', reset);+ document.querySelectorAll('.adj').forEach(b => {+ b.addEventListener('click', () => adjust(parseInt(b.dataset.delta, 10)));+ });+ document.addEventListener('keydown', (e) => {+ if (e.code === 'Space') { e.preventDefault(); toggle(); }+ else if (e.key === 'r' || e.key === 'R') { reset(); }+ });++ setPlayingUI(false);+ requestAnimationFrame(tick);+})();+</script>+</body>+</html>
index.htmladded+219@@ -0,0 +1,219 @@+<!DOCTYPE html>+<html lang="en">+<head>+<meta charset="UTF-8">+<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">+<title>5-Minute Timer</title>+<style>+ :root {+ --bg: #0d1117;+ --panel: #161b22;+ --text: #e6edf3;+ --muted: #8b949e;+ --border: #30363d;+ }+ * { box-sizing: border-box; margin: 0; padding: 0; -webkit-user-select: none; user-select: none; }+ html, body { height: 100%; }+ body {+ background: radial-gradient(ellipse at center, #11161f 0%, var(--bg) 70%);+ color: var(--text);+ font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;+ display: flex; flex-direction: column; align-items: center; justify-content: center;+ min-height: 100%; gap: 40px; padding: 24px;+ }++ .timer { position: relative; width: min(78vw, 420px); aspect-ratio: 1; display: grid; place-items: center; }+ .ring { position: absolute; inset: 0; width: 100%; height: 100%; transform: rotate(-90deg); }+ .ring .track { fill: none; stroke: var(--border); stroke-width: 8; }+ .ring .progress {+ fill: none; stroke-width: 12; stroke-linecap: round;+ transition: stroke 0.25s linear;+ filter: drop-shadow(0 0 10px currentColor);+ }++ .readout { display: flex; flex-direction: column; align-items: center; gap: 6px; }+ .time {+ font-variant-numeric: tabular-nums;+ font-family: "SF Mono", "JetBrains Mono", ui-monospace, Menlo, Consolas, monospace;+ font-size: clamp(48px, 17vw, 96px); font-weight: 700; line-height: 1;+ letter-spacing: 1px; transition: color 0.25s linear;+ }+ .label { font-size: 13px; letter-spacing: 2px; text-transform: uppercase; color: var(--muted); }++ /* Flash the WHOLE background red when expired (past zero) */+ body.expired { animation: bgFlash 1s ease-in-out infinite; } /* 1 Hz */+ @keyframes bgFlash {+ 0%, 100% { background: var(--bg); }+ 50% { background: #c01f28; }+ }++ .controls { display: flex; gap: 14px; }+ button {+ font-family: inherit; font-size: 15px; font-weight: 600;+ color: var(--text); background: var(--panel);+ border: 1px solid var(--border); border-radius: 10px;+ padding: 12px 22px; min-width: 110px; cursor: pointer;+ transition: background 0.15s, border-color 0.15s, transform 0.05s;+ display: inline-flex; align-items: center; justify-content: center; gap: 8px;+ }+ button:hover { background: #1f2630; border-color: #484f58; }+ button:active { transform: translateY(1px); }+ button.primary { background: #1f6feb22; border-color: #1f6feb88; color: #cfe2ff; }+ button.primary:hover { background: #1f6feb33; }+ button svg { width: 16px; height: 16px; fill: currentColor; }++ .adjust { display: flex; gap: 8px; flex-wrap: wrap; justify-content: center; max-width: 430px; }+ .adj { min-width: 58px; padding: 9px 12px; font-size: 14px; }+ .adj[data-delta^="-"] { color: #ffc2c2; border-color: #5a2a2a; }+ .adj[data-delta^="-"]:hover { background: #261313; border-color: #7a3a3a; }+ .adj:not([data-delta^="-"]) { color: #bdf0cd; border-color: #2c5a3e; }+ .adj:not([data-delta^="-"]):hover { background: #16271c; border-color: #3c7a52; }+</style>+</head>+<body>+ <div class="timer" id="timer">+ <svg class="ring" viewBox="0 0 240 240">+ <circle class="track" cx="120" cy="120" r="110"></circle>+ <circle class="progress" id="progress" cx="120" cy="120" r="110"></circle>+ </svg>+ <div class="readout">+ <div class="time" id="time">5:00</div>+ <div class="label" id="label">ready</div>+ </div>+ </div>++ <div class="controls">+ <button class="primary" id="playBtn" aria-label="Play or pause">+ <svg id="playIcon" viewBox="0 0 24 24"><path d="M8 5v14l11-7z"/></svg>+ <span id="playLabel">Start</span>+ </button>+ <button id="resetBtn" aria-label="Reset">+ <svg viewBox="0 0 24 24"><path d="M12 5V1L7 6l5 5V7a5 5 0 1 1-5 5H5a7 7 0 1 0 7-7z"/></svg>+ <span>Reset</span>+ </button>+ </div>++ <div class="adjust" id="adjust">+ <button class="adj" data-delta="-60">−60s</button>+ <button class="adj" data-delta="-30">−30s</button>+ <button class="adj" data-delta="-10">−10s</button>+ <button class="adj" data-delta="10">+10s</button>+ <button class="adj" data-delta="30">+30s</button>+ <button class="adj" data-delta="60">+60s</button>+ </div>++<script>+(function () {+ const TOTAL = 5 * 60; // 5 minutes, seconds+ const R = 110;+ const CIRC = 2 * Math.PI * R;++ const $timer = document.getElementById('timer');+ const $time = document.getElementById('time');+ const $label = document.getElementById('label');+ const $progress = document.getElementById('progress');+ const $playBtn = document.getElementById('playBtn');+ const $playIcon = document.getElementById('playIcon');+ const $playLabel = document.getElementById('playLabel');+ const $resetBtn = document.getElementById('resetBtn');++ $progress.style.strokeDasharray = CIRC;++ const ICON_PLAY = 'M8 5v14l11-7z';+ const ICON_PAUSE = 'M6 5h4v14H6zM14 5h4v14h-4z';++ let remaining = TOTAL; // seconds, may go negative+ let running = false;+ let endAt = 0; // performance.now()/1000 + remaining, while running++ function now() { return performance.now() / 1000; }++ // green (120°) at full time -> red (0°) at zero+ function colorFor(r) {+ if (r > 0) {+ const hue = 120 * Math.max(0, Math.min(1, r / TOTAL));+ return `hsl(${hue.toFixed(1)}, 85%, 55%)`;+ }+ return 'hsl(0, 90%, 58%)';+ }++ function fmt(r) {+ let neg = false, secs;+ if (r > 0) { secs = Math.ceil(r - 1e-9); }+ else { secs = Math.ceil(-r - 1e-9); neg = secs > 0; }+ const m = Math.floor(secs / 60);+ const s = secs % 60;+ return (neg ? '-' : '') + m + ':' + String(s).padStart(2, '0');+ }++ function render() {+ const expired = remaining <= 0;+ const flashing = expired && running; // only strobe while actually counting past 0+ const color = colorFor(remaining);++ $time.textContent = fmt(remaining);+ $time.style.color = flashing ? '#ffffff' : color; // white over the strobe; red when paused-expired+ $progress.style.stroke = color;++ // ring: full at TOTAL, empty at <=0+ const frac = Math.max(0, Math.min(1, remaining / TOTAL));+ $progress.style.strokeDashoffset = CIRC * (1 - frac);++ $timer.classList.toggle('expired', expired);+ document.body.classList.toggle('expired', flashing); // strobe stops when paused+ $label.textContent = expired ? 'time up' : (running ? 'running' : (remaining === TOTAL ? 'ready' : 'paused'));+ }++ function tick() {+ if (running) remaining = endAt - now();+ render();+ requestAnimationFrame(tick);+ }++ function setPlayingUI(isRunning) {+ $playIcon.querySelector('path').setAttribute('d', isRunning ? ICON_PAUSE : ICON_PLAY);+ $playLabel.textContent = isRunning ? 'Pause' : (remaining === TOTAL ? 'Start' : 'Resume');+ }++ function play() {+ if (running) return;+ endAt = now() + remaining;+ running = true;+ setPlayingUI(true);+ }+ function pause() {+ if (!running) return;+ remaining = endAt - now();+ running = false;+ setPlayingUI(false);+ }+ function toggle() { running ? pause() : play(); }+ function reset() {+ running = false;+ remaining = TOTAL;+ setPlayingUI(false);+ render();+ }+ function adjust(delta) {+ if (running) { endAt += delta; remaining = endAt - now(); }+ else { remaining += delta; }+ setPlayingUI(running); // refresh Start/Resume label+ render();+ }++ $playBtn.addEventListener('click', toggle);+ $resetBtn.addEventListener('click', reset);+ document.querySelectorAll('.adj').forEach(b => {+ b.addEventListener('click', () => adjust(parseInt(b.dataset.delta, 10)));+ });+ document.addEventListener('keydown', (e) => {+ if (e.code === 'Space') { e.preventDefault(); toggle(); }+ else if (e.key === 'r' || e.key === 'R') { reset(); }+ });++ setPlayingUI(false);+ requestAnimationFrame(tick);+})();+</script>+</body>+</html>
install.shadded+34@@ -0,0 +1,34 @@+#!/bin/bash+# Install the 5-Minute Timer: stage the self-contained web app + a `timer` launcher.+set -e++APP_DIR="$HOME/.local/share/adom-timer"+BIN_DIR="$HOME/.local/bin"+MOD_DIR="$(cd "$(dirname "$0")" && pwd)"++mkdir -p "$APP_DIR" "$BIN_DIR"++# The timer is a single self-contained HTML file — no build, no server deps.+cp "$MOD_DIR/index.html" "$APP_DIR/index.html"++# Launcher: serve the app on a free port and print the URL.+cat > "$BIN_DIR/timer" <<'EOF'+#!/bin/bash+# Serve the 5-Minute Timer locally and print its URL.+APP_DIR="$HOME/.local/share/adom-timer"+PORT="${TIMER_PORT:-8741}"+echo "Serving 5-Minute Timer at http://localhost:$PORT/"+echo "(Ctrl-C to stop. Override the port with TIMER_PORT=NNNN timer.)"+exec python3 -m http.server "$PORT" --directory "$APP_DIR"+EOF+chmod +x "$BIN_DIR/timer"++# Install the skill so Claude Code knows about the app.+SKILL_DIR="$HOME/.claude/skills/timer"+mkdir -p "$SKILL_DIR"+cp "$MOD_DIR/SKILL.md" "$SKILL_DIR/SKILL.md"++echo "OK: 5-Minute Timer installed."+echo " - Run: timer (serves it locally, opens on a port)"+echo " - File: $APP_DIR/index.html (open directly in any browser)"+case ":$PATH:" in *":$BIN_DIR:"*) ;; *) echo " note: add $BIN_DIR to PATH to use the 'timer' command." ;; esac
package.jsonadded+23@@ -0,0 +1,23 @@+{+ "slug": "timer",+ "version": "0.1.0",+ "type": "app",+ "description": "A 5-minute countdown timer that shifts green to red as it expires, then flashes red at 1 Hz and counts negative. Play/pause, reset, and +/- 10/30/60s adjust buttons. Self-contained single-file web app.",+ "dependencies": {},+ "files": [+ "SKILL.md",+ "README.md",+ "page.json",+ "install.sh",+ "uninstall.sh",+ "index.html",+ "bin/**",+ "demo/**",+ "skills/**",+ "screenshots/**"+ ],+ "scripts": {+ "install": "./install.sh",+ "uninstall": "./uninstall.sh"+ }+}
page.json+5−26@@ -1,32 +1,11 @@ {- "schema_version": 1, "type": "app", "slug": "timer", "title": "5-Minute Timer", "brief": "A countdown timer that goes green to red, then flashes red and counts negative when time's up.", "version": "0.1.0",- "visibility": {- "public": true- },- "install": {- "binary_name": "timer",- "install_dir": "",- "install_hint": "",- "version_cmd": ""- },- "author": {- "id": "695820315b5f1e4db2fcf602",- "name": "Kyle Bergstedt",- "email": "[email protected]"- },- "tags": [],- "license": "proprietary",- "hero": null,- "sample_prompts": [],- "discovery_triggers": [],- "discovery_pitch": null,- "metadata": {},- "created_at": "2026-06-29T19:33:38.282Z",- "updated_at": "2026-06-29T19:33:38.282Z",- "skills": []-}+ "visibility": {"public": true},+ "hero": {"type": "image", "path": "screenshots/hero.png"},+ "install": {"binary_name": "timer"},+ "metadata": {"platform": "any"}+}
screenshots/expired.pngadded⋯ 1 unchanged line ⋯
screenshots/hero.pngadded⋯ 1 unchanged line ⋯
screenshots/ready.pngadded⋯ 1 unchanged line ⋯
uninstall.shadded+7@@ -0,0 +1,7 @@+#!/bin/bash+# Remove the 5-Minute Timer app, launcher, and skill.+set -e+rm -rf "$HOME/.local/share/adom-timer"+rm -f "$HOME/.local/bin/timer"+rm -rf "$HOME/.claude/skills/timer"+echo "OK: 5-Minute Timer uninstalled."