diff --git a/index.html b/index.html index 214e185..b112850 100644 --- a/index.html +++ b/index.html @@ -117,13 +117,7 @@ -
-
-
-

Loading...

-
-
-
+
diff --git a/js/bucketlist.js b/js/bucketlist.js index abff7e2..cc24f5d 100644 --- a/js/bucketlist.js +++ b/js/bucketlist.js @@ -1,4 +1,4 @@ -// Bucketlist tab — real-time Firestore list +// Bucketlist tab — password-gated, real-time Firestore list const BL_FB_CONFIG = { apiKey: "AIzaSyDuSjEGEKx5FnWYnQq8f_owbpYRBRrl5x0", authDomain: "esef-514bf.firebaseapp.com", @@ -8,17 +8,49 @@ const BL_FB_CONFIG = { appId: "1:353969651290:web:41e3aaf8e54c743702a263" }; -document.addEventListener("DOMContentLoaded", () => { - const tab = document.getElementById("bucketlist"); - if (!tab) return; +// ═══════════════════════════════════════════════════════════ +// PASSWORD GATE — items never touch DOM until authenticated +// ═══════════════════════════════════════════════════════════ - if (!firebase.apps.length) firebase.initializeApp(BL_FB_CONFIG); - const db = firebase.firestore(); +// --- SHA-256 via Web Crypto --------------------------------------------------- +async function sha256(text) { + const buf = new TextEncoder().encode(text); + const hash = await crypto.subtle.digest("SHA-256", buf); + return Array.from(new Uint8Array(hash)) + .map(b => b.toString(16).padStart(2, "0")) + .join(""); +} + +// --- Lock-screen HTML (only thing in DOM before auth) ------------------------- +function showLock(container) { + container.innerHTML = ` +
+
+

Bucketlist

+ + + +

+
+
+ `; +} + +// --- Build the real panel + attach Firestore after auth ----------------------- +function unlock(container, db, hash) { + container.innerHTML = ` +
+
+

Loading...

