From c22091e7f8b81dd6e7bcddb7e651e30801e8ae03 Mon Sep 17 00:00:00 2001 From: Pranav Mendon Date: Mon, 16 Mar 2026 21:39:18 +0530 Subject: [PATCH 1/2] fix: upgraded the timeline section --- hackx/index.html | 74 +++++++++++++++++++++++ hackx/sections.js | 151 +++++++++++++++++++++++++++++++++++++++++++++- hackx/style.css | 117 ++++++++++++++++++++++++++++++++--- 3 files changed, 331 insertions(+), 11 deletions(-) diff --git a/hackx/index.html b/hackx/index.html index 1fa5999c..638f83ac 100644 --- a/hackx/index.html +++ b/hackx/index.html @@ -279,32 +279,106 @@

// TIMELINE

MAR 15
+
REGISTRATION OPENS
+
+
+ 09:00 AM + Registration Phase Begins +
+
APR 08
+
REGISTRATION CLOSES
+
+
+ 11:59 PM + Last Day to Register +
+
APR 11
+
OPENING CEREMONY
+
+
+ 10:00 AM + Placeholder +
+
+ 11:00 AM + Placeholder +
+
+ 12:00 PM + Placeholder +
+
APR 11-12
+
24 HOURS OF HACKING
+
+
+ 12:00 PM + Placeholder +
+
+ 07:00 PM + Placeholder +
+
+ 12:00 AM + Placeholder +
+
+ 07:00 AM + Placeholder +
+
APR 12
+
DEMOS & JUDGING
+
+
+ 11:00 AM + Placeholder +
+
+ 12:00 PM + Placeholder +
+
+ 02:00 PM + Placeholder +
+
APR 12
+
CLOSING CEREMONY & PRIZES
+
+
+ 04:00 PM + Winners Announcement +
+
+ 05:00 PM + Prize Distribution & Closing Remarks +
+
diff --git a/hackx/sections.js b/hackx/sections.js index 66cc5f88..5c22ced8 100644 --- a/hackx/sections.js +++ b/hackx/sections.js @@ -18,6 +18,7 @@ initFooterCanvas(); // Core (one-time CSS transitions, no jank) initScrollFadeIn(); + initTimelineParallaxReveal(); initPrizeCounters(); // initScrollProgressBar(); // removed — was showing blue line at top initAnnouncements(); @@ -396,6 +397,154 @@ } } + // ===== TIMELINE PARALLAX REVEAL (timeline-only) ===== + function initTimelineParallaxReveal() { + const timelineItems = Array.from(document.querySelectorAll('.timeline-item')); + if (timelineItems.length === 0) return; + const itemStates = new Map(); + let rafId = null; + + function clamp(value, min, max) { + return Math.min(Math.max(value, min), max); + } + + function smoothstep(t) { + return t * t * (3 - 2 * t); + } + + function setTimelineHeight(item, state) { + if (!state.timetable) return; + item.style.setProperty('--tt-height', `${state.timetable.scrollHeight}px`); + } + + function initTimelineState() { + timelineItems.forEach((item) => { + item.classList.remove('timeline-open'); + item.style.setProperty('--date-scale', '1'); + item.style.setProperty('--date-glow', '0'); + item.style.setProperty('--timeline-reveal', '0'); + + const timetable = item.querySelector('.timeline-timetable'); + const events = Array.from(item.querySelectorAll('.timetable-event')); + + events.forEach(eventEl => { + eventEl.classList.remove('is-visible'); + eventEl.style.opacity = '0'; + eventEl.style.transform = 'translateY(8px)'; + }); + + const state = { + target: 0, + current: 0, + events, + timetable, + }; + + itemStates.set(item, state); + setTimelineHeight(item, state); + }); + } + + function updateTimelineHeights() { + timelineItems.forEach((item) => { + const state = itemStates.get(item); + if (!state) return; + setTimelineHeight(item, state); + }); + } + + function computeTargetsFromScroll() { + const vh = window.innerHeight || document.documentElement.clientHeight; + timelineItems.forEach(item => { + const state = itemStates.get(item); + if (!state) return; + + const rect = item.getBoundingClientRect(); + const eventCount = state.events.length; + if (eventCount === 0) return; + + // Complete reveal by viewport midpoint (50% height) + const start = vh * 0.9; + const end = vh * 0.5; + const progress = clamp((start - rect.top) / Math.max(start - end, 1), 0, 1); + state.target = smoothstep(progress); + }); + } + + function applyRevealProgress(item, revealProgress) { + const state = itemStates.get(item); + if (!state) return; + const eventCount = state.events.length; + if (eventCount === 0) return; + + item.classList.toggle('timeline-open', revealProgress > 0.01); + item.style.setProperty('--date-scale', (1 + revealProgress * 0.06).toFixed(3)); + item.style.setProperty('--date-glow', revealProgress.toFixed(3)); + item.style.setProperty('--timeline-reveal', revealProgress.toFixed(3)); + + state.events.forEach((eventEl, index) => { + // Slight overlap between items so the sequence feels continuous. + const stepSpan = 1 / (eventCount + 0.35); + const stepStart = index * stepSpan; + const localProgress = clamp((revealProgress - stepStart) / Math.max(stepSpan, 0.0001), 0, 1); + + eventEl.style.opacity = localProgress.toFixed(3); + eventEl.style.transform = `translateY(${(1 - localProgress) * 8}px)`; + eventEl.classList.toggle('is-visible', localProgress > 0.6); + }); + } + + function startRafLoop() { + if (rafId !== null) return; + + const tick = () => { + let hasActiveMotion = false; + + timelineItems.forEach((item) => { + const state = itemStates.get(item); + if (!state) return; + + const delta = state.target - state.current; + if (Math.abs(delta) > 0.0008) { + state.current += delta * 0.16; + hasActiveMotion = true; + } else { + state.current = state.target; + } + + applyRevealProgress(item, state.current); + }); + + if (hasActiveMotion) { + rafId = requestAnimationFrame(tick); + } else { + rafId = null; + } + }; + + rafId = requestAnimationFrame(tick); + } + + function updateFromScroll() { + computeTargetsFromScroll(); + startRafLoop(); + } + + initTimelineState(); + updateFromScroll(); + window.addEventListener('scroll', updateFromScroll, { passive: true }); + window.addEventListener('resize', () => { + updateTimelineHeights(); + updateFromScroll(); + }); + + // Ensure timeline positions are correct after fonts/layout settle. + setTimeout(() => { + updateTimelineHeights(); + updateFromScroll(); + }, 120); + } + // ===== PRIZE COUNTERS (easeOutExpo for satisfying deceleration) ===== function initPrizeCounters() { const amounts = document.querySelectorAll('.prize-amount, .pool-amount'); @@ -1322,7 +1471,7 @@ `; document.head.appendChild(style); - document.querySelectorAll('.readout-row, .prize-entry, .timeline-item').forEach(el => { + document.querySelectorAll('.readout-row, .prize-entry').forEach(el => { el.classList.add('_line-trace-wrap'); if (el.style.position === '' || el.style.position === 'static') { el.style.position = 'relative'; diff --git a/hackx/style.css b/hackx/style.css index aa1adb91..759ba183 100644 --- a/hackx/style.css +++ b/hackx/style.css @@ -460,6 +460,7 @@ body { border-top: 2px solid #FF174420; } + .timeline { position: relative; max-width: 800px; @@ -480,9 +481,9 @@ body { .timeline-item { position: relative; display: flex; - align-items: baseline; - gap: 40px; - padding: 30px 0; + align-items: flex-start; + gap: 28px; + padding: 16px 0; border-bottom: 1px solid #FF174410; opacity: 0; transform: translateY(10px); @@ -518,13 +519,22 @@ body { } .timeline-date { - font-size: clamp(20px, 3vw, 32px); - font-weight: bold; - letter-spacing: 0.06em; + font-size: clamp(18px, 2.2vw, 26px); + font-weight: 700; + letter-spacing: 0.04em; color: #FF1744B0; - min-width: 120px; - text-align: right; - flex-shrink: 0; + width: 190px; + height: 56px; + display: flex; + align-items: center; + justify-content: flex-start; + text-align: left; + box-sizing: border-box; + transform: scale(var(--date-scale, 1)); + text-shadow: 0 0 calc(16px * var(--date-glow, 0)) rgba(255, 23, 68, 0.45); + transition: transform 0.22s ease, text-shadow 0.22s ease; + padding-right: 1.25rem; + will-change: transform, text-shadow; } .timeline-item.active .timeline-date { @@ -537,12 +547,95 @@ body { letter-spacing: 3px; padding-left: 30px; color: var(--text); + opacity: 0; + transform: translateY(8px); + transition: opacity 0.35s ease, transform 0.35s ease; } .timeline-item.active .timeline-event { color: var(--gold); } +.timeline-item.timeline-open .timeline-event { + opacity: 1; + transform: translateY(0); +} + +/* ===== TIMELINE TIMETABLE ===== */ +.timetable-align { + display: flex; + flex-direction: column; + gap: 8px; +} + +.timeline-timetable { + margin-top: 12px; + margin-left: 20px; + max-height: calc(var(--tt-height, 0px) * var(--timeline-reveal, 0)); + overflow: hidden; + opacity: calc(0.12 + (var(--timeline-reveal, 0) * 0.88)); + transition: max-height 0.32s cubic-bezier(0.22, 1, 0.36, 1), opacity 0.28s ease; + position: relative; + padding-left: 3rem; + will-change: max-height, opacity; +} + +.timetable-event { + display: flex; + gap: 12px; + padding: 8px 0; + align-items: baseline; + font-size: 12px; + letter-spacing: 0.5px; + position: relative; + opacity: 0; + transform: translateY(8px); + transition: opacity 0.24s linear, transform 0.32s cubic-bezier(0.22, 1, 0.36, 1); + will-change: transform, opacity; +} + +.timetable-event.is-visible { + opacity: 1; + transform: translateY(0); +} + +.timetable-event::before { + content: ''; + position: absolute; + left: -32px; + top: 6px; + width: 5px; + height: 5px; + border: 1px solid #FF174440; + background: #0A0008; + border-radius: 50%; + flex-shrink: 0; + transition: all 0.4s ease; +} + + +.timetable-event.is-visible::before { + border-color: #FF1744CC; + box-shadow: 0 0 6px #FF174420; +} + +.event-time { + color: var(--gold); + font-weight: bold; + min-width: 65px; + letter-spacing: 2px; + flex-shrink: 0; +} + +.event-desc { + color: var(--text-dim); + line-height: 1.3; +} + +.timeline-item.visible .event-desc { + color: var(--text); +} + /* ===== FAQ SECTION ===== */ #faq-section { border-top: 2px solid #FF174420; @@ -1395,9 +1488,13 @@ body { } .timeline-date { + justify-content: flex-start; text-align: left; - min-width: auto; + width: 190px; + min-width: 190px; font-size: clamp(16px, 4vw, 24px); + padding-left: 0; + padding-right: 1rem; } .timeline-event { From 75433ab4894a8485f314d065207493b5efbc7a82 Mon Sep 17 00:00:00 2001 From: Pranav Mendon Date: Tue, 17 Mar 2026 00:30:36 +0530 Subject: [PATCH 2/2] fix: minor fixes to timetable --- hackx/index.html | 85 ++++++++++++++++++++++++++--------------------- hackx/sections.js | 6 ++-- 2 files changed, 50 insertions(+), 41 deletions(-) diff --git a/hackx/index.html b/hackx/index.html index 638f83ac..fd393cce 100644 --- a/hackx/index.html +++ b/hackx/index.html @@ -281,23 +281,15 @@

