feat: opengraph design
10
README.md
|
@ -1 +1,9 @@
|
|||
# erudite
|
||||

|
||||
|
||||
<div align="center">
|
||||
|
||||
## astro-erudite
|
||||
|
||||
astro-erudite is an opinionated, no-frills static blogging template built with [Astro](https://astro.build/) and [Tailwind](https://tailwindcss.com/). Extraordinarily loosely based on the [Astro Micro](https://astro-micro.vercel.app/) theme by [trevortylerlee](https://github.com/trevortylerlee).
|
||||
|
||||
</div>
|
||||
|
|
Before Width: | Height: | Size: 20 KiB |
Before Width: | Height: | Size: 9.9 KiB |
BIN
public/favicons/android-chrome-192x192.png
Normal file
After Width: | Height: | Size: 856 B |
BIN
public/favicons/android-chrome-512x512.png
Normal file
After Width: | Height: | Size: 1.9 KiB |
BIN
public/favicons/apple-touch-icon.png
Normal file
After Width: | Height: | Size: 838 B |
9
public/favicons/browserconfig.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<browserconfig>
|
||||
<msapplication>
|
||||
<tile>
|
||||
<square150x150logo src="/favicons/mstile-150x150.png"/>
|
||||
<TileColor>#da532c</TileColor>
|
||||
</tile>
|
||||
</msapplication>
|
||||
</browserconfig>
|
BIN
public/favicons/favicon-16x16.png
Normal file
After Width: | Height: | Size: 495 B |
BIN
public/favicons/favicon-32x32.png
Normal file
After Width: | Height: | Size: 598 B |
BIN
public/favicons/favicon.ico
Normal file
After Width: | Height: | Size: 15 KiB |
BIN
public/favicons/mstile-150x150.png
Normal file
After Width: | Height: | Size: 1 KiB |
18
public/favicons/safari-pinned-tab.svg
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" standalone="no"?>
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 20010904//EN"
|
||||
"http://www.w3.org/TR/2001/REC-SVG-20010904/DTD/svg10.dtd">
|
||||
<svg version="1.0" xmlns="http://www.w3.org/2000/svg"
|
||||
width="512.000000pt" height="512.000000pt" viewBox="0 0 512.000000 512.000000"
|
||||
preserveAspectRatio="xMidYMid meet">
|
||||
<metadata>
|
||||
Created by potrace 1.14, written by Peter Selinger 2001-2017
|
||||
</metadata>
|
||||
<g transform="translate(0.000000,512.000000) scale(0.100000,-0.100000)"
|
||||
fill="#000000" stroke="none">
|
||||
<path d="M0 4420 l0 -700 2320 0 2320 0 0 -230 0 -230 -2320 0 -2320 0 0 -700
|
||||
0 -700 2320 0 2320 0 0 -230 0 -230 -2320 0 -2320 0 0 -700 0 -700 2560 0
|
||||
2560 0 0 235 0 235 -2320 0 -2320 0 0 230 0 230 2320 0 2320 0 0 700 0 700
|
||||
-2320 0 -2320 0 0 230 0 230 2320 0 2320 0 0 700 0 700 -2320 0 -2320 0 0 230
|
||||
0 230 2320 0 2320 0 0 235 0 235 -2560 0 -2560 0 0 -700z"/>
|
||||
</g>
|
||||
</svg>
|
After Width: | Height: | Size: 877 B |
19
public/favicons/site.webmanifest
Normal file
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"name": "",
|
||||
"short_name": "",
|
||||
"icons": [
|
||||
{
|
||||
"src": "/favicons/android-chrome-192x192.png",
|
||||
"sizes": "192x192",
|
||||
"type": "image/png"
|
||||
},
|
||||
{
|
||||
"src": "/favicons/android-chrome-512x512.png",
|
||||
"sizes": "512x512",
|
||||
"type": "image/png"
|
||||
}
|
||||
],
|
||||
"theme_color": "#121212",
|
||||
"background_color": "#121212",
|
||||
"display": "standalone"
|
||||
}
|
BIN
public/static/1200x630.png
Normal file
After Width: | Height: | Size: 4.5 KiB |
BIN
public/static/512x512.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
public/static/logo.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
20
public/static/logo.svg
Normal file
|
@ -0,0 +1,20 @@
|
|||
<svg width="512" height="512" viewBox="0 0 512 512" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_271_118)">
|
||||
<rect width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="93" width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="186" width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="279" width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="372" width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="465" width="512" height="47" fill="#CCCCCC"/>
|
||||
<rect y="47" width="48" height="46" fill="#CCCCCC"/>
|
||||
<rect x="464" y="140" width="48" height="46" fill="#CCCCCC"/>
|
||||
<rect y="233" width="48" height="46" fill="#CCCCCC"/>
|
||||
<rect x="464" y="326" width="48" height="46" fill="#CCCCCC"/>
|
||||
<rect y="419" width="48" height="46" fill="#CCCCCC"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_271_118">
|
||||
<rect width="512" height="512" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
After Width: | Height: | Size: 857 B |
BIN
public/static/twitter-card.png
Normal file
After Width: | Height: | Size: 17 KiB |
|
@ -1,7 +1,7 @@
|
|||
---
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
import { Image } from 'astro:assets'
|
||||
import Link from '@components/Link.astro'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
|
||||
type Props = {
|
||||
author: CollectionEntry<'authors'>
|
||||
|
@ -15,12 +15,12 @@ const { name, avatar, bio } = author.data
|
|||
class="rounded-xl border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
|
||||
>
|
||||
<Link href={`/authors/${author.slug}`} class="flex flex-wrap gap-4">
|
||||
<Image
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={avatar}
|
||||
alt={`Avatar of ${name}`}
|
||||
width={128}
|
||||
height={128}
|
||||
class="rounded-xl object-cover"
|
||||
fallback={name[0]}
|
||||
className="size-32 rounded-md"
|
||||
/>
|
||||
<div class="flex-grow">
|
||||
<h3 class="mb-1 text-lg font-semibold">{name}</h3>
|
||||
|
|
|
@ -4,6 +4,7 @@ import { formatDate, readingTime, parseAuthors } from '@lib/utils'
|
|||
import { Image } from 'astro:assets'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
import Link from './Link.astro'
|
||||
|
||||
type Props = {
|
||||
|
@ -54,12 +55,12 @@ const authors = await parseAuthors(entry.data.authors ?? [])
|
|||
<>
|
||||
{authors.map((author) => (
|
||||
<div class="flex items-center gap-x-1.5">
|
||||
<Image
|
||||
<AvatarComponent
|
||||
client:load
|
||||
src={author.avatar}
|
||||
alt={author.name}
|
||||
width={18}
|
||||
height={18}
|
||||
class="rounded-full"
|
||||
fallback={author.name[0]}
|
||||
className="size-5 rounded-full"
|
||||
/>
|
||||
<span>{author.name}</span>
|
||||
</div>
|
||||
|
|
|
@ -15,7 +15,7 @@ interface Props {
|
|||
|
||||
const canonicalURL = new URL(Astro.url.pathname, Astro.site)
|
||||
|
||||
const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
|
||||
const { title, description, image = '/static/twitter-card.png' } = Astro.props
|
||||
---
|
||||
|
||||
<meta charset="utf-8" />
|
||||
|
@ -28,6 +28,30 @@ const { title, description, image = '/blog-placeholder-1.jpg' } = Astro.props
|
|||
<meta name="title" content={title} />
|
||||
<meta name="description" content={description} />
|
||||
|
||||
<link
|
||||
rel="apple-touch-icon"
|
||||
sizes="180x180"
|
||||
href="/favicons/apple-touch-icon.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="32x32"
|
||||
href="/favicons/favicon-32x32.png"
|
||||
/>
|
||||
<link
|
||||
rel="icon"
|
||||
type="image/png"
|
||||
sizes="16x16"
|
||||
href="/favicons/favicon-16x16.png"
|
||||
/>
|
||||
<link rel="manifest" href="/favicons/site.webmanifest" />
|
||||
<link rel="mask-icon" href="/favicons/safari-pinned-tab.svg" color="#5bbad5" />
|
||||
<link rel="shortcut icon" href="/favicons/favicon.ico" />
|
||||
<meta name="msapplication-TileColor" content="#da532c" />
|
||||
<meta name="msapplication-config" content="/favicons/browserconfig.xml" />
|
||||
<meta name="theme-color" content="#121212" />
|
||||
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
<meta property="og:title" content={title} />
|
||||
|
|
|
@ -3,6 +3,8 @@ import Container from '@components/Container.astro'
|
|||
import Link from '@components/Link.astro'
|
||||
import { SITE } from '@consts'
|
||||
import { ModeToggle } from '@/components/ui/mode-toggle'
|
||||
import { Image } from 'astro:assets'
|
||||
import logo from '../../public/static/logo.svg'
|
||||
|
||||
const items = [
|
||||
{ href: '/blog', label: 'blog' },
|
||||
|
@ -16,11 +18,12 @@ const items = [
|
|||
transition:persist
|
||||
>
|
||||
<Container>
|
||||
<div class="flex items-center justify-between py-4">
|
||||
<div class="flex flex-wrap items-center justify-between gap-x-4 py-4">
|
||||
<Link
|
||||
href="/"
|
||||
class="text-xl font-semibold transition-colors duration-300 hover:text-primary"
|
||||
class="flex flex-shrink-0 items-center gap-2 text-xl font-semibold transition-colors duration-300 hover:text-primary"
|
||||
>
|
||||
<Image src={logo} alt="Logo" class="size-8 rounded-sm" />
|
||||
{SITE.TITLE}
|
||||
</Link>
|
||||
<div class="flex items-center gap-4">
|
||||
|
|
|
@ -10,7 +10,7 @@ const Avatar = React.forwardRef<
|
|||
<AvatarPrimitive.Root
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full',
|
||||
'relative flex h-10 w-10 shrink-0 overflow-hidden',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
@ -37,7 +37,7 @@ const AvatarFallback = React.forwardRef<
|
|||
<AvatarPrimitive.Fallback
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex h-full w-full items-center justify-center rounded-full bg-muted',
|
||||
'flex h-full w-full items-center justify-center bg-muted',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
@ -45,4 +45,25 @@ const AvatarFallback = React.forwardRef<
|
|||
))
|
||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
||||
|
||||
export { Avatar, AvatarFallback, AvatarImage }
|
||||
interface AvatarComponentProps {
|
||||
src?: string
|
||||
alt?: string
|
||||
fallback?: string
|
||||
className?: string
|
||||
}
|
||||
|
||||
const AvatarComponent: React.FC<AvatarComponentProps> = ({
|
||||
src,
|
||||
alt,
|
||||
fallback,
|
||||
className,
|
||||
}) => {
|
||||
return (
|
||||
<Avatar className={className}>
|
||||
<AvatarImage src={src} alt={alt} />
|
||||
<AvatarFallback>{fallback}</AvatarFallback>
|
||||
</Avatar>
|
||||
)
|
||||
}
|
||||
|
||||
export default AvatarComponent
|
||||
|
|
|
@ -5,7 +5,7 @@ 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 transition-colors focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2',
|
||||
'inline-flex items-center rounded-full border px-2.5 py-0.5 text-xs transition-colors focus:outline-none focus:ring focus:ring-ring',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
|
|
|
@ -8,7 +8,7 @@ const Card = React.forwardRef<
|
|||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('rounded-xl border bg-card text-card-foreground', className)}
|
||||
className={cn('bg-card text-card-foreground rounded-xl border', className)}
|
||||
{...props}
|
||||
/>
|
||||
))
|
||||
|
|
|
@ -41,7 +41,7 @@ export function ModeToggle() {
|
|||
<span className="sr-only">Toggle theme</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuContent align="end" className="bg-background">
|
||||
<DropdownMenuItem onClick={() => setThemeState('theme-light')}>
|
||||
<Sun className="mr-2 size-4" />
|
||||
<span>Light</span>
|
||||
|
|
|
@ -3,7 +3,7 @@ title: '2022 Post'
|
|||
description: 'This a dummy post written in the year 2022.'
|
||||
date: '2022-01-01'
|
||||
tags: ['dummy', 'placeholder']
|
||||
image: '/1200x630.png'
|
||||
image: '/static/1200x630.png'
|
||||
---
|
||||
|
||||
This is a dummy post written in the year 2022.
|
||||
|
|
|
@ -3,7 +3,7 @@ title: '2023 Post'
|
|||
description: 'This a dummy post written in the year 2023.'
|
||||
date: '2023-01-01'
|
||||
tags: ['dummy', 'placeholder']
|
||||
image: '/1200x630.png'
|
||||
image: '/static/1200x630.png'
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ title: '2024 Post'
|
|||
description: 'This a dummy post written in the year 2024 (with multiple authors).'
|
||||
date: '2024-01-01'
|
||||
tags: ['dummy', 'placeholder']
|
||||
image: '/1200x630.png'
|
||||
image: '/static/1200x630.png'
|
||||
authors: ['enscribe', 'jktrn']
|
||||
---
|
||||
|
||||
|
|
|
@ -3,7 +3,7 @@ title: 'The State of Static Blogs in 2024'
|
|||
description: 'There should not be a single reason why you would need a command palette search bar to find a blog post on your own site.'
|
||||
date: '2024-07-25'
|
||||
tags: ['webdev', 'opinion']
|
||||
image: '/1200x630.png'
|
||||
image: '/static/1200x630.png'
|
||||
authors: ['enscribe']
|
||||
---
|
||||
|
||||
|
@ -19,7 +19,7 @@ astro-erudite is written in Astro, a framework hyperoptimized for static content
|
|||
|
||||
This is a non-exhaustive list of features I believe are essential for a frictionless developer experience:
|
||||
|
||||
- [shadcn/ui](https://ui.shadcn.com) is a pretty controversial component library. I love it. I don't care much for the components themselves as they are literally [Radix](https://www.radix-ui.com/) primitive wrappers. However, the best part is arguably its take on [theming](https://ui.shadcn.com/docs/theming), which introduces a convention involving CSS colors such as `background` and `foreground` into your Tailwind configuration so that styling is a breeze. These classes also automatically adapt to the user's selected theme, and as such you don't need to worry about adding an equivalent `dark:` style to all of your theming. shadcn/ui turns `"bg-neutral-50 text-neutral-900 dark:bg-neutral-900 dark:text-neutral-50"` into `"bg-background text-foreground"`, both more semantic and easier to blanket edit (if you wanted to change all your blues in your site to indigos, you would need to go around every single class and change it rather than editing a single CSS variable). Other utiliy colors such as `secondary`, `muted`, `accent`, and `destructive` also exist and are very self-explanatory in name (and also have an equivalent `-foreground` class, e.g. `secondary-foreground`, which you can apply to text on top of these colors).
|
||||
- [shadcn/ui](https://ui.shadcn.com) is a pretty controversial component library. I love it. I don't care much for the components themselves as they are literally [Radix](https://www.radix-ui.com/) primitive wrappers. However, the best part is arguably its take on [theming](https://ui.shadcn.com/docs/theming), which introduces a convention involving CSS colors such as `background` and `foreground` into your Tailwind configuration so that styling is a breeze. These classes also automatically adapt to the user's selected theme, and as such you don't need to worry about adding an equivalent `dark:` style to all of your theming. shadcn/ui turns `"bg-stone-50 text-stone-900 dark:bg-stone-900 dark:text-stone-50"` into `"bg-background text-foreground"`, both more semantic and easier to blanket edit (if you wanted to change all your blues in your site to indigos, you would need to go around every single class and change it rather than editing a single CSS variable). Other utiliy colors such as `secondary`, `muted`, `accent`, and `destructive` also exist and are very self-explanatory in name (and also have an equivalent `-foreground` class, e.g. `secondary-foreground`, which you can apply to text on top of these colors).
|
||||
- [Tailwind Typography](https://github.com/tailwindlabs/tailwindcss-typography) is a plugin that automatically styles any content surrounded by an `<article>{:html}` tag in a way which makes it readable and blog-post-friendly. It does this via a `prose` class which you can wrap anything with to style the interior content. This is especially useful for HTML you don't control, e.g. a post rendered from Markdown. Although your control over the rendering is a bit less fine-grained, you're also already using Tailwind so this right has long been forsaken.
|
||||
- [Shiki](https://github.com/shikijs/shiki) is a syntax highlighter for code blocks. Although Astro code blocks utilize Shiki under the hood, I've actually disabled the default code blocks in this template so that they don't collide with my preferred library [rehype-pretty-code](https://rehype-pretty.pages.dev), which is _also_ powered by Shiki but allows for line numbers, line highlighting, inline code snytax highlighting, and a transformers API for advanced customization such as manual `diff` visualization and line blurring. This library does not ship with any CSS, and it's up to you to style the code blocks and code block titles as you see fit. I've provided styles in `src/styles/global.css` within the `@layer components{:css}` directive if you wish to fiddle with them. The following code block is an example of how to style code blocks using rehype-pretty-code, and was generated with the following Markdown code:
|
||||
|
||||
|
@ -115,5 +115,5 @@ Within the blog itself (as in the layout, appearance, and navigation) are featur
|
|||
- You really don't need a comments section via [Giscus](https://giscus.app). This opens up a can of worms involving the ability to spam comments and the necessity to moderate them. If you want organic discussion about your blog posts to happen, then share on social media and let people discuss there.
|
||||
- Speaking of sharing on social media, let's get rid of the share buttons. Please inform me of a single time you have used a share button on a blog post.
|
||||
- You really don't need a <abbr title="Content Management System">CMS</abbr> unless you have thousands of posts and/or are willing to navigate through a clunky management interface. Markdown and folders is really all you need, which you can organize to your preference via folder or file naming conventions.
|
||||
- If you have literally anything involving an `.env` file in a blog post, please reconsider what you are doing.
|
||||
- If you have literally anything involving an `.env` file in a blogging site, please think about what you are doing very carefully.
|
||||
- Please do not override the browser's <kbd>Ctrl</kbd> + <kbd>K</kbd> functionality to open up a command palette. There should not be a single reason why a user would use a small context menu to browse your blog over the `/blog` route. Most of the time, command palettes on sites do nothing more than regurgitate shortcuts that are already on the same page you're hiding with the palette's modal.
|
||||
|
|
|
@ -7,23 +7,30 @@ import { SITE } from '@consts'
|
|||
type Props = {
|
||||
title: string
|
||||
description: string
|
||||
image?: string
|
||||
}
|
||||
|
||||
const { title, description } = Astro.props
|
||||
const { title, description, image } = Astro.props
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<Head title={`${title} | ${SITE.TITLE}`} description={description} />
|
||||
<Head
|
||||
title={`${title} | ${SITE.TITLE}`}
|
||||
description={description}
|
||||
image={image}
|
||||
/>
|
||||
</head>
|
||||
<body
|
||||
class="box-border flex h-fit min-h-screen flex-col gap-y-6 bg-background px-4 font-sans text-foreground antialiased"
|
||||
>
|
||||
<Header />
|
||||
<main class="flex-grow">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
<body>
|
||||
<div
|
||||
class="box-border flex h-fit min-h-screen flex-col gap-y-6 bg-background px-4 font-sans text-foreground antialiased"
|
||||
>
|
||||
<Header />
|
||||
<main class="flex-grow">
|
||||
<slot />
|
||||
</main>
|
||||
<Footer />
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
|
|
@ -29,14 +29,14 @@ export async function parseAuthors(authors: string[]) {
|
|||
const author = await getEntry('authors', slug)
|
||||
return {
|
||||
name: author?.data?.name || slug,
|
||||
avatar: author?.data?.avatar || '/512x512.png',
|
||||
avatar: author?.data?.avatar || '/static/512x512.png',
|
||||
isRegistered: !!author,
|
||||
}
|
||||
} catch (error) {
|
||||
console.error(`Error fetching author with slug ${slug}:`, error)
|
||||
return {
|
||||
name: slug,
|
||||
avatar: '/512x512.png',
|
||||
avatar: '/static/512x512.png',
|
||||
isRegistered: false,
|
||||
}
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ const authorPosts = allPosts
|
|||
|
||||
<Layout
|
||||
title={`${author.data.name} - Author`}
|
||||
description={author.data.bio || `Profile of ${author.data.name}`}
|
||||
description={author.data.bio || `Profile of ${author.data.name}.`}
|
||||
>
|
||||
<Container>
|
||||
<Breadcrumb className="mb-6">
|
||||
|
|
|
@ -16,7 +16,7 @@ import { HomeIcon } from 'lucide-react'
|
|||
const authors = await getCollection('authors')
|
||||
---
|
||||
|
||||
<Layout title="Authors" description="Authors">
|
||||
<Layout title="Authors" description="A list of authors on this site.">
|
||||
<Container>
|
||||
<Breadcrumb className="mb-6">
|
||||
<BreadcrumbList>
|
||||
|
|
|
@ -62,7 +62,11 @@ const { Content, headings } = await post.render()
|
|||
const authors = await parseAuthors(post.data.authors ?? [])
|
||||
---
|
||||
|
||||
<Layout title={post.data.title} description={post.data.description}>
|
||||
<Layout
|
||||
title={post.data.title}
|
||||
description={post.data.description}
|
||||
image={post.data.image ?? '/static/1200x630.png'}
|
||||
>
|
||||
<Container>
|
||||
<Breadcrumb className="mb-6">
|
||||
<BreadcrumbList>
|
||||
|
@ -89,7 +93,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
|||
alt={post.data.title}
|
||||
width={1200}
|
||||
height={630}
|
||||
class="mb-8 rounded-xl object-cover shadow-lg"
|
||||
class="mb-8 rounded-xl object-cover"
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
@ -162,7 +166,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
|||
|
||||
{headings.length > 0 && <TableOfContents headings={headings} />}
|
||||
|
||||
<article class="prose prose-neutral max-w-none dark:prose-invert">
|
||||
<article class="prose prose-stone max-w-none dark:prose-invert">
|
||||
<Content />
|
||||
</article>
|
||||
|
||||
|
@ -170,7 +174,7 @@ const authors = await parseAuthors(post.data.authors ?? [])
|
|||
</Container>
|
||||
|
||||
<Button
|
||||
variant="secondary"
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="group fixed bottom-8 right-8 z-50 hidden"
|
||||
id="scroll-to-top"
|
||||
|
|
|
@ -20,7 +20,7 @@ const blog = (await getCollection('blog'))
|
|||
.slice(0, SITE.NUM_POSTS_ON_HOMEPAGE)
|
||||
---
|
||||
|
||||
<Layout title="Home" description="Home">
|
||||
<Layout title={SITE.TITLE} description={SITE.DESCRIPTION}>
|
||||
<Container class="flex flex-col gap-y-6">
|
||||
<section>
|
||||
<Card>
|
||||
|
|
|
@ -112,7 +112,7 @@
|
|||
|
||||
/* Code block titles */
|
||||
[data-rehype-pretty-code-title] {
|
||||
@apply rounded-t-xl border-x border-t px-4 py-2 text-sm font-medium !text-foreground;
|
||||
@apply rounded-t-xl border-x border-t px-4 py-2 text-sm font-medium text-foreground;
|
||||
|
||||
/* Remove top margin from code block if a title is present */
|
||||
& + pre {
|
||||
|
|
|
@ -10,22 +10,9 @@ const config: Config = {
|
|||
sans: ['Geist Sans', ...defaultTheme.fontFamily.sans],
|
||||
mono: ['Geist Mono', ...defaultTheme.fontFamily.mono],
|
||||
},
|
||||
borderRadius: {
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)',
|
||||
},
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
card: {
|
||||
DEFAULT: 'hsl(var(--card))',
|
||||
foreground: 'hsl(var(--card-foreground))',
|
||||
},
|
||||
popover: {
|
||||
DEFAULT: 'hsl(var(--popover))',
|
||||
foreground: 'hsl(var(--popover-foreground))',
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))',
|
||||
|
@ -51,15 +38,7 @@ const config: Config = {
|
|||
foreground: 'hsl(var(--destructive-foreground))',
|
||||
},
|
||||
border: 'hsl(var(--border))',
|
||||
input: 'hsl(var(--input))',
|
||||
ring: 'hsl(var(--ring))',
|
||||
chart: {
|
||||
'1': 'hsl(var(--chart-1))',
|
||||
'2': 'hsl(var(--chart-2))',
|
||||
'3': 'hsl(var(--chart-3))',
|
||||
'4': 'hsl(var(--chart-4))',
|
||||
'5': 'hsl(var(--chart-5))',
|
||||
},
|
||||
},
|
||||
typography: {
|
||||
DEFAULT: {
|
||||
|
|