+
+
+ `; const listEl = document.getElementById("bl-list"); if (!listEl) return; - db.collection("bucketlist_items").onSnapshot(snap => { + // items live under bucketlist_data//items — the path IS the key + db.collection("bucketlist_data").doc(hash).collection("items").onSnapshot(snap => { const items = []; snap.forEach(doc => { const d = doc.data(); @@ -32,29 +64,89 @@ document.addEventListener("DOMContentLoaded", () => { }); }); items.sort((a, b) => (a.utc || "").localeCompare(b.utc || "")); - render(items); + render(listEl, items); }, () => { if (listEl) listEl.innerHTML = '

Could not load.

'; }); +} - function render(items) { - if (items.length === 0) { - listEl.innerHTML = '

Nothing here yet.

'; - return; +function render(listEl, items) { + if (items.length === 0) { + listEl.innerHTML = '

Nothing here yet.

'; + return; + } + + // NOTE: item.done adds .bl-done class — hook for completed-item styling (CSS still TODO) + listEl.innerHTML = items.map(item => + '
' + + '
' + esc(item.title) + '
' + + (item.date ? '
' + esc(item.date) + '
' : '') + + (item.description ? '
' + esc(item.description) + '
' : '') + + '
' + ).join(""); +} + +function esc(s) { + if (!s) return ""; + return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); +} + +// ═══════════════════════════════════════════════════════════ +// ENTRY +// ═══════════════════════════════════════════════════════════ + +document.addEventListener("DOMContentLoaded", () => { + const tab = document.getElementById("bucketlist"); + if (!tab) return; + + if (!firebase.apps.length) firebase.initializeApp(BL_FB_CONFIG); + const db = firebase.firestore(); + + const container = tab; // the
itself — nothing else exists inside it + + // --- Already authenticated this session? --------------------------------- + const savedHash = sessionStorage.getItem("bl_hash"); + if (savedHash) { + unlock(container, db, savedHash); + return; + } + + // --- Show lock screen ---------------------------------------------------- + showLock(container); + + const userInput = document.getElementById("bl-user"); + const passInput = document.getElementById("bl-pass"); + const submitBtn = document.getElementById("bl-submit"); + const errEl = document.getElementById("bl-err"); + + async function tryUnlock() { + const user = userInput.value.trim(); + const pass = passInput.value; + if (!user || !pass) return; + + submitBtn.disabled = true; + errEl.textContent = ""; + + // hash = sha256("username:password") — both must match the Firestore doc ID + const hash = await sha256(user + ":" + pass); + try { + const doc = await db.collection("bucketlist_auth").doc(hash).get(); + if (doc.exists) { + sessionStorage.setItem("bl_hash", hash); + unlock(container, db, hash); + } else { + errEl.textContent = "Wrong credentials."; + passInput.value = ""; + passInput.focus(); + } + } catch (e) { + errEl.textContent = "Could not verify. Try again."; } - - listEl.innerHTML = items.map(item => - '
' + - '
' + esc(item.title) + '
' + - (item.date ? '
' + esc(item.date) + '
' : '') + - (item.description ? '
' + esc(item.description) + '
' : '') + - '
' - ).join(""); + submitBtn.disabled = false; } - function esc(s) { - if (!s) return ""; - return String(s).replace(/&/g,"&").replace(//g,">").replace(/"/g,""").replace(/'/g,"'"); - } + submitBtn.onclick = tryUnlock; + userInput.onkeydown = e => { if (e.key === "Enter") passInput.focus(); }; + passInput.onkeydown = e => { if (e.key === "Enter") tryUnlock(); }; }); diff --git a/style/bucketlist.css b/style/bucketlist.css index f647fd6..3a2919f 100644 --- a/style/bucketlist.css +++ b/style/bucketlist.css @@ -1,4 +1,65 @@ -/* Bucketlist tab — baked-in left panel, internal scroll only */ +/* Bucketlist tab — password gated, baked-in left panel, internal scroll only */ + +/* ── Lock screen (pitch black, centered form) ─────────────── */ +.bl-lock { + width: 100%; + height: 100vh; + background: #000; + display: flex; + align-items: center; + justify-content: center; +} +.bl-lock-box { + text-align: center; + color: #F4F2F5; +} +.bl-lock-box h2 { + margin: 0 0 24px; + font-size: 1.6rem; + font-weight: 400; + letter-spacing: 2px; +} +.bl-lock-box input { + display: block; + width: 260px; + margin: 0 auto 12px; + padding: 10px 14px; + background: #1a1a1a; + border: 1px solid rgba(255, 255, 255, 0.15); + border-radius: 6px; + color: #fff; + font-size: 0.95rem; + text-align: center; + outline: none; + transition: border-color 0.2s; +} +.bl-lock-box input:focus { + border-color: rgba(184, 68, 87, 0.5); +} +.bl-lock-box button { + width: 260px; + padding: 10px 0; + background: rgba(184, 68, 87, 0.8); + border: none; + border-radius: 6px; + color: #fff; + font-size: 0.9rem; + cursor: pointer; + transition: background 0.2s; +} +.bl-lock-box button:hover { + background: rgba(184, 68, 87, 1); +} +.bl-lock-box button:disabled { + opacity: 0.5; + cursor: not-allowed; +} +#bl-err { + color: #e05555; + font-size: 0.78rem; + margin-top: 12px; + min-height: 1.2em; +} /* ── Lock section to viewport so body never scrolls ────────── */ #bucketlist.tab-content { @@ -46,6 +107,10 @@ box-shadow: 0 3px 16px rgba(184, 68, 87, 0.08); } +/* TODO: .bl-done styles — dim + strikethrough for completed items */ +/* .bl-item.bl-done { opacity: 0.5; } + .bl-item.bl-done .bl-title { text-decoration: line-through; } */ + .bl-item .bl-title { color: #F4F2F5; font-size: 0.95rem;