chore: update

This commit is contained in:
enscribe 2024-09-10 17:51:46 -07:00
parent 8fe228e243
commit 43e35a3f8b
No known key found for this signature in database
GPG key ID: 9BBD5C4114E25322
54 changed files with 1013 additions and 496 deletions

View file

@ -1 +1 @@
# erudite # erudite

26
package-lock.json generated
View file

@ -17,6 +17,7 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3", "@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3", "@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",
@ -1823,6 +1824,31 @@
} }
} }
}, },
"node_modules/@radix-ui/react-avatar": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-avatar/-/react-avatar-1.1.0.tgz",
"integrity": "sha512-Q/PbuSMk/vyAd/UoIShVGZ7StHHeRFYU7wXmi5GV+8cLXflZAEpHL/F697H1klrzxKXNtZ97vWiC0q3RKUH8UA==",
"dependencies": {
"@radix-ui/react-context": "1.1.0",
"@radix-ui/react-primitive": "2.0.0",
"@radix-ui/react-use-callback-ref": "1.1.0",
"@radix-ui/react-use-layout-effect": "1.1.0"
},
"peerDependencies": {
"@types/react": "*",
"@types/react-dom": "*",
"react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc",
"react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc"
},
"peerDependenciesMeta": {
"@types/react": {
"optional": true
},
"@types/react-dom": {
"optional": true
}
}
},
"node_modules/@radix-ui/react-collection": { "node_modules/@radix-ui/react-collection": {
"version": "1.1.0", "version": "1.1.0",
"resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz", "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.0.tgz",

View file

@ -21,6 +21,7 @@
"@astrojs/tailwind": "^5.1.0", "@astrojs/tailwind": "^5.1.0",
"@fontsource/geist-mono": "^5.0.3", "@fontsource/geist-mono": "^5.0.3",
"@fontsource/geist-sans": "^5.0.3", "@fontsource/geist-sans": "^5.0.3",
"@radix-ui/react-avatar": "^1.1.0",
"@radix-ui/react-dropdown-menu": "^2.1.1", "@radix-ui/react-dropdown-menu": "^2.1.1",
"@radix-ui/react-icons": "^1.3.0", "@radix-ui/react-icons": "^1.3.0",
"@radix-ui/react-slot": "^1.1.0", "@radix-ui/react-slot": "^1.1.0",

BIN
public/1200x630.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 155 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 241 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

View file

@ -1,17 +0,0 @@
<svg width="179" height="32" viewBox="0 0 179 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_8_30)">
<path d="M173 0H6C2.68629 0 0 2.68629 0 6V26C0 29.3137 2.68629 32 6 32H173C176.314 32 179 29.3137 179 26V6C179 2.68629 176.314 0 173 0Z" fill="#2E51ED"/>
<path d="M15.027 23.227H14.781L13.556 22.049V21.813L15.429 20.011H16.727L16.9 20.178V21.426L15.027 23.227ZM13.556 9.89999V9.66399L14.781 8.48499H15.027L16.9 10.287V11.535L16.727 11.701H15.429L13.556 9.89999ZM24.343 19.429H22.561L22.411 19.286V15.273C22.411 14.559 22.12 14.005 21.224 13.986C20.764 13.975 20.236 13.986 19.673 14.007L19.588 14.091V19.284L19.439 19.427H17.657L17.507 19.284V12.429L17.657 12.285H21.669C23.229 12.285 24.492 13.5 24.492 15V19.286L24.343 19.429ZM15.28 16.86H8.15L8 16.716V14.998L8.149 14.855H15.28L15.43 14.998V16.716L15.28 16.859V16.86ZM33.853 16.86H26.722L26.572 16.716V14.998L26.722 14.855H33.853L34.002 14.998V16.716L33.853 16.859V16.86ZM19.973 10.143V4.99999L20.122 4.85699H21.909L22.057 4.99999V10.143L21.909 10.287H20.122L19.973 10.143ZM19.973 26.714V21.571L20.122 21.428H21.909L22.057 21.571V26.714L21.909 26.857H20.122L19.973 26.714ZM155.15 10.64C154.72 11.06 154.51 11.64 154.51 12.39V13.43H153.28V15.1H154.51V21.19H156.47V15.1H158.11V13.43H156.47V12.38C156.47 11.85 156.75 11.58 157.3 11.58H158.34V9.99999H156.94C156.18 9.99999 155.59 10.21 155.16 10.64H155.15ZM150.93 10.13C150.57 10.13 150.27 10.25 150.05 10.48C149.84 10.7 149.73 10.98 149.73 11.32C149.73 11.66 149.84 11.95 150.05 12.19C150.27 12.42 150.57 12.54 150.93 12.54C151.29 12.54 151.57 12.42 151.78 12.19C152 11.96 152.12 11.67 152.12 11.32C152.12 10.97 152.01 10.7 151.78 10.48C151.56 10.25 151.28 10.13 150.93 10.13ZM73.23 10.14H75.19V21.19H73.23V10.14Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M47.32 10.63C48.49 10.63 49.49 10.84 50.33 11.27C51.17 11.69 51.81 12.3 52.25 13.09C52.7 13.88 52.92 14.83 52.92 15.92C52.92 17.01 52.7 17.96 52.25 18.75C51.81 19.54 51.17 20.14 50.33 20.56C49.49 20.98 48.49 21.19 47.32 21.19H44V10.63H47.32ZM47.23 19.22C48.31 19.22 49.16 18.93 49.78 18.35V18.36C50.41 17.77 50.72 16.97 50.72 15.92C50.72 14.87 50.41 14.05 49.78 13.47C49.16 12.89 48.31 12.6 47.23 12.6H46.11V19.22H47.23ZM58.16 13.29C58.96 13.29 59.65 13.45 60.25 13.79L60.24 13.78C60.85 14.11 61.31 14.57 61.64 15.17C61.97 15.77 62.13 16.47 62.13 17.26V17.92H56.28C56.31 18.18 56.36 18.44 56.47 18.66C56.63 18.96 56.85 19.19 57.14 19.36C57.43 19.53 57.78 19.61 58.19 19.61C58.6 19.61 58.95 19.54 59.23 19.41C59.51 19.28 59.72 19.09 59.86 18.86H61.96C61.8 19.33 61.54 19.76 61.19 20.13C60.85 20.51 60.41 20.79 59.89 21C59.38 21.21 58.8 21.31 58.17 21.31C57.38 21.31 56.68 21.15 56.07 20.82C55.47 20.49 55.01 20.02 54.67 19.41C54.34 18.81 54.18 18.1 54.18 17.3C54.18 16.5 54.34 15.8 54.67 15.2C55 14.6 55.46 14.13 56.06 13.79C56.67 13.46 57.36 13.29 58.16 13.29ZM58.16 14.99C57.8 14.99 57.47 15.08 57.18 15.26C56.89 15.42 56.66 15.66 56.49 15.97C56.41 16.14 56.35 16.31 56.31 16.49H60.05C60.0095 16.2241 59.9069 15.9714 59.7505 15.7525C59.5941 15.5336 59.3884 15.3546 59.15 15.23C58.86 15.07 58.52 14.99 58.16 14.99ZM70.16 13.74C69.64 13.42 69.04 13.26 68.35 13.26C67.66 13.26 67.06 13.42 66.57 13.74C66.28 13.93 66.05 14.19 65.86 14.47V13.43H63.9V23.85H65.86V20.16C66.06 20.45 66.29 20.7 66.57 20.9C67.06 21.23 67.65 21.39 68.35 21.39C69.05 21.39 69.63 21.23 70.16 20.91C70.68 20.58 71.09 20.12 71.38 19.51C71.67 18.89 71.81 18.17 71.81 17.33C71.81 16.49 71.67 15.75 71.38 15.15C71.09 14.53 70.68 14.07 70.16 13.75V13.74ZM69.5 18.6C69.33 18.95 69.1 19.22 68.8 19.41C68.51 19.6 68.18 19.69 67.81 19.69C67.22 19.69 66.75 19.48 66.4 19.07C66.05 18.65 65.87 18.08 65.87 17.35C65.87 16.62 66.05 16.07 66.4 15.66C66.75 15.25 67.23 15.04 67.81 15.04C68.18 15.04 68.51 15.14 68.8 15.33C69.1 15.52 69.33 15.79 69.5 16.14C69.67 16.49 69.75 16.89 69.75 17.34C69.75 17.79 69.67 18.23 69.5 18.59V18.6ZM82.85 13.79C82.23 13.44 81.51 13.26 80.68 13.26C79.85 13.26 79.13 13.44 78.51 13.79C77.9 14.14 77.44 14.62 77.11 15.23C76.78 15.85 76.62 16.54 76.62 17.32C76.62 18.1 76.78 18.79 77.11 19.41C77.44 20.02 77.9 20.5 78.51 20.85C79.13 21.2 79.85 21.38 80.68 21.38C81.51 21.38 82.23 21.2 82.85 20.85C83.47 20.5 83.93 20.01 84.25 19.39C84.58 18.77 84.74 18.08 84.74 17.32C84.74 16.56 84.58 15.85 84.25 15.23C83.93 14.61 83.47 14.13 82.85 13.79ZM82.43 18.49C82.27 18.83 82.04 19.09 81.73 19.27C81.43 19.46 81.08 19.55 80.68 19.55C80.28 19.55 79.91 19.46 79.62 19.27C79.32 19.08 79.09 18.82 78.92 18.49C78.76 18.15 78.68 17.76 78.68 17.31C78.68 16.86 78.76 16.46 78.92 16.12C79.09 15.78 79.32 15.53 79.62 15.35C79.92 15.16 80.27 15.07 80.68 15.07C81.09 15.07 81.43 15.16 81.73 15.35C82.04 15.53 82.27 15.79 82.43 16.13C82.6 16.47 82.68 16.86 82.68 17.31C82.68 17.76 82.6 18.15 82.43 18.49Z" fill="white"/>
<path d="M87.11 13.43L89.15 18.5L91.26 13.43H93.15L88.84 23.75H86.95L88.14 20.9L85.13 13.43H87.11ZM102.71 10.98H100.75V13.43H99.26V15.1H100.75V21.19H102.71V15.1H104.39V13.43H102.71V10.98Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M109.18 13.26C110.01 13.26 110.73 13.44 111.35 13.79C111.97 14.13 112.43 14.61 112.75 15.23C113.08 15.85 113.24 16.56 113.24 17.32C113.24 18.08 113.08 18.77 112.75 19.39C112.43 20.01 111.97 20.5 111.35 20.85C110.73 21.2 110.01 21.38 109.18 21.38C108.35 21.38 107.63 21.2 107.01 20.85C106.4 20.5 105.94 20.02 105.61 19.41C105.28 18.79 105.12 18.1 105.12 17.32C105.12 16.54 105.28 15.85 105.61 15.23C105.94 14.62 106.4 14.14 107.01 13.79C107.63 13.44 108.35 13.26 109.18 13.26ZM110.23 19.27C110.54 19.09 110.77 18.83 110.93 18.49C111.1 18.15 111.18 17.76 111.18 17.31C111.18 16.86 111.1 16.47 110.93 16.13C110.77 15.79 110.54 15.53 110.23 15.35C109.93 15.16 109.59 15.07 109.18 15.07C108.77 15.07 108.42 15.16 108.12 15.35C107.82 15.53 107.59 15.78 107.42 16.12C107.26 16.46 107.18 16.86 107.18 17.31C107.18 17.76 107.26 18.15 107.42 18.49C107.59 18.82 107.82 19.08 108.12 19.27C108.41 19.46 108.78 19.55 109.18 19.55C109.58 19.55 109.93 19.46 110.23 19.27Z" fill="white"/>
<path d="M126.91 16.02L122.22 10.63H120.41V21.19H122.52V14.26L126.91 19.31V21.19H129.02V10.63H126.91V16.02Z" fill="white"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M134.5 13.29C135.3 13.29 135.99 13.45 136.59 13.79L136.58 13.78C137.19 14.11 137.65 14.57 137.98 15.17C138.31 15.77 138.47 16.47 138.47 17.26V17.92H132.62C132.65 18.18 132.7 18.44 132.81 18.66C132.97 18.96 133.19 19.19 133.48 19.36C133.77 19.53 134.12 19.61 134.53 19.61C134.94 19.61 135.29 19.54 135.57 19.41C135.85 19.28 136.06 19.09 136.2 18.86H138.3C138.14 19.33 137.88 19.76 137.53 20.13C137.19 20.51 136.75 20.79 136.23 21C135.72 21.21 135.14 21.31 134.51 21.31C133.72 21.31 133.02 21.15 132.41 20.82C131.81 20.49 131.35 20.02 131.01 19.41C130.68 18.81 130.52 18.1 130.52 17.3C130.52 16.5 130.68 15.8 131.01 15.2C131.34 14.6 131.8 14.13 132.4 13.79C133.01 13.46 133.7 13.29 134.5 13.29ZM134.5 14.99C134.14 14.99 133.81 15.08 133.52 15.26C133.23 15.42 133 15.66 132.83 15.97C132.75 16.14 132.69 16.31 132.65 16.49H136.39C136.349 16.224 136.247 15.9714 136.09 15.7525C135.934 15.5336 135.728 15.3546 135.49 15.23C135.2 15.07 134.86 14.99 134.5 14.99Z" fill="white"/>
<path d="M142.58 10.98H140.62V13.43H139.14V15.1H140.62V21.19H142.58V15.1H144.26V13.43H142.58V10.98ZM145.92 10.14H147.88V21.19H145.92V10.14ZM149.95 21.18V13.26C150.21 13.45 150.53 13.55 150.93 13.55C151.33 13.55 151.66 13.46 151.91 13.26V21.18H149.95ZM163.14 18.5L165.26 13.43H167.15L162.84 23.75H160.95L162.14 20.9L159.13 13.43H161.1L163.14 18.5Z" fill="white"/>
</g>
<defs>
<clipPath id="clip0_8_30">
<rect width="179" height="32" fill="white"/>
</clipPath>
</defs>
</svg>

Before

Width:  |  Height:  |  Size: 7.5 KiB

File diff suppressed because one or more lines are too long

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 52 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 354 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 218 KiB

View file

@ -1,42 +0,0 @@
---
import type { CollectionEntry } from 'astro:content'
type Props = {
entry: CollectionEntry<'blog'>
}
const { entry } = Astro.props as {
entry: CollectionEntry<'blog'>
}
---
<a
href={`/${entry.collection}/${entry.slug}`}
class="not-prose group relative flex flex-nowrap rounded-lg border px-4 py-3 pr-10 transition-colors duration-300 ease-in-out"
>
<div class="flex flex-1 flex-col truncate">
<div class="font-semibold">
{entry.data.title}
</div>
<div class="text-sm">
{entry.data.description}
</div>
</div>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="absolute right-2 top-1/2 size-5 -translate-y-1/2 fill-none stroke-current stroke-2"
>
<line
x1="5"
y1="12"
x2="19"
y2="12"
class="translate-x-3 scale-x-0 transition-transform duration-300 ease-in-out group-focus-visible:translate-x-0 group-focus-visible:scale-x-100"
></line>
<polyline
points="12 5 19 12 12 19"
class="-translate-x-1 transition-transform duration-300 ease-in-out group-focus-visible:translate-x-0"
></polyline>
</svg>
</a>

View file

@ -0,0 +1,29 @@
---
import type { CollectionEntry } from 'astro:content'
import { Image } from 'astro:assets'
type Props = {
author: CollectionEntry<'authors'>
}
const { author } = Astro.props
const { name, avatar, bio } = author.data
---
<div
class="rounded-lg border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
>
<a href={`/authors/${author.slug}`} class="flex flex-wrap gap-4">
<Image
src={avatar}
alt={`Avatar of ${name}`}
width={128}
height={128}
class="rounded-lg object-cover"
/>
<div class="flex-grow">
<h3 class="mb-1 text-lg font-semibold">{name}</h3>
<p class="text-sm text-muted-foreground">{bio}</p>
</div>
</a>
</div>

View file

@ -1,33 +0,0 @@
---
type Props = {
href: string
}
const { href } = Astro.props
---
<a
href={href}
class="not-prose group relative flex w-fit flex-nowrap rounded border py-1.5 pl-7 pr-3 transition-colors duration-300 ease-in-out"
>
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
class="absolute left-2 top-1/2 size-4 -translate-y-1/2 fill-none stroke-current stroke-2"
>
<line
x1="5"
y1="12"
x2="19"
y2="12"
class="translate-x-2 scale-x-0 transition-transform duration-300 ease-in-out"
></line>
<polyline
points="12 5 5 12 12 19"
class="translate-x-1 transition-transform duration-300 ease-in-out"
></polyline>
</svg>
<div class="text-sm">
<slot />
</div>
</a>

View file

@ -0,0 +1,61 @@
---
import type { CollectionEntry } from 'astro:content'
import { formatDate, readingTime } from '@lib/utils'
import { Image } from 'astro:assets'
import { Badge } from '@/components/ui/badge'
type Props = {
entry: CollectionEntry<'blog'>
}
const { entry } = Astro.props as {
entry: CollectionEntry<'blog'>
}
const formattedDate = formatDate(entry.data.date)
const readTime = readingTime(entry.body)
---
<div
class="not-prose rounded-lg border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
>
<a href={`/${entry.collection}/${entry.slug}`} class="flex flex-wrap gap-4">
{
entry.data.image && (
<div class="flex-shrink-0">
<Image
src={entry.data.image}
alt={entry.data.title}
width={200}
height={200}
class="rounded-lg object-cover"
/>
</div>
)
}
<div class="flex-grow">
<h3 class="mb-1 text-lg font-semibold">
{entry.data.title}
</h3>
<p class="mb-2 text-sm text-muted-foreground">
{entry.data.description}
</p>
<div
class="mb-2 flex items-center space-x-2 text-xs text-muted-foreground"
>
<span>{formattedDate}</span>
<span>&bull;</span>
<span>{readTime}</span>
</div>
{
entry.data.tags && (
<div class="flex flex-wrap gap-2">
{entry.data.tags.map((tag) => (
<Badge variant="secondary">{tag}</Badge>
))}
</div>
)
}
</div>
</a>
</div>

View file

@ -6,10 +6,16 @@ import { ModeToggle } from '@components/ui/mode-toggle'
<footer class="py-4"> <footer class="py-4">
<Container> <Container>
<div class="flex justify-between items-center"> <div
<div class="flex items-center space-x-4"> class="flex flex-col items-center justify-center gap-y-2 sm:flex-row sm:justify-between"
<ModeToggle client:load /> >
<p class="text-sm text-muted-foreground">&copy; {new Date().getFullYear()} All rights reserved.</p> <div class="flex items-center">
<div class="mr-2 hidden sm:block">
<ModeToggle client:load />
</div>
<p class="text-sm text-muted-foreground">
&copy; {new Date().getFullYear()} All rights reserved.
</p>
</div> </div>
<SocialIcons /> <SocialIcons />
</div> </div>

View file

@ -7,27 +7,34 @@ const items = [
{ href: '/blog', label: 'blog' }, { href: '/blog', label: 'blog' },
{ href: '/authors', label: 'authors' }, { href: '/authors', label: 'authors' },
{ href: '/about', label: 'about' }, { href: '/about', label: 'about' },
{ href: '/tags', label: 'tags' },
] ]
--- ---
<header class="sticky top-0 z-10 bg-background/50 backdrop-blur-md" transition:persist> <header
class="sticky top-0 z-10 bg-background/50 backdrop-blur-md"
transition:persist
>
<Container> <Container>
<div class="flex items-center justify-between py-4"> <div class="flex items-center justify-between py-4">
<Link href="/" class="text-xl font-semibold hover:text-primary transition-colors duration-300"> <Link
href="/"
class="text-xl font-semibold transition-colors duration-300 hover:text-primary"
>
{SITE.TITLE} {SITE.TITLE}
</Link> </Link>
<nav class="flex items-center gap-4 md:gap-6 text-sm"> <nav class="flex items-center gap-4 text-sm sm:gap-6">
{items.map((item, index) => ( {
<Fragment key={item.href}> items.map((item, index) => (
<Link <Fragment key={item.href}>
href={item.href} <Link
class="transition-colors hover:text-foreground/80 text-foreground/60 capitalize" href={item.href}
> class="capitalize text-foreground/60 transition-colors hover:text-foreground/80"
{item.label} >
</Link> {item.label}
</Fragment> </Link>
))} </Fragment>
))
}
</nav> </nav>
</div> </div>
</Container> </Container>

View file

@ -8,7 +8,13 @@ type Props = {
'data-heading'?: string 'data-heading'?: string
} }
const { href, external, class: className, 'data-heading': dataHeading, ...rest } = Astro.props const {
href,
external,
class: className,
'data-heading': dataHeading,
...rest
} = Astro.props
--- ---
<a <a
@ -16,10 +22,10 @@ const { href, external, class: className, 'data-heading': dataHeading, ...rest }
target={external ? '_blank' : '_self'} target={external ? '_blank' : '_self'}
class={cn( class={cn(
'inline-block transition-colors duration-300 ease-in-out hover:underline underline-offset-[3px]', 'inline-block transition-colors duration-300 ease-in-out hover:underline underline-offset-[3px]',
className className,
)} )}
data-heading={dataHeading} data-heading={dataHeading}
{...rest} {...rest}
> >
<slot /> <slot />
</a> </a>

