Preboard-Schedule / index.html
SolarumAsteridion's picture
Update index.html
fa111ab verified
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Study Terminal</title>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;600;900&family=JetBrains+Mono:wght@400;500;700&display=swap" rel="stylesheet">
<style>
:root {
--bg-base: #0a0a0a;
--pigment-ochre: #d97706; /* SST */
--pigment-terracotta: #ef4444; /* Exam/Urgent */
--pigment-slate: #3b82f6; /* Hindi */
--pigment-moss: #22c55e; /* English */
--pigment-bone: #f8fafc;
--surface-layer: rgba(255, 255, 255, 0.03);
--transition-fluid: all 0.4s cubic-bezier(0.2, 0.8, 0.2, 1);
}
html { scroll-behavior: smooth; }
* { margin: 0; padding: 0; box-sizing: border-box; -webkit-font-smoothing: antialiased; }
body {
background-color: var(--bg-base);
color: var(--pigment-bone);
font-family: 'Inter', sans-serif;
overflow-x: hidden;
min-height: 100vh;
}
/* Grainy Texture */
.grain-overlay {
position: fixed; top: 0; left: 0; width: 100%; height: 100%; pointer-events: none; z-index: 999; opacity: 0.05;
background: url("data:image/svg+xml,%3Csvg viewBox='0 0 200 200' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.65' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E");
}
.container { max-width: 1100px; margin: 60px auto; padding: 0 30px; }
/* --- HEADER --- */
header { margin-bottom: 100px; display: flex; flex-direction: column; md:flex-row; justify-content: space-between; align-items: flex-end; gap: 30px; }
.title-block h1 {
font-weight: 900; font-size: 4rem; line-height: 0.9; text-transform: uppercase; letter-spacing: -0.04em;
background: linear-gradient(180deg, #fff 0%, rgba(255,255,255,0.4) 100%);
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
}
/* Futuristic Progress Bar */
.global-progress { font-family: 'JetBrains Mono', monospace; text-align: right; width: 300px; }
.global-progress .label { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.2em; color: rgba(255,255,255,0.5); margin-bottom: 12px; display: flex; justify-content: space-between; }
.progress-track {
height: 8px; width: 100%; background: rgba(255,255,255,0.05); border: 1px solid rgba(255,255,255,0.1);
position: relative; overflow: hidden; border-radius: 2px;
}
/* Segmented look using repeating gradient */
.progress-fill {
height: 100%; width: 0%;
background: var(--pigment-ochre);
box-shadow: 0 0 20px var(--pigment-ochre);
transition: width 1s cubic-bezier(0.22, 1, 0.36, 1);
position: relative;
}
.progress-fill::after {
content: ''; position: absolute; inset: 0;
background-image: repeating-linear-gradient(90deg, transparent, transparent 4px, #000 4px, #000 6px);
opacity: 0.3;
}
/* --- CARD SYSTEM --- */
.schedule-grid { display: flex; flex-direction: column; gap: 100px; padding-bottom: 150px; }
.day-strata {
position: relative; display: grid; grid-template-columns: 1fr 2fr; gap: 40px;
/* Hidden initially for scroll animation */
opacity: 0; transform: translateY(50px); transition: opacity 0.8s ease, transform 0.8s cubic-bezier(0.2, 0.8, 0.2, 1);
}
.day-strata.visible { opacity: 1; transform: translateY(0); }
/* Responsive */
@media (max-width: 900px) {
.day-strata { grid-template-columns: 1fr; gap: 20px; }
.title-block h1 { font-size: 3rem; }
.strata-info { position: relative; top: 0; margin-bottom: 20px; }
header { align-items: flex-start; }
.global-progress { text-align: left; width: 100%; }
}
/* Colors rotate */
.day-strata:nth-child(4n+1) { --accent: var(--pigment-ochre); }
.day-strata:nth-child(4n+2) { --accent: var(--pigment-slate); }
.day-strata:nth-child(4n+3) { --accent: var(--pigment-moss); }
.day-strata:nth-child(4n+4) { --accent: var(--pigment-terracotta); }
/* Sidebar Info */
.strata-info { position: sticky; top: 40px; height: fit-content; }
.day-number { font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; color: var(--accent); margin-bottom: 8px; display: block; opacity: 0.8; }
.day-name { font-size: 2.5rem; font-weight: 700; margin-bottom: 15px; letter-spacing: -0.02em; line-height: 1; }
.active-day-marker {
display: inline-flex; align-items: center; gap: 6px; font-size: 0.7rem; font-family: 'JetBrains Mono';
background: var(--pigment-bone); color: var(--bg-base); font-weight: 700;
padding: 4px 8px; margin-top: 10px; border-radius: 2px;
animation: pulse 2s infinite;
}
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.6; } }
/* Mini Progress Bar under Date */
.strata-visual { height: 2px; width: 100%; background: rgba(255,255,255,0.1); position: relative; overflow: hidden; margin-top: 10px; }
.strata-visual-fill {
position: absolute; left: 0; top: 0; height: 100%; width: 0%;
background: var(--accent); box-shadow: 0 0 10px var(--accent); transition: width 0.5s ease;
}
/* --- TASKS LAYER --- */
.tasks-layer {
background: var(--surface-layer); border: 1px solid rgba(255,255,255,0.05); padding: 30px;
position: relative; transition: var(--transition-fluid);
}
/* Accent line on left */
.tasks-layer::before {
content: ''; position: absolute; top: 0; left: 0; width: 2px; height: 0%;
background: var(--accent); transition: var(--transition-fluid);
}
.day-strata:hover .tasks-layer::before { height: 100%; }
.day-strata:hover .tasks-layer { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.1); }
/* --- ALERTS --- */
.alert-box {
margin-bottom: 25px; padding: 12px 16px;
border: 1px solid; border-left-width: 4px;
font-family: 'JetBrains Mono', monospace; font-size: 0.8rem; letter-spacing: -0.02em;
display: flex; align-items: center; gap: 12px;
background: rgba(0,0,0,0.3);
}
.alert-sst { border-color: var(--pigment-ochre); color: var(--pigment-ochre); }
.alert-hindi { border-color: var(--pigment-slate); color: var(--pigment-slate); }
.alert-english { border-color: var(--pigment-moss); color: var(--pigment-moss); }
.alert-exam { border-color: var(--pigment-terracotta); color: var(--pigment-terracotta); background: rgba(239, 68, 68, 0.05); box-shadow: 0 0 20px rgba(239, 68, 68, 0.1); animation: glowPulse 3s infinite; }
@keyframes glowPulse { 0%, 100% { box-shadow: 0 0 15px rgba(239, 68, 68, 0.1); } 50% { box-shadow: 0 0 25px rgba(239, 68, 68, 0.25); } }
/* Task Items */
.task-item {
display: flex; align-items: flex-start; padding: 20px 0;
border-bottom: 1px solid rgba(255,255,255,0.05); cursor: pointer; transition: var(--transition-fluid);
}
.task-item:last-child { border-bottom: none; padding-bottom: 0; }
.task-item:first-child { padding-top: 0; }
.checkbox {
width: 20px; height: 20px; border: 1px solid rgba(255,255,255,0.3); margin-right: 20px; margin-top: 2px;
position: relative; transition: var(--transition-fluid); flex-shrink: 0; border-radius: 2px;
}
.task-item:hover .checkbox { border-color: var(--accent); box-shadow: 0 0 10px rgba(255,255,255,0.1); }
.checkbox.checked { background: var(--accent); border-color: var(--accent); box-shadow: 0 0 15px var(--accent); }
.checkbox.checked::after {
content: '✓'; position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%);
color: #000; font-size: 12px; font-weight: 900;
}
.task-content { flex-grow: 1; }
.task-title { font-size: 1.05rem; font-weight: 500; display: block; margin-bottom: 6px; transition: color 0.3s; line-height: 1.4; }
.task-meta {
font-family: 'JetBrains Mono', monospace; font-size: 0.7rem;
color: rgba(255,255,255,0.4); text-transform: uppercase; letter-spacing: 0.05em;
display: inline-block; padding: 2px 6px; border: 1px solid rgba(255,255,255,0.1); border-radius: 4px;
}
.task-item.completed .task-title { opacity: 0.3; text-decoration: line-through; text-decoration-color: var(--accent); }
.task-item.completed .task-meta { opacity: 0.3; }
/* Pigment Blobs */
.pigment-blob {
position: absolute; width: 400px; height: 400px; filter: blur(100px); opacity: 0.1; z-index: -1; border-radius: 50%;
}
</style>
</head>
<body>
<div class="grain-overlay"></div>
<div class="pigment-blob" style="background: var(--pigment-ochre); top: 0%; right: 0%;"></div>
<div class="pigment-blob" style="background: var(--pigment-slate); top: 40%; left: -10%;"></div>
<div class="pigment-blob" style="background: var(--pigment-moss); bottom: 10%; right: -10%;"></div>
<main class="container">
<header>
<div class="title-block">
<h1>Study<br>Terminal</h1>
</div>
<!-- Futuristic Progress Bar -->
<div class="global-progress">
<div class="label">
<span>System Sync</span>
<span id="global-percent-text">0%</span>
</div>
<div class="progress-track">
<div id="global-progress-bar" class="progress-fill"></div>
</div>
</div>
</header>
<section id="schedule-grid" class="schedule-grid">
<div style="opacity: 0.5; font-family: 'JetBrains Mono'; margin-top: 50px;">INITIALIZING DATA STREAM...</div>
</section>
</main>
<script type="module">
import { initializeApp } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-app.js";
import { getDatabase, ref, onValue, update, remove } from "https://www.gstatic.com/firebasejs/10.7.1/firebase-database.js";
const firebaseConfig = {
apiKey: "AIzaSyCBGYdGdPjYJiKsTMjVYZ9mf9F82ns7g4Q",
authDomain: "pikachu-rxppbp.firebaseapp.com",
databaseURL: "https://pikachu-rxppbp.firebaseio.com",
projectId: "pikachu-rxppbp",
storageBucket: "pikachu-rxppbp.appspot.com",
messagingSenderId: "241970333280",
appId: "1:241970333280:web:704e8930bd591c138d6505"
};
const app = initializeApp(firebaseConfig);
const database = getDatabase(app);
const dataRef = ref(database, 'study_schedule_data');
const progressRef = ref(database, 'study_schedule_progress');
let scheduleData = [];
let checkedState = {};
let structureRendered = false;
const container = document.getElementById('schedule-grid');
const globalPercentText = document.getElementById('global-percent-text');
const globalProgressBar = document.getElementById('global-progress-bar');
// Observer for smooth scroll animations
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, { threshold: 0.1, rootMargin: "0px 0px -50px 0px" });
// 1. Structure Listener
onValue(dataRef, (snapshot) => {
scheduleData = snapshot.val() || [];
renderStructure();
updateVisualsOnly();
setTimeout(scrollToToday, 500); // Small delay to ensure render complete
});
// 2. Progress Listener
onValue(progressRef, (snapshot) => {
checkedState = snapshot.val() || {};
if (structureRendered) {
updateVisualsOnly();
}
});
function renderStructure() {
if (structureRendered && container.children.length > 1) return;
if (!scheduleData.length) return;
container.innerHTML = '';
scheduleData.forEach((data, index) => {
if (!data) return;
// --- TASKS HTML ---
let tasksHtml = '';
if (data.tasks) {
data.tasks.forEach(subjectGroup => {
subjectGroup.content.forEach((task, tIdx) => {
const taskId = `day-${data.day}-sub-${subjectGroup.sub}-${tIdx}`;
tasksHtml += `
<div id="item-${taskId}" class="task-item" onclick="window.toggleTask('${taskId}')">
<div id="check-${taskId}" class="checkbox"></div>
<div class="task-content">
<span class="task-title">${task}</span>
<span class="task-meta">${subjectGroup.sub}</span>
</div>
</div>
`;
});
});
}
// --- ALERT HTML (Logic Restored) ---
let alertHtml = '';
// Check if exam (High Priority)
if (data.exam) {
alertHtml = `
<div class="alert-box alert-exam">
<span style="font-size:1.2rem">⚡</span>
<div>
<div style="font-weight:700; text-transform:uppercase;">CRITICAL: EXAM EVENT</div>
<div>${data.exam}</div>
</div>
</div>`;
}
// Check if alert (Standard)
else if (data.alert) {
let alertClass = '';
let icon = '⚠️';
// Determine Color based on Alert Type or Content
const type = (data.alertType || data.alert).toLowerCase();
if (type.includes('sst')) { alertClass = 'alert-sst'; icon = '📙'; }
else if (type.includes('hindi')) { alertClass = 'alert-hindi'; icon = '📘'; }
else if (type.includes('english')) { alertClass = 'alert-english'; icon = '📗'; }
alertHtml = `
<div class="alert-box ${alertClass}">
<span style="font-size:1.2rem">${icon}</span>
<div>${data.alert}</div>
</div>`;
}
// --- CARD ASSEMBLY ---
const card = document.createElement('div');
card.className = 'day-strata';
card.id = `card-day-${index}`;
card.setAttribute('data-date', data.date);
card.innerHTML = `
<aside class="strata-info">
<span class="day-number">NODE ${String(data.day).padStart(2, '0')}</span>
<h2 class="day-name">
${data.date.split(' ')[0]}
<span style="font-size:1rem; opacity:0.5; display:block; margin-top:5px; font-weight:300;">
${data.date.split(' ').slice(1).join(' ')}
</span>
</h2>
<div class="strata-visual">
<div id="bar-${index}" class="strata-visual-fill"></div>
</div>
<div class="marker-container"></div>
</aside>
<div class="tasks-layer">
${alertHtml}
${tasksHtml}
</div>
`;
container.appendChild(card);
observer.observe(card); // Add to smooth scroll observer
});
structureRendered = true;
}
function updateVisualsOnly() {
if (!scheduleData.length) return;
let totalTasks = 0;
let completedTasks = 0;
scheduleData.forEach((data, index) => {
if (!data) return;
let dayTasksTotal = 0;
let dayTasksDone = 0;
if (data.tasks) {
data.tasks.forEach(subjectGroup => {
subjectGroup.content.forEach((task, tIdx) => {
const taskId = `day-${data.day}-sub-${subjectGroup.sub}-${tIdx}`;
const isChecked = checkedState[taskId] === true;
totalTasks++;
dayTasksTotal++;
const itemEl = document.getElementById(`item-${taskId}`);
const checkEl = document.getElementById(`check-${taskId}`);
if (itemEl && checkEl) {
if (isChecked) {
itemEl.classList.add('completed');
checkEl.classList.add('checked');
dayTasksDone++;
completedTasks++;
} else {
itemEl.classList.remove('completed');
checkEl.classList.remove('checked');
}
}
});
});
}
const dayPercent = dayTasksTotal === 0 ? 0 : (dayTasksDone / dayTasksTotal) * 100;
const barEl = document.getElementById(`bar-${index}`);
if (barEl) barEl.style.width = `${dayPercent}%`;
});
const globalPercent = totalTasks === 0 ? 0 : Math.round((completedTasks / totalTasks) * 100);
if (globalPercentText) globalPercentText.innerText = `${globalPercent}%`;
if (globalProgressBar) globalProgressBar.style.width = `${globalPercent}%`;
}
function scrollToToday() {
const today = new Date();
// Assuming current year 2025 based on previous context, but generic logic here
const dayNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
const monthNames = ["Jan", "Feb", "Mar", "Apr", "May", "Jun", "Jul", "Aug", "Sep", "Oct", "Nov", "Dec"];
const currentDay = String(today.getDate());
const currentMonth = monthNames[today.getMonth()];
// Format match: "17 Dec"
const matchString = `${currentDay} ${currentMonth}`;
const cards = document.querySelectorAll('.day-strata');
let found = false;
cards.forEach(card => {
const dateAttr = card.getAttribute('data-date');
// Match logic: Checks if "17 Dec" is inside "Tue 17 Dec"
if (dateAttr && dateAttr.includes(matchString) && !found) {
card.scrollIntoView({ behavior: 'smooth', block: 'center' });
// Add "ACTIVE" marker
const markerContainer = card.querySelector('.marker-container');
if (markerContainer) {
markerContainer.innerHTML = `<span class="active-day-marker">● ACTIVE CYCLE</span>`;
}
found = true;
}
});
}
window.toggleTask = (id) => {
// Instant UI Feedback
const itemEl = document.getElementById(`item-${id}`);
const checkEl = document.getElementById(`check-${id}`);
if (itemEl) itemEl.classList.toggle('completed');
if (checkEl) checkEl.classList.toggle('checked');
if (checkedState[id]) {
remove(ref(database, `study_schedule_progress/${id}`));
} else {
update(progressRef, { [id]: true });
}
};
</script>
</body>
</html>