|
|
<!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; |
|
|
--pigment-terracotta: #ef4444; |
|
|
--pigment-slate: #3b82f6; |
|
|
--pigment-moss: #22c55e; |
|
|
--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; |
|
|
} |
|
|
|
|
|
|
|
|
.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 { 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; |
|
|
} |
|
|
|
|
|
|
|
|
.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; |
|
|
} |
|
|
|
|
|
|
|
|
.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; |
|
|
} |
|
|
|
|
|
|
|
|
.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; |
|
|
|
|
|
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); } |
|
|
|
|
|
|
|
|
@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%; } |
|
|
} |
|
|
|
|
|
|
|
|
.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); } |
|
|
|
|
|
|
|
|
.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; } } |
|
|
|
|
|
|
|
|
.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 { |
|
|
background: var(--surface-layer); border: 1px solid rgba(255,255,255,0.05); padding: 30px; |
|
|
position: relative; transition: var(--transition-fluid); |
|
|
} |
|
|
|
|
|
|
|
|
.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); } |
|
|
|
|
|
|
|
|
.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-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-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> |
|
|
|
|
|
|
|
|
<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'); |
|
|
|
|
|
|
|
|
const observer = new IntersectionObserver((entries) => { |
|
|
entries.forEach(entry => { |
|
|
if (entry.isIntersecting) { |
|
|
entry.target.classList.add('visible'); |
|
|
} |
|
|
}); |
|
|
}, { threshold: 0.1, rootMargin: "0px 0px -50px 0px" }); |
|
|
|
|
|
|
|
|
onValue(dataRef, (snapshot) => { |
|
|
scheduleData = snapshot.val() || []; |
|
|
renderStructure(); |
|
|
updateVisualsOnly(); |
|
|
setTimeout(scrollToToday, 500); |
|
|
}); |
|
|
|
|
|
|
|
|
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; |
|
|
|
|
|
|
|
|
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> |
|
|
`; |
|
|
}); |
|
|
}); |
|
|
} |
|
|
|
|
|
|
|
|
let alertHtml = ''; |
|
|
|
|
|
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>`; |
|
|
} |
|
|
|
|
|
else if (data.alert) { |
|
|
let alertClass = ''; |
|
|
let icon = '⚠️'; |
|
|
|
|
|
|
|
|
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>`; |
|
|
} |
|
|
|
|
|
|
|
|
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); |
|
|
}); |
|
|
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(); |
|
|
|
|
|
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()]; |
|
|
|
|
|
|
|
|
const matchString = `${currentDay} ${currentMonth}`; |
|
|
|
|
|
const cards = document.querySelectorAll('.day-strata'); |
|
|
let found = false; |
|
|
|
|
|
cards.forEach(card => { |
|
|
const dateAttr = card.getAttribute('data-date'); |
|
|
|
|
|
if (dateAttr && dateAttr.includes(matchString) && !found) { |
|
|
card.scrollIntoView({ behavior: 'smooth', block: 'center' }); |
|
|
|
|
|
|
|
|
const markerContainer = card.querySelector('.marker-container'); |
|
|
if (markerContainer) { |
|
|
markerContainer.innerHTML = `<span class="active-day-marker">● ACTIVE CYCLE</span>`; |
|
|
} |
|
|
found = true; |
|
|
} |
|
|
}); |
|
|
} |
|
|
|
|
|
window.toggleTask = (id) => { |
|
|
|
|
|
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> |