Add donate widget

This commit is contained in:
SirBlob committed 2026-10-31 22:53:17 -04:00
1 parent cc7e8b0497
commit ff1d2213dc
5 files changed
+280

No files matched your search

+1
View File
@@ -5,6 +5,7 @@
"hostname": "engineering", "hostname": "engineering",
"title": "Computer Science Student", "title": "Computer Science Student",
"email": "sirblob0@gmail.com", "email": "sirblob0@gmail.com",
"donateUrl": "https://ko-fi.com/sirblob",
"avatar": "/blob_nerd.png", "avatar": "/blob_nerd.png",
"photo": "/portfolio-photo.jpg", "photo": "/portfolio-photo.jpg",
"bio": "An engineer who loves making things. I build fun coding projects, compete in hackathons and robotics competitions, and enjoy games like Minecraft and Pokémon TCG Live. I'm interested in Open Source, Robotics, Embedded Systems, and AI/ML.", "bio": "An engineer who loves making things. I build fun coding projects, compete in hackathons and robotics competitions, and enjoy games like Minecraft and Pokémon TCG Live. I'm interested in Open Source, Robotics, Embedded Systems, and AI/ML.",
+2
View File
@@ -7,6 +7,7 @@
import DiscordStatus from './DiscordStatus.svelte'; import DiscordStatus from './DiscordStatus.svelte';
import Battery from './Battery.svelte'; import Battery from './Battery.svelte';
import Music from './Music.svelte'; import Music from './Music.svelte';
import Donate from './Donate.svelte';
</script> </script>
<header class="bar"> <header class="bar">
@@ -25,6 +26,7 @@
<Music /> <Music />
<DiscordStatus /> <DiscordStatus />
<Battery /> <Battery />
<Donate />
<ThemeMenu /> <ThemeMenu />
</div> </div>
</header> </header>
+47
View File
@@ -0,0 +1,47 @@
<script lang="ts">
import Icon from '@iconify/svelte';
import { profile } from '#lib/content';
import DonatePopover from './DonatePopover.svelte';
let open = $state(false);
const discordUrl = profile.socials.find((social) => social.name === 'Discord')?.url ?? '';
</script>
{#if profile.donateUrl}
<div class="donate">
<button class="tray-button donate-button" title="Support me on Ko-fi" aria-expanded={open} onclick={() => (open = !open)}>
<Icon icon="simple-icons:kofi" width="16" />
</button>
{#if open}
<DonatePopover donateUrl={profile.donateUrl} {discordUrl} onclose={() => (open = false)} />
{/if}
</div>
{/if}
<style>
.donate {
position: relative;
}
.donate-button:hover :global(svg) {
animation: heartbeat 0.8s ease-in-out infinite;
}
@keyframes heartbeat {
0%,
100% {
transform: scale(1);
}
50% {
transform: scale(1.2);
}
}
@media (prefers-reduced-motion: reduce) {
.donate-button:hover :global(svg) {
animation: none;
}
}
</style>
@@ -0,0 +1,229 @@
<script lang="ts">
import Icon from '@iconify/svelte';
let { donateUrl, discordUrl, onclose }: { donateUrl: string; discordUrl: string; onclose: () => void } = $props();
function handleKeydown(event: KeyboardEvent) {
if (event.key === 'Escape') onclose();
}
</script>
<svelte:window onkeydown={handleKeydown} />
<button class="backdrop" aria-label="Close donate card" onclick={onclose}></button>
<div class="popover" role="dialog" aria-label="Support me">
<div class="popover-surface">
<header class="header">
<Icon icon="simple-icons:kofi" width="22" />
<span class="title">Support the dev</span>
</header>
<div class="body">
<p>
As a broke college student, every donation goes straight to the important things: textbooks, energy drinks,
and upgrading my diet from instant ramen to <em>slightly more expensive</em> instant ramen.
</p>
<div class="plea" aria-label="I need money">
<span class="plea-text">I NEED MONEY</span>
<span class="ramen" aria-hidden="true"><Icon icon="mdi:noodles" width="26" /></span>
</div>
{#if discordUrl}
<a class="commissions" href={discordUrl} target="_blank" rel="noopener noreferrer">
<span class="status-dot"></span>
<span>Website commissions: <b>OPEN</b></span>
<span class="commissions-link"><Icon icon="ic:baseline-discord" width="14" /> DM me</span>
</a>
{/if}
</div>
<a class="donate-button" href={donateUrl} target="_blank" rel="noopener noreferrer">
<Icon icon="simple-icons:kofi" width="18" />
Donate on Ko-fi
</a>
</div>
</div>
<style>
.backdrop {
position: fixed;
inset: 0;
z-index: 40;
background: none;
border: none;
cursor: default;
}
.popover {
position: absolute;
top: calc(100% + 8px);
right: 0;
z-index: 50;
width: 360px;
padding: var(--border-size);
background: var(--border-active);
animation: window-pop-in 0.15s ease-out;
}
.popover-surface {
background: var(--background);
}
.header {
display: flex;
align-items: center;
gap: 0.6rem;
padding: 0.9rem 1rem 0;
color: var(--accent);
}
.title {
font-size: 1.05rem;
font-weight: 700;
}
.body {
display: flex;
flex-direction: column;
gap: 0.75rem;
padding: 0.75rem 1rem 1rem;
font-size: 0.85rem;
line-height: 1.55;
}
p {
margin: 0;
}
em {
color: var(--yellow);
}
.plea {
display: flex;
align-items: center;
justify-content: center;
gap: 0.6rem;
padding: 0.5rem;
border: 1px dashed var(--red);
background: color-mix(in srgb, var(--red) 8%, transparent);
}
.plea-text {
color: var(--red);
font-family: 'Comic Sans MS', 'Comic Neue', 'Chalkboard SE', cursive;
font-size: 1.6rem;
font-weight: 900;
letter-spacing: 0.04em;
text-shadow: 2px 2px 0 color-mix(in srgb, var(--yellow) 60%, transparent);
animation: wobble 1.6s ease-in-out infinite;
}
.ramen {
display: grid;
color: var(--yellow);
animation: steam 2s ease-in-out infinite;
}
.commissions {
display: flex;
align-items: center;
gap: 0.5rem;
padding: 0.5rem 0.7rem;
border: 1px solid color-mix(in srgb, var(--green) 50%, transparent);
background: color-mix(in srgb, var(--green) 8%, transparent);
}
.commissions:hover {
border-color: var(--green);
}
.commissions b {
color: var(--green);
}
.status-dot {
flex-shrink: 0;
width: 9px;
height: 9px;
border-radius: 50%;
background: var(--green);
animation: pulse 1.6s ease-out infinite;
}
.commissions-link {
display: inline-flex;
align-items: center;
gap: 0.2rem;
margin-left: auto;
color: var(--muted);
}
.commissions:hover .commissions-link {
color: var(--green);
}
.donate-button {
display: flex;
align-items: center;
justify-content: center;
gap: 0.5rem;
padding: 0.7rem;
background: var(--accent);
color: var(--background);
font-weight: 700;
transition: filter 0.15s ease;
}
.donate-button:hover {
filter: brightness(1.1);
}
@keyframes wobble {
0%,
100% {
transform: rotate(-3deg) scale(1);
}
50% {
transform: rotate(3deg) scale(1.06);
}
}
@keyframes steam {
0%,
100% {
transform: translateY(0);
}
50% {
transform: translateY(-3px);
}
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 color-mix(in srgb, var(--green) 70%, transparent);
}
100% {
box-shadow: 0 0 0 7px transparent;
}
}
@media (prefers-reduced-motion: reduce) {
.plea-text,
.ramen,
.status-dot {
animation: none;
}
}
@media (max-width: 640px) {
.popover {
position: fixed;
top: calc(var(--bar-height) + 6px);
right: 8px;
left: 8px;
width: auto;
}
}
</style>
+1
View File
@@ -17,6 +17,7 @@ export interface Profile {
hostname: string; hostname: string;
title: string; title: string;
email: string; email: string;
donateUrl?: string;
avatar: string; avatar: string;
photo?: string; photo?: string;
bio: string; bio: string;