diff --git a/hackx/index.html b/hackx/index.html index 1fa5999c..fd393cce 100644 --- a/hackx/index.html +++ b/hackx/index.html @@ -279,32 +279,117 @@

// TIMELINE

MAR 15
+
REGISTRATION OPENS
+
+
-
APR 08
+
APR 04
+
REGISTRATION CLOSES
+
+
APR 11
+
OPENING CEREMONY
+
+
+ 08:30 AM + Reporting +
+
+ 09:30 AM + Inauguration Ceremony in KJSSE Auditorium +
+
+
APR 11-12
+
24 HOURS OF HACKING
+
+
+ 11:00 AM + Hacking Period Starts +
+
+ 01:00 PM + Lunch +
+
+ 04:30 PM + Evening Snacks +
+
+ 06:00 PM + Mentoring Round 1 +
+
+ 08:30 PM + Dinner +
+
+ 12:00 AM + Midnight Snacks +
+
+ 01:00 AM + Mentoring Round 2 +
+
+ 08:00 AM + Breakfast +
+
+ 11:00 AM + Hacking Period Ends +
+
+
APR 12
+
DEMOS & JUDGING
+
+
+ 11:15 AM + Evaluation Round 1 +
+
+ 01:00 PM + Lunch +
+
+ 03:30 PM + Final Evaluation Round +
+
+
APR 12
+
CLOSING CEREMONY & PRIZES
+
+
+ 04:30 PM + Evening Snacks +
+
+ 05:30 PM + Prize Distribution & Closing Remarks +
+
diff --git a/hackx/sections.js b/hackx/sections.js index 66cc5f88..a97c3e92 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,152 @@ } } + // ===== 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(); + // 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; + + 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); + 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 +1469,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 {