WIP: My personal website
1<script lang="ts">
2 import { reveal } from '$lib/actions/reveal';
3 import moover from '$lib/assets/projects/moover.webp';
4 import npmx from '$lib/assets/projects/npmx.png';
5 import gatekeeper from '$lib/assets/projects/gatekeeper.jpg';
6 import badger from '$lib/assets/projects/badger.jpg';
7 import twentyfortyeight from '$lib/assets/projects/2048.png';
8 import giveaways from '$lib/assets/projects/giveaways.webp';
9
10 type Project = {
11 name: string;
12 description: string;
13 image: string;
14 href: string;
15 };
16
17 const projects: Project[] = [
18 {
19 name: 'PDS MOOver',
20 description:
21 'A tool kit to help you migrate between atproto PDS instances as well as recover and restore your atproto account.',
22 image: moover,
23 href: 'https://pdsmoover.com'
24 },
25 {
26 name: 'npmx',
27 description:
28 'I am a core member of the npmx project. A fast, modern browser for the npm registry. I host the pds npmx.social and help with atproto social integrations ',
29 image: npmx,
30 href: 'https://npmx.dev'
31 },
32 {
33 name: 'PDS Gatekeeper',
34 description:
35 'A sidecar service (a microservice if you will) that sites in front of the PDS to provide extra security in the form of 2FA for users, captcha, on signups, and customizable rate limits.',
36 image: gatekeeper,
37 href: 'https://github.com/baileytownsend/reveal-ui'
38 },
39 {
40 name: 'Rusty Badger',
41 description:
42 'A simple Rust firmware written for the Badger 2040 W from Pimoroni. Counts wifi networks found, reads a shtc3 sensor for temperature and humidity, and displays the results on the Badger display.',
43 image: badger,
44 href: 'https://tangled.org/pds.dad/rusty-badger'
45 },
46 {
47 name: 'at://2048',
48 description:
49 '2048 written in rust with atproto features to track your game history and to share with friends. Some have called it the first game on atproto, others have said "Hey does that count if it\'s just saving game history to the repo??" others have said "dear god why did you write this in web assembly"',
50 image: twentyfortyeight,
51 href: 'https://2048.blue'
52 },
53 {
54 name: 'Label Watcher',
55 description:
56 'Automated moderation made easier for PDS admins. Subscribe to multiple atproto labelers and receive an email or have an auto takedown if a selected label is found on a user on a PDS you manage.',
57 image: 'https://tangled.org/pds.dad/label-watcher/opengraph',
58 href: 'https://tangled.org/pds.dad/label-watcher'
59 },
60 {
61 name: 'at://giveaways',
62 description:
63 'Host a giveaway from the comfort of your own Bluesky post. Choose randomly from who liked, reposts, or any combination of the two. Powered by microcosm.',
64 image: giveaways,
65 href: 'https://giveaways.baileytownsend.dev/'
66 }
67 ];
68
69 const INITIAL_COUNT = 4;
70 const LOAD_STEP = 2;
71 let visibleCount = $state(INITIAL_COUNT);
72 const visibleProjects = $derived(projects.slice(0, visibleCount));
73 const hasMore = $derived(visibleCount < projects.length);
74</script>
75
76<div id="projects" class="mt-10 flex flex-col items-center justify-center md:mt-20" use:reveal>
77 <div class="flex flex-col items-center justify-center">
78 <h1 class="text-center font-urbanist text-2xl font-semibold md:text-5xl">Open Source</h1>
79 <span class="text-md mt-2 px-2 text-center font-urbanist md:mt-4 md:px-5 md:text-xl">
80 A few things I'm building and sharing with the community.
81 </span>
82 </div>
83 <div class="container mt-10 grid gap-10 p-4 md:grid-cols-2 xl:grid-cols-3">
84 {#each visibleProjects as item (item.name)}
85 <div class="card bg-base-100 shadow-sm transition duration-300 hover:-translate-y-1">
86 <figure>
87 <img class="h-48 w-full object-cover" src={item.image} alt={item.name} />
88 </figure>
89 <div class="card-body">
90 <h2 class="card-title font-urbanist text-2xl font-black">{item.name}</h2>
91 <p class="text-md font-urbanist font-medium opacity-60">{item.description}</p>
92 <div class="mt-2 card-actions justify-end">
93 <a
94 class="btn font-urbanist btn-primary"
95 href={item.href}
96 target="_blank"
97 rel="noopener noreferrer"
98 >
99 View Project
100 </a>
101 </div>
102 </div>
103 </div>
104 {/each}
105 </div>
106 {#if hasMore}
107 <button
108 class="btn mt-10 font-urbanist btn-primary"
109 onclick={() => (visibleCount = Math.min(visibleCount + LOAD_STEP, projects.length))}
110 >
111 Load More
112 </button>
113 {/if}
114</div>