refactor: biome lint
All checks were successful
build dist / build-dist (push) Successful in 32s

This commit is contained in:
z0x 2025-04-24 22:12:22 -04:00
parent 844d31754d
commit 36870785bc
35 changed files with 1344 additions and 1330 deletions

View file

@ -1,20 +1,20 @@
{ {
"$schema": "https://ui.shadcn.com/schema.json", "$schema": "https://ui.shadcn.com/schema.json",
"style": "new-york", "style": "new-york",
"rsc": false, "rsc": false,
"tsx": true, "tsx": true,
"tailwind": { "tailwind": {
"config": "tailwind.config.ts", "config": "tailwind.config.ts",
"css": "src/styles/global.css", "css": "src/styles/global.css",
"baseColor": "neutral", "baseColor": "neutral",
"cssVariables": true, "cssVariables": true,
"prefix": "" "prefix": ""
}, },
"aliases": { "aliases": {
"components": "@/components", "components": "@/components",
"utils": "@/lib/utils", "utils": "@/lib/utils",
"ui": "@/components/ui", "ui": "@/components/ui",
"lib": "@/lib", "lib": "@/lib",
"hooks": "@/hooks" "hooks": "@/hooks"
} }
} }

View file

@ -1,63 +1,63 @@
{ {
"name": "astro-erudite", "name": "astro-erudite",
"type": "module", "type": "module",
"version": "1.4.2", "version": "1.4.2",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "astro dev", "dev": "astro dev",
"start": "astro dev", "start": "astro dev",
"build": "astro build", "build": "astro build",
"preview": "astro preview", "preview": "astro preview",
"astro": "astro", "astro": "astro",
"prettier": "prettier --write .", "prettier": "prettier --write .",
"postinstall": "patch-package" "postinstall": "patch-package"
}, },
"dependencies": { "dependencies": {
"@astrojs/markdown-remark": "^6.3.1", "@astrojs/markdown-remark": "^6.3.1",
"@astrojs/partytown": "^2.1.4", "@astrojs/partytown": "^2.1.4",
"@astrojs/react": "^4.2.5", "@astrojs/react": "^4.2.5",
"@astrojs/rss": "^4.0.11", "@astrojs/rss": "^4.0.11",
"@astrojs/sitemap": "^3.3.1", "@astrojs/sitemap": "^3.3.1",
"@expressive-code/plugin-collapsible-sections": "^0.40.2", "@expressive-code/plugin-collapsible-sections": "^0.40.2",
"@expressive-code/plugin-line-numbers": "^0.40.2", "@expressive-code/plugin-line-numbers": "^0.40.2",
"@fontsource-variable/geist": "^5.2.5", "@fontsource-variable/geist": "^5.2.5",
"@fontsource-variable/geist-mono": "^5.2.5", "@fontsource-variable/geist-mono": "^5.2.5",
"@iconify-json/lucide": "^1.2.39", "@iconify-json/lucide": "^1.2.39",
"@iconify-json/simple-icons": "^1.2.33", "@iconify-json/simple-icons": "^1.2.33",
"@r4ai/remark-callout": "^0.6.2", "@r4ai/remark-callout": "^0.6.2",
"@radix-ui/react-avatar": "^1.1.7", "@radix-ui/react-avatar": "^1.1.7",
"@radix-ui/react-dropdown-menu": "^2.1.12", "@radix-ui/react-dropdown-menu": "^2.1.12",
"@radix-ui/react-icons": "^1.3.2", "@radix-ui/react-icons": "^1.3.2",
"@radix-ui/react-scroll-area": "^1.2.6", "@radix-ui/react-scroll-area": "^1.2.6",
"@radix-ui/react-separator": "^1.1.4", "@radix-ui/react-separator": "^1.1.4",
"@radix-ui/react-slot": "^1.2.0", "@radix-ui/react-slot": "^1.2.0",
"@tailwindcss/vite": "^4.1.4", "@tailwindcss/vite": "^4.1.4",
"@types/react": "19.0.0", "@types/react": "19.0.0",
"@types/react-dom": "19.0.0", "@types/react-dom": "19.0.0",
"@yeskunall/astro-umami": "^0.0.5", "@yeskunall/astro-umami": "^0.0.5",
"astro": "^5.7.5", "astro": "^5.7.5",
"astro-expressive-code": "^0.40.2", "astro-expressive-code": "^0.40.2",
"astro-icon": "^1.1.5", "astro-icon": "^1.1.5",
"class-variance-authority": "^0.7.1", "class-variance-authority": "^0.7.1",
"clsx": "^2.1.1", "clsx": "^2.1.1",
"lucide-react": "^0.469.0", "lucide-react": "^0.469.0",
"patch-package": "^8.0.0", "patch-package": "^8.0.0",
"react": "19.0.0", "react": "19.0.0",
"react-dom": "19.0.0", "react-dom": "19.0.0",
"rehype-document": "^7.0.3", "rehype-document": "^7.0.3",
"rehype-external-links": "^3.0.0", "rehype-external-links": "^3.0.0",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"rehype-pretty-code": "^0.14.1", "rehype-pretty-code": "^0.14.1",
"remark-emoji": "^5.0.1", "remark-emoji": "^5.0.1",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"remark-sectionize": "^2.1.0", "remark-sectionize": "^2.1.0",
"remark-toc": "^9.0.0", "remark-toc": "^9.0.0",
"tailwind-merge": "^3.2.0", "tailwind-merge": "^3.2.0",
"tailwindcss": "^4.1.4", "tailwindcss": "^4.1.4",
"typescript": "^5.8.3" "typescript": "^5.8.3"
}, },
"devDependencies": { "devDependencies": {
"@biomejs/biome": "^1.9.4" "@biomejs/biome": "^1.9.4"
}, },
"trustedDependencies": ["@biomejs/biome", "esbuild", "sharp"] "trustedDependencies": ["@biomejs/biome", "esbuild", "sharp"]
} }

View file

@ -1,20 +1,20 @@
--- ---
import { Separator } from '@/components/ui/separator' import { Image } from "astro:assets";
import { formatDate, readingTime } from '@/lib/utils' import type { CollectionEntry } from "astro:content";
import { Image } from 'astro:assets' import { Separator } from "@/components/ui/separator";
import type { CollectionEntry } from 'astro:content' import { formatDate, readingTime } from "@/lib/utils";
import Link from './Link.astro' import Link from "./Link.astro";
type Props = { type Props = {
entry: CollectionEntry<'blog'> entry: CollectionEntry<"blog">;
} };
const { entry } = Astro.props as { const { entry } = Astro.props as {
entry: CollectionEntry<'blog'> entry: CollectionEntry<"blog">;
} };
const formattedDate = formatDate(entry.data.date) const formattedDate = formatDate(entry.data.date);
const readTime = readingTime(entry.body!) const readTime = readingTime(entry.body!);
--- ---
<div <div

View file

@ -1,26 +1,26 @@
--- ---
import { import {
Breadcrumb, Breadcrumb,
BreadcrumbItem, BreadcrumbItem,
BreadcrumbLink, BreadcrumbLink,
BreadcrumbList, BreadcrumbList,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
} from '@/components/ui/breadcrumb' } from "@/components/ui/breadcrumb";
import { Icon } from 'astro-icon/components' import { Icon } from "astro-icon/components";
export interface BreadcrumbItem { export interface BreadcrumbItem {
href?: string href?: string;
label: string label: string;
icon?: string icon?: string;
} }
interface Props { interface Props {
items: BreadcrumbItem[] items: BreadcrumbItem[];
class?: string class?: string;
} }
const { items, class: className } = Astro.props const { items, class: className } = Astro.props;
--- ---
<Breadcrumb className={className}> <Breadcrumb className={className}>

View file

@ -1,11 +1,11 @@
--- ---
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
interface Props { interface Props {
class?: string class?: string;
} }
const { class: className } = Astro.props const { class: className } = Astro.props;
--- ---
<div class={cn('mx-auto max-w-(--breakpoint-md) px-4', className)}> <div class={cn('mx-auto max-w-(--breakpoint-md) px-4', className)}>

View file

@ -1,8 +1,8 @@
--- ---
import Container from '@/components/Container.astro' import Container from "@/components/Container.astro";
import { Separator } from '@/components/ui/separator' import { Separator } from "@/components/ui/separator";
import { SOCIAL_LINKS } from '@/consts' import { SOCIAL_LINKS } from "@/consts";
import SocialIcons from './SocialIcons.astro' import SocialIcons from "./SocialIcons.astro";
--- ---
<footer class="py-4"> <footer class="py-4">

View file

@ -1,22 +1,22 @@
--- ---
import '@fontsource-variable/geist' import "@fontsource-variable/geist";
import '@fontsource-variable/geist-mono' import "@fontsource-variable/geist-mono";
import '../styles/callout.css' import "../styles/callout.css";
import '../styles/global.css' import "../styles/global.css";
import '../styles/typography.css' import "../styles/typography.css";
import { SITE } from '@/consts' import { ClientRouter } from "astro:transitions";
import { ClientRouter } from 'astro:transitions' import { SITE } from "@/consts";
interface Props { interface Props {
title: string title: string;
description: string description: string;
image?: string image?: string;
} }
const canonicalURL = new URL(Astro.url.pathname, Astro.site) const canonicalURL = new URL(Astro.url.pathname, Astro.site);
const { title, description, image = '/static/twitter-card.png' } = Astro.props const { title, description, image = "/static/twitter-card.png" } = Astro.props;
--- ---
<meta charset="utf-8" /> <meta charset="utf-8" />

View file

@ -1,8 +1,8 @@
--- ---
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 { ModeToggle } from '@/components/ui/mode-toggle' import { ModeToggle } from "@/components/ui/mode-toggle";
import { SITE } from '@/consts' import { SITE } from "@/consts";
--- ---
<header <header

View file

@ -1,15 +1,15 @@
--- ---
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
type Props = { type Props = {
href: string href: string;
external?: boolean external?: boolean;
class?: string class?: string;
underline?: boolean underline?: boolean;
[key: string]: any [key: string]: any;
} };
const { href, external, class: className, underline, ...rest } = Astro.props const { href, external, class: className, underline, ...rest } = Astro.props;
--- ---
<a <a

View file

@ -1,10 +1,10 @@
--- ---
import Link from '@/components/Link.astro' import Link from "@/components/Link.astro";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import { Icon } from 'astro-icon/components' import { Icon } from "astro-icon/components";
const { prevPost, nextPost } = Astro.props const { prevPost, nextPost } = Astro.props;
--- ---
<div class="col-start-2 grid grid-cols-1 gap-4 sm:grid-cols-2"> <div class="col-start-2 grid grid-cols-1 gap-4 sm:grid-cols-2">

