Skip to content
Open
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
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ A simple, community-maintained open-source task tracker. Add tasks, check them o
- Single-page, zero-build static web app
- Tasks persist in `localStorage` — no account, no server, no tracking
- Keyboard-friendly (more shortcuts coming, see #5)
- Light & dark themes (dark coming, see #1)
- Light and dark themes
- MIT licensed

## Quick start
Expand Down
31 changes: 29 additions & 2 deletions index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,39 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TaskForge — open-source task tracker</title>
<script>
(() => {
let storedTheme = null;
try {
storedTheme = window.localStorage.getItem("taskforge.theme");
} catch {
storedTheme = null;
}
const prefersDark = window.matchMedia("(prefers-color-scheme: dark)").matches;
const theme = storedTheme || (prefersDark ? "dark" : "light");
document.documentElement.dataset.theme = theme;
})();
</script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<header class="app-header">
<h1>TaskForge</h1>
<p class="tagline">A tiny task list that lives in your browser.</p>
<div class="header-content">
<div>
<h1>TaskForge</h1>
<p class="tagline">A tiny task list that lives in your browser.</p>
</div>
<button
id="theme-toggle"
class="theme-toggle"
type="button"
aria-label="Switch to dark theme"
aria-pressed="false"
>
<span class="theme-toggle-icon" aria-hidden="true"></span>
<span class="theme-toggle-text">Theme</span>
</button>
</div>
</header>

<main class="app-main">
Expand Down
20 changes: 20 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@ const form = document.getElementById("task-form");
const input = document.getElementById("task-input");
const list = document.getElementById("task-list");
const emptyState = document.getElementById("empty-state");
const themeToggle = document.getElementById("theme-toggle");

let tasks = load();

function currentTheme() {
return document.documentElement.dataset.theme === "dark" ? "dark" : "light";
}

function setTheme(theme, persist = false) {
document.documentElement.dataset.theme = theme;
if (persist) {
window.localStorage.setItem("taskforge.theme", theme);
}
const nextTheme = theme === "dark" ? "light" : "dark";
themeToggle.setAttribute("aria-label", `Switch to ${nextTheme} theme`);
themeToggle.setAttribute("aria-pressed", String(theme === "dark"));
}

function render() {
list.innerHTML = "";
if (tasks.length === 0) {
Expand Down Expand Up @@ -60,4 +75,9 @@ form.addEventListener("submit", (e) => {
render();
});

themeToggle.addEventListener("click", () => {
setTheme(currentTheme() === "dark" ? "light" : "dark", true);
});

setTheme(currentTheme());
render();
75 changes: 71 additions & 4 deletions styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,25 @@
--accent: #0969da;
--accent-fg: #ffffff;
--done: #8c959f;
--panel: #f6f8fa;
--danger: #cf222e;
--danger-bg: rgba(207, 34, 46, 0.08);
--radius: 6px;
color-scheme: light;
}

:root[data-theme="dark"] {
--bg: #0d1117;
--fg: #e6edf3;
--muted: #8b949e;
--border: #30363d;
--accent: #2f81f7;
--accent-fg: #ffffff;
--done: #6e7681;
--panel: #161b22;
--danger: #ff7b72;
--danger-bg: rgba(248, 81, 73, 0.14);
color-scheme: dark;
}

* {
Expand All @@ -24,14 +42,23 @@ body {

.app-header {
padding: 2rem 1.25rem 1rem;
text-align: center;
background: var(--panel);
border-bottom: 1px solid var(--border);
}

.header-content {
max-width: 36rem;
margin: 0 auto;
display: flex;
align-items: center;
justify-content: space-between;
gap: 1rem;
}

.app-header h1 {
margin: 0;
font-size: 1.75rem;
letter-spacing: -0.01em;
letter-spacing: 0;
}

.tagline {
Expand All @@ -40,6 +67,38 @@ body {
font-size: 0.95rem;
}

.theme-toggle {
flex: 0 0 auto;
display: inline-flex;
align-items: center;
gap: 0.4rem;
min-height: 2.25rem;
padding: 0.45rem 0.7rem;
font: inherit;
color: var(--fg);
background: var(--bg);
border: 1px solid var(--border);
border-radius: var(--radius);
cursor: pointer;
}

.theme-toggle:hover {
border-color: var(--accent);
}

.theme-toggle:focus-visible {
outline: 2px solid var(--accent);
outline-offset: 2px;
}

.theme-toggle-icon {
width: 1rem;
height: 1rem;
border: 2px solid currentColor;
border-radius: 50%;
background: linear-gradient(90deg, currentColor 50%, transparent 50%);
}

.app-main {
max-width: 36rem;
margin: 0 auto;
Expand Down Expand Up @@ -98,6 +157,7 @@ body {
padding: 0.6rem 0.75rem;
border: 1px solid var(--border);
border-radius: var(--radius);
background: var(--panel);
}

.task-item.done .task-title {
Expand All @@ -122,8 +182,8 @@ body {
}

.task-delete:hover {
color: #cf222e;
background: rgba(207, 34, 46, 0.08);
color: var(--danger);
background: var(--danger-bg);
}

.empty-state {
Expand All @@ -142,3 +202,10 @@ body {
.app-footer a {
color: inherit;
}

@media (max-width: 34rem) {
.header-content {
align-items: flex-start;
flex-direction: column;
}
}