// TIMELINE

MAR 15
REGISTRATION OPENS
-
-
- 09:00 AM - Registration Phase Begins -
+
-
APR 08
+
APR 04
REGISTRATION CLOSES
-
-
- 11:59 PM - Last Day to Register -
+
@@ -307,17 +299,14 @@

// TIMELINE

OPENING CEREMONY
- 10:00 AM - Placeholder + 08:30 AM + Reporting
- 11:00 AM - Placeholder + 09:30 AM + Inauguration Ceremony in KJSSE Auditorium +
-
- 12:00 PM - Placeholder -
@@ -327,21 +316,42 @@

// TIMELINE

24 HOURS OF HACKING
- 12:00 PM - Placeholder + 11:00 AM + Hacking Period Starts +
+
+ 01:00 PM + Lunch +
+
+ 04:30 PM + Evening Snacks
- 07:00 PM - Placeholder + 06:00 PM + Mentoring Round 1 +
+
+ 08:30 PM + Dinner
12:00 AM - Placeholder + Midnight Snacks
- 07:00 AM - Placeholder -
+ 01:00 AM + Mentoring Round 2 +
+
+ 08:00 AM + Breakfast +
+
+ 11:00 AM + Hacking Period Ends +
+
@@ -351,17 +361,18 @@

// TIMELINE

