Skip to content

Commit da7e582

Browse files
committed
Add Lanyard Discord presence and now playing footer widget
1 parent fcf7b9e commit da7e582

File tree

2 files changed

+310
-13
lines changed

2 files changed

+310
-13
lines changed

src/main.ts

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
2749
const 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+
396522
async 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);
450585
void loadWriteups();

0 commit comments

Comments
 (0)