View file

@ -1,29 +0,0 @@
---
import type { CollectionEntry } from 'astro:content'
import { Image } from 'astro:assets'
type Props = {
member: CollectionEntry<'authors'>
}
const { member } = Astro.props
const { name, avatar, bio } = member.data
---
<div
class="not-prose flex size-full flex-col gap-4 overflow-hidden rounded-xl border p-6 hover:bg-secondary sm:flex-row sm:items-center"
>
<Image
src={avatar}
alt={`Avatar of ${name}`}
width={256}
height={256}
class="aspect-square size-32 rounded-md object-cover"
/>
<div class="flex flex-col justify-between">
<a href={`/authors/${member.slug}`}>
<h3 class="mb-2 text-3xl text-foreground">{name}</h3>
<p class="mb-4 text-sm text-foreground">{bio}</p>
</a>
</div>
</div>

View file

@ -10,10 +10,6 @@ export interface Props {
const { URL, icon, icon_size } = Astro.props const { URL, icon, icon_size } = Astro.props
--- ---
<a <a href={URL} target={'_blank'} class={`inline-block ${icon_size}`}>
href={URL}
target={'_blank'}
class={`inline-block ${icon_size}`}
>
<i class={`bi bi-${icon}`}></i> <i class={`bi bi-${icon}`}></i>
</a> </a>

View file

@ -1,7 +1,13 @@
--- ---
import { Twitter, Github, Linkedin, Mail, GraduationCap, Rss } from 'lucide-react' import {
Twitter,
Github,
Linkedin,
Mail,
GraduationCap,
Rss,
} from 'lucide-react'
import { SITE } from '@consts' import { SITE } from '@consts'
--- ---
<ul class="not-prose flex flex-wrap gap-2"> <ul class="not-prose flex flex-wrap gap-2">

View file

@ -27,16 +27,20 @@ function buildToc(headings: Heading[]) {
} }
--- ---
<details open class="block xl:hidden rounded-lg border p-3 mb-8"> <details open class="mb-8 block rounded-lg border p-3 xl:hidden">
<summary class="text-xl font-semibold">Table of Contents</summary> <summary class="cursor-pointer text-xl font-semibold"
>Table of Contents</summary
>
<nav> <nav>
<ul class="py-3"> <ul class="py-3">
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)} {toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
</ul> </ul>
</nav> </nav>
</details> </details>
<nav class="sticky top-16 hidden xl:block h-0 w-[calc(50vw-50%-4rem)] overflow-wrap-break-word text-xs leading-4 translate-x-[calc(-100%-2em)]"> <nav
<h2 class="text-xl font-semibold mb-4">Table of Contents</h2> class="overflow-wrap-break-word sticky top-16 hidden h-0 w-[calc(50vw-50%-4rem)] translate-x-[calc(-100%-2em)] text-xs leading-4 xl:block"
>
<h2 class="mb-4 text-xl font-semibold">Table of Contents</h2>
<ul class="space-y-2"> <ul class="space-y-2">
{toc.map((heading) => <TableOfContentsHeading heading={heading} />)} {toc.map((heading) => <TableOfContentsHeading heading={heading} />)}
</ul> </ul>

View file

@ -5,13 +5,15 @@ import Link from './Link.astro'
const { heading } = Astro.props const { heading } = Astro.props
--- ---
<li class="list-inside list-disc px-6 py-1.5 xl:list-none xl:p-0 text-sm"> <li
class="list-inside list-disc px-6 py-1.5 text-sm text-foreground/60 xl:list-none xl:p-0"
>
<Link href={'#' + heading.slug} class="toc-link" data-heading={heading.slug}> <Link href={'#' + heading.slug} class="toc-link" data-heading={heading.slug}>
{heading.text} {heading.text}
</Link> </Link>
{ {
heading.subheadings.length > 0 && ( heading.subheadings.length > 0 && (
<ul class="translate-x-3 xl:translate-x-0 xl:ml-4 xl:mt-2 xl:space-y-2"> <ul class="translate-x-3 xl:ml-4 xl:mt-2 xl:translate-x-0 xl:space-y-2">
{heading.subheadings.map((subheading: Heading) => ( {heading.subheadings.map((subheading: Heading) => (
<Astro.self heading={subheading} /> <Astro.self heading={subheading} />
))} ))}
@ -22,28 +24,30 @@ const { heading } = Astro.props
<script> <script>
function updateActiveHeading() { function updateActiveHeading() {
const headings = document.querySelectorAll('h2, h3, h4, h5, h6'); const headings = document.querySelectorAll('h2, h3, h4, h5, h6')
const tocLinks = document.querySelectorAll('.toc-link'); const tocLinks = document.querySelectorAll('.toc-link')
let currentHeading = ''; let currentHeading = ''
headings.forEach(heading => { headings.forEach((heading) => {
const top = heading.getBoundingClientRect().top; const top = heading.getBoundingClientRect().top
if (top < 100) { if (top < 200) {
currentHeading = heading.id; currentHeading = heading.id
} }
}); })
tocLinks.forEach(link => { tocLinks.forEach((link) => {
const headingSlug = link.getAttribute('data-heading'); const headingSlug = link.getAttribute('data-heading')
if (headingSlug === currentHeading) { if (headingSlug === currentHeading) {
link.classList.add('underline'); link.classList.add('underline')
link.classList.add('text-foreground')
} else { } else {
link.classList.remove('underline'); link.classList.remove('underline')
link.classList.remove('text-foreground')
} }
}); })
} }
window.addEventListener('scroll', updateActiveHeading); window.addEventListener('scroll', updateActiveHeading)
window.addEventListener('load', updateActiveHeading); window.addEventListener('load', updateActiveHeading)
</script> </script>

View file

@ -0,0 +1,48 @@
import * as AvatarPrimitive from '@radix-ui/react-avatar'
import * as React from 'react'
import { cn } from '@/lib/utils'
const Avatar = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Root>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Root
ref={ref}
className={cn(
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
className,
)}
{...props}
/>
))
Avatar.displayName = AvatarPrimitive.Root.displayName
const AvatarImage = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Image>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Image
ref={ref}
className={cn('aspect-square h-full w-full', className)}
{...props}
/>
))
AvatarImage.displayName = AvatarPrimitive.Image.displayName
const AvatarFallback = React.forwardRef<
React.ElementRef<typeof AvatarPrimitive.Fallback>,
React.ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
>(({ className, ...props }, ref) => (
<AvatarPrimitive.Fallback
ref={ref}
className={cn(
'flex h-full w-full items-center justify-center rounded-full bg-muted',
className,
)}
{...props}
/>
))
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
export { Avatar, AvatarFallback, AvatarImage }

View file

@ -0,0 +1,36 @@
import { cva, type VariantProps } from 'class-variance-authority'
import * as React from 'react'
import { cn } from '@/lib/utils'
const badgeVariants = cva(
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
{
variants: {
variant: {
default:
'border-transparent bg-primary text-primary-foreground shadow hover:bg-primary/80',
secondary:
'border-transparent bg-secondary text-secondary-foreground hover:bg-secondary/80',
destructive:
'border-transparent bg-destructive text-destructive-foreground shadow hover:bg-destructive/80',
outline: 'text-foreground',
},
},
defaultVariants: {
variant: 'default',
},
},
)
export interface BadgeProps
extends React.HTMLAttributes<HTMLDivElement>,
VariantProps<typeof badgeVariants> {}
function Badge({ className, variant, ...props }: BadgeProps) {
return (
<div className={cn(badgeVariants({ variant }), className)} {...props} />
)
}
export { Badge, badgeVariants }

View file

@ -1,37 +1,37 @@
import * as React from "react" import { Slot } from '@radix-ui/react-slot'
import { Slot } from "@radix-ui/react-slot" import { cva, type VariantProps } from 'class-variance-authority'
import { cva, type VariantProps } from "class-variance-authority" import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const buttonVariants = cva( const buttonVariants = cva(
"inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50", 'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50',
{ {
variants: { variants: {
variant: { variant: {
default: default:
"bg-primary text-primary-foreground shadow hover:bg-primary/90", 'bg-primary text-primary-foreground shadow hover:bg-primary/90',
destructive: destructive:
"bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90", 'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
outline: outline:
"border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground", 'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
secondary: secondary:
"bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80", 'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
ghost: "hover:bg-accent hover:text-accent-foreground", ghost: 'hover:bg-accent hover:text-accent-foreground',
link: "text-primary underline-offset-4 hover:underline", link: 'text-primary underline-offset-4 hover:underline',
}, },
size: { size: {
default: "h-9 px-4 py-2", default: 'h-9 px-4 py-2',
sm: "h-8 rounded-md px-3 text-xs", sm: 'h-8 rounded-md px-3 text-xs',
lg: "h-10 rounded-md px-8", lg: 'h-10 rounded-md px-8',
icon: "h-9 w-9", icon: 'h-9 w-9',
}, },
}, },
defaultVariants: { defaultVariants: {
variant: "default", variant: 'default',
size: "default", size: 'default',
}, },
} },
) )
export interface ButtonProps export interface ButtonProps
@ -42,7 +42,7 @@ export interface ButtonProps
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
({ className, variant, size, asChild = false, ...props }, ref) => { ({ className, variant, size, asChild = false, ...props }, ref) => {
const Comp = asChild ? Slot : "button" const Comp = asChild ? Slot : 'button'
return ( return (
<Comp <Comp
className={cn(buttonVariants({ variant, size, className }))} className={cn(buttonVariants({ variant, size, className }))}
@ -50,8 +50,8 @@ const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
{...props} {...props}
/> />
) )
} },
) )
Button.displayName = "Button" Button.displayName = 'Button'
export { Button, buttonVariants } export { Button, buttonVariants }