DEMOS & JUDGING
- 11:00 AM - Placeholder + 11:15 AM + Evaluation Round 1
- 12:00 PM - Placeholder + 01:00 PM + Lunch
- 02:00 PM - Placeholder -
+ 03:30 PM + Final Evaluation Round +
+
@@ -371,11 +382,11 @@

// TIMELINE

CLOSING CEREMONY & PRIZES
- 04:00 PM - Winners Announcement + 04:30 PM + Evening Snacks
- 05:00 PM + 05:30 PM Prize Distribution & Closing Remarks
diff --git a/hackx/sections.js b/hackx/sections.js index 5c22ced8..a97c3e92 100644 --- a/hackx/sections.js +++ b/hackx/sections.js @@ -460,9 +460,6 @@ if (!state) return; const rect = item.getBoundingClientRect(); - const eventCount = state.events.length; - if (eventCount === 0) return; - // Complete reveal by viewport midpoint (50% height) const start = vh * 0.9; const end = vh * 0.5; @@ -475,13 +472,14 @@ const state = itemStates.get(item); if (!state) return; const eventCount = state.events.length; - if (eventCount === 0) return; item.classList.toggle('timeline-open', revealProgress > 0.01); item.style.setProperty('--date-scale', (1 + revealProgress * 0.06).toFixed(3)); item.style.setProperty('--date-glow', revealProgress.toFixed(3)); item.style.setProperty('--timeline-reveal', revealProgress.toFixed(3)); + if (eventCount === 0) return; + state.events.forEach((eventEl, index) => { // Slight overlap between items so the sequence feels continuous. const stepSpan = 1 / (eventCount + 0.35);