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
20 changes: 19 additions & 1 deletion index.html
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,14 @@
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>TaskForge — open-source task tracker</title>
<script>
(() => {
const saved = window.localStorage.getItem("taskforge.theme");
if (saved === "dark" || saved === "light") {
document.documentElement.dataset.theme = saved;
}
})();
</script>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
Expand All @@ -12,14 +20,24 @@
<h1>TaskForge</h1>
<p class="tagline">A tiny task list that lives in your browser.</p>
</div>
<div class="auth-panel" aria-live="polite">
<div class="header-actions">
<button
id="theme-toggle"
class="theme-toggle"
type="button"
aria-pressed="false"
>
Dark theme
</button>
<div class="auth-panel" aria-live="polite">
<img id="auth-avatar" class="auth-avatar" hidden />
<span id="auth-status" class="auth-status">
Tasks are stored on this device.
</span>
<button id="auth-action" class="auth-action" type="button">
Sign in with GitHub
</button>
</div>
</div>
</header>

Expand Down
35 changes: 35 additions & 0 deletions src/app.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,11 +15,14 @@ const authStatus = document.getElementById("auth-status");
const authAction = document.getElementById("auth-action");
const authAvatar = document.getElementById("auth-avatar");
const authMessage = document.getElementById("auth-message");
const themeToggle = document.getElementById("theme-toggle");
const THEME_KEY = "taskforge.theme";

let tasks = [];
let user = null;

async function init() {
initTheme();
const localTasks = loadLocal();
let completedSignIn = false;
try {
Expand Down Expand Up @@ -96,6 +99,11 @@ form.addEventListener("submit", async (e) => {
render();
});

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

authAction.addEventListener("click", async () => {
authAction.disabled = true;
try {
Expand Down Expand Up @@ -147,3 +155,30 @@ function mergeTasks(localTasks, remoteTasks) {
}

init();

function initTheme() {
const saved = window.localStorage.getItem(THEME_KEY);
if (saved === "dark" || saved === "light") {
applyTheme(saved, false);
return;
}
applyTheme(preferredTheme(), false);
}

function preferredTheme() {
return window.matchMedia("(prefers-color-scheme: dark)").matches
? "dark"
: "light";
}

function currentTheme() {
return document.documentElement.dataset.theme || preferredTheme();
}

function applyTheme(theme, persist) {
document.documentElement.dataset.theme = theme;
if (persist) window.localStorage.setItem(THEME_KEY, theme);
const dark = theme === "dark";
themeToggle.setAttribute("aria-pressed", String(dark));
themeToggle.textContent = dark ? "Light theme" : "Dark theme";
}
58 changes: 56 additions & 2 deletions styles.css
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
:root {
color-scheme: light;
--bg: #ffffff;
--fg: #1f2328;
--muted: #6e7781;
--border: #d0d7de;
--accent: #0969da;
--accent-fg: #ffffff;
--done: #8c959f;
--panel-bg: #f6f8fa;
--danger: #cf222e;
--danger-bg: rgba(207, 34, 46, 0.08);
--radius: 6px;
}

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

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

* {
box-sizing: border-box;
}
Expand Down Expand Up @@ -43,6 +77,13 @@ body {
font-size: 0.95rem;
}

.header-actions {
display: flex;
align-items: center;
justify-content: flex-end;
gap: 0.75rem;
}

.auth-panel {
display: flex;
align-items: center;
Expand All @@ -64,6 +105,7 @@ body {
overflow-wrap: anywhere;
}

.theme-toggle,
.auth-action {
flex: 0 0 auto;
padding: 0.45rem 0.75rem;
Expand All @@ -75,6 +117,12 @@ body {
cursor: pointer;
}

.theme-toggle {
color: var(--fg);
background: var(--panel-bg);
border-color: var(--border);
}

.auth-action:disabled {
cursor: wait;
opacity: 0.7;
Expand Down Expand Up @@ -171,8 +219,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 @@ -198,6 +246,12 @@ body {
flex-direction: column;
}

.header-actions {
align-items: stretch;
flex-direction: column;
width: 100%;
}

.auth-panel {
justify-content: flex-start;
min-width: 0;
Expand Down