View file

@ -1,12 +1,12 @@
import * as React from "react" import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu'
import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu"
import { import {
CheckIcon, CheckIcon,
ChevronRightIcon, ChevronRightIcon,
DotFilledIcon, DotFilledIcon,
} from "@radix-ui/react-icons" } from '@radix-ui/react-icons'
import * as React from 'react'
import { cn } from "@/lib/utils" import { cn } from '@/lib/utils'
const DropdownMenu = DropdownMenuPrimitive.Root const DropdownMenu = DropdownMenuPrimitive.Root
@ -29,9 +29,9 @@ const DropdownMenuSubTrigger = React.forwardRef<
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
ref={ref} ref={ref}
className={cn( className={cn(
"flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent", 'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
> >
@ -49,8 +49,8 @@ const DropdownMenuSubContent = React.forwardRef<
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
ref={ref} ref={ref}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className className,
)} )}
{...props} {...props}
/> />
@ -67,9 +67,9 @@ const DropdownMenuContent = React.forwardRef<
ref={ref} ref={ref}
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
"z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md", 'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
"data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2", 'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
className className,
)} )}
{...props} {...props}
/> />
@ -86,9 +86,9 @@ const DropdownMenuItem = React.forwardRef<
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
/> />
@ -102,8 +102,8 @@ const DropdownMenuCheckboxItem = React.forwardRef<
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
@ -126,8 +126,8 @@ const DropdownMenuRadioItem = React.forwardRef<
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
ref={ref} ref={ref}
className={cn( className={cn(
"relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50", 'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
className className,
)} )}
{...props} {...props}
> >
@ -150,9 +150,9 @@ const DropdownMenuLabel = React.forwardRef<
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
ref={ref} ref={ref}
className={cn( className={cn(
"px-2 py-1.5 text-sm font-semibold", 'px-2 py-1.5 text-sm font-semibold',
inset && "pl-8", inset && 'pl-8',
className className,
)} )}
{...props} {...props}
/> />
@ -165,7 +165,7 @@ const DropdownMenuSeparator = React.forwardRef<
>(({ className, ...props }, ref) => ( >(({ className, ...props }, ref) => (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
ref={ref} ref={ref}
className={cn("-mx-1 my-1 h-px bg-muted", className)} className={cn('-mx-1 my-1 h-px bg-muted', className)}
{...props} {...props}
/> />
)) ))
@ -177,27 +177,27 @@ const DropdownMenuShortcut = ({
}: React.HTMLAttributes<HTMLSpanElement>) => { }: React.HTMLAttributes<HTMLSpanElement>) => {
return ( return (
<span <span
className={cn("ml-auto text-xs tracking-widest opacity-60", className)} className={cn('ml-auto text-xs tracking-widest opacity-60', className)}
{...props} {...props}
/> />
) )
} }
DropdownMenuShortcut.displayName = "DropdownMenuShortcut" DropdownMenuShortcut.displayName = 'DropdownMenuShortcut'
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuTrigger,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioItem, DropdownMenuContent,
DropdownMenuGroup,
DropdownMenuItem,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuPortal,
DropdownMenuRadioGroup,
DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuGroup,
DropdownMenuPortal,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubContent, DropdownMenuSubContent,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuRadioGroup, DropdownMenuTrigger,
} }