View file

@ -1,15 +1,15 @@
--- ---
import Link from '@/components/Link.astro' import Link from "@/components/Link.astro";
import { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
import { ICON_MAP } from '@/consts' import { ICON_MAP } from "@/consts";
import type { SocialLink } from '@/types' import type { SocialLink } from "@/types";
import { Icon } from 'astro-icon/components' import { Icon } from "astro-icon/components";
interface Props { interface Props {
links: SocialLink[] links: SocialLink[];
} }
const { links } = Astro.props const { links } = Astro.props;
--- ---
<ul class="flex flex-wrap gap-2" role="list"> <ul class="flex flex-wrap gap-2" role="list">

View file

@ -1,23 +1,23 @@
--- ---
import { ScrollArea } from '@/components/ui/scroll-area' import { ScrollArea } from "@/components/ui/scroll-area";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
import type { MarkdownHeading } from 'astro' import type { MarkdownHeading } from "astro";
import { Icon } from 'astro-icon/components' import { Icon } from "astro-icon/components";
type Props = { type Props = {
headings: MarkdownHeading[] headings: MarkdownHeading[];
} };
const { headings } = Astro.props const { headings } = Astro.props;
function getHeadingMargin(depth: number): string { function getHeadingMargin(depth: number): string {
const margins: Record<number, string> = { const margins: Record<number, string> = {
3: 'ml-4', 3: "ml-4",
4: 'ml-8', 4: "ml-8",
5: 'ml-12', 5: "ml-12",
6: 'ml-16', 6: "ml-16",
} };
return margins[depth] || '' return margins[depth] || "";
} }
--- ---

View file

@ -1,8 +1,8 @@
--- ---
import Link from './Link.astro' import Link from "./Link.astro";
import type { Heading } from './TableOfContents.astro' import type { Heading } from "./TableOfContents.astro";
const { heading } = Astro.props const { heading } = Astro.props;
--- ---
<li <li

View file

@ -1,74 +1,74 @@
import * as React from 'react' import * as AvatarPrimitive from "@radix-ui/react-avatar";
import * as AvatarPrimitive from '@radix-ui/react-avatar' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
function Avatar({ function Avatar({
className, className,
...props ...props
}: React.ComponentProps<typeof AvatarPrimitive.Root>) { }: React.ComponentProps<typeof AvatarPrimitive.Root>) {
return ( return (
<AvatarPrimitive.Root <AvatarPrimitive.Root
data-slot="avatar" data-slot="avatar"
className={cn( className={cn(
'relative flex size-8 shrink-0 overflow-hidden rounded-full', "relative flex size-8 shrink-0 overflow-hidden rounded-full",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function AvatarImage({ function AvatarImage({
className, className,
...props ...props
}: React.ComponentProps<typeof AvatarPrimitive.Image>) { }: React.ComponentProps<typeof AvatarPrimitive.Image>) {
return ( return (
<AvatarPrimitive.Image <AvatarPrimitive.Image
data-slot="avatar-image" data-slot="avatar-image"
className={cn('aspect-square size-full', className)} className={cn("aspect-square size-full", className)}
{...props} {...props}
/> />
) );
} }
function AvatarFallback({ function AvatarFallback({
className, className,
...props ...props
}: React.ComponentProps<typeof AvatarPrimitive.Fallback>) { }: React.ComponentProps<typeof AvatarPrimitive.Fallback>) {
return ( return (
<AvatarPrimitive.Fallback <AvatarPrimitive.Fallback
data-slot="avatar-fallback" data-slot="avatar-fallback"
className={cn( className={cn(
'bg-muted flex size-full items-center justify-center rounded-full', "bg-muted flex size-full items-center justify-center rounded-full",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
const AvatarComponent: React.FC<AvatarComponentProps> = ({ const AvatarComponent: React.FC<AvatarComponentProps> = ({
src, src,
alt, alt,
fallback, fallback,
className, className,
}) => { }) => {
return ( return (
<Avatar className={className}> <Avatar className={className}>
<AvatarImage src={src} alt={alt} /> <AvatarImage src={src} alt={alt} />
<AvatarFallback>{fallback}</AvatarFallback> <AvatarFallback>{fallback}</AvatarFallback>
</Avatar> </Avatar>
) );
} };
export default AvatarComponent export default AvatarComponent;
interface AvatarComponentProps { interface AvatarComponentProps {
src?: string src?: string;
alt?: string alt?: string;
fallback: string fallback: string;
className?: string className?: string;
} }
export { Avatar, AvatarImage, AvatarFallback } export { Avatar, AvatarImage, AvatarFallback };

View file

@ -1,46 +1,46 @@
import * as React from 'react' import { Slot } from "@radix-ui/react-slot";
import { Slot } from '@radix-ui/react-slot' import { type VariantProps, cva } from "class-variance-authority";
import { cva, type VariantProps } from 'class-variance-authority' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
const badgeVariants = cva( const badgeVariants = cva(
'inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden', "inline-flex items-center justify-center rounded-md border px-2 py-0.5 text-xs font-medium w-fit whitespace-nowrap shrink-0 [&>svg]:size-3 gap-1 [&>svg]:pointer-events-none focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive transition-[color,box-shadow] overflow-hidden",
{ {
variants: { variants: {
variant: { variant: {
default: default:
'border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90', "border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
secondary: secondary:
'border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90', "border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
destructive: destructive:
'border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70', "border-transparent bg-destructive text-white [a&]:hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/70",
outline: outline:
'text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground', "text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
}, },
}, },
defaultVariants: { defaultVariants: {
variant: 'default', variant: "default",
}, },
}, },
) );
function Badge({ function Badge({
className, className,
variant, variant,
asChild = false, asChild = false,
...props ...props
}: React.ComponentProps<'span'> & }: React.ComponentProps<"span"> &
VariantProps<typeof badgeVariants> & { asChild?: boolean }) { VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
const Comp = asChild ? Slot : 'span' const Comp = asChild ? Slot : "span";
return ( return (
<Comp <Comp
data-slot="badge" data-slot="badge"
className={cn(badgeVariants({ variant }), className)} className={cn(badgeVariants({ variant }), className)}
{...props} {...props}
/> />
) );
} }
export { Badge, badgeVariants } export { Badge, badgeVariants };

View file

@ -1,108 +1,108 @@
import * as React from 'react' import { cn } from "@/lib/utils";
import { Slot } from '@radix-ui/react-slot' import { ChevronRightIcon, DotsHorizontalIcon } from "@radix-ui/react-icons";
import { cn } from '@/lib/utils' import { Slot } from "@radix-ui/react-slot";
import { ChevronRightIcon, DotsHorizontalIcon } from '@radix-ui/react-icons' import type * as React from "react";
function Breadcrumb({ ...props }: React.ComponentProps<'nav'>) { function Breadcrumb({ ...props }: React.ComponentProps<"nav">) {
return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} /> return <nav aria-label="breadcrumb" data-slot="breadcrumb" {...props} />;
} }
function BreadcrumbList({ className, ...props }: React.ComponentProps<'ol'>) { function BreadcrumbList({ className, ...props }: React.ComponentProps<"ol">) {
return ( return (
<ol <ol
data-slot="breadcrumb-list" data-slot="breadcrumb-list"
className={cn( className={cn(
'text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5', "text-muted-foreground flex flex-wrap items-center gap-1.5 text-sm break-words sm:gap-2.5",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function BreadcrumbItem({ className, ...props }: React.ComponentProps<'li'>) { function BreadcrumbItem({ className, ...props }: React.ComponentProps<"li">) {
return ( return (
<li <li
data-slot="breadcrumb-item" data-slot="breadcrumb-item"
className={cn('inline-flex items-center gap-1.5', className)} className={cn("inline-flex items-center gap-1.5", className)}
{...props} {...props}
/> />
) );
} }
function BreadcrumbLink({ function BreadcrumbLink({
asChild, asChild,
className, className,
...props ...props
}: React.ComponentProps<'a'> & { }: React.ComponentProps<"a"> & {
asChild?: boolean asChild?: boolean;
}) { }) {
const Comp = asChild ? Slot : 'a' const Comp = asChild ? Slot : "a";
return ( return (
<Comp <Comp
data-slot="breadcrumb-link" data-slot="breadcrumb-link"
className={cn('hover:text-foreground transition-colors', className)} className={cn("hover:text-foreground transition-colors", className)}
{...props} {...props}
/> />
) );
} }
function BreadcrumbPage({ className, ...props }: React.ComponentProps<'span'>) { function BreadcrumbPage({ className, ...props }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-page" data-slot="breadcrumb-page"
role="link" role="link"
aria-disabled="true" aria-disabled="true"
aria-current="page" aria-current="page"
className={cn('text-foreground font-normal', className)} className={cn("text-foreground font-normal", className)}
{...props} {...props}
/> />
) );
} }
function BreadcrumbSeparator({ function BreadcrumbSeparator({
children, children,
className, className,
...props ...props
}: React.ComponentProps<'li'>) { }: React.ComponentProps<"li">) {
return ( return (
<li <li
data-slot="breadcrumb-separator" data-slot="breadcrumb-separator"
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn('[&>svg]:size-3.5', className)} className={cn("[&>svg]:size-3.5", className)}
{...props} {...props}
> >
{children ?? <ChevronRightIcon />} {children ?? <ChevronRightIcon />}
</li> </li>
) );
} }
function BreadcrumbEllipsis({ function BreadcrumbEllipsis({
className, className,
...props ...props
}: React.ComponentProps<'span'>) { }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="breadcrumb-ellipsis" data-slot="breadcrumb-ellipsis"
role="presentation" role="presentation"
aria-hidden="true" aria-hidden="true"
className={cn('flex size-9 items-center justify-center', className)} className={cn("flex size-9 items-center justify-center", className)}
{...props} {...props}
> >
<DotsHorizontalIcon className="size-4" /> <DotsHorizontalIcon className="size-4" />
<span className="sr-only">More</span> <span className="sr-only">More</span>
</span> </span>
) );
} }
export { export {
Breadcrumb, Breadcrumb,
BreadcrumbList, BreadcrumbList,
BreadcrumbItem, BreadcrumbItem,
BreadcrumbLink, BreadcrumbLink,
BreadcrumbPage, BreadcrumbPage,
BreadcrumbSeparator, BreadcrumbSeparator,
BreadcrumbEllipsis, BreadcrumbEllipsis,
} };

View file

@ -1,92 +1,92 @@
import * as React from 'react' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
function Card({ className, ...props }: React.ComponentProps<'div'>) { function Card({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card" data-slot="card"
className={cn( className={cn(
'bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm', "bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardHeader({ className, ...props }: React.ComponentProps<'div'>) { function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-header" data-slot="card-header"
className={cn( className={cn(
'@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6', "@container/card-header grid auto-rows-min grid-rows-[auto_auto] items-start gap-1.5 px-6 has-data-[slot=card-action]:grid-cols-[1fr_auto] [.border-b]:pb-6",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardTitle({ className, ...props }: React.ComponentProps<'div'>) { function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-title" data-slot="card-title"
className={cn('leading-none font-medium', className)} className={cn("leading-none font-medium", className)}
{...props} {...props}
/> />
) );
} }
function CardDescription({ className, ...props }: React.ComponentProps<'div'>) { function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-description" data-slot="card-description"
className={cn('text-muted-foreground text-sm', className)} className={cn("text-muted-foreground text-sm", className)}
{...props} {...props}
/> />
) );
} }
function CardAction({ className, ...props }: React.ComponentProps<'div'>) { function CardAction({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-action" data-slot="card-action"
className={cn( className={cn(
'col-start-2 row-span-2 row-start-1 self-start justify-self-end', "col-start-2 row-span-2 row-start-1 self-start justify-self-end",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function CardContent({ className, ...props }: React.ComponentProps<'div'>) { function CardContent({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-content" data-slot="card-content"
className={cn('px-6', className)} className={cn("px-6", className)}
{...props} {...props}
/> />
) );
} }
function CardFooter({ className, ...props }: React.ComponentProps<'div'>) { function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
return ( return (
<div <div
data-slot="card-footer" data-slot="card-footer"
className={cn('flex items-center px-6 [.border-t]:pt-6', className)} className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
{...props} {...props}
/> />
) );
} }
export { export {
Card, Card,
CardHeader, CardHeader,
CardFooter, CardFooter,
CardTitle, CardTitle,
CardAction, CardAction,
CardDescription, CardDescription,
CardContent, CardContent,
} };

View file

@ -1,255 +1,255 @@
import * as React from 'react' import * as DropdownMenuPrimitive from "@radix-ui/react-dropdown-menu";
import * as DropdownMenuPrimitive from '@radix-ui/react-dropdown-menu' import { CheckIcon, ChevronRightIcon, CircleIcon } from "lucide-react";
import { CheckIcon, ChevronRightIcon, CircleIcon } from 'lucide-react' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
function DropdownMenu({ function DropdownMenu({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Root>) {
return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} /> return <DropdownMenuPrimitive.Root data-slot="dropdown-menu" {...props} />;
} }
function DropdownMenuPortal({ function DropdownMenuPortal({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Portal>) {
return ( return (
<DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} /> <DropdownMenuPrimitive.Portal data-slot="dropdown-menu-portal" {...props} />
) );
} }
function DropdownMenuTrigger({ function DropdownMenuTrigger({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Trigger>) {
return ( return (
<DropdownMenuPrimitive.Trigger <DropdownMenuPrimitive.Trigger
data-slot="dropdown-menu-trigger" data-slot="dropdown-menu-trigger"
{...props} {...props}
/> />
) );
} }
function DropdownMenuContent({ function DropdownMenuContent({
className, className,
sideOffset = 4, sideOffset = 4,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Content>) {
return ( return (
<DropdownMenuPrimitive.Portal> <DropdownMenuPrimitive.Portal>
<DropdownMenuPrimitive.Content <DropdownMenuPrimitive.Content
data-slot="dropdown-menu-content" data-slot="dropdown-menu-content"
sideOffset={sideOffset} sideOffset={sideOffset}
className={cn( className={cn(
'bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md', "bg-popover text-popover-foreground z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border p-1 shadow-md",
className, className,
)} )}
{...props} {...props}
/> />
</DropdownMenuPrimitive.Portal> </DropdownMenuPrimitive.Portal>
) );
} }
function DropdownMenuGroup({ function DropdownMenuGroup({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Group>) {
return ( return (
<DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} /> <DropdownMenuPrimitive.Group data-slot="dropdown-menu-group" {...props} />
) );
} }
function DropdownMenuItem({ function DropdownMenuItem({
className, className,
inset, inset,
variant = 'default', variant = "default",
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & { }: React.ComponentProps<typeof DropdownMenuPrimitive.Item> & {
inset?: boolean inset?: boolean;
variant?: 'default' | 'destructive' variant?: "default" | "destructive";
}) { }) {
return ( return (
<DropdownMenuPrimitive.Item <DropdownMenuPrimitive.Item
data-slot="dropdown-menu-item" data-slot="dropdown-menu-item"
data-inset={inset} data-inset={inset}
data-variant={variant} data-variant={variant}
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 dark:data-[variant=destructive]:focus:bg-destructive/20 data-[variant=destructive]:focus:text-destructive data-[variant=destructive]:*:[svg]:!text-destructive [&_svg:not([class*='text-'])]:text-muted-foreground relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function DropdownMenuCheckboxItem({ function DropdownMenuCheckboxItem({
className, className,
children, children,
checked, checked,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.CheckboxItem>) {
return ( return (
<DropdownMenuPrimitive.CheckboxItem <DropdownMenuPrimitive.CheckboxItem
data-slot="dropdown-menu-checkbox-item" data-slot="dropdown-menu-checkbox-item"
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
checked={checked} checked={checked}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<CheckIcon className="size-4" /> <CheckIcon className="size-4" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.CheckboxItem> </DropdownMenuPrimitive.CheckboxItem>
) );
} }
function DropdownMenuRadioGroup({ function DropdownMenuRadioGroup({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioGroup>) {
return ( return (
<DropdownMenuPrimitive.RadioGroup <DropdownMenuPrimitive.RadioGroup
data-slot="dropdown-menu-radio-group" data-slot="dropdown-menu-radio-group"
{...props} {...props}
/> />
) );
} }
function DropdownMenuRadioItem({ function DropdownMenuRadioItem({
className, className,
children, children,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.RadioItem>) {
return ( return (
<DropdownMenuPrimitive.RadioItem <DropdownMenuPrimitive.RadioItem
data-slot="dropdown-menu-radio-item" data-slot="dropdown-menu-radio-item"
className={cn( className={cn(
"focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4", "focus:bg-accent focus:text-accent-foreground relative flex cursor-default items-center gap-2 rounded-sm py-1.5 pr-2 pl-8 text-sm outline-hidden select-none data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
className, className,
)} )}
{...props} {...props}
> >
<span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center"> <span className="pointer-events-none absolute left-2 flex size-3.5 items-center justify-center">
<DropdownMenuPrimitive.ItemIndicator> <DropdownMenuPrimitive.ItemIndicator>
<CircleIcon className="size-2 fill-current" /> <CircleIcon className="size-2 fill-current" />
</DropdownMenuPrimitive.ItemIndicator> </DropdownMenuPrimitive.ItemIndicator>
</span> </span>
{children} {children}
</DropdownMenuPrimitive.RadioItem> </DropdownMenuPrimitive.RadioItem>
) );
} }
function DropdownMenuLabel({ function DropdownMenuLabel({
className, className,
inset, inset,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & { }: React.ComponentProps<typeof DropdownMenuPrimitive.Label> & {
inset?: boolean inset?: boolean;
}) { }) {
return ( return (
<DropdownMenuPrimitive.Label <DropdownMenuPrimitive.Label
data-slot="dropdown-menu-label" data-slot="dropdown-menu-label"
data-inset={inset} data-inset={inset}
className={cn( className={cn(
'px-2 py-1.5 text-sm font-medium data-[inset]:pl-8', "px-2 py-1.5 text-sm font-medium data-[inset]:pl-8",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function DropdownMenuSeparator({ function DropdownMenuSeparator({
className, className,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Separator>) {
return ( return (
<DropdownMenuPrimitive.Separator <DropdownMenuPrimitive.Separator
data-slot="dropdown-menu-separator" data-slot="dropdown-menu-separator"
className={cn('bg-border -mx-1 my-1 h-px', className)} className={cn("bg-border -mx-1 my-1 h-px", className)}
{...props} {...props}
/> />
) );
} }
function DropdownMenuShortcut({ function DropdownMenuShortcut({
className, className,
...props ...props
}: React.ComponentProps<'span'>) { }: React.ComponentProps<"span">) {
return ( return (
<span <span
data-slot="dropdown-menu-shortcut" data-slot="dropdown-menu-shortcut"
className={cn( className={cn(
'text-muted-foreground ml-auto text-xs tracking-widest', "text-muted-foreground ml-auto text-xs tracking-widest",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function DropdownMenuSub({ function DropdownMenuSub({
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.Sub>) {
return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} /> return <DropdownMenuPrimitive.Sub data-slot="dropdown-menu-sub" {...props} />;
} }
function DropdownMenuSubTrigger({ function DropdownMenuSubTrigger({
className, className,
inset, inset,
children, children,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & { }: React.ComponentProps<typeof DropdownMenuPrimitive.SubTrigger> & {
inset?: boolean inset?: boolean;
}) { }) {
return ( return (
<DropdownMenuPrimitive.SubTrigger <DropdownMenuPrimitive.SubTrigger
data-slot="dropdown-menu-sub-trigger" data-slot="dropdown-menu-sub-trigger"
data-inset={inset} data-inset={inset}
className={cn( className={cn(
'focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8', "focus:bg-accent focus:text-accent-foreground data-[state=open]:bg-accent data-[state=open]:text-accent-foreground flex cursor-default items-center rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[inset]:pl-8",
className, className,
)} )}
{...props} {...props}
> >
{children} {children}
<ChevronRightIcon className="ml-auto size-4" /> <ChevronRightIcon className="ml-auto size-4" />
</DropdownMenuPrimitive.SubTrigger> </DropdownMenuPrimitive.SubTrigger>
) );
} }
function DropdownMenuSubContent({ function DropdownMenuSubContent({
className, className,
...props ...props
}: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) { }: React.ComponentProps<typeof DropdownMenuPrimitive.SubContent>) {
return ( return (
<DropdownMenuPrimitive.SubContent <DropdownMenuPrimitive.SubContent
data-slot="dropdown-menu-sub-content" data-slot="dropdown-menu-sub-content"
className={cn( className={cn(
'bg-popover text-popover-foreground z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg', "bg-popover text-popover-foreground z-50 min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-hidden rounded-md border p-1 shadow-lg",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
export { export {
DropdownMenu, DropdownMenu,
DropdownMenuPortal, DropdownMenuPortal,
DropdownMenuTrigger, DropdownMenuTrigger,
DropdownMenuContent, DropdownMenuContent,
DropdownMenuGroup, DropdownMenuGroup,
DropdownMenuLabel, DropdownMenuLabel,
DropdownMenuItem, DropdownMenuItem,
DropdownMenuCheckboxItem, DropdownMenuCheckboxItem,
DropdownMenuRadioGroup, DropdownMenuRadioGroup,
DropdownMenuRadioItem, DropdownMenuRadioItem,
DropdownMenuSeparator, DropdownMenuSeparator,
DropdownMenuShortcut, DropdownMenuShortcut,
DropdownMenuSub, DropdownMenuSub,
DropdownMenuSubTrigger, DropdownMenuSubTrigger,
DropdownMenuSubContent, DropdownMenuSubContent,
} };

View file

@ -1,70 +1,70 @@
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";
import { Laptop, Moon, Sun } from 'lucide-react' import { Laptop, Moon, Sun } from "lucide-react";
import * as React from 'react' import * as React from "react";
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.add('disable-transitions') document.documentElement.classList.add("disable-transitions");
document.documentElement.classList[isDark ? 'add' : 'remove']('dark') document.documentElement.classList[isDark ? "add" : "remove"]("dark");
window window
.getComputedStyle(document.documentElement) .getComputedStyle(document.documentElement)
.getPropertyValue('opacity') .getPropertyValue("opacity");
requestAnimationFrame(() => { requestAnimationFrame(() => {
document.documentElement.classList.remove('disable-transitions') document.documentElement.classList.remove("disable-transitions");
}) });
}, [theme]) }, [theme]);
return ( return (
<DropdownMenu modal={false}> <DropdownMenu modal={false}>
<DropdownMenuTrigger asChild> <DropdownMenuTrigger asChild>
<Button <Button
variant="outline" variant="outline"
size="icon" size="icon"
className="group" className="group"
title="Toggle theme" title="Toggle theme"
> >
<Sun className="size-4 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" /> <Sun className="size-4 scale-100 rotate-0 transition-all dark:scale-0 dark:-rotate-90" />
<Moon className="absolute size-4 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" /> <Moon className="absolute size-4 scale-0 rotate-90 transition-all dark:scale-100 dark:rotate-0" />
<span className="sr-only">Toggle theme</span> <span className="sr-only">Toggle theme</span>
</Button> </Button>
</DropdownMenuTrigger> </DropdownMenuTrigger>
<DropdownMenuContent align="end" className="bg-background"> <DropdownMenuContent align="end" className="bg-background">
<DropdownMenuItem onClick={() => setThemeState('theme-light')}> <DropdownMenuItem onClick={() => setThemeState("theme-light")}>
<Sun className="mr-2 size-4" /> <Sun className="mr-2 size-4" />
<span>Light</span> <span>Light</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState('dark')}> <DropdownMenuItem onClick={() => setThemeState("dark")}>
<Moon className="mr-2 size-4" /> <Moon className="mr-2 size-4" />
<span>Dark</span> <span>Dark</span>
</DropdownMenuItem> </DropdownMenuItem>
<DropdownMenuItem onClick={() => setThemeState('system')}> <DropdownMenuItem onClick={() => setThemeState("system")}>
<Laptop className="mr-2 size-4" /> <Laptop className="mr-2 size-4" />
<span>System</span> <span>System</span>
</DropdownMenuItem> </DropdownMenuItem>
</DropdownMenuContent> </DropdownMenuContent>
</DropdownMenu> </DropdownMenu>
) );
} }

View file

@ -1,195 +1,194 @@
import * as React from 'react'
import { import {
ChevronLeftIcon, ChevronLeftIcon,
ChevronRightIcon, ChevronRightIcon,
MoreHorizontalIcon, MoreHorizontalIcon,
} from 'lucide-react' } from "lucide-react";
import type * as React from "react";
import { cn } from '@/lib/utils' import { type Button, buttonVariants } from "@/components/ui/button";
import { Button, buttonVariants } from '@/components/ui/button' import { cn } from "@/lib/utils";
function Pagination({ className, ...props }: React.ComponentProps<'nav'>) { function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
return ( return (
<nav <nav
role="navigation" aria-label="pagination"
aria-label="pagination" data-slot="pagination"
data-slot="pagination" className={cn("mx-auto flex w-full justify-center", className)}
className={cn('mx-auto flex w-full justify-center', className)} {...props}
{...props} />
/> );
)
} }
function PaginationContent({ function PaginationContent({
className, className,
...props ...props
}: React.ComponentProps<'ul'>) { }: React.ComponentProps<"ul">) {
return ( return (
<ul <ul
data-slot="pagination-content" data-slot="pagination-content"
className={cn('flex flex-row items-center gap-1', className)} className={cn("flex flex-row items-center gap-1", className)}
{...props} {...props}
/> />
) );
} }
function PaginationItem({ ...props }: React.ComponentProps<'li'>) { function PaginationItem({ ...props }: React.ComponentProps<"li">) {
return <li data-slot="pagination-item" {...props} /> return <li data-slot="pagination-item" {...props} />;
} }
type PaginationLinkProps = { type PaginationLinkProps = {
isActive?: boolean isActive?: boolean;
isDisabled?: boolean isDisabled?: boolean;
} & Pick<React.ComponentProps<typeof Button>, 'size'> & } & Pick<React.ComponentProps<typeof Button>, "size"> &
React.ComponentProps<'a'> React.ComponentProps<"a">;
function PaginationLink({ function PaginationLink({
className, className,
isActive, isActive,
isDisabled, isDisabled,
size = 'icon', size = "icon",
...props ...props
}: PaginationLinkProps) { }: PaginationLinkProps) {
return ( return (
<a <a
aria-current={isActive ? 'page' : undefined} aria-current={isActive ? "page" : undefined}
data-slot="pagination-link" data-slot="pagination-link"
data-active={isActive} data-active={isActive}
data-disabled={isDisabled} data-disabled={isDisabled}
className={cn( className={cn(
buttonVariants({ buttonVariants({
variant: isActive ? 'outline' : 'ghost', variant: isActive ? "outline" : "ghost",
size, size,
}), }),
isDisabled && 'pointer-events-none opacity-50', isDisabled && "pointer-events-none opacity-50",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
function PaginationPrevious({ function PaginationPrevious({
className, className,
isDisabled, isDisabled,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to previous page" aria-label="Go to previous page"
size="default" size="default"
className={cn('gap-1 px-2.5 sm:pl-2.5', className)} className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
isDisabled={isDisabled} isDisabled={isDisabled}
{...props} {...props}
> >
<ChevronLeftIcon /> <ChevronLeftIcon />
<span className="hidden sm:block">Previous</span> <span className="hidden sm:block">Previous</span>
</PaginationLink> </PaginationLink>
) );
} }
function PaginationNext({ function PaginationNext({
className, className,
isDisabled, isDisabled,
...props ...props
}: React.ComponentProps<typeof PaginationLink>) { }: React.ComponentProps<typeof PaginationLink>) {
return ( return (
<PaginationLink <PaginationLink
aria-label="Go to next page" aria-label="Go to next page"
size="default" size="default"
className={cn('gap-1 px-2.5 sm:pr-2.5', className)} className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
isDisabled={isDisabled} isDisabled={isDisabled}
{...props} {...props}
> >
<span className="hidden sm:block">Next</span> <span className="hidden sm:block">Next</span>
<ChevronRightIcon /> <ChevronRightIcon />
</PaginationLink> </PaginationLink>
) );
} }
function PaginationEllipsis({ function PaginationEllipsis({
className, className,
...props ...props
}: React.ComponentProps<'span'>) { }: React.ComponentProps<"span">) {
return ( return (
<span <span
aria-hidden aria-hidden
data-slot="pagination-ellipsis" data-slot="pagination-ellipsis"
className={cn('flex size-9 items-center justify-center', className)} className={cn("flex size-9 items-center justify-center", className)}
{...props} {...props}
> >
<MoreHorizontalIcon className="size-4" /> <MoreHorizontalIcon className="size-4" />
<span className="sr-only">More pages</span> <span className="sr-only">More pages</span>
</span> </span>
) );
} }
const PaginationComponent: React.FC<PaginationProps> = ({ const PaginationComponent: React.FC<PaginationProps> = ({
currentPage, currentPage,
totalPages, totalPages,
baseUrl, baseUrl,
}) => { }) => {
const pages = Array.from({ length: totalPages }, (_, i) => i + 1) const pages = Array.from({ length: totalPages }, (_, i) => i + 1);
const getPageUrl = (page: number) => { const getPageUrl = (page: number) => {
if (page === 1) return baseUrl if (page === 1) return baseUrl;
return `${baseUrl}${page}` return `${baseUrl}${page}`;
} };
return ( return (
<Pagination> <Pagination>
<PaginationContent className="flex-wrap"> <PaginationContent className="flex-wrap">
<PaginationItem> <PaginationItem>
<PaginationPrevious <PaginationPrevious
href={currentPage > 1 ? getPageUrl(currentPage - 1) : undefined} href={currentPage > 1 ? getPageUrl(currentPage - 1) : undefined}
isDisabled={currentPage === 1} isDisabled={currentPage === 1}
/> />
</PaginationItem> </PaginationItem>
{pages.map((page) => ( {pages.map((page) => (
<PaginationItem key={page}> <PaginationItem key={page}>
<PaginationLink <PaginationLink
href={getPageUrl(page)} href={getPageUrl(page)}
isActive={page === currentPage} isActive={page === currentPage}
> >
{page} {page}
</PaginationLink> </PaginationLink>
</PaginationItem> </PaginationItem>
))} ))}
{totalPages > 5 && ( {totalPages > 5 && (
<PaginationItem> <PaginationItem>
<PaginationEllipsis /> <PaginationEllipsis />
</PaginationItem> </PaginationItem>
)} )}
<PaginationItem> <PaginationItem>
<PaginationNext <PaginationNext
href={ href={
currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined currentPage < totalPages ? getPageUrl(currentPage + 1) : undefined
} }
isDisabled={currentPage === totalPages} isDisabled={currentPage === totalPages}
/> />
</PaginationItem> </PaginationItem>
</PaginationContent> </PaginationContent>
</Pagination> </Pagination>
) );
} };
interface PaginationProps { interface PaginationProps {
currentPage: number currentPage: number;
totalPages: number totalPages: number;
baseUrl: string baseUrl: string;
} }
export default PaginationComponent export default PaginationComponent;
export { export {
Pagination, Pagination,
PaginationContent, PaginationContent,
PaginationLink, PaginationLink,
PaginationItem, PaginationItem,
PaginationPrevious, PaginationPrevious,
PaginationNext, PaginationNext,
PaginationEllipsis, PaginationEllipsis,
} };

View file

@ -1,56 +1,56 @@
import * as React from 'react' import * as ScrollAreaPrimitive from "@radix-ui/react-scroll-area";
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
function ScrollArea({ function ScrollArea({
className, className,
children, children,
...props ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.Root>) {
return ( return (
<ScrollAreaPrimitive.Root <ScrollAreaPrimitive.Root
data-slot="scroll-area" data-slot="scroll-area"
className={cn('relative', className)} className={cn("relative", className)}
{...props} {...props}
> >
<ScrollAreaPrimitive.Viewport <ScrollAreaPrimitive.Viewport
data-slot="scroll-area-viewport" data-slot="scroll-area-viewport"
className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1" className="ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 size-full rounded-[inherit] transition-[color,box-shadow] focus-visible:ring-4 focus-visible:outline-1"
> >
{children} {children}
</ScrollAreaPrimitive.Viewport> </ScrollAreaPrimitive.Viewport>
<ScrollBar /> <ScrollBar />
<ScrollAreaPrimitive.Corner /> <ScrollAreaPrimitive.Corner />
</ScrollAreaPrimitive.Root> </ScrollAreaPrimitive.Root>
) );
} }
function ScrollBar({ function ScrollBar({
className, className,
orientation = 'vertical', orientation = "vertical",
...props ...props
}: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) { }: React.ComponentProps<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>) {
return ( return (
<ScrollAreaPrimitive.ScrollAreaScrollbar <ScrollAreaPrimitive.ScrollAreaScrollbar
data-slot="scroll-area-scrollbar" data-slot="scroll-area-scrollbar"
orientation={orientation} orientation={orientation}
className={cn( className={cn(
'flex touch-none p-px transition-colors select-none', "flex touch-none p-px transition-colors select-none",
orientation === 'vertical' && orientation === "vertical" &&
'h-full w-2.5 border-l border-l-transparent', "h-full w-2.5 border-l border-l-transparent",
orientation === 'horizontal' && orientation === "horizontal" &&
'h-2.5 flex-col border-t border-t-transparent', "h-2.5 flex-col border-t border-t-transparent",
className, className,
)} )}
{...props} {...props}
> >
<ScrollAreaPrimitive.ScrollAreaThumb <ScrollAreaPrimitive.ScrollAreaThumb
data-slot="scroll-area-thumb" data-slot="scroll-area-thumb"
className="bg-border relative flex-1 rounded-full" className="bg-border relative flex-1 rounded-full"
/> />
</ScrollAreaPrimitive.ScrollAreaScrollbar> </ScrollAreaPrimitive.ScrollAreaScrollbar>
) );
} }
export { ScrollArea, ScrollBar } export { ScrollArea, ScrollBar };

View file

@ -1,26 +1,26 @@
import * as React from 'react' import * as SeparatorPrimitive from "@radix-ui/react-separator";
import * as SeparatorPrimitive from '@radix-ui/react-separator' import type * as React from "react";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
function Separator({ function Separator({
className, className,
orientation = 'horizontal', orientation = "horizontal",
decorative = true, decorative = true,
...props ...props
}: React.ComponentProps<typeof SeparatorPrimitive.Root>) { }: React.ComponentProps<typeof SeparatorPrimitive.Root>) {
return ( return (
<SeparatorPrimitive.Root <SeparatorPrimitive.Root
data-slot="separator-root" data-slot="separator-root"
decorative={decorative} decorative={decorative}
orientation={orientation} orientation={orientation}
className={cn( className={cn(
'bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px', "bg-border shrink-0 data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
className, className,
)} )}
{...props} {...props}
/> />
) );
} }
export { Separator } export { Separator };

View file

@ -1,18 +1,18 @@
import { glob } from 'astro/loaders' import { defineCollection, z } from "astro:content";
import { defineCollection, z } from 'astro:content' import { glob } from "astro/loaders";
const blog = defineCollection({ const blog = defineCollection({
loader: glob({ pattern: '**/*.{md,mdx}', base: './src/content' }), loader: glob({ pattern: "**/*.{md,mdx}", base: "./src/content" }),
schema: ({ image }) => schema: ({ image }) =>
z.object({ z.object({
title: z.string(), title: z.string(),
description: z.string(), description: z.string(),
date: z.coerce.date(), date: z.coerce.date(),
image: image().optional(), image: image().optional(),
tags: z.array(z.string()).optional(), tags: z.array(z.string()).optional(),
authors: z.array(z.string()).optional(), authors: z.array(z.string()).optional(),
draft: z.boolean().optional(), draft: z.boolean().optional(),
}), }),
}) });
export const collections = { blog } export const collections = { blog };

View file

@ -1,15 +1,15 @@
--- ---
import Footer from '@/components/Footer.astro' import Footer from "@/components/Footer.astro";
import Head from '@/components/Head.astro' import Head from "@/components/Head.astro";
import Header from '@/components/Header.astro' import Header from "@/components/Header.astro";
import { SITE } from '@/consts' import { SITE } from "@/consts";
type Props = { type Props = {
title: string title: string;
description: string description: string;
} };
const { title, description } = Astro.props const { title, description } = Astro.props;
--- ---
<!doctype html> <!doctype html>

View file

@ -1,27 +1,27 @@
import { getEntry } from 'astro:content' import { getEntry } from "astro:content";
export async function parseAuthors(authors: string[]) { export async function parseAuthors(authors: string[]) {
if (!authors || authors.length === 0) return [] if (!authors || authors.length === 0) return [];
const parseAuthor = async (id: string) => { const parseAuthor = async (id: string) => {
try { try {
const author = await getEntry('authors', id) const author = await getEntry("authors", id);
return { return {
id, id,
name: author?.data?.name || id, name: author?.data?.name || id,
avatar: author?.data?.avatar || '/static/logo.png', avatar: author?.data?.avatar || "/static/logo.png",
isRegistered: !!author, isRegistered: !!author,
} };
} catch (error) { } catch (error) {
console.error(`Error fetching author with id ${id}:`, error) console.error(`Error fetching author with id ${id}:`, error);
return { return {
id, id,
name: id, name: id,
avatar: '/static/logo.png', avatar: "/static/logo.png",
isRegistered: false, isRegistered: false,
} };
} }
} };
return await Promise.all(authors.map(parseAuthor)) return await Promise.all(authors.map(parseAuthor));
} }

View file

@ -1,21 +1,21 @@
import { type ClassValue, clsx } from 'clsx' import { type ClassValue, clsx } from "clsx";
import { twMerge } from 'tailwind-merge' import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) { export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs)) return twMerge(clsx(inputs));
} }
export function formatDate(date: Date) { export function formatDate(date: Date) {
return Intl.DateTimeFormat('en-US', { return Intl.DateTimeFormat("en-US", {
year: 'numeric', year: "numeric",
month: 'long', month: "long",
day: 'numeric', day: "numeric",
}).format(date) }).format(date);
} }
export function readingTime(html: string) { export function readingTime(html: string) {
const textOnly = html.replace(/<[^>]+>/g, '') const textOnly = html.replace(/<[^>]+>/g, "");
const wordCount = textOnly.split(/\s+/).length const wordCount = textOnly.split(/\s+/).length;
const readingTimeMinutes = (wordCount / 200 + 1).toFixed() const readingTimeMinutes = (wordCount / 200 + 1).toFixed();
return `${readingTimeMinutes} min read` return `${readingTimeMinutes} min read`;
} }

View file

@ -1,11 +1,11 @@
--- ---
import Breadcrumbs from '@/components/Breadcrumbs.astro' import Breadcrumbs from "@/components/Breadcrumbs.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 { buttonVariants } from '@/components/ui/button' import { buttonVariants } from "@/components/ui/button";
import { SITE } from '@/consts' import { SITE } from "@/consts";
import Layout from '@/layouts/Layout.astro' import Layout from "@/layouts/Layout.astro";
import { cn } from '@/lib/utils' import { cn } from "@/lib/utils";
--- ---
<Layout title="404" description={SITE.description}> <Layout title="404" description={SITE.description}>

View file

@ -1,37 +1,39 @@
--- ---
import BlogCard from '@/components/BlogCard.astro' import { type CollectionEntry, getCollection } from "astro:content";
import Breadcrumbs from '@/components/Breadcrumbs.astro' import BlogCard from "@/components/BlogCard.astro";
import Container from '@/components/Container.astro' import Breadcrumbs from "@/components/Breadcrumbs.astro";
import PaginationComponent from '@/components/ui/pagination' import Container from "@/components/Container.astro";
import { SITE } from '@/consts' import PaginationComponent from "@/components/ui/pagination";
import Layout from '@/layouts/Layout.astro' import { SITE } from "@/consts";
import type { PaginateFunction } from 'astro' import Layout from "@/layouts/Layout.astro";
import { type CollectionEntry, getCollection } from 'astro:content' import type { PaginateFunction } from "astro";
export async function getStaticPaths({ export async function getStaticPaths({
paginate, paginate,
}: { }: {
paginate: PaginateFunction paginate: PaginateFunction;
}) { }) {
const allPosts = await getCollection('blog', ({ data }) => !data.draft) const allPosts = await getCollection("blog", ({ data }) => !data.draft);
return paginate( return paginate(
allPosts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()), allPosts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()),
{ pageSize: SITE.postsPerPage }, { pageSize: SITE.postsPerPage },
) );
} }
const { page } = Astro.props const { page } = Astro.props;
const postsByYear = page.data.reduce( const postsByYear = page.data.reduce(
(acc: Record<string, CollectionEntry<'blog'>[]>, post) => { (acc: Record<string, CollectionEntry<"blog">[]>, post) => {
const year = post.data.date.getFullYear().toString() const year = post.data.date.getFullYear().toString();
;(acc[year] ??= []).push(post) (acc[year] ??= []).push(post);
return acc return acc;
}, },
{}, {},
) );
const years = Object.keys(postsByYear).sort((a, b) => parseInt(b) - parseInt(a)) const years = Object.keys(postsByYear).sort(
(a, b) => Number.parseInt(b) - Number.parseInt(a),
);
--- ---
<Layout title="Blog" description="Blog"> <Layout title="Blog" description="Blog">

View file

@ -1,16 +1,16 @@
--- ---
import BlogCard from '@/components/BlogCard.astro' import { getCollection } from "astro:content";
import Container from '@/components/Container.astro' import BlogCard from "@/components/BlogCard.astro";
import Link from '@/components/Link.astro' import Container from "@/components/Container.astro";
import { buttonVariants } from '@/components/ui/button' import Link from "@/components/Link.astro";
import { SITE } from '@/consts' import { buttonVariants } from "@/components/ui/button";
import Layout from '@/layouts/Layout.astro' import { SITE } from "@/consts";
import { getCollection } from 'astro:content' import Layout from "@/layouts/Layout.astro";
const blog = (await getCollection('blog')) const blog = (await getCollection("blog"))
.filter((post) => !post.data.draft) .filter((post) => !post.data.draft)
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) .sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
.slice(0, SITE.featuredPostCount) .slice(0, SITE.featuredPostCount);
--- ---
<Layout title="Home" description={SITE.description}> <Layout title="Home" description={SITE.description}>

View file

@ -1,4 +1,4 @@
import type { APIRoute } from 'astro' import type { APIRoute } from "astro";
const getRobotsTxt = (sitemapURL: URL) => ` const getRobotsTxt = (sitemapURL: URL) => `
User-agent: AI2Bot User-agent: AI2Bot
@ -49,9 +49,9 @@ User-agent: YouBot
Disallow: / Disallow: /
Sitemap: ${sitemapURL.href} Sitemap: ${sitemapURL.href}
` `;
export const GET: APIRoute = ({ site }) => { export const GET: APIRoute = ({ site }) => {
const sitemapURL = new URL('sitemap-index.xml', site) const sitemapURL = new URL("sitemap-index.xml", site);
return new Response(getRobotsTxt(sitemapURL)) return new Response(getRobotsTxt(sitemapURL));
} };

View file

@ -1,26 +1,26 @@
import { SITE } from '@/consts' import { getCollection } from "astro:content";
import rss from '@astrojs/rss' import { SITE } from "@/consts";
import type { APIContext } from 'astro' import rss from "@astrojs/rss";
import { getCollection } from 'astro:content' import type { APIContext } from "astro";
export async function GET(context: APIContext) { export async function GET(context: APIContext) {
try { try {
const posts = await getCollection('blog', ({ data }) => !data.draft) const posts = await getCollection("blog", ({ data }) => !data.draft);
posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf()) posts.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf());
return rss({ return rss({
title: SITE.title, title: SITE.title,
description: SITE.description, description: SITE.description,
site: context.site ?? SITE.href, site: context.site ?? SITE.href,
items: posts.map((post) => ({ items: posts.map((post) => ({
title: post.data.title, title: post.data.title,
description: post.data.description, description: post.data.description,
pubDate: post.data.date, pubDate: post.data.date,
link: `/blog/${post.id}/`, link: `/blog/${post.id}/`,
})), })),
}) });
} catch (error) { } catch (error) {
console.error('Error generating RSS feed:', error) console.error("Error generating RSS feed:", error);
return new Response('Error generating RSS feed', { status: 500 }) return new Response("Error generating RSS feed", { status: 500 });
} }
} }

View file

@ -1,386 +1,397 @@
@import 'tailwindcss'; @import "tailwindcss";
[data-callout] { [data-callout] {
& { & {
@apply my-6 space-y-2 rounded-lg border border-blue-600/20 bg-blue-400/20 p-4 pb-5 dark:border-blue-800/20 dark:bg-blue-600/10; @apply my-6 space-y-2 rounded-lg border border-blue-600/20 bg-blue-400/20 p-4 pb-5 dark:border-blue-800/20 dark:bg-blue-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply flex flex-row items-start gap-2 p-0 font-bold text-blue-500; @apply flex flex-row items-start gap-2 p-0 font-bold text-blue-500;
} }
&:not:only-child { &:not:only-child {
@apply mb-2; @apply mb-2;
} }
&:empty::after { &:empty::after {
content: "Note"; content: "Note";
} }
&::before { &::before {
@apply mt-1 block h-5 w-5 bg-current content-[""]; @apply mt-1 block h-5 w-5 bg-current content-[""];
mask-repeat: no-repeat; mask-repeat: no-repeat;
mask-size: cover; mask-size: cover;
/* lucide-pencil */ /* lucide-pencil */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTE3IDNhMi44NSAyLjgzIDAgMSAxIDQgNEw3LjUgMjAuNUwyIDIybDEuNS01LjVabS0yIDJsNCA0Ii8+PC9zdmc+"); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTE3IDNhMi44NSAyLjgzIDAgMSAxIDQgNEw3LjUgMjAuNUwyIDIybDEuNS01LjVabS0yIDJsNCA0Ii8+PC9zdmc+");
} }
} }
& > [data-callout-body] { & > [data-callout-body] {
& { & {
@apply space-y-2; @apply space-y-2;
} }
& > * { & > * {
@apply m-0; @apply m-0;
} }
} }
} }
details[data-callout] > summary[data-callout-title] { details[data-callout] > summary[data-callout-title] {
& { & {
@apply cursor-pointer; @apply cursor-pointer;
} }
&::after { &::after {
@apply w-full bg-right bg-no-repeat; @apply w-full bg-right bg-no-repeat;
content: ""; content: "";
/* lucide:chevron-right */ /* lucide:chevron-right */
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im05IDE4bDYtNmwtNi02Ii8+PC9zdmc+"); background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im05IDE4bDYtNmwtNi02Ii8+PC9zdmc+");
background-size: 1.5rem; background-size: 1.5rem;
} }
&:not(:empty)::after { &:not(:empty)::after {
@apply my-auto ml-auto h-6 w-6; @apply my-auto ml-auto h-6 w-6;
} }
} }
details[data-callout][open] > summary[data-callout-title]::after { details[data-callout][open] > summary[data-callout-title]::after {
/* lucide:chevron-down */ /* lucide:chevron-down */
background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im02IDlsNiA2bDYtNiIvPjwvc3ZnPg=="); background-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiIGQ9Im02IDlsNiA2bDYtNiIvPjwvc3ZnPg==");
} }
[data-callout][data-callout-type="info"] { [data-callout][data-callout-type="info"] {
& { & {
@apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10; @apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-blue-500; @apply text-blue-500;
} }
&:empty::after { &:empty::after {
content: "Info"; content: "Info";
} }
&::before { &::before {
/* lucide:info */ /* lucide:info */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTEyIDE2di00bTAtNGguMDEiLz48L2c+PC9zdmc+"); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxjaXJjbGUgY3g9IjEyIiBjeT0iMTIiIHI9IjEwIi8+PHBhdGggZD0iTTEyIDE2di00bTAtNGguMDEiLz48L2c+PC9zdmc+");
} }
} }
} }
[data-callout][data-callout-type="todo"] { [data-callout][data-callout-type="todo"] {
& { & {
@apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10; @apply border-blue-600/20 bg-blue-400/20 dark:border-blue-800/20 dark:bg-blue-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-blue-500; @apply text-blue-500;
} }
&:empty::after { &:empty::after {
content: "ToDo"; content: "ToDo";
} }
&::before { &::before {
/* lucide:circle-check-big */ /* lucide:circle-check-big */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik0yMiAxMS4wOFYxMmExMCAxMCAwIDEgMS01LjkzLTkuMTQiLz48cGF0aCBkPSJtOSAxMWwzIDNMMjIgNCIvPjwvZz48L3N2Zz4="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iIzg4ODg4OCIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Ik0yMiAxMS4wOFYxMmExMCAxMCAwIDEgMS01LjkzLTkuMTQiLz48cGF0aCBkPSJtOSAxMWwzIDNMMjIgNCIvPjwvZz48L3N2Zz4=");
} }
} }
} }
[data-callout][data-callout-type="abstract"], [data-callout][data-callout-type="abstract"],
[data-callout][data-callout-type="summary"], [data-callout][data-callout-type="summary"],
[data-callout][data-callout-type="tldr"] { [data-callout][data-callout-type="tldr"] {
& { & {
@apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10; @apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-cyan-500; @apply text-cyan-500;
} }
&::before { &::before {
/* lucide:clipboard-list */ /* lucide:clipboard-list */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIi8+PHBhdGggZD0iTTE2IDRoMmEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJINmEyIDIgMCAwIDEtMi0yVjZhMiAyIDAgMCAxIDItMmgybTQgN2g0bS00IDVoNG0tOC01aC4wMU04IDE2aC4wMSIvPjwvZz48L3N2Zz4="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHJlY3Qgd2lkdGg9IjgiIGhlaWdodD0iNCIgeD0iOCIgeT0iMiIgcng9IjEiIHJ5PSIxIi8+PHBhdGggZD0iTTE2IDRoMmEyIDIgMCAwIDEgMiAydjE0YTIgMiAwIDAgMS0yIDJINmEyIDIgMCAwIDEtMi0yVjZhMiAyIDAgMCAxIDItMmgybTQgN2g0bS00IDVoNG0tOC01aC4wMU04IDE2aC4wMSIvPjwvZz48L3N2Zz4=");
} }
} }
} }
[data-callout][data-callout-type="abstract"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="abstract"]
content: "Abstract"; > [data-callout-title]:empty::after {
content: "Abstract";
} }
[data-callout][data-callout-type="summary"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="summary"]
content: "Summary"; > [data-callout-title]:empty::after {
content: "Summary";
} }
[data-callout][data-callout-type="tldr"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="tldr"] > [data-callout-title]:empty::after {
content: "TL;DR"; content: "TL;DR";
} }
[data-callout][data-callout-type="tip"], [data-callout][data-callout-type="tip"],
[data-callout][data-callout-type="hint"], [data-callout][data-callout-type="hint"],
[data-callout][data-callout-type="important"] { [data-callout][data-callout-type="important"] {
& { & {
@apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10; @apply border-cyan-600/20 bg-cyan-400/20 dark:border-cyan-800/20 dark:bg-cyan-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-cyan-500; @apply text-cyan-500;
} }
&::before { &::before {
/* lucide:flame */ /* lucide:flame */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTguNSAxNC41QTIuNSAyLjUgMCAwIDAgMTEgMTJjMC0xLjM4LS41LTItMS0zYy0xLjA3Mi0yLjE0My0uMjI0LTQuMDU0IDItNmMuNSAyLjUgMiA0LjkgNCA2LjVjMiAxLjYgMyAzLjUgMyA1LjVhNyA3IDAgMSAxLTE0IDBjMC0xLjE1My40MzMtMi4yOTQgMS0zYTIuNSAyLjUgMCAwIDAgMi41IDIuNSIvPjwvc3ZnPg=="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTguNSAxNC41QTIuNSAyLjUgMCAwIDAgMTEgMTJjMC0xLjM4LS41LTItMS0zYy0xLjA3Mi0yLjE0My0uMjI0LTQuMDU0IDItNmMuNSAyLjUgMiA0LjkgNCA2LjVjMiAxLjYgMyAzLjUgMyA1LjVhNyA3IDAgMSAxLTE0IDBjMC0xLjE1My40MzMtMi4yOTQgMS0zYTIuNSAyLjUgMCAwIDAgMi41IDIuNSIvPjwvc3ZnPg==");
} }
} }
} }
[data-callout][data-callout-type="tip"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="tip"] > [data-callout-title]:empty::after {
content: "Tip"; content: "Tip";
} }
[data-callout][data-callout-type="hint"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="hint"] > [data-callout-title]:empty::after {
content: "Hint"; content: "Hint";
} }
[data-callout][data-callout-type="important"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="important"]
content: "Important"; > [data-callout-title]:empty::after {
content: "Important";
} }
[data-callout][data-callout-type="success"], [data-callout][data-callout-type="success"],
[data-callout][data-callout-type="check"], [data-callout][data-callout-type="check"],
[data-callout][data-callout-type="done"] { [data-callout][data-callout-type="done"] {
& { & {
@apply border-green-600/20 bg-green-400/20 dark:border-green-800/20 dark:bg-green-600/10; @apply border-green-600/20 bg-green-400/20 dark:border-green-800/20 dark:bg-green-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-green-500; @apply text-green-500;
} }
&::before { &::before {
/* lucide:check */ /* lucide:check */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+"); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+");
} }
} }
} }
[data-callout][data-callout-type="success"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="success"]
content: "Success"; > [data-callout-title]:empty::after {
content: "Success";
} }
[data-callout][data-callout-type="check"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="check"] > [data-callout-title]:empty::after {
content: "Check"; content: "Check";
} }
[data-callout][data-callout-type="done"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="done"] > [data-callout-title]:empty::after {
content: "Done"; content: "Done";
} }
[data-callout][data-callout-type="question"], [data-callout][data-callout-type="question"],
[data-callout][data-callout-type="help"], [data-callout][data-callout-type="help"],
[data-callout][data-callout-type="faq"] { [data-callout][data-callout-type="faq"] {
& { & {
@apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10; @apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-orange-500; @apply text-orange-500;
} }
&::before { &::before {
/* lucide:circle-help */ /* lucide:circle-help */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48cGF0aCBkPSJNOS4wOSA5YTMgMyAwIDAgMSA1LjgzIDFjMCAyLTMgMy0zIDNtLjA4IDRoLjAxIi8+PC9nPjwvc3ZnPg=="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PGNpcmNsZSBjeD0iMTIiIGN5PSIxMiIgcj0iMTAiLz48cGF0aCBkPSJNOS4wOSA5YTMgMyAwIDAgMSA1LjgzIDFjMCAyLTMgMy0zIDNtLjA4IDRoLjAxIi8+PC9nPjwvc3ZnPg==");
} }
} }
} }
[data-callout][data-callout-type="question"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="question"]
content: "Question"; > [data-callout-title]:empty::after {
content: "Question";
} }
[data-callout][data-callout-type="help"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="help"] > [data-callout-title]:empty::after {
content: "Help"; content: "Help";
} }
[data-callout][data-callout-type="faq"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="faq"] > [data-callout-title]:empty::after {
content: "FAQ"; content: "FAQ";
} }
[data-callout][data-callout-type="warning"], [data-callout][data-callout-type="warning"],
[data-callout][data-callout-type="caution"], [data-callout][data-callout-type="caution"],
[data-callout][data-callout-type="attention"] { [data-callout][data-callout-type="attention"] {
& { & {
@apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10; @apply border-orange-600/20 bg-orange-400/20 dark:border-orange-800/20 dark:bg-orange-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-orange-500; @apply text-orange-500;
} }
&::before { &::before {
/* lucide:triangle-alert */ /* lucide:triangle-alert */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0ibTIxLjczIDE4bC04LTE0YTIgMiAwIDAgMC0zLjQ4IDBsLTggMTRBMiAyIDAgMCAwIDQgMjFoMTZhMiAyIDAgMCAwIDEuNzMtM00xMiA5djRtMCA0aC4wMSIvPjwvc3ZnPg=="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0ibTIxLjczIDE4bC04LTE0YTIgMiAwIDAgMC0zLjQ4IDBsLTggMTRBMiAyIDAgMCAwIDQgMjFoMTZhMiAyIDAgMCAwIDEuNzMtM00xMiA5djRtMCA0aC4wMSIvPjwvc3ZnPg==");
} }
} }
} }
[data-callout][data-callout-type="warning"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="warning"]
content: "Warning"; > [data-callout-title]:empty::after {
content: "Warning";
} }
[data-callout][data-callout-type="caution"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="caution"]
content: "Caution"; > [data-callout-title]:empty::after {
content: "Caution";
} }
[data-callout][data-callout-type="attention"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="attention"]
content: "Attention"; > [data-callout-title]:empty::after {
content: "Attention";
} }
[data-callout][data-callout-type="failure"], [data-callout][data-callout-type="failure"],
[data-callout][data-callout-type="fail"], [data-callout][data-callout-type="fail"],
[data-callout][data-callout-type="missing"] { [data-callout][data-callout-type="missing"] {
& { & {
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10; @apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-red-500; @apply text-red-500;
} }
&::before { &::before {
/* lucide:check */ /* lucide:check */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+"); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTIwIDZMOSAxN2wtNS01Ii8+PC9zdmc+");
} }
} }
} }
[data-callout][data-callout-type="failure"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="failure"]
content: "Failure"; > [data-callout-title]:empty::after {
content: "Failure";
} }
[data-callout][data-callout-type="fail"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="fail"] > [data-callout-title]:empty::after {
content: "Fail"; content: "Fail";
} }
[data-callout][data-callout-type="missing"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="missing"]
content: "Missing"; > [data-callout-title]:empty::after {
content: "Missing";
} }
[data-callout][data-callout-type="danger"], [data-callout][data-callout-type="danger"],
[data-callout][data-callout-type="error"] { [data-callout][data-callout-type="error"] {
& { & {
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10; @apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-red-500; @apply text-red-500;
} }
&::before { &::before {
/* lucide:zap */ /* lucide:zap */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXphcCI+PHBhdGggZD0iTTQgMTRhMSAxIDAgMCAxLS43OC0xLjYzbDkuOS0xMC4yYS41LjUgMCAwIDEgLjg2LjQ2bC0xLjkyIDYuMDJBMSAxIDAgMCAwIDEzIDEwaDdhMSAxIDAgMCAxIC43OCAxLjYzbC05LjkgMTAuMmEuNS41IDAgMCAxLS44Ni0uNDZsMS45Mi02LjAyQTEgMSAwIDAgMCAxMSAxNHoiLz48L3N2Zz4="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9ImN1cnJlbnRDb2xvciIgc3Ryb2tlLXdpZHRoPSIyIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIGNsYXNzPSJsdWNpZGUgbHVjaWRlLXphcCI+PHBhdGggZD0iTTQgMTRhMSAxIDAgMCAxLS43OC0xLjYzbDkuOS0xMC4yYS41LjUgMCAwIDEgLjg2LjQ2bC0xLjkyIDYuMDJBMSAxIDAgMCAwIDEzIDEwaDdhMSAxIDAgMCAxIC43OCAxLjYzbC05LjkgMTAuMmEuNS41IDAgMCAxLS44Ni0uNDZsMS45Mi02LjAyQTEgMSAwIDAgMCAxMSAxNHoiLz48L3N2Zz4=");
} }
} }
} }
[data-callout][data-callout-type="danger"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="danger"] > [data-callout-title]:empty::after {
content: "Danger"; content: "Danger";
} }
[data-callout][data-callout-type="error"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="error"] > [data-callout-title]:empty::after {
content: "Error"; content: "Error";
} }
[data-callout][data-callout-type="bug"] { [data-callout][data-callout-type="bug"] {
& { & {
@apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10; @apply border-red-600/20 bg-red-400/20 dark:border-red-800/20 dark:bg-red-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-red-500; @apply text-red-500;
} }
&::before { &::before {
/* lucide:bug */ /* lucide:bug */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggZD0ibTggMmwxLjg4IDEuODhtNC4yNCAwTDE2IDJNOSA3LjEzdi0xYTMuMDAzIDMuMDAzIDAgMSAxIDYgMHYxIi8+PHBhdGggZD0iTTEyIDIwYy0zLjMgMC02LTIuNy02LTZ2LTNhNCA0IDAgMCAxIDQtNGg0YTQgNCAwIDAgMSA0IDR2M2MwIDMuMy0yLjcgNi02IDZtMCAwdi05Ii8+PHBhdGggZD0iTTYuNTMgOUM0LjYgOC44IDMgNy4xIDMgNW0zIDhIMm0xIDhjMC0yLjEgMS43LTMuOSAzLjgtNE0yMC45NyA1YzAgMi4xLTEuNiAzLjgtMy41IDRNMjIgMTNoLTRtLS44IDRjMi4xLjEgMy44IDEuOSAzLjggNCIvPjwvZz48L3N2Zz4="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxnIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiI+PHBhdGggZD0ibTggMmwxLjg4IDEuODhtNC4yNCAwTDE2IDJNOSA3LjEzdi0xYTMuMDAzIDMuMDAzIDAgMSAxIDYgMHYxIi8+PHBhdGggZD0iTTEyIDIwYy0zLjMgMC02LTIuNy02LTZ2LTNhNCA0IDAgMCAxIDQtNGg0YTQgNCAwIDAgMSA0IDR2M2MwIDMuMy0yLjcgNi02IDZtMCAwdi05Ii8+PHBhdGggZD0iTTYuNTMgOUM0LjYgOC44IDMgNy4xIDMgNW0zIDhIMm0xIDhjMC0yLjEgMS43LTMuOSAzLjgtNE0yMC45NyA1YzAgMi4xLTEuNiAzLjgtMy41IDRNMjIgMTNoLTRtLS44IDRjMi4xLjEgMy44IDEuOSAzLjggNCIvPjwvZz48L3N2Zz4=");
} }
} }
} }
[data-callout][data-callout-type="bug"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="bug"] > [data-callout-title]:empty::after {
content: "Bug"; content: "Bug";
} }
[data-callout][data-callout-type="example"] { [data-callout][data-callout-type="example"] {
& { & {
@apply border-purple-600/20 bg-purple-400/20 dark:border-purple-800/20 dark:bg-purple-600/10; @apply border-purple-600/20 bg-purple-400/20 dark:border-purple-800/20 dark:bg-purple-600/10;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-purple-500; @apply text-purple-500;
} }
&::before { &::before {
/* lucide:list */ /* lucide:list */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTggNmgxM004IDEyaDEzTTggMThoMTNNMyA2aC4wMU0zIDEyaC4wMU0zIDE4aC4wMSIvPjwvc3ZnPg=="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTggNmgxM004IDEyaDEzTTggMThoMTNNMyA2aC4wMU0zIDEyaC4wMU0zIDE4aC4wMSIvPjwvc3ZnPg==");
} }
} }
} }
[data-callout][data-callout-type="example"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="example"]
content: "Example"; > [data-callout-title]:empty::after {
content: "Example";
} }
[data-callout][data-callout-type="quote"], [data-callout][data-callout-type="quote"],
[data-callout][data-callout-type="cite"] { [data-callout][data-callout-type="cite"] {
& { & {
@apply border-zinc-600/20 bg-zinc-400/20 dark:border-zinc-800/20 dark:bg-zinc-600/15; @apply border-zinc-600/20 bg-zinc-400/20 dark:border-zinc-800/20 dark:bg-zinc-600/15;
} }
& > [data-callout-title] { & > [data-callout-title] {
& { & {
@apply text-zinc-500; @apply text-zinc-500;
} }
&::before { &::before {
/* lucide:quote */ /* lucide:quote */
mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTMgMjFjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ni0yLjAxNy0yLTJINGMtMS4yNSAwLTIgLjc1LTIgMS45NzJWMTFjMCAxLjI1Ljc1IDIgMiAyYzEgMCAxIDAgMSAxdjFjMCAxLTEgMi0yIDJzLTEgLjAwOC0xIDEuMDMxVjIwYzAgMSAwIDEgMSAxbTEyIDBjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ny0yLjAxNy0yLTJoLTRjLTEuMjUgMC0yIC43NS0yIDEuOTcyVjExYzAgMS4yNS43NSAyIDIgMmguNzVjMCAyLjI1LjI1IDQtMi43NSA0djNjMCAxIDAgMSAxIDEiLz48L3N2Zz4="); mask-image: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxZW0iIGhlaWdodD0iMWVtIiB2aWV3Qm94PSIwIDAgMjQgMjQiPjxwYXRoIGZpbGw9Im5vbmUiIHN0cm9rZT0iY3VycmVudENvbG9yIiBzdHJva2UtbGluZWNhcD0icm91bmQiIHN0cm9rZS1saW5lam9pbj0icm91bmQiIHN0cm9rZS13aWR0aD0iMiIgZD0iTTMgMjFjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ni0yLjAxNy0yLTJINGMtMS4yNSAwLTIgLjc1LTIgMS45NzJWMTFjMCAxLjI1Ljc1IDIgMiAyYzEgMCAxIDAgMSAxdjFjMCAxLTEgMi0yIDJzLTEgLjAwOC0xIDEuMDMxVjIwYzAgMSAwIDEgMSAxbTEyIDBjMyAwIDctMSA3LThWNWMwLTEuMjUtLjc1Ny0yLjAxNy0yLTJoLTRjLTEuMjUgMC0yIC43NS0yIDEuOTcyVjExYzAgMS4yNS43NSAyIDIgMmguNzVjMCAyLjI1LjI1IDQtMi43NSA0djNjMCAxIDAgMSAxIDEiLz48L3N2Zz4=");
} }
} }
} }
[data-callout][data-callout-type="quote"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="quote"] > [data-callout-title]:empty::after {
content: "Quote"; content: "Quote";
} }
[data-callout][data-callout-type="cite"] > [data-callout-title]:empty::after { [data-callout][data-callout-type="cite"] > [data-callout-title]:empty::after {
content: "Cite"; content: "Cite";
} }

View file

@ -1,91 +1,93 @@
@import 'tailwindcss'; @import "tailwindcss";
@custom-variant dark (&:is(.dark *)); @custom-variant dark (&:is(.dark *));
@theme inline { @theme inline {
--font-sans: 'Geist Variable', ui-sans-serif, system-ui, sans-serif, 'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji'; --font-sans: "Geist Variable", ui-sans-serif, system-ui, sans-serif,
--font-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, 'Liberation Mono', 'Courier New', monospace; "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--font-mono: "Geist Mono Variable", ui-monospace, SFMono-Regular, Menlo,
Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--color-background: var(--background); --color-background: var(--background);
--color-foreground: var(--foreground); --color-foreground: var(--foreground);
--color-primary: var(--primary); --color-primary: var(--primary);
--color-primary-foreground: var(--primary-foreground); --color-primary-foreground: var(--primary-foreground);
--color-secondary: var(--secondary); --color-secondary: var(--secondary);
--color-secondary-foreground: var(--secondary-foreground); --color-secondary-foreground: var(--secondary-foreground);
--color-muted: var(--muted); --color-muted: var(--muted);
--color-muted-foreground: var(--muted-foreground); --color-muted-foreground: var(--muted-foreground);
--color-accent: var(--accent); --color-accent: var(--accent);
--color-accent-foreground: var(--accent-foreground); --color-accent-foreground: var(--accent-foreground);
--color-additive: var(--additive); --color-additive: var(--additive);
--color-additive-foreground: var(--additive-foreground); --color-additive-foreground: var(--additive-foreground);
--color-destructive: var(--destructive); --color-destructive: var(--destructive);
--color-destructive-foreground: var(--destructive-foreground); --color-destructive-foreground: var(--destructive-foreground);
--color-border: var(--border); --color-border: var(--border);
--color-ring: var(--ring); --color-ring: var(--ring);
} }
:root { :root {
--background: oklch(1 0 0); --background: oklch(1 0 0);
--foreground: oklch(0.145 0 0); --foreground: oklch(0.145 0 0);
--primary: oklch(0.205 0 0); --primary: oklch(0.205 0 0);
--primary-foreground: oklch(0.985 0 0); --primary-foreground: oklch(0.985 0 0);
--secondary: oklch(0.97 0 0); --secondary: oklch(0.97 0 0);
--secondary-foreground: oklch(0.205 0 0); --secondary-foreground: oklch(0.205 0 0);
--muted: oklch(0.97 0 0); --muted: oklch(0.97 0 0);
--muted-foreground: oklch(0.556 0 0); --muted-foreground: oklch(0.556 0 0);
--accent: oklch(0.97 0 0); --accent: oklch(0.97 0 0);
--accent-foreground: oklch(0.205 0 0); --accent-foreground: oklch(0.205 0 0);
--destructive: oklch(0.577 0.245 27.325); --destructive: oklch(0.577 0.245 27.325);
--border: oklch(0.922 0 0); --border: oklch(0.922 0 0);
--ring: oklch(0.708 0 0); --ring: oklch(0.708 0 0);
} }
.dark { .dark {
--background: oklch(0.145 0 0); --background: oklch(0.145 0 0);
--foreground: oklch(0.985 0 0); --foreground: oklch(0.985 0 0);
--primary: oklch(0.922 0 0); --primary: oklch(0.922 0 0);
--primary-foreground: oklch(0.205 0 0); --primary-foreground: oklch(0.205 0 0);
--secondary: oklch(0.269 0 0); --secondary: oklch(0.269 0 0);
--secondary-foreground: oklch(0.985 0 0); --secondary-foreground: oklch(0.985 0 0);
--muted: oklch(0.269 0 0); --muted: oklch(0.269 0 0);
--muted-foreground: oklch(0.708 0 0); --muted-foreground: oklch(0.708 0 0);
--accent: oklch(0.269 0 0); --accent: oklch(0.269 0 0);
--accent-foreground: oklch(0.985 0 0); --accent-foreground: oklch(0.985 0 0);
--destructive: oklch(0.704 0.191 22.216); --destructive: oklch(0.704 0.191 22.216);
--border: oklch(1 0 0 / 10%); --border: oklch(1 0 0 / 10%);
--ring: oklch(0.556 0 0); --ring: oklch(0.556 0 0);
} }
@layer base { @layer base {
*, *,
::after, ::after,
::before, ::before,
::backdrop, ::backdrop,
::file-selector-button { ::file-selector-button {
@apply border-border outline-ring/50 tracking-tight; @apply border-border outline-ring/50 tracking-tight;
} }
html { html {
@apply bg-background text-foreground scheme-light; @apply bg-background text-foreground scheme-light;
&.dark { &.dark {
@apply scheme-dark; @apply scheme-dark;
} }
::-webkit-scrollbar-corner { ::-webkit-scrollbar-corner {
@apply bg-transparent; @apply bg-transparent;
} }
} }
.disable-transitions, .disable-transitions,
.disable-transitions * { .disable-transitions * {
@apply transition-none!; @apply transition-none!;
} }
} }

View file

@ -1,16 +1,16 @@
export type Site = { export type Site = {
title: string title: string;
description: string description: string;
href: string href: string;
featuredPostCount: number featuredPostCount: number;
postsPerPage: number postsPerPage: number;
} };
export type SocialLink = { export type SocialLink = {
href: string href: string;
label: string label: string;
} };
export type IconMap = { export type IconMap = {
[key: string]: string [key: string]: string;
} };

View file

@ -1,12 +1,12 @@
{ {
"extends": "astro/tsconfigs/strict", "extends": "astro/tsconfigs/strict",
"compilerOptions": { "compilerOptions": {
"strictNullChecks": true, "strictNullChecks": true,
"baseUrl": ".", "baseUrl": ".",
"paths": { "paths": {
"@/*": ["./src/*"] "@/*": ["./src/*"]
}, },
"jsx": "react-jsx", "jsx": "react-jsx",
"jsxImportSource": "react" "jsxImportSource": "react"
} }
} }