<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
<title>Going to College?</title>
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@400;600;700;800&display=swap" rel="stylesheet">
<style>
:root{--bg1:#eef1ff;--bg2:#fdeff6;--card:rgba(255,255,255,.75);--text:#1b1b2f;--muted:#6c6c85;--line:rgba(80,80,140,.14);--input:rgba(255,255,255,.9);--yes:#12a066;--yesbg:rgba(18,160,102,.13);--no:#e0483e;--nobg:rgba(224,72,62,.12);--maybe:#d48806;--maybebg:rgba(212,136,6,.14);--g1:#6366f1;--g2:#ec4899;--shadow:0 10px 30px rgba(70,60,140,.12);box-sizing:border-box;padding-top:env(safe-area-inset-top,0px);padding-bottom:env(safe-area-inset-bottom,0px)}
@media (prefers-color-scheme:dark){:root:not([data-theme="light"]){--bg1:#12121f;--bg2:#1d1230;--card:rgba(35,35,58,.7);--text:#f1f1fb;--muted:#a3a3c0;--line:rgba(255,255,255,.09);--input:rgba(15,15,30,.6);--yes:#4ade9b;--yesbg:rgba(74,222,155,.14);--no:#ff8a80;--nobg:rgba(255,138,128,.14);--maybe:#fbbf4a;--maybebg:rgba(251,191,74,.14);--shadow:0 10px 30px rgba(0,0,0,.4)}}
:root[data-theme="dark"]{--bg1:#12121f;--bg2:#1d1230;--card:rgba(35,35,58,.7);--text:#f1f1fb;--muted:#a3a3c0;--line:rgba(255,255,255,.09);--input:rgba(15,15,30,.6);--yes:#4ade9b;--yesbg:rgba(74,222,155,.14);--no:#ff8a80;--nobg:rgba(255,138,128,.14);--maybe:#fbbf4a;--maybebg:rgba(251,191,74,.14);--shadow:0 10px 30px rgba(0,0,0,.4)}
html{scroll-padding-top:env(safe-area-inset-top,0px)}
*{box-sizing:border-box}
body{margin:0;min-height:100vh;background:radial-gradient(circle at 15% 0%,var(--bg1),transparent 60%),radial-gradient(circle at 100% 100%,var(--bg2),transparent 60%),var(--bg1);background-attachment:fixed;color:var(--text);font-family:"Plus Jakarta Sans",system-ui,-apple-system,"Segoe UI",Roboto,sans-serif;line-height:1.45}
main{max-width:560px;margin:0 auto;padding:26px 16px 48px}
.hero{text-align:center;margin-bottom:22px}
.hero h1{font-size:1.9rem;font-weight:800;margin:0 0 6px;letter-spacing:-.5px;background:linear-gradient(90deg,var(--g1),var(--g2));-webkit-background-clip:text;background-clip:text;color:transparent}
.pill{display:inline-block;padding:6px 14px;border-radius:99px;background:var(--card);border:1px solid var(--line);font-size:.85rem;font-weight:600;backdrop-filter:blur(10px);-webkit-backdrop-filter:blur(10px)}
.timer{display:block;margin-top:8px;font-size:.8rem;color:var(--muted)}
.card{background:var(--card);border:1px solid var(--line);border-radius:22px;padding:20px;margin-bottom:18px;box-shadow:var(--shadow);backdrop-filter:blur(14px);-webkit-backdrop-filter:blur(14px)}
h2{font-size:1.05rem;font-weight:700;margin:0 0 14px}
label{display:block;font-size:.78rem;font-weight:600;color:var(--muted);margin:0 0 6px;text-transform:uppercase;letter-spacing:.06em}
input[type=text]{width:100%;padding:13px 14px;font:inherit;font-size:1rem;border:1.5px solid var(--line);border-radius:14px;background:var(--input);color:var(--text);outline:none;transition:border-color .2s,box-shadow .2s}
input[type=text]:focus{border-color:var(--g1);box-shadow:0 0 0 4px rgba(99,102,241,.18)}
.row{display:flex;gap:12px;margin-bottom:16px}.row>div:first-child{flex:2}.row>div:last-child{flex:1}
select{width:100%;padding:13px 14px;font:inherit;font-size:1rem;border:1.5px solid var(--line);border-radius:14px;background:var(--input);color:var(--text);outline:none;transition:border-color .2s,box-shadow .2s}
select:focus{border-color:var(--g1);box-shadow:0 0 0 4px rgba(99,102,241,.18)}
#periodWrap{margin-bottom:18px}
.choices{display:grid;grid-template-columns:repeat(3,1fr);gap:10px;margin:2px 0 18px}
.choice{padding:14px 4px;border:1.5px solid var(--line);border-radius:16px;background:var(--input);color:var(--text);font:inherit;font-size:.92rem;font-weight:600;cursor:pointer;transition:transform .15s,border-color .2s,background .2s}
.choice:active{transform:scale(.96)}
.choice span{display:block;font-size:1.4rem;margin-bottom:2px}
.choice.sel[data-v=yes]{border-color:var(--yes);background:var(--yesbg);color:var(--yes)}
.choice.sel[data-v=no]{border-color:var(--no);background:var(--nobg);color:var(--no)}
.choice.sel[data-v=maybe]{border-color:var(--maybe);background:var(--maybebg);color:var(--maybe)}
.submit{width:100%;padding:15px;font:inherit;font-size:1rem;font-weight:700;border:0;border-radius:16px;background:linear-gradient(90deg,var(--g1),var(--g2));color:#fff;cursor:pointer;box-shadow:0 8px 20px rgba(120,80,220,.35);transition:transform .15s,opacity .2s}
.submit:active{transform:scale(.98)}.submit:disabled{opacity:.55}
.msg{font-size:.85rem;color:var(--muted);margin-top:10px;min-height:1.2em;text-align:center}
.stats{display:flex;gap:10px;margin-bottom:16px}
.stat{flex:1;text-align:center;border-radius:16px;padding:12px 4px;font-size:.75rem;font-weight:600}
.stat b{display:block;font-size:1.7rem;font-weight:800;line-height:1.1}
.s-yes{background:var(--yesbg);color:var(--yes)}.s-no{background:var(--nobg);color:var(--no)}.s-maybe{background:var(--maybebg);color:var(--maybe)}
.entry{display:flex;align-items:center;gap:12px;padding:12px 0;border-top:1px solid var(--line)}
.entry:first-child{border-top:0}
.av{width:38px;height:38px;border-radius:50%;display:grid;place-items:center;color:#fff;font-weight:700;flex:none;background:linear-gradient(135deg,var(--g1),var(--g2))}
.who{flex:1;min-width:0}.nm{font-weight:700;overflow-wrap:anywhere}.rl{font-size:.78rem;color:var(--muted)}
.badge{padding:5px 12px;border-radius:99px;font-size:.78rem;font-weight:700;white-space:nowrap}
.del{border:0;background:none;color:var(--muted);cursor:pointer;font-size:1.05rem;padding:4px}
.empty{color:var(--muted);text-align:center;padding:16px 0}
</style>
</head>
<body>
<main>
<div class="hero">
<h1>ð Going to college?</h1>
<span class="pill" id="dateLine"></span>
<span class="timer" id="timer"></span>
<div id="user-info" class="timer" style="display:none; margin-top:12px;">
Signed in as <span id="user-name-display" style="font-weight:bold; color:var(--text);"></span>
· <a href="#" id="btn-logout" style="color:var(--g1); text-decoration:none; font-weight:600;">Sign out</a>
</div>
</div>
<!-- Auth Section -->
<div id="auth-section" class="card" style="text-align:center;">
<h2>Welcome!</h2>
<p style="font-size:0.85rem; color:var(--muted); margin-bottom:20px;">
Sign in to coordinate with Jenith, Mehrab, Ovi, and the rest of your section.
</p>
<button id="btn-login" class="submit" style="display:flex; align-items:center; justify-content:center; gap:8px;">
<svg style="width:18px;height:18px" viewBox="0 0 24 24">
<path fill="#fff" d="M22.56 12.25c0-.78-.07-1.53-.2-2.25H12v4.26h5.92c-.26 1.37-1.04 2.53-2.21 3.31v2.77h3.57c2.08-1.92 3.28-4.74 3.28-8.09z"/>
<path fill="#fff" d="M12 23c2.97 0 5.46-.98 7.28-2.66l-3.57-2.77c-.98.66-2.23 1.06-3.71 1.06-2.86 0-5.29-1.93-6.16-4.53H2.18v2.84C3.99 20.53 7.7 23 12 23z"/>
<path fill="#fff" d="M5.84 14.09c-.22-.66-.35-1.36-.35-2.09s.13-1.43.35-2.09V7.07H2.18C1.43 8.55 1 10.22 1 12s.43 3.45 1.18 4.93l2.85-2.22.81-.62z"/>
<path fill="#fff" d="M12 5.38c1.62 0 3.06.56 4.21 1.64l3.15-3.15C17.45 2.09 14.97 1 12 1 7.7 1 3.99 3.47 2.18 7.07l3.66 2.84c.87-2.6 3.3-4.53 6.16-4.53z"/>
</svg>
Sign in with Google
</button>
</div>
<!-- Main App Section -->
<div id="app-section" style="display:none;">
<div class="card">
<div class="row">
<div><label for="name">Your name</label><input type="text" id="name" maxlength="40" autocomplete="off" placeholder="e.g. Amiyo"></div>
<div><label for="roll">Roll</label><input type="text" id="roll" maxlength="20" autocomplete="off" placeholder="12"></div>
</div>
<label>Are you going?</label>
<div class="choices" id="choices">
<button type="button" class="choice" data-v="yes"><span>â
</span>Yes</button>
<button type="button" class="choice" data-v="maybe"><span>ð¤</span>Not sure</button>
<button type="button" class="choice" data-v="no"><span>â</span>No</button>
</div>
<div id="periodWrap">
<label for="period">Going on the ___ period</label>
<select id="period">
<option value="">Select your starting period</option>
<option>1st</option><option>2nd</option><option>3rd</option><option>4th</option>
<option>5th</option><option>6th</option><option>7th</option><option>8th</option>
</select>
</div>
<button class="submit" id="save">Save my answer</button>
<div class="msg" id="msg"></div>
</div>
<div class="card">
<h2>Everyone's answers</h2>
<div class="stats">
<div class="stat s-yes"><b id="cYes">0</b>Going</div>
<div class="stat s-maybe"><b id="cMaybe">0</b>Not sure</div>
<div class="stat s-no"><b id="cNo">0</b>Not going</div>
</div>
<div id="list"><div class="empty">Loadingâ¦</div></div>
</div>
</div>
</main>
<!-- Firebase Module -->
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-app.js";
import { getAuth, signInWithPopup, GoogleAuthProvider, onAuthStateChanged, signOut } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-auth.js";
import { getFirestore, doc, setDoc, deleteDoc, onSnapshot, collection } from "https://www.gstatic.com/firebasejs/11.6.1/firebase-firestore.js";
// --- ADD YOUR FIREBASE CONFIG HERE ---
const firebaseConfig = {
apiKey: "AIzaSyD9KiaaIGk6ATmyXNXe30kVLnUWyLnpxUI",
authDomain: "college-roll.firebaseapp.com",
projectId: "college-roll",
storageBucket: "college-roll.firebasestorage.app",
messagingSenderId: "499463954685",
appId: "1:499463954685:web:54c4b03468e4d7571cde94",
measurementId: "G-B632ZNGB25"
};
// Initialize Firebase
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const LABEL = {yes: "Going", maybe: "Not sure", no: "Not going"};
const RESET_HOUR = 12; // Reset at 12 PM (noon)
const $ = id => document.getElementById(id);
const p = n => String(n).padStart(2, "0");
const msg = s => $("msg").textContent = s;
function esc(s) { const d = document.createElement("div"); d.textContent = String(s == null ? "" : s); return d.innerHTML; }
let choice = null, unsub = null, dateKey = "", currentUser = null;
// Time & Date Logic (Resets at 12 PM for the next day)
function target() {
const now = new Date();
const t = new Date(now);
const after = now.getHours() >= RESET_HOUR;
if(after) t.setDate(t.getDate() + 1);
const next = new Date(now);
next.setHours(RESET_HOUR, 0, 0, 0);
if(now >= next) next.setDate(next.getDate() + 1);
return {
key: t.getFullYear() + "-" + p(t.getMonth() + 1) + "-" + p(t.getDate()),
date: t, after: after, next: next, now: now
};
}
function tick() {
const t = target();
$("dateLine").textContent = (t.after ? "Tomorrow · " : "Today · ") + t.date.toLocaleDateString(undefined, {weekday: "long", day: "numeric", month: "short"});
const ms = t.next - t.now;
const h = Math.floor(ms / 3600000);
const m = Math.floor(ms % 3600000 / 60000);
$("timer").textContent = "List resets at 12:00 PM · in " + h + "h " + p(m) + "m";
if (t.key !== dateKey) {
dateKey = t.key;
subscribe();
}
}
// Auth Observers
onAuthStateChanged(auth, (user) => {
if (user) {
currentUser = user;
$("auth-section").style.display = "none";
$("app-section").style.display = "block";
$("user-info").style.display = "block";
$("user-name-display").textContent = user.displayName || "User";
try {
const s = JSON.parse(localStorage.getItem("me") || "{}");
if(s.name) $("name").value = s.name;
if(s.roll) $("roll").value = s.roll;
} catch(e) {}
subscribe();
} else {
currentUser = null;
$("auth-section").style.display = "block";
$("app-section").style.display = "none";
$("user-info").style.display = "none";
if(unsub) { unsub(); unsub = null; }
}
});
// Event Listeners
$("btn-login").addEventListener("click", () => {
const provider = new GoogleAuthProvider();
signInWithPopup(auth, provider).catch(err => alert("Sign in failed: " + err.message));
});
$("btn-logout").addEventListener("click", (e) => {
e.preventDefault();
signOut(auth);
});
$("choices").addEventListener("click", (e) => {
const b = e.target.closest(".choice");
if(!b) return;
choice = b.dataset.v;
$("periodWrap").style.display = choice === "no" ? "none" : "";
document.querySelectorAll(".choice").forEach(x => x.classList.toggle("sel", x === b));
});
// Render List
function render(items) {
const c = {yes: 0, maybe: 0, no: 0};
items.sort((a, b) => String(a.name).localeCompare(String(b.name)));
items.forEach(i => { if(c[i.going] !== undefined) c[i.going]++; });
$("cYes").textContent = c.yes;
$("cMaybe").textContent = c.maybe;
$("cNo").textContent = c.no;
if(!items.length) {
$("list").innerHTML = '<div class="empty">No answers yet. Be the first! â¨</div>';
return;
}
$("list").innerHTML = items.map(i => {
const mine = currentUser && i.uid === currentUser.uid;
const ini = esc((String(i.name || "?").trim()[0] || "?").toUpperCase());
return '<div class="entry"><div class="av">' + ini + '</div><div class="who"><div class="nm">' + esc(i.name) + (mine ? ' (you)' : '') + '</div><div class="rl">Roll ' + esc(i.roll) + (i.period && i.going !== "no" ? ' · from ' + esc(i.period) + ' period' : '') + '</div></div>' +
'<span class="badge s-' + esc(i.going) + '">' + (LABEL[i.going] || "?") + '</span>' +
(mine ? '<button class="del" data-uid="' + esc(i.uid) + '" title="Remove my answer">ð</button>' : '') + '</div>';
}).join("");
}
// Delete Answer
$("list").addEventListener("click", async (e) => {
const b = e.target.closest(".del");
if(!b || !currentUser) return;
try {
await deleteDoc(doc(db, "rollcall_" + dateKey, b.dataset.uid));
msg("Removed.");
} catch(err) {
msg("Couldn't remove. Try again.");
}
});
// Save Answer
$("save").addEventListener("click", async () => {
if(!currentUser) return;
const name = $("name").value.trim();
const roll = $("roll").value.trim();
if(!name || !roll) { msg("Please enter your name and roll."); return; }
if(!choice) { msg("Pick Yes, Not sure or No."); return; }
const period = choice === "no" ? "" : $("period").value;
if(choice !== "no" && !period) { msg("Select which period you're joining from."); return; }
try { localStorage.setItem("me", JSON.stringify({name, roll})); } catch(e) {}
$("save").disabled = true;
try {
await setDoc(doc(db, "rollcall_" + dateKey, currentUser.uid), {
uid: currentUser.uid,
name: name,
roll: roll,
going: choice,
period: period,
updatedAt: Date.now()
});
msg("Saved â You can change it any time before 12 PM.");
} catch(err) {
console.error(err);
msg("Couldn't save. Please check your connection.");
}
$("save").disabled = false;
});
// Real-time Sync
function subscribe() {
if(!currentUser) return;
if(unsub) { unsub(); unsub = null; }
$("list").innerHTML = '<div class="empty">Loadingâ¦</div>';
const colRef = collection(db, "rollcall_" + dateKey);
unsub = onSnapshot(colRef, (snap) => {
const items = [];
snap.forEach(doc => items.push(doc.data()));
render(items);
}, (err) => {
console.error(err);
$("list").innerHTML = '<div class="empty">Couldn\'t load answers.</div>';
});
}
// Start Clock
tick();
setInterval(tick, 15000);
</script>
</body>
</html>