1+ // ── 1. Project cards: populate grid from <template> if still empty ─────
2+ ( function ( ) {
3+ const grid = document . getElementById ( 'projectsGrid' ) ;
4+ if ( ! grid || grid . children . length > 0 ) return ;
5+ const tmpl = document . getElementById ( 'projectsTemplate' ) ;
6+ if ( ! tmpl ) return ;
7+ const tmplGrid = tmpl . content . querySelector ( '.projects-grid' ) ;
8+ if ( tmplGrid ) grid . innerHTML = tmplGrid . innerHTML ;
9+ } ) ( ) ;
10+
11+ // ── 2. Timeline items: reveal on scroll ───────────────────────────────
12+ ( function ( ) {
13+ const items = document . querySelectorAll ( '.timeline-item[data-reveal]' ) ;
14+ if ( ! items . length ) return ;
15+ const io = new IntersectionObserver ( entries => {
16+ entries . forEach ( e => {
17+ if ( e . isIntersecting ) { e . target . classList . add ( 'visible' ) ; io . unobserve ( e . target ) ; }
18+ } ) ;
19+ } , { threshold : 0.15 } ) ;
20+ items . forEach ( el => io . observe ( el ) ) ;
21+ } ) ( ) ;
22+
23+ // ── 3. Track-Route badge: hidden until timeline scrolls into view ──────
24+ ( function ( ) {
25+ const badge = document . querySelector ( '.timeline-route-badge' ) ;
26+ const section = document . getElementById ( 'timelineSection' ) ;
27+ if ( ! badge || ! section ) return ;
28+ Object . assign ( badge . style , {
29+ opacity : '0' , transform : 'translateY(-16px)' ,
30+ transition : 'opacity .55s cubic-bezier(.22,1,.36,1), transform .55s cubic-bezier(.22,1,.36,1)'
31+ } ) ;
32+ const io = new IntersectionObserver ( entries => {
33+ if ( entries [ 0 ] . isIntersecting ) {
34+ badge . style . opacity = '1' ; badge . style . transform = 'translateY(0)' ;
35+ io . disconnect ( ) ;
36+ }
37+ } , { threshold : 0.05 } ) ;
38+ io . observe ( section ) ;
39+ } ) ( ) ;
40+
41+ // ── 4. Hero content: guarantee visibility if animations stall ──────────
42+ ( function ( ) {
43+ const shell = document . querySelector ( '.hero-shell' ) ;
44+ if ( ! shell ) return ;
45+ setTimeout ( ( ) => {
46+ shell . querySelectorAll ( '.hero-logo-header,.hero-badge-row,.hero-title-wrapper,.hero-subtitle,.hero-cta-row,.hero-meta' )
47+ . forEach ( el => { el . style . opacity = '1' ; el . style . transform = 'translateY(0)' ; } ) ;
48+ } , 800 ) ;
49+ } ) ( ) ;
50+
51+ // ── 5. Sidebar: hidden during hero+timeline, appears at projects section
52+ ( function ( ) {
53+ const projectsSection = document . getElementById ( 'projectsSection' ) ;
54+
55+ // Not the index page (sub-pages have sidebar-active on <body> already)
56+ if ( ! projectsSection ) {
57+ document . body . classList . add ( 'sidebar-active' ) ;
58+ return ;
59+ }
60+
61+ // Index page: start with sidebar hidden
62+ document . body . classList . remove ( 'sidebar-active' ) ;
63+
64+ const THRESHOLD = 0.05 ;
65+
66+ const io = new IntersectionObserver ( entries => {
67+ entries . forEach ( entry => {
68+ if ( entry . isIntersecting ) {
69+ // Projects section in view → show sidebar
70+ document . body . classList . add ( 'sidebar-active' ) ;
71+ } else {
72+ // Scrolled back above projects → hide sidebar
73+ const rect = projectsSection . getBoundingClientRect ( ) ;
74+ if ( rect . top > 0 ) document . body . classList . remove ( 'sidebar-active' ) ;
75+ }
76+ } ) ;
77+ } , { threshold : THRESHOLD } ) ;
78+
79+ io . observe ( projectsSection ) ;
80+ } ) ( ) ;
0 commit comments