bucketlist tab enforced
This commit is contained in:
1 parent
650211e8ab
commit
bedabf1d44
3 files changed
+184
-33
No files matched your search
+1
-7
@@ -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">
|
||||||
|
|||||||
+117
-25
@@ -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,29 +64,89 @@ 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 =>
|
||||||
|
'<div class="bl-item' + (item.done ? ' bl-done' : '') + '">' +
|
||||||
|
'<div class="bl-title">' + esc(item.title) + '</div>' +
|
||||||
|
(item.date ? '<div class="bl-date">' + esc(item.date) + '</div>' : '') +
|
||||||
|
(item.description ? '<div class="bl-desc">' + esc(item.description) + '</div>' : '') +
|
||||||
|
'</div>'
|
||||||
|
).join("");
|
||||||
|
}
|
||||||
|
|
||||||
|
function esc(s) {
|
||||||
|
if (!s) return "";
|
||||||
|
return String(s).replace(/&/g,"&").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 <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;
|
||||||
listEl.innerHTML = items.map(item =>
|
|
||||||
'<div class="bl-item' + (item.done ? ' bl-done' : '') + '">' +
|
|
||||||
'<div class="bl-title">' + esc(item.title) + '</div>' +
|
|
||||||
(item.date ? '<div class="bl-date">' + esc(item.date) + '</div>' : '') +
|
|
||||||
(item.description ? '<div class="bl-desc">' + esc(item.description) + '</div>' : '') +
|
|
||||||
'</div>'
|
|
||||||
).join("");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function esc(s) {
|
submitBtn.onclick = tryUnlock;
|
||||||
if (!s) return "";
|
userInput.onkeydown = e => { if (e.key === "Enter") passInput.focus(); };
|
||||||
return String(s).replace(/&/g,"&").replace(/</g,"<")
|
passInput.onkeydown = e => { if (e.key === "Enter") tryUnlock(); };
|
||||||
.replace(/>/g,">").replace(/"/g,""").replace(/'/g,"'");
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
+66
-1
@@ -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;
|
||||||
|
|||||||
Reference in new issue
Block a user