View file

@ -1,30 +1,30 @@
import * as React from "react" import { Moon, Sun } from 'lucide-react'
import { Moon, Sun } from "lucide-react" import * as React from 'react'
import { Button } from "@/components/ui/button" import { Button } from '@/components/ui/button'
import { import {
DropdownMenu, DropdownMenu,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuTrigger, DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu" } from '@/components/ui/dropdown-menu'
export function ModeToggle() { export function ModeToggle() {
const [theme, setThemeState] = React.useState< const [theme, setThemeState] = React.useState<
"theme-light" | "dark" | "system" 'theme-light' | 'dark' | 'system'
>("theme-light") >('theme-light')
React.useEffect(() => { React.useEffect(() => {
const isDarkMode = document.documentElement.classList.contains("dark") const isDarkMode = document.documentElement.classList.contains('dark')
setThemeState(isDarkMode ? "dark" : "theme-light") setThemeState(isDarkMode ? 'dark' : 'theme-light')
}, []) }, [])
React.useEffect(() => { React.useEffect(() => {
const isDark = const isDark =
theme === "dark" || theme === 'dark' ||
(theme === "system" && (theme === 'system' &&
window.matchMedia("(prefers-color-scheme: dark)").matches) window.matchMedia('(prefers-color-scheme: dark)').matches)
document.documentElement.classList[isDark ? "add" : "remove"]("dark") document.documentElement.classList[isDark ? 'add' : 'remove']('dark')
}, [theme]) }, [theme])
return ( return (
@ -37,13 +37,13 @@ export function ModeToggle() {
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end"> <DropdownMenuContent align="end">
<DropdownMenuItem onClick={() => setThemeState("theme-light")}> <DropdownMenuItem onClick={() => setThemeState('theme-light')}>
Light Light
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("dark")}> <DropdownMenuItem onClick={() => setThemeState('dark')}>
Dark Dark
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState("system")}> <DropdownMenuItem onClick={() => setThemeState('system')}>
System System
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>

View file

@ -8,7 +8,8 @@ export type Site = {
export const SITE: Site = { export const SITE: Site = {
TITLE: 'astro-erudite', TITLE: 'astro-erudite',
DESCRIPTION: 'astro-erudite is a opinionated, no-frills blogging template. Built with Astro.', DESCRIPTION:
'astro-erudite is a opinionated, no-frills blogging template. Built with Astro.',
EMAIL: 'youremail@gmail.com', EMAIL: 'youremail@gmail.com',
NUM_POSTS_ON_HOMEPAGE: 2, NUM_POSTS_ON_HOMEPAGE: 2,
SITEURL: 'https://astro-erudite.vercel.app', SITEURL: 'https://astro-erudite.vercel.app',

View file

@ -1,112 +0,0 @@
---
title: '[Logs] What is new in Astro Micro Academic'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
__This is bold text__
*This is italic text*
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>> ...by using additional greater-than signs right next to each other...
> > > ...or with spaces between arrows.
## Lists
Unordered
+ Create a list by starting a line with `+`, `-`, or `*`
+ Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
* Ac tristique libero volutpat at
+ Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
+ Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
1. You can use sequential numbers...
1. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
``` js
var foo = function (bar) {
return bar++;
};
console.log(foo(5));
```
## Tables
| Option | Description |
| ------ | ----------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| ------:| -----------:|
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ "title text!")

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2022-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2023-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -0,0 +1,113 @@
---
title: 'Blog Post'
description: 'Features, enhancements, and changes.'
date: '2024-07-25'
tags: ['log', 'rss-feed']
image: '/1200x630.png'
author: 'enscribe'
---
## h2 Heading
### h3 Heading
#### h4 Heading
##### h5 Heading
###### h6 Heading
## Emphasis
**This is bold text**
**This is bold text**
_This is italic text_
_This is italic text_
~~Strikethrough~~
## Blockquotes
> Blockquotes can also be nested...
>
> > ...by using additional greater-than signs right next to each other...
> >
> > > ...or with spaces between arrows.
## Lists
Unordered
- Create a list by starting a line with `+`, `-`, or `*`
- Sub-lists are made by indenting 2 spaces:
- Marker character change forces new list start:
- Ac tristique libero volutpat at
* Facilisis in pretium nisl aliquet
- Nulla volutpat aliquam velit
- Very easy!
Ordered
1. Lorem ipsum dolor sit amet
2. Consectetur adipiscing elit
3. Integer molestie lorem at massa
4. You can use sequential numbers...
5. ...or keep all the numbers as `1.`
Start numbering with offset:
57. foo
1. bar
## Code
Inline `code`
Indented code
// Some comments
line 1 of code
line 2 of code
line 3 of code
Block code "fences"
```
Sample text here...
```
Syntax highlighting
```js
var foo = function (bar) {
return bar++
}
console.log(foo(5))
```
## Tables
| Option | Description |
| ------ | ------------------------------------------------------------------------- |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
Right aligned columns
| Option | Description |
| -----: | ------------------------------------------------------------------------: |
| data | path to data files to supply the data that will be passed into templates. |
| engine | engine to be used for processing templates. Handlebars is the default. |
| ext | extension to be used for dest files. |
## Links
[link text](http://dev.nodeca.com)
[link with title](http://nodeca.github.io/pica/demo/ 'title text!')

View file

@ -7,6 +7,7 @@ const blog = defineCollection({
description: z.string(), description: z.string(),
date: z.coerce.date(), date: z.coerce.date(),
draft: z.boolean().optional(), draft: z.boolean().optional(),
image: z.string().optional(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
author: z.union([reference('authors'), z.string()]).optional(), author: z.union([reference('authors'), z.string()]).optional(),

View file

@ -18,7 +18,7 @@ const { title, description } = Astro.props
<Head title={`${title} | ${SITE.TITLE}`} description={description} /> <Head title={`${title} | ${SITE.TITLE}`} description={description} />
</head> </head>
<body <body
class="box-border flex h-fit min-h-screen flex-col px-4 font-sans bg-background text-foreground antialiased gap-y-4" class="box-border flex h-fit min-h-screen flex-col gap-y-4 bg-background px-4 font-sans text-foreground antialiased"
> >
<Header /> <Header />
<main class="flex-grow"> <main class="flex-grow">

View file

@ -2,18 +2,18 @@
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import Link from '@components/Link.astro' import Link from '@components/Link.astro'
import BackToPrevious from '@components/BackToPrevious.astro'
import { SITE } from '@consts' import { SITE } from '@consts'
import { Button } from '@/components/ui/button'
--- ---
<Layout title="404" description={SITE.DESCRIPTION}> <Layout title="404" description={SITE.DESCRIPTION}>
<Container> <Container>
<div class="mt-16 grid place-items-center gap-3"> <div class="mt-16 grid place-items-center gap-3">
<h4 class="text-2xl font-semibold"> <h4 class="text-2xl font-semibold">404: Page not found</h4>
404: Page not found
</h4>
<span> <span>
<BackToPrevious href="/">Go to home page</BackToPrevious> <a href="/">
<Button variant="outline"> Go to home page </Button>
</a>
</span> </span>
</div> </div>
</Container> </Container>

View file

@ -1,62 +1,35 @@
--- ---
import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import { Image } from 'astro:assets' import Link from '@components/Link.astro'
import { SITE } from '@consts'
--- ---
<Layout title="About" description="About"> <Layout title="About" description={SITE.DESCRIPTION}>
<Container> <Container>
<div class="space-y-10"> <article class="prose max-w-none dark:prose-invert">
<div class="font-semibold">About</div> <h1 class="mb-8 text-3xl font-bold">About Us</h1>
<section class="not-prose flex flex-col gap-4 text-justify"> <p class="mb-4">
<p class="text-justify"> Welcome to {SITE.TITLE}, a platform dedicated to sharing insights,
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores porro knowledge, and experiences in the world of technology and beyond. Our
hic minima incidunt explicabo obcaecati consectetur consequuntur at mission is to provide valuable content that informs, inspires, and
quisquam commodi. engages our readers.
</p> </p>
<p class="mb-8">
Founded in 2023, we've grown into a community of passionate writers and
tech enthusiasts. Our team brings diverse expertise to the table,
covering topics ranging from software development and artificial
intelligence to digital culture and tech ethics.
</p>
<p class="text-justify"> <h2 class="mb-4 text-2xl font-semibold">Get in Touch</h2>
Lorem ipsum dolor sit amet consectetur adipisicing elit. Dolores porro <p class="mb-4">
hic minima incidunt explicabo obcaecati consectetur consequuntur at We love hearing from our readers! Whether you have a question,
quisquam commodi. suggestion, or just want to say hello, don't hesitate to reach out.
</p> </p>
</section> <p>
<Link href="/contact" class="font-semibold">Contact Us</Link>
<div class="flex flex-col justify-center md:flex-row"> </p>
<div class="my-10 text-center"> </article>
<div
class="h-[250px] w-[350px] -rotate-6 overflow-hidden rounded-xl object-cover"
>
<Image
src={'/astro-nano.png'}
alt={'life2'}
width={350}
height={250}
class="h-[250px] w-[350px] overflow-hidden rounded-xl object-cover"
/>
</div>
<p class="mt-4 text-sm">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
<div class="mx-10 my-10 text-center">
<div
class="mx-auto h-[250px] w-[150px] rotate-6 rounded-xl object-cover sm:ml-auto"
>
<Image
src={'/astro-micro.jpg'}
alt={'life2'}
width={150}
height={250}
class="h-[250px] w-[150px] rounded-xl object-cover"
/>
</div>
<p class="mt-4 text-sm">
Lorem ipsum dolor sit amet, consectetur adipisicing elit.
</p>
</div>
</div>
</div>
</Container> </Container>
</Layout> </Layout>

View file

@ -1,29 +1,32 @@
--- ---
import { type CollectionEntry, getCollection, getEntry } from 'astro:content' import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro' import Container from '@components/Container.astro'
import AuthorCard from '@components/AuthorCard.astro'
import BlogCard from '@components/BlogCard.astro'
import { Button } from '@/components/ui/button'
export async function getStaticPaths() { export async function getStaticPaths() {
const authors = await getCollection('authors') const authors = await getCollection('authors')
return authors.map((member) => ({ return authors.map((author) => ({
params: { slug: member.slug }, params: { slug: author.slug },
props: { member }, props: { author },
})) }))
} }
type Props = { type Props = {
member: CollectionEntry<'authors'> author: CollectionEntry<'authors'>
} }
const { member } = Astro.props const { author } = Astro.props
const allPosts = await getCollection('blog') const allPosts = await getCollection('blog')
const memberPosts = allPosts const authorPosts = allPosts
.filter((post) => { .filter((post) => {
if (typeof post.data.author === 'string') { if (typeof post.data.author === 'string') {
return post.data.author === member.data.name && !post.data.draft return post.data.author === author.data.name && !post.data.draft
} else if (post.data.author && 'slug' in post.data.author) { } else if (post.data.author && 'slug' in post.data.author) {
return post.data.author.slug === member.slug && !post.data.draft return post.data.author.slug === author.slug && !post.data.draft
} }
return false return false
}) })
@ -31,10 +34,35 @@ const memberPosts = allPosts
--- ---
<Layout <Layout
title={`${member.data.name} - Team Member`} title={`${author.data.name} - Author`}
description={member.data.bio || `Profile of ${member.data.name}`} description={author.data.bio || `Profile of ${author.data.name}`}
> >
<section class="mx-auto flex max-w-screen-xl flex-col gap-4"> <Container>
<MemberCard member={member} /> <a href="/authors">
</section> <Button variant="ghost" className="mb-8">&larr; Back to authors</Button>
</a>
<div class="space-y-10">
<section>
<AuthorCard author={author} />
</section>
<section class="space-y-4">
<h2 class="text-2xl font-semibold">Posts by {author.data.name}</h2>
{
authorPosts.length > 0 ? (
<ul class="not-prose flex flex-col gap-4">
{authorPosts.map((post) => (
<li>
<BlogCard entry={post} />
</li>
))}
</ul>
) : (
<p class="text-muted-foreground">
No posts available from this author.
</p>
)
}
</section>
</div>
</Container>
</Layout> </Layout>

View file

@ -1,21 +1,25 @@
--- ---
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import MemberCard from '@components/MemberCard.astro' import Container from '@components/Container.astro'
import AuthorCard from '@components/AuthorCard.astro'
const authors = await getCollection('authors') const authors = await getCollection('authors')
--- ---
<Layout title="authors" description="authors"> <Layout title="Authors" description="Authors">
<section> <Container>
<ul class="not-prose grid grid-cols-1 gap-4 lg:grid-cols-2 xl:grid-cols-3"> <div class="space-y-10">
{ <h1 class="text-3xl font-semibold">Authors</h1>
authors.map((member) => ( <ul class="not-prose flex flex-col gap-4">
<li> {
<MemberCard member={member} /> authors.map((author) => (
</li> <li>
)) <AuthorCard author={author} />
} </li>
</ul> ))
</section> }
</ul>
</div>
</Container>
</Layout> </Layout>

View file

@ -1,12 +1,14 @@
--- ---
import { type CollectionEntry, getCollection } from 'astro:content' import { type CollectionEntry, getCollection, getEntry } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import FormattedDate from '@components/FormattedDate.astro' import FormattedDate from '@components/FormattedDate.astro'
import { readingTime } from '@lib/utils' import { readingTime } from '@lib/utils'
import BackToPrevious from '@components/BackToPrevious.astro'
import PostNavigation from '@components/PostNavigation.astro' import PostNavigation from '@components/PostNavigation.astro'
import TableOfContents from '@components/TableOfContents.astro' import TableOfContents from '@components/TableOfContents.astro'
import { Image } from 'astro:assets'
import { Badge } from '@/components/ui/badge'
import { Button } from '@/components/ui/button'
export async function getStaticPaths() { export async function getStaticPaths() {
const posts = (await getCollection('blog')) const posts = (await getCollection('blog'))
@ -45,47 +47,112 @@ const prevPost = getPrevPost(currentPostSlug)
const post = Astro.props const post = Astro.props
const { Content, headings } = await post.render() const { Content, headings } = await post.render()
let author = null
if (
post.data.author &&
typeof post.data.author === 'object' &&
'collection' in post.data.author
) {
author = await getEntry(post.data.author)
} else if (typeof post.data.author === 'string') {
author = {
data: {
name: post.data.author,
avatar: '/favicons/android-chrome-512x512.png',
},
}
}
--- ---
<Layout title={post.data.title} description={post.data.description}> <Layout title={post.data.title} description={post.data.description}>
<Container> <Container>
<div class="animate"> <a href="/blog">
<BackToPrevious href="/blog">Back to blog</BackToPrevious> <Button variant="ghost" className="mb-8">&larr; Back to blog</Button>
</div> </a>
<div class="my-10 space-y-4"> {
<div class="flex items-center gap-1.5"> post.data.image && (
<div class="font-base text-sm"> <div class="mb-8 flex justify-center">
<FormattedDate date={post.data.date} /> <Image
</div> src={post.data.image}
&bull; alt={post.data.title}
<div class="font-base text-sm"> width={1200}
{readingTime(post.body)} height={630}
class="rounded-lg object-cover shadow-lg"
/>
</div> </div>
)
}
<div class="mb-8 flex flex-col items-center gap-y-4">
<div class="flex items-center gap-3 text-sm text-muted-foreground">
<FormattedDate date={post.data.date} />
<span>&bull;</span>
<span>{readingTime(post.body)}</span>
</div> </div>
<h1 class="text-4xl font-semibold"> <h1 class="text-4xl font-bold leading-tight sm:text-5xl">
{post.data.title} {post.data.title}
</h1> </h1>
<div class="font-base text-sm"> <div class="flex flex-wrap gap-2">
{ {
post.data.tags && post.data.tags.length > 0 ? ( post.data.tags && post.data.tags.length > 0 ? (
post.data.tags.map((tag) => ( post.data.tags.map((tag) => (
<div class="my-1 inline-block"> <Button variant="outline" asChild>
<a <a href={`/tags/${tag}`}>
href={`/tags/${tag}`} <Badge variant="outline">{tag}</Badge>
class="mx-1 rounded-full px-2 py-1 transition-colors duration-300 ease-in-out"
>
#{tag}
</a> </a>
</div> </Button>
)) ))
) : ( ) : (
<span>No tags available</span> <span class="text-sm text-muted-foreground">No tags available</span>
) )
} }
</div> </div>
{
author && (
<div>
{typeof post.data.author === 'object' &&
'collection' in post.data.author ? (
<Button variant="ghost" asChild>
<a
class="group flex items-center gap-3 rounded-xl p-2 transition-colors duration-300 ease-in-out hover:bg-secondary"
href={`/authors/${post.data.author.slug}`}
>
<Image
src={author.data.avatar}
alt={`Avatar of ${author.data.name}`}
width={48}
height={48}
class="rounded-full"
/>
<div>
<span class="font-medium group-hover:text-primary">
{author.data.name}
</span>
<p class="text-sm text-muted-foreground">Author</p>
</div>
</a>
</Button>
) : (
<div class="flex items-center gap-3 rounded-xl p-2">
<Image
src={author.data.avatar || '/default-avatar.png'}
alt={`Avatar of ${author.data.name}`}
width={48}
height={48}
class="rounded-full"
/>
<div>
<span class="font-medium">{author.data.name}</span>
<p class="text-sm text-muted-foreground">Author</p>
</div>
</div>
)}
</div>
)
}
</div> </div>
{headings.length > 0 && <TableOfContents headings={headings} />} {headings.length > 0 && <TableOfContents headings={headings} />}
<article class="prose prose-neutral max-w-full dark:prose-invert prose-img:mx-auto prose-img:my-auto"> <article class="max-w-full">
<Content /> <Content />
<div class="mt-24"> <div class="mt-24">
<PostNavigation prevPost={prevPost} nextPost={nextPost} /> <PostNavigation prevPost={prevPost} nextPost={nextPost} />

View file

@ -2,7 +2,7 @@
import { type CollectionEntry, getCollection } from 'astro:content' import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import ArrowCard from '@components/ArrowCard.astro' import BlogCard from '@components/BlogCard.astro'
const data = (await getCollection('blog')) const data = (await getCollection('blog'))
.filter((post) => !post.data.draft) .filter((post) => !post.data.draft)
@ -36,7 +36,7 @@ const years = Object.keys(posts).sort((a, b) => parseInt(b) - parseInt(a))
<ul class="not-prose flex flex-col gap-4"> <ul class="not-prose flex flex-col gap-4">
{posts[year].map((post) => ( {posts[year].map((post) => (
<li> <li>
<ArrowCard entry={post} /> <BlogCard entry={post} />
</li> </li>
))} ))}
</ul> </ul>

View file

@ -2,7 +2,7 @@
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import { SITE } from '@consts' import { SITE } from '@consts'
import ArrowCard from '@components/ArrowCard.astro' import BlogCard from '@components/BlogCard.astro'
import Link from '@components/Link.astro' import Link from '@components/Link.astro'
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
@ -14,7 +14,7 @@ const blog = (await getCollection('blog'))
<Layout title="Home" description="Home"> <Layout title="Home" description="Home">
<Container> <Container>
<section class="space-y-6"> <section class="space-y-4">
<div class="flex flex-wrap items-center justify-between gap-y-2"> <div class="flex flex-wrap items-center justify-between gap-y-2">
<h2 class="font-semibold">Latest posts</h2> <h2 class="font-semibold">Latest posts</h2>
<Link href="/blog"> See all posts </Link> <Link href="/blog"> See all posts </Link>
@ -23,7 +23,7 @@ const blog = (await getCollection('blog'))
{ {
blog.map((post) => ( blog.map((post) => (
<li> <li>
<ArrowCard entry={post} /> <BlogCard entry={post} />
</li> </li>
)) ))
} }

View file

@ -2,7 +2,7 @@
import { type CollectionEntry, getCollection } from 'astro:content' import { type CollectionEntry, getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import ArrowCard from '@components/ArrowCard.astro' import BlogCard from '@components/BlogCard.astro'
type BlogPost = CollectionEntry<'blog'> type BlogPost = CollectionEntry<'blog'>
@ -36,11 +36,11 @@ export async function getStaticPaths() {
> >
<Container> <Container>
<div class="space-y-10"> <div class="space-y-10">
<div class="font-semibold"> <div class="flex items-center gap-2">
Tag: <span <h1 class="text-3xl font-semibold">Posts tagged with</h1>
class="mx-2 rounded-full px-3 py-2 transition-colors duration-300 ease-in-out" <span class="rounded-full bg-secondary px-4 py-2 text-2xl font-medium">
>#{tag}</span {tag}
> </span>
</div> </div>
<div class="space-y-4"> <div class="space-y-4">
{ {
@ -49,7 +49,7 @@ export async function getStaticPaths() {
<div> <div>
<ul class="not-prose flex flex-col gap-4"> <ul class="not-prose flex flex-col gap-4">
<li> <li>
<ArrowCard entry={post} /> <BlogCard entry={post} />
</li> </li>
</ul> </ul>
</div> </div>

View file

@ -2,7 +2,7 @@
import { getCollection } from 'astro:content' import { getCollection } from 'astro:content'
import Layout from '@layouts/Layout.astro' import Layout from '@layouts/Layout.astro'
import Container from '@components/Container.astro' import Container from '@components/Container.astro'
import Image from 'astro/components/Image.astro' import { Badge } from '@/components/ui/badge'
const blog = (await getCollection('blog')).filter((post) => !post.data.draft) const blog = (await getCollection('blog')).filter((post) => !post.data.draft)
@ -14,21 +14,18 @@ const tags = blog
<Layout title="Tags" description="Tags"> <Layout title="Tags" description="Tags">
<Container> <Container>
<div class="space-y-10"> <div class="space-y-10">
<div class="font-semibold">Tags</div> <h1 class="text-3xl font-semibold">Tags</h1>
<ul class="flex flex-wrap"> <div class="flex flex-wrap gap-2">
{ {
tags.map((tag) => ( tags.map((tag) => (
<li class="my-3"> <a href={`/tags/${tag}`}>
<a <Badge variant="secondary" className="hover:bg-secondary/80">
href={`/tags/${tag}`}
class="mx-2 rounded-full px-3 py-2 transition-colors duration-300 ease-in-out"
>
#{tag} #{tag}
</a> </Badge>
</li> </a>
)) ))
} }
</ul> </div>
</div> </div>
</Container> </Container>
</Layout> </Layout>

View file

@ -93,7 +93,7 @@
html { html {
color-scheme: light; color-scheme: light;
scrollbar-gutter: stable; scrollbar-gutter: stable both-edges;
} }
html.dark { html.dark {

View file

@ -4,6 +4,7 @@
@layer base { @layer base {
article { article {
@apply prose prose-neutral dark:prose-invert;
@apply prose-h1:scroll-m-20 prose-h1:text-4xl prose-h1:font-extrabold prose-h1:tracking-tight prose-h1:lg:text-5xl; @apply prose-h1:scroll-m-20 prose-h1:text-4xl prose-h1:font-extrabold prose-h1:tracking-tight prose-h1:lg:text-5xl;
@apply prose-h2:mt-10 prose-h2:scroll-m-20 prose-h2:border-b prose-h2:pb-2 prose-h2:text-3xl prose-h2:font-semibold prose-h2:tracking-tight prose-h2:transition-colors prose-h2:first:mt-0; @apply prose-h2:mt-10 prose-h2:scroll-m-20 prose-h2:border-b prose-h2:pb-2 prose-h2:text-3xl prose-h2:font-semibold prose-h2:tracking-tight prose-h2:transition-colors prose-h2:first:mt-0;
@apply prose-h3:mt-8 prose-h3:scroll-m-20 prose-h3:text-2xl prose-h3:font-semibold prose-h3:tracking-tight; @apply prose-h3:mt-8 prose-h3:scroll-m-20 prose-h3:text-2xl prose-h3:font-semibold prose-h3:tracking-tight;
@ -12,5 +13,6 @@
@apply prose-blockquote:mt-6 prose-blockquote:border-l-2 prose-blockquote:pl-6 prose-blockquote:italic; @apply prose-blockquote:mt-6 prose-blockquote:border-l-2 prose-blockquote:pl-6 prose-blockquote:italic;
@apply prose-ul:my-6 prose-ul:ml-6 prose-ul:list-disc prose-ul:[&>li]:mt-2; @apply prose-ul:my-6 prose-ul:ml-6 prose-ul:list-disc prose-ul:[&>li]:mt-2;
@apply prose-pre:border prose-pre:border-border; @apply prose-pre:border prose-pre:border-border;
@apply prose-img:mx-auto prose-img:my-auto;
} }
} }