@@ -24,6 +24,28 @@ type WriteupMeta = {
2424 tags : string [ ] ;
2525} ;
2626
27+ type LanyardActivity = {
28+ type : number ;
29+ name : string ;
30+ details ?: string ;
31+ state ?: string ;
32+ } ;
33+
34+ type LanyardData = {
35+ discord_status : 'online' | 'idle' | 'dnd' | 'offline' ;
36+ listening_to_spotify : boolean ;
37+ spotify ?: {
38+ song : string ;
39+ artist : string ;
40+ } ;
41+ activities : LanyardActivity [ ] ;
42+ } ;
43+
44+ type LanyardResponse = {
45+ success : boolean ;
46+ data : LanyardData ;
47+ } ;
48+
2749const techStack = [
2850 'Git' ,
2951 'C#' ,
@@ -123,6 +145,7 @@ app.innerHTML = `
123145 <h1>
124146 Hi, I am <span class="gradient-name">Marco Pisco</span>.
125147 </h1>
148+ <p class="hero-alias">aka <span class="alias-name" data-tip="nvld">neverland</span></p>
126149 <p>
127150 Student and Developer based in Torres Vedras, Lisbon, Portugal.
128151 </p>
@@ -243,6 +266,12 @@ app.innerHTML = `
243266 <a href="https://www.linkedin.com/in/marco-p-440068329/" target="_blank" rel="noreferrer">LinkedIn</a>
244267 </div>
245268 </div>
269+ <div class="container discord-wrap">
270+ <div id="discord-presence" class="discord-presence">
271+ <span class="status-dot offline"></span>
272+ <p class="discord-line">Discord: connecting...</p>
273+ </div>
274+ </div>
246275 </footer>
247276` ;
248277
@@ -393,6 +422,103 @@ function closeWriteup(): void {
393422 viewer . innerHTML = '' ;
394423}
395424
425+ function setupScrollReveal ( root : ParentNode = document ) : void {
426+ const targets = root . querySelectorAll < HTMLElement > (
427+ '#about h2, #about p, #skills h2, #skills .pill, #experience h2, #experience .card, #education h2, #education .card, #writeups h2, #writeups .card, #contact h2, #contact p, #contact .cta' ,
428+ ) ;
429+
430+ if ( targets . length === 0 ) {
431+ return ;
432+ }
433+
434+ let stagger = 0 ;
435+ for ( const target of targets ) {
436+ if ( ! target . classList . contains ( 'reveal-text' ) ) {
437+ target . classList . add ( 'reveal-text' ) ;
438+ target . style . transitionDelay = `${ Math . min ( stagger * 55 , 260 ) } ms` ;
439+ stagger += 1 ;
440+ }
441+ }
442+
443+ if ( ! ( 'IntersectionObserver' in window ) ) {
444+ for ( const target of targets ) {
445+ target . classList . add ( 'in-view' ) ;
446+ }
447+ return ;
448+ }
449+
450+ const observer = new IntersectionObserver (
451+ ( entries ) => {
452+ for ( const entry of entries ) {
453+ if ( entry . isIntersecting ) {
454+ entry . target . classList . add ( 'in-view' ) ;
455+ observer . unobserve ( entry . target ) ;
456+ }
457+ }
458+ } ,
459+ {
460+ threshold : 0.18 ,
461+ rootMargin : '0px 0px -8% 0px' ,
462+ } ,
463+ ) ;
464+
465+ for ( const target of targets ) {
466+ if ( ! target . classList . contains ( 'in-view' ) ) {
467+ observer . observe ( target ) ;
468+ }
469+ }
470+ }
471+
472+ function mapDiscordStatus ( status : LanyardData [ 'discord_status' ] ) : string {
473+ if ( status === 'dnd' ) {
474+ return 'Do Not Disturb' ;
475+ }
476+ if ( status === 'idle' ) {
477+ return 'Idle' ;
478+ }
479+ if ( status === 'online' ) {
480+ return 'Online' ;
481+ }
482+ return 'Offline' ;
483+ }
484+
485+ async function updateDiscordPresence ( ) : Promise < void > {
486+ const presence = document . querySelector < HTMLElement > ( '#discord-presence' ) ;
487+ if ( ! presence ) {
488+ return ;
489+ }
490+
491+ try {
492+ const response = await fetch ( 'https://api.lanyard.rest/v1/users/1060304285457448970' ) ;
493+ if ( ! response . ok ) {
494+ throw new Error ( 'Lanyard unavailable' ) ;
495+ }
496+
497+ const payload = ( await response . json ( ) ) as LanyardResponse ;
498+ if ( ! payload . success || ! payload . data ) {
499+ throw new Error ( 'Invalid Lanyard payload' ) ;
500+ }
501+
502+ const { data } = payload ;
503+ const statusLabel = mapDiscordStatus ( data . discord_status ) ;
504+ const nowPlaying = data . listening_to_spotify && data . spotify
505+ ? `${ data . spotify . song } - ${ data . spotify . artist } `
506+ : 'Nothing playing' ;
507+
508+ presence . innerHTML = `
509+ <span class="status-dot ${ data . discord_status } "></span>
510+ <p class="discord-line"><strong>Discord:</strong> ${ escapeHtml ( statusLabel ) } </p>
511+ <p class="discord-line"><strong>Now playing:</strong> ${ escapeHtml ( nowPlaying ) } </p>
512+ ` ;
513+ } catch {
514+ presence . innerHTML = `
515+ <span class="status-dot offline"></span>
516+ <p class="discord-line"><strong>Discord:</strong> unavailable</p>
517+ <p class="discord-line"><strong>Now playing:</strong> unavailable</p>
518+ ` ;
519+ }
520+ }
521+
396522async function loadWriteups ( ) : Promise < void > {
397523 const list = document . querySelector < HTMLElement > ( '#writeups-list' ) ;
398524 if ( ! list ) {
@@ -409,6 +535,7 @@ async function loadWriteups(): Promise<void> {
409535 const writeups = ( await response . json ( ) ) as WriteupMeta [ ] ;
410536 if ( ! Array . isArray ( writeups ) || writeups . length === 0 ) {
411537 list . innerHTML = '<article class="card"><p class="summary">No writeups yet... :(</p></article>' ;
538+ setupScrollReveal ( list ) ;
412539 return ;
413540 }
414541
@@ -442,9 +569,17 @@ async function loadWriteups(): Promise<void> {
442569 }
443570 } ) ;
444571 }
572+
573+ setupScrollReveal ( list ) ;
445574 } catch {
446575 list . innerHTML = '<article class="card"><p class="summary">Writeups index missing. Add files to public/writeups.</p></article>' ;
576+ setupScrollReveal ( list ) ;
447577 }
448578}
449579
580+ setupScrollReveal ( ) ;
581+ void updateDiscordPresence ( ) ;
582+ setInterval ( ( ) => {
583+ void updateDiscordPresence ( ) ;
584+ } , 20000 ) ;
450585void loadWriteups ( ) ;
0 commit comments