diff --git a/assets/scripts/api-auth.js b/assets/scripts/api-auth.js
new file mode 100644
index 0000000..6c87ef5
--- /dev/null
+++ b/assets/scripts/api-auth.js
@@ -0,0 +1,48 @@
+(function () {
+ "use strict";
+
+ const STORAGE_KEY = "orgBackendApiKey";
+ const HEADER = "X-Org-Api-Key";
+
+ function getStoredKey() {
+ return window.localStorage.getItem(STORAGE_KEY) || "";
+ }
+
+ function requestKey() {
+ const key = window.prompt("API key");
+ if (key && key.trim()) {
+ window.localStorage.setItem(STORAGE_KEY, key.trim());
+ return key.trim();
+ }
+ return "";
+ }
+
+ function authHeaders(existingHeaders = {}, options = {}) {
+ const headers = new Headers(existingHeaders);
+ let key = getStoredKey();
+ if (!key && options.prompt !== false) {
+ key = requestKey();
+ }
+ if (key) headers.set(HEADER, key);
+ return headers;
+ }
+
+ async function authFetch(input, options = {}) {
+ const headers = authHeaders(options.headers, options);
+ const response = await fetch(input, { ...options, headers });
+ if (response.status !== 401) return response;
+
+ window.localStorage.removeItem(STORAGE_KEY);
+ if (options.retry === false) return response;
+
+ const retryHeaders = authHeaders(options.headers, options);
+ if (!retryHeaders.has(HEADER)) return response;
+ return fetch(input, { ...options, headers: retryHeaders, retry: false });
+ }
+
+ window.orgAuth = {
+ fetch: authFetch,
+ headers: authHeaders,
+ clear: () => window.localStorage.removeItem(STORAGE_KEY),
+ };
+})();
diff --git a/assets/scripts/auth.js b/assets/scripts/auth.js
deleted file mode 100644
index 5300186..0000000
--- a/assets/scripts/auth.js
+++ /dev/null
@@ -1,102 +0,0 @@
-(function () {
- "use strict";
-
- const TOKEN_KEY = "orgWebJwt";
- const LOGIN_PATH = "/home/login.html";
-
- function token() {
- return window.localStorage.getItem(TOKEN_KEY) || "";
- }
-
- function isLoginPage() {
- return window.location.pathname === LOGIN_PATH;
- }
-
- function loginUrl() {
- const next = window.location.pathname + window.location.search + window.location.hash;
- return `${LOGIN_PATH}?next=${encodeURIComponent(next)}`;
- }
-
- function redirectToLogin() {
- if (!isLoginPage()) {
- window.location.assign(loginUrl());
- }
- }
-
- function authHeaders(existingHeaders = {}) {
- const headers = new Headers(existingHeaders);
- const jwt = token();
- if (jwt) headers.set("Authorization", `Bearer ${jwt}`);
- return headers;
- }
-
- async function authFetch(input, options = {}) {
- const response = await fetch(input, {
- ...options,
- headers: authHeaders(options.headers),
- });
-
- if (response.status === 401) {
- window.localStorage.removeItem(TOKEN_KEY);
- redirectToLogin();
- }
-
- return response;
- }
-
- async function login(username, password) {
- const response = await fetch("/api/auth/login", {
- method: "POST",
- headers: { "Content-Type": "application/json" },
- body: JSON.stringify({ username, password }),
- });
-
- const data = await response.json().catch(() => ({}));
- if (!response.ok) {
- throw new Error(data.error || "Login failed");
- }
-
- window.localStorage.setItem(TOKEN_KEY, data.token);
- return data.token;
- }
-
- function initLoginForm() {
- const form = document.getElementById("login-form");
- if (!form) return;
-
- const message = document.getElementById("login-message");
- form.addEventListener("submit", async (event) => {
- event.preventDefault();
- const submit = form.querySelector("button[type='submit']");
- if (submit) submit.disabled = true;
- if (message) message.textContent = "";
-
- try {
- await login(form.username.value.trim(), form.password.value);
- const params = new URLSearchParams(window.location.search);
- window.location.assign(params.get("next") || "/");
- } catch (error) {
- if (message) message.textContent = error.message;
- } finally {
- if (submit) submit.disabled = false;
- }
- });
- }
-
- window.orgAuth = {
- fetch: authFetch,
- headers: authHeaders,
- token,
- logout: () => {
- window.localStorage.removeItem(TOKEN_KEY);
- redirectToLogin();
- },
- };
-
- document.addEventListener("DOMContentLoaded", () => {
- initLoginForm();
- if (!isLoginPage() && !token()) {
- redirectToLogin();
- }
- });
-})();
diff --git a/assets/scripts/comments.js b/assets/scripts/comments.js
index 7dcd221..c6ef45e 100755
--- a/assets/scripts/comments.js
+++ b/assets/scripts/comments.js
@@ -121,7 +121,7 @@ function renderComments(comments) {
* ----------------------------- */
async function fetchComments() {
- const res = await window.orgAuth.fetch(`/api/comments/${pageSlug}`);
+ const res = await fetch(`/api/comments/${pageSlug}`);
if (!res.ok) {
console.error("Failed to fetch comments");
return [];
diff --git a/assets/scripts/competency-status-board.js b/assets/scripts/competency-status-board.js
index bd11f82..cec3183 100755
--- a/assets/scripts/competency-status-board.js
+++ b/assets/scripts/competency-status-board.js
@@ -228,8 +228,6 @@ function renderBoard(items) {
}
async function loadBoard() {
- if (!document.getElementById("kanban-board")) return;
-
const res = await window.orgAuth.fetch(`/api/competencies/items?group=${currentLevel}`);
const items = await res.json();
diff --git a/assets/scripts/wird-tracker.js b/assets/scripts/wird-tracker.js
index 8f4123e..b2deb73 100755
--- a/assets/scripts/wird-tracker.js
+++ b/assets/scripts/wird-tracker.js
@@ -328,8 +328,6 @@
// ── Boot ─────────────────────────────────────────────────
document.addEventListener("DOMContentLoaded", async () => {
- if (!document.getElementById("wird-app")) return;
-
setTodayLabel();
await Promise.all([loadAll(), loadMotalah()]);
renderToday();
diff --git a/assets/styles/misc.css b/assets/styles/misc.css
index e4e5ae0..4d1e050 100644
--- a/assets/styles/misc.css
+++ b/assets/styles/misc.css
@@ -427,29 +427,3 @@ h2[id], h3[id], h4[id] { scroll-margin-top: 6.5rem; }
background-color: var(--active-toc);
/* border: 1px solid var(--border, #ccc); */
}
-.auth-page {
- min-height: 70vh;
- display: grid;
- place-items: center;
- padding: 3rem 1rem;
-}
-
-.auth-form {
- width: min(100%, 360px);
- display: grid;
- gap: 0.8rem;
-}
-
-.auth-form h1 {
- margin: 0 0 0.5rem;
-}
-
-.auth-form input {
- width: 100%;
- box-sizing: border-box;
-}
-
-.auth-message {
- min-height: 1.4em;
- color: #a33;
-}
diff --git a/build-site.el b/build-site.el
index 2ed577b..91f2a19 100755
--- a/build-site.el
+++ b/build-site.el
@@ -182,7 +182,7 @@ FMT / ARGS are passed to `format'."
(format "" f))
'("script.js"
"lunr.js"
- "auth.js"
+ "api-auth.js"
"competency-status-board.js"
"notes.js"
"comments.js"
diff --git a/home/guide/setup.org b/home/guide/setup.org
index 87344fa..3653323 100755
--- a/home/guide/setup.org
+++ b/home/guide/setup.org
@@ -74,7 +74,7 @@ The script is as follows, at the start I have some metadata relating to the file
-
+
diff --git a/home/login.org b/home/login.org
deleted file mode 100644
index 7833bd3..0000000
--- a/home/login.org
+++ /dev/null
@@ -1,21 +0,0 @@
-#+TITLE: Login
-#+OPTIONS: toc:nil num:nil
-#+NO_SIDENOTES: t
-#+COMMENTS: nil
-#+SLUG: login
-
-#+BEGIN_EXPORT html
-
-
-
-#+END_EXPORT
diff --git a/posts/posts-list.org b/posts/posts-list.org
index c72bb91..1ddbc63 100644
--- a/posts/posts-list.org
+++ b/posts/posts-list.org
@@ -4,7 +4,7 @@
See the categories: @@html:Categories@@
* Posts:
-- [[file:career/career-list.org][Career List]] @@html:09-05-2026 00:35@@
+- [[file:career/career-list.org][Career List]] @@html:09-05-2026 00:25@@
- [[file:posts-list.sync-conflict-20260417-233432-VT6366A.org][Posts List]] @@html:14-04-2026 16:36@@
- [[file:career/ha-dr.org][High Availability, Disaster Recovery and Business Continuity]] @@html:11-03-2026 17:18@@ @@html:learning@@ @@html:notes@@
- [[file:career/javascript.org][Understands the Javascript language]] @@html:11-03-2026 16:52@@ @@html:learning@@ @@html:notes@@
diff --git a/recently-updated.org b/recently-updated.org
index d7e0fb6..a6b6aa7 100644
--- a/recently-updated.org
+++ b/recently-updated.org
@@ -2,8 +2,7 @@
#+OPTIONS: toc:nil num:nil
* Recently Updated (top 26 files)
-- [[file:home/login.org][Login]] @@html:2026-05-09 00:33@@
-- [[file:home/guide/setup.org][Setup]] @@html:2026-05-09 00:33@@
+- [[file:home/guide/setup.org][Setup]] @@html:2026-05-09 00:20@@
- [[file:blogs/2026/05-may/ai-datacamp-08-05.org][AI Datacamp]] @@html:2026-05-08 12:49@@
- [[file:blogs/2026/05-may/using-codex-07-05-26.org][Using Codex]] @@html:2026-05-07 16:12@@
- [[file:blogs/2026/05-may/03-05-week-review.org][[03-05-2026] - Weekly Review]] @@html:2026-05-03 12:00@@
@@ -28,3 +27,4 @@
- [[file:blogs/2026/04-april/joining-new-team-meeting-02-04-2026.org][New team meeting]] @@html:2026-04-02 16:03@@
- [[file:blogs/2026/03-march/intellectually-challenging-myself-30-03-26.org][Intellectually challenging oneself]] @@html:2026-03-31 11:14@@
- [[file:blogs/2026/03-march/cooking-dinner-29-03-26.org][Cooking Dinner (num)]] @@html:2026-03-30 11:49@@
+- [[file:blogs/2026/03-march/29-03-week-review.org][[29-03-2026] - Weekly Review]] @@html:2026-03-29 12:00@@
diff --git a/sitemap.org b/sitemap.org
index a84c6ca..cd0e1b4 100644
--- a/sitemap.org
+++ b/sitemap.org
@@ -11,12 +11,12 @@
- [[file:tags/notes.org][Tag: notes]]
- [[file:tags/review.org][Tag: review]]
- [[file:tags/website.org][Tag: website]]
+ - [[file:tags/life.org][Tag: life]]
- [[file:tags/update.org][Tag: update]]
- [[file:tags/insights.org][Tag: insights]]
- - [[file:tags/life.org][Tag: life]]
+ - [[file:tags/emacs.org][Tag: emacs]]
- [[file:tags/education.org][Tag: education]]
- [[file:tags/reading.org][Tag: reading]]
- - [[file:tags/emacs.org][Tag: emacs]]
- [[file:tags/maths.org][Tag: maths]]
- posts
- [[file:posts/posts-intro.org][Posts Introduction]]
@@ -60,7 +60,6 @@
- [[file:home/services.org][Service]]
- [[file:home/status.org][Competency Status Board]]
- [[file:home/wird-tracker.org][Wird Tracker]]
- - [[file:home/login.org][Login]]
- [[file:home/categories.org][Categories]]
- guide
- [[file:home/guide/wird-tracker-guide.org][Wird Tracker — Technical Guide]]