bucketlist tab enforced

This commit is contained in:
esef committed 2026-07-20 23:16:51 +03:00
1 parent 650211e8ab
commit bedabf1d44
3 files changed
+168 -17

No files matched your search

+1 -7
View File
@@ -117,13 +117,7 @@
<div id="gallery-container" class="gallery-grid"></div> <div id="gallery-container" class="gallery-grid"></div>
</section> </section>
<section id="bucketlist" class="tab-content"> <section id="bucketlist" class="tab-content"></section>
<div class="bl-panel">
<div class="bl-list" id="bl-list">
<p class="bl-empty">Loading...</p>
</div>
</div>
</section>
<section id="yuukabot" class="tab-content"> <section id="yuukabot" class="tab-content">
<header class="dash-header"> <header class="dash-header">
+101 -9
View File
@@ -1,4 +1,4 @@
// Bucketlist tab — real-time Firestore list // Bucketlist tab — password-gated, real-time Firestore list
const BL_FB_CONFIG = { const BL_FB_CONFIG = {
apiKey: "AIzaSyDuSjEGEKx5FnWYnQq8f_owbpYRBRrl5x0", apiKey: "AIzaSyDuSjEGEKx5FnWYnQq8f_owbpYRBRrl5x0",
authDomain: "esef-514bf.firebaseapp.com", authDomain: "esef-514bf.firebaseapp.com",
@@ -8,17 +8,49 @@ const BL_FB_CONFIG = {
appId: "1:353969651290:web:41e3aaf8e54c743702a263" appId: "1:353969651290:web:41e3aaf8e54c743702a263"
}; };
document.addEventListener("DOMContentLoaded", () => { // ═══════════════════════════════════════════════════════════
const tab = document.getElementById("bucketlist"); // PASSWORD GATE — items never touch DOM until authenticated
if (!tab) return; // ═══════════════════════════════════════════════════════════
if (!firebase.apps.length) firebase.initializeApp(BL_FB_CONFIG); // --- SHA-256 via Web Crypto ---------------------------------------------------
const db = firebase.firestore(); 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 = `
<div class="bl-lock">
<div class="bl-lock-box">
<h2>Bucketlist</h2>
<input type="text" id="bl-user" placeholder="Username" autofocus>
<input type="password" id="bl-pass" placeholder="Password">
<button id="bl-submit">Unlock</button>
<p id="bl-err"></p>
</div>
</div>
`;
}
// --- Build the real panel + attach Firestore after auth -----------------------
function unlock(container, db, hash) {
container.innerHTML = `
<div class="bl-panel">
<div class="bl-list" id="bl-list">
<p class="bl-empty">Loading...</p>
</div>
</div>
`;
const listEl = document.getElementById("bl-list"); const listEl = document.getElementById("bl-list");
if (!listEl) return; if (!listEl) return;
db.collection("bucketlist_items").onSnapshot(snap => { // items live under bucketlist_data/<sha256(user:pass)>/items — the path IS the key
db.collection("bucketlist_data").doc(hash).collection("items").onSnapshot(snap => {
const items = []; const items = [];
snap.forEach(doc => { snap.forEach(doc => {
const d = doc.data(); const d = doc.data();
@@ -32,17 +64,19 @@ document.addEventListener("DOMContentLoaded", () => {
}); });
}); });
items.sort((a, b) => (a.utc || "").localeCompare(b.utc || "")); items.sort((a, b) => (a.utc || "").localeCompare(b.utc || ""));
render(items); render(listEl, items);
}, () => { }, () => {
if (listEl) listEl.innerHTML = '<p class="bl-empty">Could not load.</p>'; if (listEl) listEl.innerHTML = '<p class="bl-empty">Could not load.</p>';
}); });
}
function render(items) { function render(listEl, items) {
if (items.length === 0) { if (items.length === 0) {
listEl.innerHTML = '<p class="bl-empty">Nothing here yet.</p>'; listEl.innerHTML = '<p class="bl-empty">Nothing here yet.</p>';
return; return;
} }
// NOTE: item.done adds .bl-done class — hook for completed-item styling (CSS still TODO)
listEl.innerHTML = items.map(item => listEl.innerHTML = items.map(item =>
'<div class="bl-item' + (item.done ? ' bl-done' : '') + '">' + '<div class="bl-item' + (item.done ? ' bl-done' : '') + '">' +
'<div class="bl-title">' + esc(item.title) + '</div>' + '<div class="bl-title">' + esc(item.title) + '</div>' +
@@ -57,4 +91,62 @@ document.addEventListener("DOMContentLoaded", () => {
return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;") return String(s).replace(/&/g,"&amp;").replace(/</g,"&lt;")
.replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"); .replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;");
} }
// ═══════════════════════════════════════════════════════════
// 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 <section> 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.";
}
submitBtn.disabled = false;
}
submitBtn.onclick = tryUnlock;
userInput.onkeydown = e => { if (e.key === "Enter") passInput.focus(); };
passInput.onkeydown = e => { if (e.key === "Enter") tryUnlock(); };
}); });
+66 -1
View File
@@ -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 ────────── */ /* ── Lock section to viewport so body never scrolls ────────── */
#bucketlist.tab-content { #bucketlist.tab-content {
@@ -46,6 +107,10 @@
box-shadow: 0 3px 16px rgba(184, 68, 87, 0.08); 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 { .bl-item .bl-title {
color: #F4F2F5; color: #F4F2F5;
font-size: 0.95rem; font-size: 0.95rem;