astro-awesomeness 0.1.3 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -80,7 +80,7 @@ function DialogContent({ children, className, showCloseButton = true, ...props }
80
80
  "data-slot": "dialog-content",
81
81
  ...props,
82
82
  children: [children, showCloseButton && /* @__PURE__ */ jsxs(Dialog$1.Close, {
83
- className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
83
+ className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
84
84
  "data-slot": "dialog-close",
85
85
  children: [/* @__PURE__ */ jsx(XIcon, {}), /* @__PURE__ */ jsx("span", {
86
86
  className: "sr-only",
@@ -259,17 +259,15 @@ const ThemeToggle = ({ ariaLabel = "Toggle theme" }) => {
259
259
  const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);
260
260
  const [override, setOverride] = useState(() => getStoredTheme());
261
261
  const theme = override ?? (prefersDark ? "dark" : "light");
262
- useEffect(() => {
263
- document.documentElement.classList.toggle("dark", theme === "dark");
264
- }, [theme]);
265
- const handleClick = () => {
262
+ const handleToggleTheme = () => {
266
263
  const next = theme === "dark" ? "light" : "dark";
267
264
  window.localStorage.setItem("theme", next);
265
+ document.documentElement.classList.toggle("dark", next === "dark");
268
266
  setOverride(next);
269
267
  };
270
268
  return /* @__PURE__ */ jsx(Button, {
271
269
  "aria-label": ariaLabel,
272
- onClick: handleClick,
270
+ onClick: handleToggleTheme,
273
271
  size: "icon",
274
272
  variant: "ghost",
275
273
  children: theme === "dark" ? /* @__PURE__ */ jsx(Sun, {}) : /* @__PURE__ */ jsx(Moon, {})
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/components/command-menu.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\nconst Button = ({\n asChild = false,\n className,\n size = \"default\",\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) => {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n {...props}\n />\n );\n};\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Content\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close asChild>\n <Button variant=\"outline\">Close</Button>\n </DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","\"use client\";\n\nimport * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n}\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger asChild>\n <Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\">\n <Search />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n window.location.href = item.url;\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useEffect, useState, useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n // useSyncExternalStore avoids hydration mismatch warnings while still letting\n // us read the actual system preference on the client.\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n useEffect(() => {\n document.documentElement.classList.toggle(\"dark\", theme === \"dark\");\n }, [theme]);\n\n const handleClick = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n window.localStorage.setItem(\"theme\", next);\n setOverride(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleClick} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["DialogPrimitive","CommandPrimitive"],"sources":["../../src/components/ui/button.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/command.tsx","../../src/components/command-menu.tsx","../../src/components/theme-toggle.tsx"],"sourcesContent":["import { cva, type VariantProps } from \"class-variance-authority\";\nimport * as React from \"react\";\nimport { Slot } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\n\nconst buttonVariants = cva(\n \"inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\",\n {\n defaultVariants: {\n size: \"default\",\n variant: \"default\",\n },\n variants: {\n size: {\n default: \"h-9 px-4 py-2 has-[>svg]:px-3\",\n icon: \"size-9\",\n \"icon-lg\": \"size-10\",\n \"icon-sm\": \"size-8\",\n \"icon-xs\": \"size-6 rounded-md [&_svg:not([class*='size-'])]:size-3\",\n lg: \"h-10 rounded-md px-6 has-[>svg]:px-4\",\n sm: \"h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5\",\n xs: \"h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3\",\n },\n variant: {\n default: \"bg-primary text-primary-foreground hover:bg-primary/90\",\n destructive:\n \"bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40\",\n ghost: \"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50\",\n link: \"text-primary underline-offset-4 hover:underline\",\n outline:\n \"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50\",\n secondary: \"bg-secondary text-secondary-foreground hover:bg-secondary/80\",\n },\n },\n },\n);\n\nconst Button = ({\n asChild = false,\n className,\n size = \"default\",\n variant = \"default\",\n ...props\n}: React.ComponentProps<\"button\"> &\n VariantProps<typeof buttonVariants> & {\n asChild?: boolean;\n }) => {\n const Comp = asChild ? Slot.Root : \"button\";\n\n return (\n <Comp\n className={cn(buttonVariants({ className, size, variant }))}\n data-size={size}\n data-slot=\"button\"\n data-variant={variant}\n {...props}\n />\n );\n};\n\nexport { Button, buttonVariants };\n","import * as React from \"react\";\nimport { XIcon } from \"lucide-react\";\nimport { Dialog as DialogPrimitive } from \"radix-ui\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Button } from \"./button\";\n\nfunction Dialog({ ...props }: React.ComponentProps<typeof DialogPrimitive.Root>) {\n return <DialogPrimitive.Root data-slot=\"dialog\" {...props} />;\n}\n\nfunction DialogTrigger({ ...props }: React.ComponentProps<typeof DialogPrimitive.Trigger>) {\n return <DialogPrimitive.Trigger data-slot=\"dialog-trigger\" {...props} />;\n}\n\nfunction DialogPortal({ ...props }: React.ComponentProps<typeof DialogPrimitive.Portal>) {\n return <DialogPrimitive.Portal data-slot=\"dialog-portal\" {...props} />;\n}\n\nfunction DialogClose({ ...props }: React.ComponentProps<typeof DialogPrimitive.Close>) {\n return <DialogPrimitive.Close data-slot=\"dialog-close\" {...props} />;\n}\n\nfunction DialogOverlay({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Overlay>) {\n return (\n <DialogPrimitive.Overlay\n className={cn(\n \"fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0\",\n className,\n )}\n data-slot=\"dialog-overlay\"\n {...props}\n />\n );\n}\n\nfunction DialogContent({\n children,\n className,\n showCloseButton = true,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Content> & {\n showCloseButton?: boolean;\n}) {\n return (\n <DialogPortal data-slot=\"dialog-portal\">\n <DialogOverlay />\n <DialogPrimitive.Content\n className={cn(\n \"fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 outline-none data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 sm:max-w-lg\",\n className,\n )}\n data-slot=\"dialog-content\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close\n className=\"absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4\"\n data-slot=\"dialog-close\"\n >\n <XIcon />\n <span className=\"sr-only\">Close</span>\n </DialogPrimitive.Close>\n )}\n </DialogPrimitive.Content>\n </DialogPortal>\n );\n}\n\nfunction DialogHeader({ className, ...props }: React.ComponentProps<\"div\">) {\n return (\n <div\n className={cn(\"flex flex-col gap-2 text-center sm:text-left\", className)}\n data-slot=\"dialog-header\"\n {...props}\n />\n );\n}\n\nfunction DialogFooter({\n children,\n className,\n showCloseButton = false,\n ...props\n}: React.ComponentProps<\"div\"> & {\n showCloseButton?: boolean;\n}) {\n return (\n <div\n className={cn(\"flex flex-col-reverse gap-2 sm:flex-row sm:justify-end\", className)}\n data-slot=\"dialog-footer\"\n {...props}\n >\n {children}\n {showCloseButton && (\n <DialogPrimitive.Close asChild>\n <Button variant=\"outline\">Close</Button>\n </DialogPrimitive.Close>\n )}\n </div>\n );\n}\n\nfunction DialogTitle({ className, ...props }: React.ComponentProps<typeof DialogPrimitive.Title>) {\n return (\n <DialogPrimitive.Title\n className={cn(\"text-lg leading-none font-semibold\", className)}\n data-slot=\"dialog-title\"\n {...props}\n />\n );\n}\n\nfunction DialogDescription({\n className,\n ...props\n}: React.ComponentProps<typeof DialogPrimitive.Description>) {\n return (\n <DialogPrimitive.Description\n className={cn(\"text-sm text-muted-foreground\", className)}\n data-slot=\"dialog-description\"\n {...props}\n />\n );\n}\n\nexport {\n Dialog,\n // fallow-ignore-next-line unused-export\n DialogClose,\n DialogContent,\n DialogDescription,\n DialogFooter,\n DialogHeader,\n // fallow-ignore-next-line unused-export\n DialogOverlay,\n // fallow-ignore-next-line unused-export\n DialogPortal,\n DialogTitle,\n DialogTrigger,\n};\n","import * as React from \"react\";\nimport { Command as CommandPrimitive } from \"cmdk\";\nimport { SearchIcon } from \"lucide-react\";\n\nimport { cn } from \"../../lib/cn\";\nimport { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from \"./dialog\";\n\nfunction Command({ className, ...props }: React.ComponentProps<typeof CommandPrimitive>) {\n return (\n <CommandPrimitive\n className={cn(\n \"flex h-full w-full flex-col overflow-hidden rounded-md bg-popover text-popover-foreground\",\n className,\n )}\n data-slot=\"command\"\n {...props}\n />\n );\n}\n\nfunction CommandDialog({\n children,\n className,\n description = \"Search for a command to run...\",\n showCloseButton = true,\n title = \"Command Palette\",\n ...props\n}: React.ComponentProps<typeof Dialog> & {\n className?: string;\n description?: string;\n showCloseButton?: boolean;\n title?: string;\n}) {\n return (\n <Dialog {...props}>\n <DialogHeader className=\"sr-only\">\n <DialogTitle>{title}</DialogTitle>\n <DialogDescription>{description}</DialogDescription>\n </DialogHeader>\n <DialogContent\n className={cn(\"overflow-hidden p-0\", className)}\n showCloseButton={showCloseButton}\n >\n <Command className=\"**:data-[slot=command-input-wrapper]:h-12 [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group]]:px-2 [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5\">\n {children}\n </Command>\n </DialogContent>\n </Dialog>\n );\n}\n\nfunction CommandInput({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Input>) {\n return (\n <div className=\"flex h-9 items-center gap-2 border-b px-3\" data-slot=\"command-input-wrapper\">\n <SearchIcon className=\"size-4 shrink-0 opacity-50\" />\n <CommandPrimitive.Input\n className={cn(\n \"flex h-10 w-full rounded-md bg-transparent py-3 text-sm outline-hidden placeholder:text-muted-foreground disabled:cursor-not-allowed disabled:opacity-50\",\n className,\n )}\n data-slot=\"command-input\"\n {...props}\n />\n </div>\n );\n}\n\nfunction CommandList({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.List>) {\n return (\n <CommandPrimitive.List\n className={cn(\"max-h-[300px] scroll-py-1 overflow-x-hidden overflow-y-auto\", className)}\n data-slot=\"command-list\"\n {...props}\n />\n );\n}\n\nfunction CommandEmpty({ ...props }: React.ComponentProps<typeof CommandPrimitive.Empty>) {\n return (\n <CommandPrimitive.Empty\n className=\"py-6 text-center text-sm\"\n data-slot=\"command-empty\"\n {...props}\n />\n );\n}\n\nfunction CommandGroup({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Group>) {\n return (\n <CommandPrimitive.Group\n className={cn(\n \"overflow-hidden p-1 text-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:py-1.5 [&_[cmdk-group-heading]]:text-xs [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group-heading]]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-group\"\n {...props}\n />\n );\n}\n\nfunction CommandSeparator({\n className,\n ...props\n}: React.ComponentProps<typeof CommandPrimitive.Separator>) {\n return (\n <CommandPrimitive.Separator\n className={cn(\"-mx-1 h-px bg-border\", className)}\n data-slot=\"command-separator\"\n {...props}\n />\n );\n}\n\nfunction CommandItem({ className, ...props }: React.ComponentProps<typeof CommandPrimitive.Item>) {\n return (\n <CommandPrimitive.Item\n className={cn(\n \"relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none data-[disabled=true]:pointer-events-none data-[disabled=true]:opacity-50 data-[selected=true]:bg-accent data-[selected=true]:text-accent-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground\",\n className,\n )}\n data-slot=\"command-item\"\n {...props}\n />\n );\n}\n\nfunction CommandShortcut({ className, ...props }: React.ComponentProps<\"span\">) {\n return (\n <span\n className={cn(\"ml-auto text-xs tracking-widest text-muted-foreground\", className)}\n data-slot=\"command-shortcut\"\n {...props}\n />\n );\n}\n\nexport {\n Command,\n // fallow-ignore-next-line unused-export\n CommandDialog,\n CommandInput,\n CommandList,\n CommandEmpty,\n CommandGroup,\n CommandItem,\n CommandShortcut,\n CommandSeparator,\n};\n","import { Search } from \"lucide-react\";\nimport { useEffect, useState } from \"react\";\n\nimport {\n Command,\n CommandEmpty,\n CommandGroup,\n CommandInput,\n CommandItem,\n CommandList,\n} from \"./ui/command\";\nimport { Dialog, DialogContent, DialogTrigger } from \"./ui/dialog\";\nimport { Button } from \"./ui/button\";\n\ntype CommandMenuItem = { description?: string; title: string; url: string };\n\ntype CommandMenuProps = {\n emptyMessage?: string;\n groupHeading?: string;\n items: Array<CommandMenuItem>;\n placeholder?: string;\n};\n\nconst CommandMenu = ({\n emptyMessage = \"No results.\",\n groupHeading = \"Posts\",\n items,\n placeholder = \"Search posts…\",\n}: CommandMenuProps) => {\n const [open, setOpen] = useState(false);\n\n useEffect(() => {\n const handleKeyDown = (event: KeyboardEvent) => {\n if (event.key === \"k\" && (event.metaKey || event.ctrlKey)) {\n event.preventDefault();\n setOpen((value) => !value);\n }\n };\n document.addEventListener(\"keydown\", handleKeyDown);\n return () => {\n document.removeEventListener(\"keydown\", handleKeyDown);\n };\n }, []);\n\n return (\n <Dialog onOpenChange={setOpen} open={open}>\n <DialogTrigger asChild>\n <Button aria-label=\"Open search\" size=\"icon\" variant=\"ghost\">\n <Search />\n </Button>\n </DialogTrigger>\n <DialogContent className=\"p-0\">\n <Command>\n <CommandInput placeholder={placeholder} />\n <CommandList>\n <CommandEmpty>{emptyMessage}</CommandEmpty>\n <CommandGroup heading={groupHeading}>\n {items.map((item) => (\n <CommandItem\n key={item.url}\n onSelect={() => {\n window.location.href = item.url;\n }}\n value={item.title}\n >\n <div className=\"flex flex-col\">\n <span>{item.title}</span>\n {item.description ? (\n <span className=\"text-muted-foreground text-xs\">{item.description}</span>\n ) : null}\n </div>\n </CommandItem>\n ))}\n </CommandGroup>\n </CommandList>\n </Command>\n </DialogContent>\n </Dialog>\n );\n};\n\nexport { CommandMenu };\nexport type { CommandMenuItem, CommandMenuProps };\n","import { Moon, Sun } from \"lucide-react\";\nimport { useState, useSyncExternalStore } from \"react\";\n\nimport { Button } from \"./ui/button\";\n\ntype Theme = \"light\" | \"dark\";\n\nconst getStoredTheme = (): Theme | null => {\n if (typeof window === \"undefined\") {\n return null;\n }\n const stored = window.localStorage.getItem(\"theme\");\n if (stored === \"light\" || stored === \"dark\") {\n return stored;\n }\n return null;\n};\n\nconst getPrefersDark = (): boolean => {\n if (typeof window === \"undefined\") {\n return false;\n }\n return window.matchMedia(\"(prefers-color-scheme: dark)\").matches;\n};\n\nconst subscribePrefersDark = (callback: () => void) => {\n if (typeof window === \"undefined\") {\n return () => {};\n }\n const media = window.matchMedia(\"(prefers-color-scheme: dark)\");\n media.addEventListener(\"change\", callback);\n return () => {\n media.removeEventListener(\"change\", callback);\n };\n};\n\ntype Props = {\n ariaLabel?: string;\n};\n\nconst ThemeToggle = ({ ariaLabel = \"Toggle theme\" }: Props) => {\n // useSyncExternalStore avoids hydration mismatch warnings while still letting\n // us read the actual system preference on the client.\n const prefersDark = useSyncExternalStore(subscribePrefersDark, getPrefersDark, () => false);\n const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());\n const theme: Theme = override ?? (prefersDark ? \"dark\" : \"light\");\n\n const handleToggleTheme = () => {\n const next: Theme = theme === \"dark\" ? \"light\" : \"dark\";\n window.localStorage.setItem(\"theme\", next);\n document.documentElement.classList.toggle(\"dark\", next === \"dark\");\n setOverride(next);\n };\n\n return (\n <Button aria-label={ariaLabel} onClick={handleToggleTheme} size=\"icon\" variant=\"ghost\">\n {theme === \"dark\" ? <Sun /> : <Moon />}\n </Button>\n );\n};\n\nexport { ThemeToggle };\n"],"mappings":"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"}
@@ -10,60 +10,22 @@ declare const postSchema: z.ZodObject<{
10
10
  coverAlt: z.ZodOptional<z.ZodString>;
11
11
  description: z.ZodString;
12
12
  draft: z.ZodDefault<z.ZodBoolean>;
13
- pubDate: z.ZodDate;
13
+ pubDate: z.ZodCoercedDate<unknown>;
14
14
  series: z.ZodOptional<z.ZodString>;
15
- tags: z.ZodDefault<z.ZodArray<z.ZodString, "many">>;
15
+ tags: z.ZodDefault<z.ZodArray<z.ZodString>>;
16
16
  title: z.ZodString;
17
- updatedDate: z.ZodOptional<z.ZodDate>;
18
- }, "strip", z.ZodTypeAny, {
19
- description: string;
20
- draft: boolean;
21
- pubDate: Date;
22
- tags: string[];
23
- title: string;
24
- author?: string | undefined;
25
- cover?: string | undefined;
26
- coverAlt?: string | undefined;
27
- series?: string | undefined;
28
- updatedDate?: Date | undefined;
29
- }, {
30
- description: string;
31
- pubDate: Date;
32
- title: string;
33
- author?: string | undefined;
34
- cover?: string | undefined;
35
- coverAlt?: string | undefined;
36
- draft?: boolean | undefined;
37
- series?: string | undefined;
38
- tags?: string[] | undefined;
39
- updatedDate?: Date | undefined;
40
- }>;
17
+ updatedDate: z.ZodOptional<z.ZodCoercedDate<unknown>>;
18
+ }, z.core.$strip>;
41
19
  declare const tagSchema: z.ZodObject<{
42
20
  description: z.ZodOptional<z.ZodString>;
43
21
  name: z.ZodString;
44
- }, "strip", z.ZodTypeAny, {
45
- name: string;
46
- description?: string | undefined;
47
- }, {
48
- name: string;
49
- description?: string | undefined;
50
- }>;
22
+ }, z.core.$strip>;
51
23
  declare const authorSchema: z.ZodObject<{
52
24
  avatar: z.ZodOptional<z.ZodString>;
53
25
  bio: z.ZodOptional<z.ZodString>;
54
26
  name: z.ZodString;
55
- url: z.ZodOptional<z.ZodString>;
56
- }, "strip", z.ZodTypeAny, {
57
- name: string;
58
- avatar?: string | undefined;
59
- bio?: string | undefined;
60
- url?: string | undefined;
61
- }, {
62
- name: string;
63
- avatar?: string | undefined;
64
- bio?: string | undefined;
65
- url?: string | undefined;
66
- }>;
27
+ url: z.ZodOptional<z.ZodURL>;
28
+ }, z.core.$strip>;
67
29
  type Post = z.infer<typeof postSchema>;
68
30
  type Tag = z.infer<typeof tagSchema>;
69
31
  type Author = z.infer<typeof authorSchema>;
@@ -26,7 +26,7 @@ const authorSchema = z.object({
26
26
  avatar: z.string().optional(),
27
27
  bio: z.string().optional(),
28
28
  name: z.string().min(1),
29
- url: z.string().url().optional()
29
+ url: z.url().optional()
30
30
  });
31
31
  //#endregion
32
32
  export { authorSchema, postSchema, content_exports as t, tagSchema };
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().min(1),\n draft: z.boolean().default(false),\n pubDate: z.coerce.date(),\n series: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.string().url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\nexport { authorSchema, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":";;;;;;;;AAEA,MAAM,aAAa,EAAE,OAAO;CAC1B,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,OAAO,EAAE,OAAO,EAAE,SAAS;CAC3B,UAAU,EAAE,OAAO,EAAE,SAAS;CAC9B,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;CAC7B,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;CAChC,SAAS,EAAE,OAAO,KAAK;CACvB,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;CACpC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,KAAK,EAAE,SAAS;AACxC,CAAC;AAED,MAAM,YAAY,EAAE,OAAO;CACzB,aAAa,EAAE,OAAO,EAAE,SAAS;CACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,KAAK,EAAE,OAAO,EAAE,SAAS;CACzB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;CACtB,KAAK,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS;AACjC,CAAC"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../../src/content/index.ts"],"sourcesContent":["import { z } from \"zod\";\n\nconst postSchema = z.object({\n author: z.string().optional(),\n cover: z.string().optional(),\n coverAlt: z.string().optional(),\n description: z.string().min(1),\n draft: z.boolean().default(false),\n pubDate: z.coerce.date(),\n series: z.string().optional(),\n tags: z.array(z.string()).default([]),\n title: z.string().min(1),\n updatedDate: z.coerce.date().optional(),\n});\n\nconst tagSchema = z.object({\n description: z.string().optional(),\n name: z.string().min(1),\n});\n\nconst authorSchema = z.object({\n avatar: z.string().optional(),\n bio: z.string().optional(),\n name: z.string().min(1),\n url: z.url().optional(),\n});\n\ntype Post = z.infer<typeof postSchema>;\ntype Tag = z.infer<typeof tagSchema>;\ntype Author = z.infer<typeof authorSchema>;\n\nexport { authorSchema, postSchema, tagSchema };\nexport type { Author, Post, Tag };\n"],"mappings":";;;;;;;;AAEA,MAAM,aAAa,EAAE,OAAO;CAC1B,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,OAAO,EAAE,OAAO,EAAE,SAAS;CAC3B,UAAU,EAAE,OAAO,EAAE,SAAS;CAC9B,aAAa,EAAE,OAAO,EAAE,IAAI,CAAC;CAC7B,OAAO,EAAE,QAAQ,EAAE,QAAQ,KAAK;CAChC,SAAS,EAAE,OAAO,KAAK;CACvB,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,MAAM,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,QAAQ,CAAC,CAAC;CACpC,OAAO,EAAE,OAAO,EAAE,IAAI,CAAC;CACvB,aAAa,EAAE,OAAO,KAAK,EAAE,SAAS;AACxC,CAAC;AAED,MAAM,YAAY,EAAE,OAAO;CACzB,aAAa,EAAE,OAAO,EAAE,SAAS;CACjC,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;AACxB,CAAC;AAED,MAAM,eAAe,EAAE,OAAO;CAC5B,QAAQ,EAAE,OAAO,EAAE,SAAS;CAC5B,KAAK,EAAE,OAAO,EAAE,SAAS;CACzB,MAAM,EAAE,OAAO,EAAE,IAAI,CAAC;CACtB,KAAK,EAAE,IAAI,EAAE,SAAS;AACxB,CAAC"}
package/dist/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as content_exports } from "./content/index.js";
2
- import { t as lib_exports } from "./lib-DRaCmJ7r.js";
2
+ import { t as lib_exports } from "./lib-BjqsRto0.js";
3
3
  export { content_exports as content, lib_exports as lib };
package/dist/lib/index.js CHANGED
@@ -1,3 +1,3 @@
1
1
  import { t as cn } from "../cn-B3UhF_Ec.js";
2
- import { a as formatDate, i as getRelatedPosts, n as slugify, r as readingTime } from "../lib-DRaCmJ7r.js";
2
+ import { a as formatDate, i as getRelatedPosts, n as slugify, r as readingTime } from "../lib-BjqsRto0.js";
3
3
  export { cn, formatDate, getRelatedPosts, readingTime, slugify };
@@ -7,20 +7,36 @@ const defaultOptions = {
7
7
  timeZone: "UTC",
8
8
  year: "numeric"
9
9
  };
10
- const formatDate = (date, locale = "en-US", options = defaultOptions) => new Intl.DateTimeFormat(locale, {
10
+ const formatterCache = /* @__PURE__ */ new Map();
11
+ const getFormatter = (locale, options) => {
12
+ const key = `${locale}:${JSON.stringify(options)}`;
13
+ let formatter = formatterCache.get(key);
14
+ if (!formatter) {
15
+ formatter = new Intl.DateTimeFormat(locale, options);
16
+ formatterCache.set(key, formatter);
17
+ }
18
+ return formatter;
19
+ };
20
+ const formatDate = (date, locale = "en-US", options = defaultOptions) => getFormatter(locale, {
11
21
  ...defaultOptions,
12
22
  ...options
13
23
  }).format(date);
14
24
  //#endregion
15
25
  //#region src/lib/get-related-posts.ts
16
- const countShared = (a, b) => {
17
- const setB = new Set(b);
18
- return a.filter((tag) => setB.has(tag)).length;
26
+ const getRelatedPosts = (current, all, n) => {
27
+ const currentTagSet = new Set(current.data.tags);
28
+ const scored = [];
29
+ for (const post of all) {
30
+ if (post.slug === current.slug) continue;
31
+ let score = 0;
32
+ for (const tag of post.data.tags) if (currentTagSet.has(tag)) score++;
33
+ scored.push({
34
+ post,
35
+ score
36
+ });
37
+ }
38
+ return scored.toSorted((x, y) => y.score - x.score).slice(0, n).map(({ post }) => post);
19
39
  };
20
- const getRelatedPosts = (current, all, n) => all.filter((post) => post.slug !== current.slug).map((post) => ({
21
- post,
22
- score: countShared(current.data.tags, post.data.tags)
23
- })).toSorted((x, y) => y.score - x.score).slice(0, n).map(({ post }) => post);
24
40
  //#endregion
25
41
  //#region src/lib/reading-time.ts
26
42
  const WORDS_PER_MINUTE = 200;
@@ -47,4 +63,4 @@ var lib_exports = /* @__PURE__ */ __exportAll({
47
63
  //#endregion
48
64
  export { formatDate as a, getRelatedPosts as i, slugify as n, readingTime as r, lib_exports as t };
49
65
 
50
- //# sourceMappingURL=lib-DRaCmJ7r.js.map
66
+ //# sourceMappingURL=lib-BjqsRto0.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"lib-BjqsRto0.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/reading-time.ts","../src/lib/slugify.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatterCache = new Map<string, Intl.DateTimeFormat>();\n\nconst getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {\n const key = `${locale}:${JSON.stringify(options)}`;\n let formatter = formatterCache.get(key);\n if (!formatter) {\n formatter = new Intl.DateTimeFormat(locale, options);\n formatterCache.set(key, formatter);\n }\n return formatter;\n};\n\nconst formatDate = (\n date: Date,\n locale: string = \"en-US\",\n options: Intl.DateTimeFormatOptions = defaultOptions,\n) => getFormatter(locale, { ...defaultOptions, ...options }).format(date);\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; slug: string };\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {\n const currentTagSet = new Set(current.data.tags);\n const scored: Array<{ post: T; score: number }> = [];\n\n for (const post of all) {\n if (post.slug === current.slug) {\n continue;\n }\n let score = 0;\n for (const tag of post.data.tags) {\n if (currentTagSet.has(tag)) {\n score++;\n }\n }\n scored.push({ post, score });\n }\n\n return scored\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n};\n\nexport { getRelatedPosts };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\n"],"mappings":";;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,iCAAiB,IAAI,IAAiC;AAE5D,MAAM,gBAAgB,QAAgB,YAAwC;CAC5E,MAAM,MAAM,GAAG,OAAO,GAAG,KAAK,UAAU,OAAO;CAC/C,IAAI,YAAY,eAAe,IAAI,GAAG;CACtC,IAAI,CAAC,WAAW;EACd,YAAY,IAAI,KAAK,eAAe,QAAQ,OAAO;EACnD,eAAe,IAAI,KAAK,SAAS;CACnC;CACA,OAAO;AACT;AAEA,MAAM,cACJ,MACA,SAAiB,SACjB,UAAsC,mBACnC,aAAa,QAAQ;CAAE,GAAG;CAAgB,GAAG;AAAQ,CAAC,EAAE,OAAO,IAAI;;;ACrBxE,MAAM,mBAAsC,SAAY,KAAe,MAAc;CACnF,MAAM,gBAAgB,IAAI,IAAI,QAAQ,KAAK,IAAI;CAC/C,MAAM,SAA4C,CAAC;CAEnD,KAAK,MAAM,QAAQ,KAAK;EACtB,IAAI,KAAK,SAAS,QAAQ,MACxB;EAEF,IAAI,QAAQ;EACZ,KAAK,MAAM,OAAO,KAAK,KAAK,MAC1B,IAAI,cAAc,IAAI,GAAG,GACvB;EAGJ,OAAO,KAAK;GAAE;GAAM;EAAM,CAAC;CAC7B;CAEA,OAAO,OACJ,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EACpC,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,WAAW,IAAI;AAC3B;;;ACvBA,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE,EAC1B,2BAAW,IAAA,OAAA,cAAA,IAAY,GAAG,GAAG,EAC7B,2BAAW,IAAA,OAAA,QAAA,IAAM,GAAG,GAAG,EACvB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B;;;ACXA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,EACf,2BAAW,IAAA,OAAA,SAAA,IAAQ,GAAG,EAAE,EACxB,YAAY,EACZ,2BAAW,IAAA,OAAA,mBAAA,IAAgB,GAAG,EAAE,EAChC,KAAK,EACL,2BAAW,IAAA,OAAA,aAAA,IAAU,GAAG,GAAG,EAC3B,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE"}
@@ -52,7 +52,6 @@
52
52
  --color-card-foreground: oklch(0.985 0 0);
53
53
  --color-popover: oklch(0.145 0 0);
54
54
  --color-popover-foreground: oklch(0.985 0 0);
55
- --color-primary: oklch(0.985 0 0);
56
55
  --color-primary-foreground: oklch(0.205 0 0);
57
56
  --color-secondary: oklch(0.269 0 0);
58
57
  --color-secondary-foreground: oklch(0.985 0 0);
@@ -67,12 +66,28 @@
67
66
  --color-ring: oklch(0.556 0 0);
68
67
  }
69
68
 
69
+ /* Opt-in to cross-document view transitions on same-origin navigations.
70
+ Gated by prefers-reduced-motion: users who request reduced motion get
71
+ instant navigation. Browsers without support degrade to instant nav. */
72
+ @media (prefers-reduced-motion: no-preference) {
73
+ @view-transition {
74
+ navigation: auto;
75
+ }
76
+ }
77
+
70
78
  @layer base {
71
79
  *,
72
80
  *::before,
73
81
  *::after {
74
82
  box-sizing: border-box;
75
83
  }
84
+ :root {
85
+ /* Inform the browser which schemes the page supports so native UI
86
+ (scrollbars, form controls, canvas) themes itself before paint and
87
+ avoids a flash of unstyled chrome. The `.dark` class still drives
88
+ our token overrides; this just covers native surfaces. */
89
+ color-scheme: light dark;
90
+ }
76
91
  html {
77
92
  -webkit-text-size-adjust: 100%;
78
93
  }
@@ -96,6 +111,23 @@
96
111
  cursor: pointer;
97
112
  }
98
113
 
114
+ /* Balanced wrapping for short heading-like blocks. Limited to top-level
115
+ headings to avoid the perf cost on arbitrary text. */
116
+ h1,
117
+ h2,
118
+ h3,
119
+ h4,
120
+ h5,
121
+ h6 {
122
+ text-wrap: balance;
123
+ }
124
+
125
+ /* Render `<time>` metadata with tabular figures so dates and reading-time
126
+ counts align between rows in lists. */
127
+ time {
128
+ font-variant-numeric: tabular-nums;
129
+ }
130
+
99
131
  ::-webkit-scrollbar {
100
132
  width: 6px;
101
133
  height: 6px;
@@ -137,19 +169,26 @@
137
169
  .prose h3 {
138
170
  font-size: 1.375rem;
139
171
  }
172
+ .prose p,
173
+ .prose li,
174
+ .prose blockquote {
175
+ /* Prefer "pretty" line breaks (no orphan words) for long-form body text.
176
+ Falls back to the default greedy wrap in browsers without support. */
177
+ text-wrap: pretty;
178
+ }
140
179
  .prose p {
141
180
  margin-top: 1em;
142
181
  margin-bottom: 1em;
143
182
  }
144
183
  .prose a {
145
- color: var(--color-accent-foreground);
184
+ color: var(--color-primary);
146
185
  text-decoration: underline;
147
186
  text-underline-offset: 0.2em;
148
187
  text-decoration-color: var(--color-border);
149
188
  transition: text-decoration-color 0.15s;
150
189
  }
151
190
  .prose a:hover {
152
- text-decoration-color: var(--color-accent-foreground);
191
+ text-decoration-color: var(--color-primary);
153
192
  }
154
193
  .prose blockquote {
155
194
  margin: 1.5em 0;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "astro-awesomeness",
3
- "version": "0.1.3",
4
- "description": "Astro 5 blog theme with shadcn primitives and a Tailwind v4 preset.",
3
+ "version": "0.4.0",
4
+ "description": "Astro 6 blog theme with shadcn primitives and a Tailwind v4 preset.",
5
5
  "keywords": [
6
6
  "astro",
7
7
  "astro-theme",
@@ -44,26 +44,26 @@
44
44
  "class-variance-authority": "^0.7.1",
45
45
  "clsx": "^2.1.1",
46
46
  "cmdk": "^1.1.1",
47
- "lucide-react": "^1.14.0",
47
+ "lucide-react": "^1.16.0",
48
48
  "radix-ui": "^1.4.3",
49
49
  "tailwind-merge": "^3.6.0",
50
50
  "tw-animate-css": "^1.4.0",
51
- "zod": "^3.24.0"
51
+ "zod": "^4.4.3"
52
52
  },
53
53
  "devDependencies": {
54
54
  "@tailwindcss/postcss": "^4.3.0",
55
55
  "@types/node": "^24.0.0",
56
- "@types/react": "^19.2.14",
57
- "@types/react-dom": "^19.2.0",
58
- "@vitest/coverage-v8": "^4.1.5",
59
- "happy-dom": "^15.11.0",
56
+ "@types/react": "^19.2.15",
57
+ "@types/react-dom": "^19.2.3",
58
+ "@vitest/coverage-v8": "^4.1.7",
59
+ "happy-dom": "^20.9.0",
60
60
  "tailwindcss": "^4.3.0",
61
61
  "typescript": "^6.0.3",
62
- "vitest": "^4.1.5",
62
+ "vitest": "^4.1.7",
63
63
  "@repo/typescript-config": "0.0.0"
64
64
  },
65
65
  "peerDependencies": {
66
- "astro": "^5.0.0",
66
+ "astro": "^6.0.0",
67
67
  "react": "^19.0.0",
68
68
  "react-dom": "^19.0.0",
69
69
  "tailwindcss": "^4.0.0"
@@ -6,6 +6,7 @@ type Props = {
6
6
  homeHref?: string;
7
7
  class?: string;
8
8
  themeToggleLabel?: string;
9
+ showThemeToggle?: boolean;
9
10
  };
10
11
 
11
12
  const {
@@ -13,16 +14,17 @@ const {
13
14
  homeHref = "/",
14
15
  class: className = "",
15
16
  themeToggleLabel = "Toggle theme",
17
+ showThemeToggle = true,
16
18
  } = Astro.props;
17
19
  ---
18
20
  <header class:list={["border-border bg-background/80 sticky top-0 z-40 border-b backdrop-blur", className]}>
19
21
  <div class="mx-auto flex max-w-3xl items-center justify-between px-4 py-3">
20
- <a href={homeHref} class="text-foreground text-base font-semibold tracking-tight">
22
+ <a href={homeHref} class="text-primary text-base font-semibold tracking-tight">
21
23
  <slot name="brand">{siteTitle}</slot>
22
24
  </a>
23
25
  <nav class="flex items-center gap-2">
24
26
  <slot name="nav" />
25
- <ThemeToggle client:load ariaLabel={themeToggleLabel} />
27
+ {showThemeToggle && <ThemeToggle client:load ariaLabel={themeToggleLabel} />}
26
28
  </nav>
27
29
  </div>
28
30
  </header>
@@ -14,9 +14,9 @@ const { post, locale } = Astro.props;
14
14
  const { title, description, pubDate, tags } = post.data;
15
15
  const href = `/blog/${post.id}`;
16
16
  ---
17
- <article class="border-border group border-b py-6 last:border-0">
17
+ <article class="post-card border-border group border-b py-6 last:border-0">
18
18
  <a href={href} class="block">
19
- <h2 class="text-foreground group-hover:text-accent-foreground text-xl font-semibold tracking-tight transition-colors">
19
+ <h2 class="text-foreground group-hover:text-primary text-xl font-semibold tracking-tight transition-colors">
20
20
  {title}
21
21
  </h2>
22
22
  <div class="text-muted-foreground mt-1 flex items-center gap-2 text-sm">
@@ -26,3 +26,14 @@ const href = `/blog/${post.id}`;
26
26
  </a>
27
27
  {tags.length > 0 && <TagList tags={tags} class="mt-3" />}
28
28
  </article>
29
+
30
+ <style>
31
+ /* Skip layout/paint for off-screen cards on long post lists. The first
32
+ three remain eagerly rendered so above-the-fold paint isn't blocked by
33
+ visibility containment evaluation. `contain-intrinsic-size: auto …`
34
+ lets the browser cache real measurements after first render. */
35
+ .post-card:nth-child(n + 4) {
36
+ content-visibility: auto;
37
+ contain-intrinsic-size: auto 200px;
38
+ }
39
+ </style>
@@ -1,5 +1,5 @@
1
1
  import { Moon, Sun } from "lucide-react";
2
- import { useEffect, useState, useSyncExternalStore } from "react";
2
+ import { useState, useSyncExternalStore } from "react";
3
3
 
4
4
  import { Button } from "./ui/button";
5
5
 
@@ -45,18 +45,15 @@ const ThemeToggle = ({ ariaLabel = "Toggle theme" }: Props) => {
45
45
  const [override, setOverride] = useState<Theme | null>(() => getStoredTheme());
46
46
  const theme: Theme = override ?? (prefersDark ? "dark" : "light");
47
47
 
48
- useEffect(() => {
49
- document.documentElement.classList.toggle("dark", theme === "dark");
50
- }, [theme]);
51
-
52
- const handleClick = () => {
48
+ const handleToggleTheme = () => {
53
49
  const next: Theme = theme === "dark" ? "light" : "dark";
54
50
  window.localStorage.setItem("theme", next);
51
+ document.documentElement.classList.toggle("dark", next === "dark");
55
52
  setOverride(next);
56
53
  };
57
54
 
58
55
  return (
59
- <Button aria-label={ariaLabel} onClick={handleClick} size="icon" variant="ghost">
56
+ <Button aria-label={ariaLabel} onClick={handleToggleTheme} size="icon" variant="ghost">
60
57
  {theme === "dark" ? <Sun /> : <Moon />}
61
58
  </Button>
62
59
  );
@@ -1,5 +1,3 @@
1
- "use client";
2
-
3
1
  import * as React from "react";
4
2
  import { Command as CommandPrimitive } from "cmdk";
5
3
  import { SearchIcon } from "lucide-react";
@@ -59,7 +59,7 @@ function DialogContent({
59
59
  {children}
60
60
  {showCloseButton && (
61
61
  <DialogPrimitive.Close
62
- className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
62
+ className="absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 focus-visible:outline-hidden disabled:pointer-events-none data-[state=open]:bg-accent data-[state=open]:text-muted-foreground [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4"
63
63
  data-slot="dialog-close"
64
64
  >
65
65
  <XIcon />
@@ -22,7 +22,7 @@ const authorSchema = z.object({
22
22
  avatar: z.string().optional(),
23
23
  bio: z.string().optional(),
24
24
  name: z.string().min(1),
25
- url: z.string().url().optional(),
25
+ url: z.url().optional(),
26
26
  });
27
27
 
28
28
  type Post = z.infer<typeof postSchema>;
@@ -28,6 +28,7 @@ const {
28
28
  <head>
29
29
  <meta charset="utf-8" />
30
30
  <meta name="viewport" content="width=device-width, initial-scale=1" />
31
+ <meta name="color-scheme" content="light dark" />
31
32
  <meta name="generator" content={Astro.generator} />
32
33
  <Seo title={title} description={description} canonical={canonical} image={image} type={type} />
33
34
  <script is:inline>
@@ -38,6 +39,14 @@ const {
38
39
  document.documentElement.classList.toggle("dark", theme === "dark");
39
40
  })();
40
41
  </script>
42
+ <script type="speculationrules" is:inline set:html={JSON.stringify({
43
+ prefetch: [
44
+ {
45
+ where: { and: [{ href_matches: "/*" }, { not: { href_matches: "/*\\?*" } }] },
46
+ eagerness: "moderate",
47
+ },
48
+ ],
49
+ })} />
41
50
  <slot name="head" />
42
51
  </head>
43
52
  <body class="bg-background text-foreground min-h-dvh flex flex-col">
@@ -20,7 +20,7 @@ const { title, description, posts, page, urlForPage, locale, siteTitle } = Astro
20
20
  <BaseLayout title={title} description={description} siteTitle={siteTitle}>
21
21
  <main class="mx-auto max-w-3xl px-4 py-10">
22
22
  <header class="mb-8">
23
- <h1 class="text-foreground text-3xl font-bold tracking-tight">{title}</h1>
23
+ <h1 class="text-primary text-3xl font-bold tracking-tight">{title}</h1>
24
24
  <p class="text-muted-foreground mt-2">{description}</p>
25
25
  </header>
26
26
  <section>
@@ -20,7 +20,7 @@ const { title, description, pubDate, updatedDate, tags } = post.data;
20
20
  <BaseLayout title={title} description={description} type="article" siteTitle={siteTitle}>
21
21
  <article class="mx-auto max-w-3xl px-4 py-10">
22
22
  <header class="mb-8">
23
- <h1 class="text-foreground text-3xl font-bold tracking-tight sm:text-4xl">{title}</h1>
23
+ <h1 class="text-primary text-3xl font-bold tracking-tight sm:text-4xl">{title}</h1>
24
24
  <div class="text-muted-foreground mt-3 flex flex-wrap items-center gap-x-3 gap-y-1 text-sm">
25
25
  <FormattedDate date={pubDate} locale={locale} />
26
26
  <span aria-hidden="true">·</span>
@@ -5,10 +5,22 @@ const defaultOptions: Intl.DateTimeFormatOptions = {
5
5
  year: "numeric",
6
6
  };
7
7
 
8
+ const formatterCache = new Map<string, Intl.DateTimeFormat>();
9
+
10
+ const getFormatter = (locale: string, options: Intl.DateTimeFormatOptions) => {
11
+ const key = `${locale}:${JSON.stringify(options)}`;
12
+ let formatter = formatterCache.get(key);
13
+ if (!formatter) {
14
+ formatter = new Intl.DateTimeFormat(locale, options);
15
+ formatterCache.set(key, formatter);
16
+ }
17
+ return formatter;
18
+ };
19
+
8
20
  const formatDate = (
9
21
  date: Date,
10
22
  locale: string = "en-US",
11
23
  options: Intl.DateTimeFormatOptions = defaultOptions,
12
- ) => new Intl.DateTimeFormat(locale, { ...defaultOptions, ...options }).format(date);
24
+ ) => getFormatter(locale, { ...defaultOptions, ...options }).format(date);
13
25
 
14
26
  export { formatDate };
@@ -1,19 +1,26 @@
1
1
  type HasTags = { data: { tags: Array<string> }; slug: string };
2
2
 
3
- const countShared = (a: Array<string>, b: Array<string>) => {
4
- const setB = new Set(b);
5
- return a.filter((tag) => setB.has(tag)).length;
6
- };
3
+ const getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) => {
4
+ const currentTagSet = new Set(current.data.tags);
5
+ const scored: Array<{ post: T; score: number }> = [];
6
+
7
+ for (const post of all) {
8
+ if (post.slug === current.slug) {
9
+ continue;
10
+ }
11
+ let score = 0;
12
+ for (const tag of post.data.tags) {
13
+ if (currentTagSet.has(tag)) {
14
+ score++;
15
+ }
16
+ }
17
+ scored.push({ post, score });
18
+ }
7
19
 
8
- const getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) =>
9
- all
10
- .filter((post) => post.slug !== current.slug)
11
- .map((post) => ({
12
- post,
13
- score: countShared(current.data.tags, post.data.tags),
14
- }))
20
+ return scored
15
21
  .toSorted((x, y) => y.score - x.score)
16
22
  .slice(0, n)
17
23
  .map(({ post }) => post);
24
+ };
18
25
 
19
26
  export { getRelatedPosts };
@@ -52,7 +52,6 @@
52
52
  --color-card-foreground: oklch(0.985 0 0);
53
53
  --color-popover: oklch(0.145 0 0);
54
54
  --color-popover-foreground: oklch(0.985 0 0);
55
- --color-primary: oklch(0.985 0 0);
56
55
  --color-primary-foreground: oklch(0.205 0 0);
57
56
  --color-secondary: oklch(0.269 0 0);
58
57
  --color-secondary-foreground: oklch(0.985 0 0);
@@ -67,12 +66,28 @@
67
66
  --color-ring: oklch(0.556 0 0);
68
67
  }
69
68
 
69
+ /* Opt-in to cross-document view transitions on same-origin navigations.
70
+ Gated by prefers-reduced-motion: users who request reduced motion get
71
+ instant navigation. Browsers without support degrade to instant nav. */
72
+ @media (prefers-reduced-motion: no-preference) {
73
+ @view-transition {
74
+ navigation: auto;
75
+ }
76
+ }
77
+
70
78
  @layer base {
71
79
  *,
72
80
  *::before,
73
81
  *::after {
74
82
  box-sizing: border-box;
75
83
  }
84
+ :root {
85
+ /* Inform the browser which schemes the page supports so native UI
86
+ (scrollbars, form controls, canvas) themes itself before paint and
87
+ avoids a flash of unstyled chrome. The `.dark` class still drives
88
+ our token overrides; this just covers native surfaces. */
89
+ color-scheme: light dark;
90
+ }
76
91
  html {
77
92
  -webkit-text-size-adjust: 100%;
78
93
  }
@@ -96,6 +111,23 @@
96
111
  cursor: pointer;
97
112
  }
98
113
 
114
+ /* Balanced wrapping for short heading-like blocks. Limited to top-level
115
+ headings to avoid the perf cost on arbitrary text. */
116
+ h1,
117
+ h2,
118
+ h3,
119
+ h4,
120
+ h5,
121
+ h6 {
122
+ text-wrap: balance;
123
+ }
124
+
125
+ /* Render `<time>` metadata with tabular figures so dates and reading-time
126
+ counts align between rows in lists. */
127
+ time {
128
+ font-variant-numeric: tabular-nums;
129
+ }
130
+
99
131
  ::-webkit-scrollbar {
100
132
  width: 6px;
101
133
  height: 6px;
@@ -137,19 +169,26 @@
137
169
  .prose h3 {
138
170
  font-size: 1.375rem;
139
171
  }
172
+ .prose p,
173
+ .prose li,
174
+ .prose blockquote {
175
+ /* Prefer "pretty" line breaks (no orphan words) for long-form body text.
176
+ Falls back to the default greedy wrap in browsers without support. */
177
+ text-wrap: pretty;
178
+ }
140
179
  .prose p {
141
180
  margin-top: 1em;
142
181
  margin-bottom: 1em;
143
182
  }
144
183
  .prose a {
145
- color: var(--color-accent-foreground);
184
+ color: var(--color-primary);
146
185
  text-decoration: underline;
147
186
  text-underline-offset: 0.2em;
148
187
  text-decoration-color: var(--color-border);
149
188
  transition: text-decoration-color 0.15s;
150
189
  }
151
190
  .prose a:hover {
152
- text-decoration-color: var(--color-accent-foreground);
191
+ text-decoration-color: var(--color-primary);
153
192
  }
154
193
  .prose blockquote {
155
194
  margin: 1.5em 0;
@@ -1 +0,0 @@
1
- {"version":3,"file":"lib-DRaCmJ7r.js","names":[],"sources":["../src/lib/format-date.ts","../src/lib/get-related-posts.ts","../src/lib/reading-time.ts","../src/lib/slugify.ts","../src/lib/index.ts"],"sourcesContent":["const defaultOptions: Intl.DateTimeFormatOptions = {\n day: \"numeric\",\n month: \"long\",\n timeZone: \"UTC\",\n year: \"numeric\",\n};\n\nconst formatDate = (\n date: Date,\n locale: string = \"en-US\",\n options: Intl.DateTimeFormatOptions = defaultOptions,\n) => new Intl.DateTimeFormat(locale, { ...defaultOptions, ...options }).format(date);\n\nexport { formatDate };\n","type HasTags = { data: { tags: Array<string> }; slug: string };\n\nconst countShared = (a: Array<string>, b: Array<string>) => {\n const setB = new Set(b);\n return a.filter((tag) => setB.has(tag)).length;\n};\n\nconst getRelatedPosts = <T extends HasTags>(current: T, all: Array<T>, n: number) =>\n all\n .filter((post) => post.slug !== current.slug)\n .map((post) => ({\n post,\n score: countShared(current.data.tags, post.data.tags),\n }))\n .toSorted((x, y) => y.score - x.score)\n .slice(0, n)\n .map(({ post }) => post);\n\nexport { getRelatedPosts };\n","const WORDS_PER_MINUTE = 200;\n\nconst readingTime = (input: string) => {\n const stripped = input\n .replaceAll(/<[^>]*>/gv, \"\")\n .replaceAll(/[#*_`>\\-]/gv, \" \")\n .replaceAll(/\\s+/gv, \" \")\n .trim();\n const words = stripped.length === 0 ? 0 : stripped.split(\" \").length;\n const minutes = Math.max(1, Math.ceil(words / WORDS_PER_MINUTE));\n return { minutes, words };\n};\n\nexport { readingTime };\n","const slugify = (input: string) =>\n input\n .normalize(\"NFD\")\n .replaceAll(/[̀-ͯ]/gv, \"\")\n .toLowerCase()\n .replaceAll(/[^a-z0-9\\s\\-]/gv, \"\")\n .trim()\n .replaceAll(/[\\s\\-]+/gv, \"-\")\n .replaceAll(/^-+|-+$/gv, \"\");\n\nexport { slugify };\n","export { cn } from \"./cn\";\nexport { formatDate } from \"./format-date\";\nexport { getRelatedPosts } from \"./get-related-posts\";\nexport { readingTime } from \"./reading-time\";\nexport { slugify } from \"./slugify\";\n"],"mappings":";;;AAAA,MAAM,iBAA6C;CACjD,KAAK;CACL,OAAO;CACP,UAAU;CACV,MAAM;AACR;AAEA,MAAM,cACJ,MACA,SAAiB,SACjB,UAAsC,mBACnC,IAAI,KAAK,eAAe,QAAQ;CAAE,GAAG;CAAgB,GAAG;AAAQ,CAAC,EAAE,OAAO,IAAI;;;ACTnF,MAAM,eAAe,GAAkB,MAAqB;CAC1D,MAAM,OAAO,IAAI,IAAI,CAAC;CACtB,OAAO,EAAE,QAAQ,QAAQ,KAAK,IAAI,GAAG,CAAC,EAAE;AAC1C;AAEA,MAAM,mBAAsC,SAAY,KAAe,MACrE,IACG,QAAQ,SAAS,KAAK,SAAS,QAAQ,IAAI,EAC3C,KAAK,UAAU;CACd;CACA,OAAO,YAAY,QAAQ,KAAK,MAAM,KAAK,KAAK,IAAI;AACtD,EAAE,EACD,UAAU,GAAG,MAAM,EAAE,QAAQ,EAAE,KAAK,EACpC,MAAM,GAAG,CAAC,EACV,KAAK,EAAE,WAAW,IAAI;;;AChB3B,MAAM,mBAAmB;AAEzB,MAAM,eAAe,UAAkB;CACrC,MAAM,WAAW,MACd,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE,EAC1B,2BAAW,IAAA,OAAA,cAAA,IAAY,GAAG,GAAG,EAC7B,2BAAW,IAAA,OAAA,QAAA,IAAM,GAAG,GAAG,EACvB,KAAK;CACR,MAAM,QAAQ,SAAS,WAAW,IAAI,IAAI,SAAS,MAAM,GAAG,EAAE;CAE9D,OAAO;EAAE,SADO,KAAK,IAAI,GAAG,KAAK,KAAK,QAAQ,gBAAgB,CAC/C;EAAG;CAAM;AAC1B;;;ACXA,MAAM,WAAW,UACf,MACG,UAAU,KAAK,EACf,2BAAW,IAAA,OAAA,SAAA,IAAQ,GAAG,EAAE,EACxB,YAAY,EACZ,2BAAW,IAAA,OAAA,mBAAA,IAAgB,GAAG,EAAE,EAChC,KAAK,EACL,2BAAW,IAAA,OAAA,aAAA,IAAU,GAAG,GAAG,EAC3B,2BAAW,IAAA,OAAA,WAAA,IAAU,GAAG,EAAE"}