WIP: My personal website
1<script lang="ts">
2 import { reveal } from '$lib/actions/reveal';
3 import LazyLoadingImg from './LazyLoadingImg.svelte';
4 import type { LatestPost, Publication } from '$lib/types';
5
6 let {
7 publications,
8 latestPosts,
9 subscriberCounts
10 }: {
11 publications: Publication[];
12 latestPosts: Promise<LatestPost[]>;
13 subscriberCounts: Promise<Record<string, number>>;
14 } = $props();
15
16 const INITIAL_COUNT = 4;
17 const LOAD_STEP = 2;
18 let visibleCount = $state(INITIAL_COUNT);
19 const visiblePublications = $derived(publications.slice(0, visibleCount));
20 const hasMore = $derived(visibleCount < publications.length);
21
22 function publishedLabel(iso: string | null): string {
23 if (!iso) return '';
24 const date = new Date(iso);
25 if (Number.isNaN(date.getTime())) return '';
26 return date.toLocaleDateString('en', { month: 'short', day: 'numeric', year: 'numeric' });
27 }
28</script>
29
30{#if publications.length > 0}
31 <div id="writings" class="mt-10 flex flex-col items-center justify-center md:mt-20" use:reveal>
32 <div class="flex flex-col items-center justify-center">
33 <h1 class="text-center font-urbanist text-2xl font-semibold md:text-5xl">Writings</h1>
34 <span class="text-md mt-2 px-2 text-center font-urbanist md:mt-4 md:px-5 md:text-xl">
35 My long form writings found across the atmosphere
36 </span>
37 </div>
38 <div class="container mt-10 grid gap-10 p-4 md:grid-cols-2 xl:grid-cols-3">
39 {#each visiblePublications as item (item.href)}
40 <div class="card bg-base-100 shadow-sm transition duration-300 hover:-translate-y-1">
41 {#if item.image}
42 <figure>
43 <LazyLoadingImg class="h-48 w-full" src={item.image} alt={item.name} />
44 </figure>
45 {/if}
46 <div class="card-body">
47 {#await subscriberCounts then counts}
48 {#if counts[item.uri] > 0}
49 <span class="badge font-urbanist badge-secondary"
50 >♥ {counts[item.uri]} subscribers</span
51 >
52 {/if}
53 {/await}
54 <h2 class="card-title font-urbanist text-2xl font-black">{item.name}</h2>
55 <p class="text-md font-urbanist font-medium opacity-60">{item.description}</p>
56 <div class="mt-2 card-actions justify-end">
57 <a
58 class="btn font-urbanist btn-primary"
59 href={item.href}
60 target="_blank"
61 rel="noopener noreferrer"
62 >
63 Read
64 </a>
65 </div>
66 </div>
67 </div>
68 {/each}
69 </div>
70 {#if hasMore}
71 <button
72 class="btn mt-10 font-urbanist btn-primary"
73 onclick={() => (visibleCount = Math.min(visibleCount + LOAD_STEP, publications.length))}
74 >
75 Load More
76 </button>
77 {/if}
78
79 <h2
80 class="mt-16 text-center font-urbanist text-xl font-semibold opacity-80 md:mt-24 md:text-3xl"
81 >
82 Latest Posts
83 </h2>
84 <span class="text-md mt-2 px-2 text-center font-urbanist md:mt-4 md:px-5 md:text-xl">
85 My most recent posts
86 </span>
87 {#await latestPosts}
88 <div class="container mt-10 grid gap-10 p-4 md:grid-cols-2 xl:grid-cols-3">
89 {#each Array(2) as _, i (i)}
90 <div class="card bg-base-100 shadow-sm">
91 <div class="h-48 w-full skeleton"></div>
92 <div class="card-body flex flex-col gap-3">
93 <div class="h-5 w-28 skeleton"></div>
94 <div class="h-7 w-3/4 skeleton"></div>
95 <div class="h-4 w-1/3 skeleton"></div>
96 <div class="h-4 w-full skeleton"></div>
97 </div>
98 </div>
99 {/each}
100 </div>
101 {:then posts}
102 {#if posts.length > 0}
103 <div class="container mt-10 grid gap-10 p-4 md:grid-cols-2 xl:grid-cols-3">
104 {#each posts as post (post.postUrl)}
105 <div class="card bg-base-100 shadow-sm transition duration-300 hover:-translate-y-1">
106 {#if post.coverImage}
107 <figure>
108 <LazyLoadingImg class="h-48 w-full" src={post.coverImage} alt={post.title} />
109 </figure>
110 {/if}
111 <div class="card-body">
112 <div class="flex flex-wrap items-center gap-2">
113 <span class="badge font-urbanist badge-primary">{post.publicationName}</span>
114 {#if post.recommendCount > 0}
115 <span class="badge font-urbanist badge-secondary"
116 >★ {post.recommendCount} recommendations</span
117 >
118 {/if}
119 </div>
120 <h3 class="mt-2 card-title font-urbanist text-2xl font-black">{post.title}</h3>
121 {#if post.publishedAt}
122 <p class="font-urbanist text-xs opacity-40">{publishedLabel(post.publishedAt)}</p>
123 {/if}
124 <p class="text-md font-urbanist font-medium opacity-60">{post.description}</p>
125 <div class="mt-2 card-actions justify-end">
126 <a
127 class="btn font-urbanist btn-primary"
128 href={post.postUrl}
129 target="_blank"
130 rel="noopener noreferrer"
131 >
132 Read
133 </a>
134 </div>
135 </div>
136 </div>
137 {/each}
138 </div>
139 {/if}
140 {/await}
141 </div>
142{/if}