restyle interim commit
This commit is contained in:
parent
dbcc643222
commit
33d553fb99
30 changed files with 594 additions and 1365 deletions
10
package-lock.json
generated
10
package-lock.json
generated
|
|
@ -21,7 +21,6 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"lucide-react": "^0.509.0",
|
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|
@ -5150,15 +5149,6 @@
|
||||||
"yallist": "^3.0.2"
|
"yallist": "^3.0.2"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/lucide-react": {
|
|
||||||
"version": "0.509.0",
|
|
||||||
"resolved": "https://registry.npmjs.org/lucide-react/-/lucide-react-0.509.0.tgz",
|
|
||||||
"integrity": "sha512-xCJHn6Uh5qF6PGml25vveCTrHJZcqS1G1MVzWZK54ZQsOiCVJk4fwY3oyo5EXS2S+aqvTpWYIfJN+PesJ0quxg==",
|
|
||||||
"license": "ISC",
|
|
||||||
"peerDependencies": {
|
|
||||||
"react": "^16.5.1 || ^17.0.0 || ^18.0.0 || ^19.0.0"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
"node_modules/magic-string": {
|
"node_modules/magic-string": {
|
||||||
"version": "0.30.17",
|
"version": "0.30.17",
|
||||||
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
"resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.17.tgz",
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@
|
||||||
"clsx": "^2.1.1",
|
"clsx": "^2.1.1",
|
||||||
"gray-matter": "^4.0.3",
|
"gray-matter": "^4.0.3",
|
||||||
"i": "^0.3.7",
|
"i": "^0.3.7",
|
||||||
"lucide-react": "^0.509.0",
|
|
||||||
"marked": "^15.0.12",
|
"marked": "^15.0.12",
|
||||||
"react": "^19.1.0",
|
"react": "^19.1.0",
|
||||||
"react-dom": "^19.1.0",
|
"react-dom": "^19.1.0",
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,8 @@ const renderer = {
|
||||||
}
|
}
|
||||||
|
|
||||||
const highlighter = await createHighlighter({
|
const highlighter = await createHighlighter({
|
||||||
themes: ["dark-plus"],
|
themes: ["gruvbox-dark-hard"],
|
||||||
|
|
||||||
langs: [
|
langs: [
|
||||||
"javascript",
|
"javascript",
|
||||||
"typescript",
|
"typescript",
|
||||||
|
|
@ -48,7 +49,8 @@ const posts = files.map((file) => {
|
||||||
(match, lang, code) => {
|
(match, lang, code) => {
|
||||||
return highlighter.codeToHtml(code.trim(), {
|
return highlighter.codeToHtml(code.trim(), {
|
||||||
lang: lang || "text",
|
lang: lang || "text",
|
||||||
theme: "dark-plus",
|
theme: "gruvbox-dark-hard",
|
||||||
|
|
||||||
transformers: [transformerColorizedBrackets()],
|
transformers: [transformerColorizedBrackets()],
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,135 +0,0 @@
|
||||||
// @ts-nocheck
|
|
||||||
import { Button } from "@/components/ui/button"
|
|
||||||
import { useTheme } from "@/context/ThemeProvider"
|
|
||||||
import { MoonStar, MenuIcon } from "lucide-react"
|
|
||||||
import { Link } from "react-router"
|
|
||||||
import headerImage from "../images/radigue_gruvbox.png"
|
|
||||||
import controlPanel from "../images/control-panel.png"
|
|
||||||
import {
|
|
||||||
NavigationMenu,
|
|
||||||
NavigationMenuContent,
|
|
||||||
NavigationMenuItem,
|
|
||||||
NavigationMenuLink,
|
|
||||||
NavigationMenuList,
|
|
||||||
NavigationMenuTrigger,
|
|
||||||
navigationMenuTriggerStyle,
|
|
||||||
NavigationMenuViewport,
|
|
||||||
} from "@/components/ui/navigation-menu"
|
|
||||||
|
|
||||||
import { Toggle } from "@/components/ui/toggle"
|
|
||||||
const Menu = () => {
|
|
||||||
return (
|
|
||||||
<NavigationMenu>
|
|
||||||
<NavigationMenuList>
|
|
||||||
{/* Desktop menu - hidden on mobile, visible on md+ */}
|
|
||||||
<div className="hidden md:flex md:space-x-3">
|
|
||||||
<NavigationMenuItem className="">
|
|
||||||
<NavigationMenuLink
|
|
||||||
asChild
|
|
||||||
className={`${navigationMenuTriggerStyle()} border-0 rounded bg-accent/20 backdrop-blur-md hover:bg-accent/40 active:bg-accent/40 focus-visible:outline-none font-semibold transition-colors`}
|
|
||||||
>
|
|
||||||
<Link to="/posts/page/1">Posts</Link>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
<NavigationMenuItem className="">
|
|
||||||
<NavigationMenuLink
|
|
||||||
asChild
|
|
||||||
className={`${navigationMenuTriggerStyle()} border-0 rounded bg-accent/20 backdrop-blur-md hover:bg-accent/40 active:bg-accent/40 focus-visible:outline-none font-semibold transition-colors`}
|
|
||||||
>
|
|
||||||
<Link to="/about">About</Link>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* Mobile dropdown - visible only on small screens */}
|
|
||||||
<NavigationMenuItem className="md:hidden px-4">
|
|
||||||
<NavigationMenuTrigger className="border-0 rounded bg-accent/20 backdrop-blur-md hover:bg-accent/40 active:bg-accent/40 focus-visible:outline-none font-semibold border-none">
|
|
||||||
<MenuIcon />
|
|
||||||
</NavigationMenuTrigger>
|
|
||||||
|
|
||||||
<NavigationMenuContent className="bg-accent/20 border-none active:border-none">
|
|
||||||
<NavigationMenuLink asChild>
|
|
||||||
<Link to="/posts/page/1" className="block">
|
|
||||||
Posts
|
|
||||||
</Link>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
<NavigationMenuLink asChild>
|
|
||||||
<Link to="/about" className="block">
|
|
||||||
About
|
|
||||||
</Link>
|
|
||||||
</NavigationMenuLink>
|
|
||||||
</NavigationMenuContent>
|
|
||||||
</NavigationMenuItem>
|
|
||||||
</NavigationMenuList>
|
|
||||||
<NavigationMenuViewport />
|
|
||||||
</NavigationMenu>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const Header = () => {
|
|
||||||
const { theme, setTheme } = useTheme()
|
|
||||||
return (
|
|
||||||
<header className="w-full h-15 flex items-center justify-center border-b border-border border-b-1 fixed top-0 z-20 bg-sidebar">
|
|
||||||
<div
|
|
||||||
className="absolute inset-0 opacity-70 dark:opacity-40"
|
|
||||||
style={{
|
|
||||||
backgroundImage: `url(${headerImage})`,
|
|
||||||
backgroundSize: "cover",
|
|
||||||
backgroundPosition: "center 30%",
|
|
||||||
backgroundRepeat: "no-repeat",
|
|
||||||
filter: "blur(0px) grayscale(10%)",
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
|
|
||||||
<div className="absolute inset-0 bg-gradient-to-b from-background/40 via-background/30 to-background/40 dark:from-background/60 dark:via-background/50 dark:to-background/60" />
|
|
||||||
|
|
||||||
<div className="w-full px-0 md:px-4 flex items-center justify-between">
|
|
||||||
<div className="flex items-center md:px-0 px-4">
|
|
||||||
<Button
|
|
||||||
variant="ghost"
|
|
||||||
asChild
|
|
||||||
className="border-0 rounded bg-accent/40 backdrop-blur-sm hover:bg-background/40 active:bg-background/40 focus-visible:outline-none transition-colors"
|
|
||||||
// className="border-0 rounded-none bg-background/20 backdrop-blur-sm hover:bg-background/40 transition-colors"
|
|
||||||
// className="border-0 rounded-none bg-background/40 backdrop-blur-md hover:bg-background transition-colors"
|
|
||||||
//
|
|
||||||
//className="border md:border-none rounded-none z-500"
|
|
||||||
>
|
|
||||||
<Link to="/">
|
|
||||||
<span className="text-xl tracking-normal font-semibold">
|
|
||||||
Systems Obscure
|
|
||||||
</span>
|
|
||||||
</Link>
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<div className="flex itemr justify-between md:gap-4 z-500">
|
|
||||||
{/*
|
|
||||||
<Toggle
|
|
||||||
className="border bg-background rounded-none"
|
|
||||||
pressed={theme === "dark"}
|
|
||||||
onPressedChange={() =>
|
|
||||||
setTheme(theme === "dark" ? "light" : "dark")
|
|
||||||
}
|
|
||||||
>
|
|
||||||
<MoonStar size={10} />
|
|
||||||
</Toggle>
|
|
||||||
|
|
||||||
*/}
|
|
||||||
|
|
||||||
<Menu />
|
|
||||||
|
|
||||||
{/*
|
|
||||||
<div className="hidden md:block">
|
|
||||||
<Button variant="ghost">
|
|
||||||
<GitMerge /> Forgejo
|
|
||||||
</Button>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
|
|
||||||
*/}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</header>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Header }
|
|
||||||
|
|
@ -1,46 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
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",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default:
|
|
||||||
"border-transparent bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
|
|
||||||
secondary:
|
|
||||||
"border-transparent bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
|
|
||||||
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/60",
|
|
||||||
outline:
|
|
||||||
"text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function Badge({
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span"> &
|
|
||||||
VariantProps<typeof badgeVariants> & { asChild?: boolean }) {
|
|
||||||
const Comp = asChild ? Slot : "span"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="badge"
|
|
||||||
className={cn(badgeVariants({ variant }), className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
|
||||||
|
|
@ -1,59 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import { Slot } from "@radix-ui/react-slot"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const buttonVariants = cva(
|
|
||||||
"inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-all disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 shrink-0 [&_svg]:shrink-0 outline-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",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default:
|
|
||||||
"bg-primary text-primary-foreground shadow-xs hover:bg-primary/90",
|
|
||||||
destructive:
|
|
||||||
"bg-destructive text-white shadow-xs hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:focus-visible:ring-destructive/40 dark:bg-destructive/60",
|
|
||||||
outline:
|
|
||||||
"border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:bg-input/30 dark:border-input dark:hover:bg-input/50",
|
|
||||||
secondary:
|
|
||||||
"bg-secondary text-secondary-foreground shadow-xs hover:bg-secondary/80",
|
|
||||||
ghost:
|
|
||||||
"hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
|
|
||||||
link: "text-primary underline-offset-4 hover:underline",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "h-9 px-4 py-2 has-[>svg]:px-3",
|
|
||||||
sm: "h-8 rounded-md gap-1.5 px-3 has-[>svg]:px-2.5",
|
|
||||||
lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
|
|
||||||
icon: "size-9",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function Button({
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
size,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"button"> &
|
|
||||||
VariantProps<typeof buttonVariants> & {
|
|
||||||
asChild?: boolean
|
|
||||||
}) {
|
|
||||||
const Comp = asChild ? Slot : "button"
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
data-slot="button"
|
|
||||||
className={cn(buttonVariants({ variant, size, className }))}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
|
||||||
|
|
@ -1,92 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function Card({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card"
|
|
||||||
className={cn(
|
|
||||||
"bg-card text-card-foreground flex flex-col gap-6 rounded-xl border py-6 shadow-sm",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardHeader({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-header"
|
|
||||||
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",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardTitle({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-title"
|
|
||||||
className={cn("leading-none font-semibold", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardDescription({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-description"
|
|
||||||
className={cn("text-muted-foreground text-sm", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardAction({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-action"
|
|
||||||
className={cn(
|
|
||||||
"col-start-2 row-span-2 row-start-1 self-start justify-self-end",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardContent({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-content"
|
|
||||||
className={cn("px-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function CardFooter({ className, ...props }: React.ComponentProps<"div">) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
data-slot="card-footer"
|
|
||||||
className={cn("flex items-center px-6 [.border-t]:pt-6", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Card,
|
|
||||||
CardHeader,
|
|
||||||
CardFooter,
|
|
||||||
CardTitle,
|
|
||||||
CardAction,
|
|
||||||
CardDescription,
|
|
||||||
CardContent,
|
|
||||||
}
|
|
||||||
|
|
@ -1,173 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import * as NavigationMenuPrimitive from "@radix-ui/react-navigation-menu"
|
|
||||||
import { cva } from "class-variance-authority"
|
|
||||||
import { ChevronDownIcon } from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
function NavigationMenu({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
viewport = true,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Root> & {
|
|
||||||
viewport?: boolean
|
|
||||||
}) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Root
|
|
||||||
data-slot="navigation-menu"
|
|
||||||
data-viewport={viewport}
|
|
||||||
className={cn(
|
|
||||||
"group/navigation-menu relative flex max-w-max flex-1 items-center justify-center",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
{viewport && <NavigationMenuViewport />}
|
|
||||||
</NavigationMenuPrimitive.Root>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuList({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.List>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.List
|
|
||||||
data-slot="navigation-menu-list"
|
|
||||||
className={cn(
|
|
||||||
"group flex flex-1 list-none items-center justify-center gap-1",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuItem({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Item>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Item
|
|
||||||
data-slot="navigation-menu-item"
|
|
||||||
className={cn("relative", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const navigationMenuTriggerStyle = cva(
|
|
||||||
"group inline-flex h-9 w-max items-center justify-center rounded-md bg-background px-4 py-2 text-sm font-medium hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=open]:hover:bg-accent data-[state=open]:text-accent-foreground data-[state=open]:focus:bg-accent data-[state=open]:bg-accent/50 focus-visible:ring-ring/50 outline-none transition-[color,box-shadow] focus-visible:ring-[3px] focus-visible:outline-1"
|
|
||||||
)
|
|
||||||
|
|
||||||
function NavigationMenuTrigger({
|
|
||||||
className,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Trigger>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Trigger
|
|
||||||
data-slot="navigation-menu-trigger"
|
|
||||||
className={cn(navigationMenuTriggerStyle(), "group", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}{" "}
|
|
||||||
{/*
|
|
||||||
|
|
||||||
<ChevronDownIcon
|
|
||||||
className="relative top-[1px] ml-1 size-3 transition duration-300 group-data-[state=open]:rotate-180"
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
|
|
||||||
|
|
||||||
*/}
|
|
||||||
</NavigationMenuPrimitive.Trigger>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuContent({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Content>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Content
|
|
||||||
data-slot="navigation-menu-content"
|
|
||||||
className={cn(
|
|
||||||
"data-[motion^=from-]:animate-in data-[motion^=to-]:animate-out data-[motion^=from-]:fade-in data-[motion^=to-]:fade-out data-[motion=from-end]:slide-in-from-right-52 data-[motion=from-start]:slide-in-from-left-52 data-[motion=to-end]:slide-out-to-right-52 data-[motion=to-start]:slide-out-to-left-52 top-0 left-0 w-full p-2 pr-2.5 md:absolute md:w-auto",
|
|
||||||
"group-data-[viewport=false]/navigation-menu:bg-popover group-data-[viewport=false]/navigation-menu:text-popover-foreground group-data-[viewport=false]/navigation-menu:data-[state=open]:animate-in group-data-[viewport=false]/navigation-menu:data-[state=closed]:animate-out group-data-[viewport=false]/navigation-menu:data-[state=closed]:zoom-out-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:zoom-in-95 group-data-[viewport=false]/navigation-menu:data-[state=open]:fade-in-0 group-data-[viewport=false]/navigation-menu:data-[state=closed]:fade-out-0 group-data-[viewport=false]/navigation-menu:top-full group-data-[viewport=false]/navigation-menu:mt-1.5 group-data-[viewport=false]/navigation-menu:overflow-hidden group-data-[viewport=false]/navigation-menu:rounded-md group-data-[viewport=false]/navigation-menu:border group-data-[viewport=false]/navigation-menu:shadow group-data-[viewport=false]/navigation-menu:duration-200 **:data-[slot=navigation-menu-link]:focus:ring-0 **:data-[slot=navigation-menu-link]:focus:outline-none",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuViewport({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Viewport>) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={cn(
|
|
||||||
"absolute top-full left-0 isolate z-50 flex justify-center"
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<NavigationMenuPrimitive.Viewport
|
|
||||||
data-slot="navigation-menu-viewport"
|
|
||||||
className={cn(
|
|
||||||
"origin-top-center bg-popover text-popover-foreground data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-90 relative mt-1.5 h-[var(--radix-navigation-menu-viewport-height)] w-full overflow-hidden rounded-md border shadow md:w-[var(--radix-navigation-menu-viewport-width)]",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuLink({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Link>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Link
|
|
||||||
data-slot="navigation-menu-link"
|
|
||||||
className={cn(
|
|
||||||
"data-[active=true]:focus:bg-accent data-[active=true]:hover:bg-accent data-[active=true]:bg-accent/50 data-[active=true]:text-accent-foreground hover:bg-accent hover:text-accent-foreground focus:bg-accent focus:text-accent-foreground focus-visible:ring-ring/50 [&_svg:not([class*='text-'])]:text-muted-foreground flex flex-col gap-1 rounded-sm p-2 text-sm transition-all outline-none focus-visible:ring-[3px] focus-visible:outline-1 [&_svg:not([class*='size-'])]:size-4",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function NavigationMenuIndicator({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof NavigationMenuPrimitive.Indicator>) {
|
|
||||||
return (
|
|
||||||
<NavigationMenuPrimitive.Indicator
|
|
||||||
data-slot="navigation-menu-indicator"
|
|
||||||
className={cn(
|
|
||||||
"data-[state=visible]:animate-in data-[state=hidden]:animate-out data-[state=hidden]:fade-out data-[state=visible]:fade-in top-full z-[1] flex h-1.5 items-end justify-center overflow-hidden",
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<div className="bg-border relative top-[60%] h-2 w-2 rotate-45 rounded-tl-sm shadow-md" />
|
|
||||||
</NavigationMenuPrimitive.Indicator>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
NavigationMenu,
|
|
||||||
NavigationMenuList,
|
|
||||||
NavigationMenuItem,
|
|
||||||
NavigationMenuContent,
|
|
||||||
NavigationMenuTrigger,
|
|
||||||
NavigationMenuLink,
|
|
||||||
NavigationMenuIndicator,
|
|
||||||
NavigationMenuViewport,
|
|
||||||
navigationMenuTriggerStyle,
|
|
||||||
}
|
|
||||||
|
|
@ -1,127 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import {
|
|
||||||
ChevronLeftIcon,
|
|
||||||
ChevronRightIcon,
|
|
||||||
MoreHorizontalIcon,
|
|
||||||
} from "lucide-react"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
import { Button, buttonVariants } from "@/components/ui/button"
|
|
||||||
|
|
||||||
function Pagination({ className, ...props }: React.ComponentProps<"nav">) {
|
|
||||||
return (
|
|
||||||
<nav
|
|
||||||
role="navigation"
|
|
||||||
aria-label="pagination"
|
|
||||||
data-slot="pagination"
|
|
||||||
className={cn("mx-auto flex w-full justify-center", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationContent({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"ul">) {
|
|
||||||
return (
|
|
||||||
<ul
|
|
||||||
data-slot="pagination-content"
|
|
||||||
className={cn("flex flex-row items-center gap-1", className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationItem({ ...props }: React.ComponentProps<"li">) {
|
|
||||||
return <li data-slot="pagination-item" {...props} />
|
|
||||||
}
|
|
||||||
|
|
||||||
type PaginationLinkProps = {
|
|
||||||
isActive?: boolean
|
|
||||||
} & Pick<React.ComponentProps<typeof Button>, "size"> &
|
|
||||||
React.ComponentProps<"a">
|
|
||||||
|
|
||||||
function PaginationLink({
|
|
||||||
className,
|
|
||||||
isActive,
|
|
||||||
size = "icon",
|
|
||||||
...props
|
|
||||||
}: PaginationLinkProps) {
|
|
||||||
return (
|
|
||||||
<a
|
|
||||||
aria-current={isActive ? "page" : undefined}
|
|
||||||
data-slot="pagination-link"
|
|
||||||
data-active={isActive}
|
|
||||||
className={cn(
|
|
||||||
buttonVariants({
|
|
||||||
variant: isActive ? "outline" : "ghost",
|
|
||||||
size,
|
|
||||||
}),
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationPrevious({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
|
||||||
return (
|
|
||||||
<PaginationLink
|
|
||||||
aria-label="Go to previous page"
|
|
||||||
size="default"
|
|
||||||
className={cn("gap-1 px-2.5 sm:pl-2.5", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ChevronLeftIcon />
|
|
||||||
<span className="hidden sm:block">Previous</span>
|
|
||||||
</PaginationLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationNext({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof PaginationLink>) {
|
|
||||||
return (
|
|
||||||
<PaginationLink
|
|
||||||
aria-label="Go to next page"
|
|
||||||
size="default"
|
|
||||||
className={cn("gap-1 px-2.5 sm:pr-2.5", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<span className="hidden sm:block">Next</span>
|
|
||||||
<ChevronRightIcon />
|
|
||||||
</PaginationLink>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
function PaginationEllipsis({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<"span">) {
|
|
||||||
return (
|
|
||||||
<span
|
|
||||||
aria-hidden
|
|
||||||
data-slot="pagination-ellipsis"
|
|
||||||
className={cn("flex size-9 items-center justify-center", className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<MoreHorizontalIcon className="size-4" />
|
|
||||||
<span className="sr-only">More pages</span>
|
|
||||||
</span>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export {
|
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationLink,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationPrevious,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationEllipsis,
|
|
||||||
}
|
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import * as TogglePrimitive from "@radix-ui/react-toggle"
|
|
||||||
import { cva, type VariantProps } from "class-variance-authority"
|
|
||||||
|
|
||||||
import { cn } from "@/lib/utils"
|
|
||||||
|
|
||||||
const toggleVariants = cva(
|
|
||||||
"inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium hover:bg-muted hover:text-muted-foreground disabled:pointer-events-none disabled:opacity-50 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 focus-visible:border-ring focus-visible:ring-ring/50 focus-visible:ring-[3px] outline-none transition-[color,box-shadow] aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 aria-invalid:border-destructive whitespace-nowrap",
|
|
||||||
{
|
|
||||||
variants: {
|
|
||||||
variant: {
|
|
||||||
default: "bg-transparent",
|
|
||||||
outline:
|
|
||||||
"border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground",
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
default: "h-9 px-2 min-w-9",
|
|
||||||
sm: "h-8 px-1.5 min-w-8",
|
|
||||||
lg: "h-10 px-2.5 min-w-10",
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
variant: "default",
|
|
||||||
size: "default",
|
|
||||||
},
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
function Toggle({
|
|
||||||
className,
|
|
||||||
variant,
|
|
||||||
size,
|
|
||||||
...props
|
|
||||||
}: React.ComponentProps<typeof TogglePrimitive.Root> &
|
|
||||||
VariantProps<typeof toggleVariants>) {
|
|
||||||
return (
|
|
||||||
<TogglePrimitive.Root
|
|
||||||
data-slot="toggle"
|
|
||||||
className={cn(toggleVariants({ variant, size, className }))}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Toggle, toggleVariants }
|
|
||||||
|
|
@ -12,12 +12,38 @@ import { convertDate } from "@/utils/convertDate"
|
||||||
|
|
||||||
const PostListing = ({ posts, title, showAllButton }) => {
|
const PostListing = ({ posts, title, showAllButton }) => {
|
||||||
return (
|
return (
|
||||||
<>
|
<div className="container mx-auto p-4 grow">
|
||||||
<div className="mb-5">
|
<div className="space-my-8">
|
||||||
<h2 className="scroll-m-20 text-[1.5rem] font-bold ">{title}</h2>
|
<section className="container">
|
||||||
</div>
|
<h2 className="text-2xl font-medium mb-4">{title}</h2>
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 gap-3">
|
|
||||||
{posts.map((post) => (
|
{posts.map((post) => (
|
||||||
|
<ul>
|
||||||
|
<li className="mb-4">
|
||||||
|
<div className="flex justify-between items-center relative gap-2 hover:bg-[#504945]">
|
||||||
|
<span className="overflow-hidden whitespace-nowrap text-muted-foreground shrink-0">
|
||||||
|
{convertDate(post.date)}
|
||||||
|
</span>
|
||||||
|
<Link
|
||||||
|
to={`/posts/${post.slug}`}
|
||||||
|
key={post.slug}
|
||||||
|
className="text-right overflow-hidden text-ellipsis whitespace-nowrap min-w-0 flex-1"
|
||||||
|
>
|
||||||
|
{post.title}
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
))}
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default PostListing
|
||||||
|
{
|
||||||
|
/*
|
||||||
|
|
||||||
<Link
|
<Link
|
||||||
to={`/posts/${post.slug}`}
|
to={`/posts/${post.slug}`}
|
||||||
key={post.slug}
|
key={post.slug}
|
||||||
|
|
@ -39,19 +65,6 @@ const PostListing = ({ posts, title, showAllButton }) => {
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
</Card>
|
</Card>
|
||||||
</Link>
|
</Link>
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
{showAllButton && (
|
|
||||||
<Button
|
|
||||||
asChild
|
|
||||||
variant="secondary"
|
|
||||||
className="w-full mt-4 rounded bg-accent/20 hover:bg-accent/40"
|
|
||||||
>
|
|
||||||
<Link to="/posts/page/1">View all</Link>
|
|
||||||
</Button>
|
|
||||||
)}
|
|
||||||
</>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export default PostListing
|
*/
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,74 +0,0 @@
|
||||||
import * as React from "react"
|
|
||||||
import { createContext, useContext, useEffect, useState } from "react"
|
|
||||||
|
|
||||||
type Theme = "dark" | "light" | "system"
|
|
||||||
|
|
||||||
type ThemeProviderProps = {
|
|
||||||
children: React.ReactNode
|
|
||||||
defaultTheme?: Theme
|
|
||||||
storageKey?: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type ThemeProviderState = {
|
|
||||||
theme: Theme
|
|
||||||
setTheme: (theme: Theme) => void
|
|
||||||
}
|
|
||||||
|
|
||||||
const initialState: ThemeProviderState = {
|
|
||||||
theme: "dark",
|
|
||||||
setTheme: () => null,
|
|
||||||
}
|
|
||||||
|
|
||||||
const ThemeProviderContext = createContext<ThemeProviderState>(initialState)
|
|
||||||
|
|
||||||
export function ThemeProvider({
|
|
||||||
children,
|
|
||||||
defaultTheme = "dark",
|
|
||||||
storageKey = "vite-ui-theme",
|
|
||||||
...props
|
|
||||||
}: ThemeProviderProps) {
|
|
||||||
const [theme, setTheme] = useState<Theme>(
|
|
||||||
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
|
||||||
)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
const root = window.document.documentElement
|
|
||||||
|
|
||||||
root.classList.remove("light", "dark")
|
|
||||||
|
|
||||||
if (theme === "system") {
|
|
||||||
const systemTheme = window.matchMedia("(prefers-color-scheme: dark)")
|
|
||||||
.matches
|
|
||||||
? "dark"
|
|
||||||
: "light"
|
|
||||||
|
|
||||||
root.classList.add(systemTheme)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
root.classList.add(theme)
|
|
||||||
}, [theme])
|
|
||||||
|
|
||||||
const value = {
|
|
||||||
theme,
|
|
||||||
setTheme: (theme: Theme) => {
|
|
||||||
localStorage.setItem(storageKey, theme)
|
|
||||||
setTheme(theme)
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<ThemeProviderContext.Provider {...props} value={value}>
|
|
||||||
{children}
|
|
||||||
</ThemeProviderContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useTheme = () => {
|
|
||||||
const context = useContext(ThemeProviderContext)
|
|
||||||
|
|
||||||
if (context === undefined)
|
|
||||||
throw new Error("useTheme must be used within a ThemeProvider")
|
|
||||||
|
|
||||||
return context
|
|
||||||
}
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.3 MiB |
BIN
src/images/gruvbox-computer.png
Normal file
BIN
src/images/gruvbox-computer.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 17 KiB |
78
src/images/gruvbox-computer.svg
Normal file
78
src/images/gruvbox-computer.svg
Normal file
|
|
@ -0,0 +1,78 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||||
|
<!-- Created with Inkscape (http://www.inkscape.org/) -->
|
||||||
|
|
||||||
|
<svg
|
||||||
|
width="284.27203mm"
|
||||||
|
height="284.27203mm"
|
||||||
|
viewBox="0 0 284.27203 284.27203"
|
||||||
|
version="1.1"
|
||||||
|
id="svg5"
|
||||||
|
xml:space="preserve"
|
||||||
|
sodipodi:docname="gruvbox-computer.svg"
|
||||||
|
inkscape:version="1.3.2 (091e20ef0f, 2023-11-25, custom)"
|
||||||
|
xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
|
||||||
|
xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
xmlns:svg="http://www.w3.org/2000/svg"><sodipodi:namedview
|
||||||
|
id="namedview1"
|
||||||
|
pagecolor="#ffffff"
|
||||||
|
bordercolor="#000000"
|
||||||
|
borderopacity="0.25"
|
||||||
|
inkscape:showpageshadow="2"
|
||||||
|
inkscape:pageopacity="0.0"
|
||||||
|
inkscape:pagecheckerboard="0"
|
||||||
|
inkscape:deskcolor="#d1d1d1"
|
||||||
|
inkscape:document-units="mm"
|
||||||
|
inkscape:zoom="0.33275303"
|
||||||
|
inkscape:cx="1158.5169"
|
||||||
|
inkscape:cy="575.50189"
|
||||||
|
inkscape:window-width="2038"
|
||||||
|
inkscape:window-height="1262"
|
||||||
|
inkscape:window-x="579"
|
||||||
|
inkscape:window-y="1459"
|
||||||
|
inkscape:window-maximized="0"
|
||||||
|
inkscape:current-layer="svg5" /><defs
|
||||||
|
id="defs2" /><rect
|
||||||
|
style="fill:#e78a4e;fill-opacity:1;stroke-width:0.440112;stroke-linecap:square;stroke-linejoin:bevel"
|
||||||
|
id="rect1"
|
||||||
|
width="284.27203"
|
||||||
|
height="284.27203"
|
||||||
|
x="2.1542969e-06"
|
||||||
|
y="4.1542967e-06" /><g
|
||||||
|
id="g4"><path
|
||||||
|
stroke="#848484"
|
||||||
|
d="M 79.951508,4.441751 H 239.85454 M 71.068008,13.325252 h 8.8835 m 151.019532,0 h 8.8835 M 62.184508,22.208754 h 8.8835 m 151.019522,0 h 17.76701 M 53.301008,31.092255 h 8.8835 m 159.903022,0 h 17.76701 M 53.301008,39.975756 h 8.8835 m 17.767,0 H 204.32053 m 17.767,0 h 17.76701 M 53.301008,48.859257 h 8.8835 m 159.903022,0 h 17.76701 M 53.301008,57.742759 h 8.8835 m 159.903022,0 h 17.76701 M 53.301008,66.62626 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.88351 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m -186.553532,8.8835 h 8.8835 m 159.903022,0 h 17.76701 m 8.8835,0 h 17.767 m -213.204032,8.8835 H 230.97104 m 8.8835,0 h 17.767 m -26.6505,8.8835 h 17.767 m 8.8835,0 h 8.8835 M 35.534008,173.22828 H 239.85454 m 8.8835,0 h 17.767 m -239.854532,8.8835 h 8.8835 m 204.320532,0 h 26.6505 m -239.854532,8.8835 h 8.8835 m 204.320532,0 h 26.6505 m -239.854532,8.8835 h 8.8835 m 97.718512,0 h 88.83501 m 17.76701,0 h 26.6505 m -239.854532,8.8835 h 8.8835 m 186.553522,0 h 53.30101 m -248.738032,8.8835 h 8.8835 m 26.6505,0 h 17.767 m 124.369022,0 h 8.8835 m 35.53401,0 h 8.8835 m 17.767,0 h 8.8835 m -257.621532,8.8835 h 26.6505 m 17.767,0 h 8.8835 m 17.76701,0 h 8.883502 m 17.767,0 h 8.8835 m 17.767,0 h 8.88351 m 17.767,0 h 8.8835 m 8.8835,0 h 79.95151 m -257.621542,8.8835 h 8.88351 m 8.8835,0 h 8.8835 m 17.767,0 h 8.8835 m 17.767,0 h 8.88351 m 17.767002,0 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.8835 m 8.8835,0 h 8.8835 m 62.18451,0 h 17.767 M 8.883498,244.29629 h 8.8835 m 8.88351,0 h 8.8835 m 8.8835,0 h 17.767 m 8.8835,0 h 17.767 m 17.767012,0 h 8.8835 m 8.8835,0 h 17.767 m 8.8835,0 h 17.76701 m 79.95151,0 h 26.6505 M 0,253.17979 h 8.883498 m 168.786532,0 h 8.8835 m 53.30101,0 h 26.6505 M 0,262.06329 h 8.883498 m 168.786532,0 h 79.95151 m -26.6505,8.8835 h 8.8835"
|
||||||
|
id="path1051"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#7c6f64;stroke-opacity:1" /><path
|
||||||
|
stroke="#c6c6c6"
|
||||||
|
d="M 79.951508,13.325252 H 222.08753 M 71.068008,31.092255 H 222.08753 M 71.068008,39.975756 h 8.8835 m 133.252522,0 h 8.8835 M 71.068008,48.859257 h 8.8835 m 133.252522,0 h 8.8835 M 71.068008,57.742759 h 8.8835 m 133.252522,0 h 8.8835 M 71.068008,66.62626 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.88351 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 H 222.08753 m 35.53401,8.8835 h 8.8835 m -17.767,8.8835 h 8.8835 m -17.767,8.88351 h 8.8835 m -204.320532,17.767 H 239.85454 m -195.437032,8.8835 h 88.835012 m 88.83501,0 h 17.76701 m -195.437032,8.8835 h 17.767 m 17.767,0 h 53.301012 m -88.835012,8.8835 h 17.767 m 17.767,0 H 204.32053 m 8.8835,0 h 8.8835 m 17.76701,0 h 8.8835 m 17.767,0 h 8.8835 m -88.83501,35.53401 h 8.8835 m 35.53401,0 h 8.8835 M 8.883498,262.06329 H 177.67003 m 8.8835,8.8835 h 44.41751"
|
||||||
|
id="path1053"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#a89984;stroke-opacity:1" /><path
|
||||||
|
stroke="#ffffff"
|
||||||
|
d="m 222.08753,13.325252 h 8.88351 M 71.068008,22.208754 H 222.08753 M 62.184508,31.092255 h 8.8835 m -8.8835,8.883501 h 8.8835 m 133.252522,0 h 8.8835 M 62.184508,48.859257 h 8.8835 m 133.252522,0 h 8.8835 M 62.184508,57.742759 h 8.8835 m 133.252522,0 h 8.8835 M 62.184508,66.62626 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.88351 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 133.252522,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 8.8835,0 H 213.20403 m -151.019522,8.8835 h 8.8835 m -35.534,35.53401 H 239.85454 m -204.320532,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m 177.670022,0 h 17.76701 m 17.767,0 h 8.8835 m -204.320532,8.8835 h 8.8835 m 17.767,0 h 8.88351 m 17.767002,0 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.8835 m -151.019522,8.8835 h 8.8835 m 17.767,0 h 8.8835 m 17.767,0 h 8.8835 m 17.767012,0 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.8835 m 26.6505,0 h 62.18451 m -239.854542,8.88351 h 8.88351 m 159.903022,0 h 62.18451 M 8.883498,253.17979 H 177.67003 m 17.767,0 h 35.53401"
|
||||||
|
id="path1055"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#d4be98;stroke-opacity:1" /><path
|
||||||
|
stroke="#000000"
|
||||||
|
d="m 239.85454,13.325252 h 8.8835 m -8.8835,8.883502 h 8.8835 m -8.8835,8.883501 h 8.8835 m -8.8835,8.883501 h 8.8835 M 79.951508,48.859257 H 195.43703 m 44.41751,0 h 8.8835 M 79.951508,57.742759 h 8.8835 m 151.019532,0 h 8.8835 M 79.951508,66.62626 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.8835 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.8835 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.8835 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.88351 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.8835 h 8.8835 m 151.019532,0 h 8.8835 m -168.786532,8.8835 h 8.8835 m 151.019532,0 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -17.767,8.8835 h 8.8835 m 26.6505,0 h 8.8835 m -222.087532,8.8835 H 230.97104 m 35.534,0 h 8.8835 m -8.8835,8.88351 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -142.13602,8.8835 h 88.83501 m -168.786522,17.767 h 8.8835 m 17.767,0 h 8.8835 m 17.767012,0 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.8835 m 17.767,0 h 8.8835 m 79.95151,0 h 8.8835 m -239.854532,8.8835 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.883502 m 17.767,0 h 8.8835 m 17.767,0 h 8.88351 m 17.767,0 h 8.8835 m 88.83501,0 h 8.8835 m -248.738032,8.88351 h 8.8835 m 17.767,0 h 8.8835 m 17.767,0 h 8.88351 m 17.767002,0 h 8.8835 m 17.767,0 h 8.8835 m 17.76701,0 h 8.8835 m 97.71851,0 h 8.8835 m -17.767,8.8835 h 8.8835 m -17.767,8.8835 h 8.8835 M 8.883498,270.94679 H 177.67003 m 62.18451,0 h 17.767 m -71.06801,8.8835 h 53.30101"
|
||||||
|
id="path1057"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#282828;stroke-opacity:1" /><path
|
||||||
|
stroke="#000084"
|
||||||
|
d="m 195.43703,48.859257 h 8.8835 m -8.8835,8.883502 h 8.8835 m -8.8835,8.883501 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.88351 h 8.8835 m -8.8835,8.8835 h 8.8835 m -8.8835,8.8835 h 8.8835 m -124.369022,8.8835 H 204.32053"
|
||||||
|
id="path1059"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#2e3b3b;stroke-opacity:1" /><path
|
||||||
|
stroke="#0000ff"
|
||||||
|
d="M 88.835008,57.742759 H 195.43703 M 88.835008,66.62626 h 8.88351 m 8.883502,0 h 88.83501 m -106.602022,8.8835 h 8.88351 m 8.883502,0 h 88.83501 M 88.835008,84.39326 H 195.43703 M 88.835008,93.27676 H 195.43703 M 88.835008,102.16027 H 195.43703 m -106.602022,8.8835 H 195.43703 m -106.602022,8.8835 H 195.43703"
|
||||||
|
id="path1061"
|
||||||
|
style="fill:none;fill-opacity:1;stroke-width:8.8835;shape-rendering:crispEdges;stroke:#7daea3;stroke-opacity:1" /><path
|
||||||
|
stroke="#01feff"
|
||||||
|
d="m 97.718518,66.62626 h 8.883502 m -8.883502,8.8835 h 8.883502"
|
||||||
|
id="path1063"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#87efcc;stroke-opacity:1" /><path
|
||||||
|
stroke="#008400"
|
||||||
|
d="m 62.184508,208.76228 h 17.767"
|
||||||
|
id="path1065"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#c4de3d;stroke-opacity:1" /><path
|
||||||
|
stroke="#838383"
|
||||||
|
d="m 97.718518,244.29629 h 8.883502 m 71.06801,0 h 8.8835 m -8.8835,26.6505 h 8.8835"
|
||||||
|
id="path1067"
|
||||||
|
style="stroke-width:8.8835;shape-rendering:crispEdges;stroke:#7c6f64;stroke-opacity:1" /></g></svg>
|
||||||
|
After Width: | Height: | Size: 9.7 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 139 KiB |
|
|
@ -1,8 +1,93 @@
|
||||||
@import url("https://fonts.googleapis.com/css2?family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&display=swap");
|
|
||||||
@import url("https://fonts.googleapis.com/css2?family=Inter:ital,opsz,wght@0,14..32,100..900;1,14..32,100..900&family=Roboto:ital,wght@0,100..900;1,100..900&display=swap");
|
|
||||||
@import "./styles/_variables.css";
|
@import "./styles/_variables.css";
|
||||||
@import "./styles/shadcn-overrides.css";
|
|
||||||
@import "./styles/shadcn-theme.css";
|
|
||||||
@import "./styles/syntax-highlighting.css";
|
|
||||||
@import "tailwindcss";
|
@import "tailwindcss";
|
||||||
@import "tw-animate-css";
|
@import "tw-animate-css";
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "iA Writer Quattro";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url(https://cdn.jsdelivr.net/fontsource/fonts/ia-writer-quattro@latest/latin-400-normal.woff2)
|
||||||
|
format("woff2"),
|
||||||
|
url(https://cdn.jsdelivr.net/fontsource/fonts/ia-writer-quattro@latest/latin-400-normal.woff)
|
||||||
|
format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
@font-face {
|
||||||
|
font-family: "iA Writer Mono";
|
||||||
|
font-style: normal;
|
||||||
|
font-display: swap;
|
||||||
|
font-weight: 400;
|
||||||
|
src:
|
||||||
|
url(https://cdn.jsdelivr.net/fontsource/fonts/ia-writer-mono@latest/latin-400-normal.woff2)
|
||||||
|
format("woff2"),
|
||||||
|
url(https://cdn.jsdelivr.net/fontsource/fonts/ia-writer-mono@latest/latin-400-normal.woff)
|
||||||
|
format("woff");
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
font-family: var(--font-sansserif);
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background-color: var(--background);
|
||||||
|
color: var(--foreground);
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: var(--color-orange-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
h2 {
|
||||||
|
color: var(--color-green-light);
|
||||||
|
}
|
||||||
|
|
||||||
|
.monospaced-font {
|
||||||
|
font-family: "iA Writer Mono";
|
||||||
|
}
|
||||||
|
.scanlined {
|
||||||
|
position: relative; /* Add this */
|
||||||
|
}
|
||||||
|
|
||||||
|
.scanlined::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute; /* Change from fixed */
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-image: linear-gradient(rgba(0, 0, 0, 0.4) 1px, transparent 1px);
|
||||||
|
background-size: 2px 2px;
|
||||||
|
background-repeat: repeat;
|
||||||
|
pointer-events: none;
|
||||||
|
z-index: 9999; /* Might want to lower this too */
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: var(--font-monospaced);
|
||||||
|
}
|
||||||
|
|
||||||
|
p code {
|
||||||
|
color: var(--foreground);
|
||||||
|
background: #504945;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0.2rem 0.3rem;
|
||||||
|
border-radius: var(--radius);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.shiki {
|
||||||
|
padding: 1rem 1.2rem;
|
||||||
|
border-radius: 0;
|
||||||
|
overflow-x: auto;
|
||||||
|
margin: 1.5rem 0;
|
||||||
|
line-height: 1.3;
|
||||||
|
/* counter-reset: line; */
|
||||||
|
font-family: var(--font-monospaced) !important;
|
||||||
|
font-size: 14px !important;
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
import { clsx, type ClassValue } from "clsx"
|
|
||||||
import { twMerge } from "tailwind-merge"
|
|
||||||
|
|
||||||
export function cn(...inputs: ClassValue[]) {
|
|
||||||
return twMerge(clsx(inputs))
|
|
||||||
}
|
|
||||||
|
|
@ -38,7 +38,7 @@ ReactDOM.createRoot(document.getElementById("root")!).render(
|
||||||
<Routes>
|
<Routes>
|
||||||
<Route index element={<HomePage />} />
|
<Route index element={<HomePage />} />
|
||||||
<Route path="/about" element={<AboutPage />} />
|
<Route path="/about" element={<AboutPage />} />
|
||||||
<Route path="/posts/page/:page" element={<PostsPage />} />
|
<Route path="/posts" element={<PostsPage />} />
|
||||||
<Route path="/posts/:slug" element={<BlogTemplate />} />
|
<Route path="/posts/:slug" element={<BlogTemplate />} />
|
||||||
<Route path="/tags/:tag" element={<TagTemplate />} />
|
<Route path="/tags/:tag" element={<TagTemplate />} />
|
||||||
</Routes>
|
</Routes>
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,15 @@ import portrait from "../images/portrait-compressed.jpg"
|
||||||
const AboutPage = () => {
|
const AboutPage = () => {
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="mb-5 ">
|
<div className="container mx-auto p-4">
|
||||||
<h2 className="scroll-m-20 text-3xl font-bold lg:text-3xl pb-3">
|
|
||||||
About
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<figure className="w-full flex flex-col items-center mb-6">
|
<figure className="w-full flex flex-col items-center mb-6">
|
||||||
|
<div className="scanlined inline-block">
|
||||||
<img
|
<img
|
||||||
alt="A portrait of the blog author"
|
alt="A portrait of the blog author"
|
||||||
src={portrait}
|
src={portrait}
|
||||||
className="w-0 flex"
|
className="w-80 flex"
|
||||||
/>
|
/>
|
||||||
|
</div>
|
||||||
<figcaption className="text-sm text-muted-foreground mt-3 text-center">
|
<figcaption className="text-sm text-muted-foreground mt-3 text-center">
|
||||||
Pictured with the WITCH computer at the{" "}
|
Pictured with the WITCH computer at the{" "}
|
||||||
<a
|
<a
|
||||||
|
|
@ -28,10 +25,12 @@ const AboutPage = () => {
|
||||||
, Bletchley Park.
|
, Bletchley Park.
|
||||||
</figcaption>
|
</figcaption>
|
||||||
</figure>
|
</figure>
|
||||||
|
|
||||||
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
||||||
I'm a self-taught software engineer based on the south coast of England.
|
I'm a self-taught software engineer based on the south coast of
|
||||||
This blog is my technical scrapbook. I document the details of my
|
England. This blog is my technical scrapbook. I document the details
|
||||||
technical life so I can have a record of progress when I look back.{" "}
|
of my technical life so I can have a record of progress when I look
|
||||||
|
back.{" "}
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
<p className="leading-[1.6] [&:not(:first-child)]:mt-6">
|
||||||
|
|
@ -103,6 +102,7 @@ const AboutPage = () => {
|
||||||
rather than use Microsoft GitHub. You can view my personal projects
|
rather than use Microsoft GitHub. You can view my personal projects
|
||||||
there.
|
there.
|
||||||
</p>
|
</p>
|
||||||
|
</div>
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,42 +1,37 @@
|
||||||
import MainTemplate from "@/templates/MainTemplate"
|
import MainTemplate from "@/templates/MainTemplate"
|
||||||
import PostListing from "@/containers/PostListing"
|
import PostListing from "@/containers/PostListing"
|
||||||
import { usePosts } from "@/hooks/usePosts"
|
import { usePosts } from "@/hooks/usePosts"
|
||||||
import roundedPortrait from "../images/round-portrait.png"
|
import gruvboxComputer from "../images/gruvbox-computer.svg"
|
||||||
import TodayILearned from "@/containers/TodayILearned"
|
import TodayILearned from "@/containers/TodayILearned"
|
||||||
|
|
||||||
const HomePage = () => {
|
const HomePage = () => {
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="mb-7 border-none border-accent/40 py-6 px-6 md:px-8 md:pt-6 bg-accent/15 opacity-90 rounded">
|
<div className="container mx-auto p-4 grow">
|
||||||
<div className="flex flex-col items-center md:flex-row md:items-start gap-4 md:gap-8 md:flex-row-reverse">
|
<div className="space-my-8">
|
||||||
{/*
|
<section className="space-y-4">
|
||||||
<div className="flex-shrink-0">
|
<div className="gap-6 flex flex-col items-center sm:flex-row">
|
||||||
<img
|
<div className="scanlined">
|
||||||
src={roundedPortrait}
|
<img src={gruvboxComputer} className="md:w-80 w-50" />
|
||||||
alt="Profile picture"
|
</div>
|
||||||
className="rounded-image w-36 h-36 md:w-38 md:h-38 object-contain border-2 rounded-full"
|
<div>
|
||||||
/>
|
<h1 className="text-4xl font-bold py-3 monospaced-font text-center sm:text-left md:text-left">
|
||||||
|
<div className="scanlined inline-block italic py-1 px-2">
|
||||||
|
systems obscure
|
||||||
</div>
|
</div>
|
||||||
*/}
|
|
||||||
|
|
||||||
<div className="text-center md:text-left flex-1">
|
|
||||||
<h1 className="scroll-m-20 text-3xl font-bold">
|
|
||||||
Another software engineer with a blog
|
|
||||||
</h1>
|
</h1>
|
||||||
<p className="leading-[1.7] mt-5">
|
<p className="leading-relaxed text-center sm:text-left md:text-left">
|
||||||
I'm a self-taught software engineer from London. This blog is a
|
software engineer at ITV, formerly BBC. this is my technical
|
||||||
technical scrapbook and digital garden.
|
scrapbook and digital garden.
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</section>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PostListing
|
<PostListing title="recent posts" posts={posts.slice(0, 5)} />
|
||||||
title="Recent posts"
|
|
||||||
posts={posts.slice(0, 5)}
|
|
||||||
showAllButton
|
|
||||||
/>
|
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,130 +1,15 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { useMemo, useEffect } from "react"
|
|
||||||
import MainTemplate from "@/templates/MainTemplate"
|
import MainTemplate from "@/templates/MainTemplate"
|
||||||
import { useParams, useNavigate } from "react-router"
|
|
||||||
import { convertDate } from "@/utils/convertDate"
|
|
||||||
import {
|
|
||||||
Pagination,
|
|
||||||
PaginationContent,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationNext,
|
|
||||||
PaginationPrevious,
|
|
||||||
} from "@/components/ui/pagination"
|
|
||||||
import {
|
|
||||||
Card,
|
|
||||||
CardDescription,
|
|
||||||
CardHeader,
|
|
||||||
CardTitle,
|
|
||||||
} from "@/components/ui/card"
|
|
||||||
import { Badge } from "@/components/ui/badge"
|
|
||||||
import { Link } from "react-router"
|
|
||||||
import { usePosts } from "@/hooks/usePosts"
|
import { usePosts } from "@/hooks/usePosts"
|
||||||
|
|
||||||
|
import PostListing from "@/containers/PostListing"
|
||||||
|
|
||||||
const PostsPage = () => {
|
const PostsPage = () => {
|
||||||
const { page } = useParams()
|
|
||||||
|
|
||||||
const navigate = useNavigate()
|
|
||||||
|
|
||||||
const { posts } = usePosts()
|
const { posts } = usePosts()
|
||||||
const postsPerPage = 8
|
|
||||||
|
|
||||||
const currentPage = Number(page) || 1
|
|
||||||
const totalPages = Math.ceil(posts.length / postsPerPage)
|
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
// Only redirect if we have posts and the page is definitively invalid
|
|
||||||
if (totalPages > 0 && (currentPage < 1 || currentPage > totalPages)) {
|
|
||||||
navigate(`/posts/page/1`, { replace: true })
|
|
||||||
}
|
|
||||||
}, [currentPage, totalPages, navigate])
|
|
||||||
|
|
||||||
const currentPosts = useMemo(() => {
|
|
||||||
const startIndex = (currentPage - 1) * postsPerPage
|
|
||||||
const endIndex = startIndex + postsPerPage
|
|
||||||
return posts.slice(startIndex, endIndex)
|
|
||||||
}, [posts, currentPage, postsPerPage])
|
|
||||||
|
|
||||||
const hasNextPage = currentPage < totalPages
|
|
||||||
const hasPrevPage = currentPage > 1
|
|
||||||
|
|
||||||
const goToNextPage = () => {
|
|
||||||
if (hasNextPage) {
|
|
||||||
navigate(`/posts/page/${currentPage + 1}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const goToPrevPage = () => {
|
|
||||||
if (hasPrevPage) {
|
|
||||||
navigate(`/posts/page/${currentPage - 1}`)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<div className="mb-5 ">
|
<PostListing title={null} posts={posts} />
|
||||||
<h2 className="scroll-m-20 text-3xl font-bold lg:text-3xl">
|
|
||||||
All posts
|
|
||||||
</h2>
|
|
||||||
</div>
|
|
||||||
<div className="flex flex-col">
|
|
||||||
<div className="grid grid-cols-1 md:grid-cols-1 gap-3 flex-grow">
|
|
||||||
{currentPosts.map((post) => (
|
|
||||||
<Link
|
|
||||||
to={`/posts/${post.slug}`}
|
|
||||||
key={post.slug}
|
|
||||||
className="block no-underline"
|
|
||||||
>
|
|
||||||
<Card
|
|
||||||
key={post.slug}
|
|
||||||
className="flex flex-col h-full hover:bg-primary/5 py-4 rounded border-none"
|
|
||||||
>
|
|
||||||
<CardHeader>
|
|
||||||
<CardTitle className="leading-snug font-bold">
|
|
||||||
{post.title}
|
|
||||||
</CardTitle>
|
|
||||||
<CardDescription className="text-sm text-muted-foreground">
|
|
||||||
<div className="flex justify-between gap-2">
|
|
||||||
<span className="text-sm">{convertDate(post.date)}</span>
|
|
||||||
<div className="hidden md:block">
|
|
||||||
{post.tags.map((tag, i) => (
|
|
||||||
<Badge
|
|
||||||
className="ml-2 cursor-pointer"
|
|
||||||
key={i}
|
|
||||||
variant="secondary"
|
|
||||||
onClick={(e) => {
|
|
||||||
e.preventDefault()
|
|
||||||
e.stopPropagation()
|
|
||||||
navigate(`/tags/${tag}`)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
{tag}
|
|
||||||
</Badge>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</CardDescription>
|
|
||||||
</CardHeader>
|
|
||||||
</Card>
|
|
||||||
</Link>
|
|
||||||
))}
|
|
||||||
</div>
|
|
||||||
<Pagination className="mt-4">
|
|
||||||
<PaginationContent>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationPrevious
|
|
||||||
className={`select-none ${hasPrevPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
|
||||||
onClick={goToPrevPage}
|
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationItem>
|
|
||||||
<PaginationNext
|
|
||||||
className={`select-none ${hasNextPage ? "cursor-pointer" : "cursor-not-allowed opacity-50"}`}
|
|
||||||
onClick={goToNextPage}
|
|
||||||
/>
|
|
||||||
</PaginationItem>
|
|
||||||
</PaginationContent>
|
|
||||||
</Pagination>
|
|
||||||
</div>
|
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,17 +1,23 @@
|
||||||
:root {
|
:root {
|
||||||
--radius: 0.3rem;
|
--radius: 0.3rem;
|
||||||
--background: oklch(1 0 0);
|
--background: #282828;
|
||||||
--foreground: oklch(0.145 0 0);
|
--foreground: #ebdbb2;
|
||||||
|
--sidebar: #3c3836;
|
||||||
|
--color-red-light: #fb4934;
|
||||||
|
--color-orange-light: #fe8019;
|
||||||
|
--color-green-light: #b8bb26;
|
||||||
|
--color-aqua-muted: #689d6a;
|
||||||
--card: oklch(1 0 0);
|
--card: oklch(1 0 0);
|
||||||
--card-foreground: oklch(0.145 0 0);
|
--card-foreground: oklch(0.145 0 0);
|
||||||
--popover: oklch(1 0 0);
|
--popover: oklch(1 0 0);
|
||||||
--popover-foreground: oklch(0.145 0 0);
|
--popover-foreground: oklch(0.145 0 0);
|
||||||
--primary: oklch(0.205 0 0);
|
--primary: #8ec07c;
|
||||||
|
--primary-muted: #689d6a;
|
||||||
--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: #928374;
|
||||||
--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);
|
||||||
|
|
@ -23,7 +29,6 @@
|
||||||
--chart-3: oklch(0.398 0.07 227.392);
|
--chart-3: oklch(0.398 0.07 227.392);
|
||||||
--chart-4: oklch(0.828 0.189 84.429);
|
--chart-4: oklch(0.828 0.189 84.429);
|
||||||
--chart-5: oklch(0.769 0.188 70.08);
|
--chart-5: oklch(0.769 0.188 70.08);
|
||||||
--sidebar: oklch(0.985 0 0);
|
|
||||||
--sidebar-foreground: oklch(0.145 0 0);
|
--sidebar-foreground: oklch(0.145 0 0);
|
||||||
--sidebar-primary: oklch(0.205 0 0);
|
--sidebar-primary: oklch(0.205 0 0);
|
||||||
--sidebar-primary-foreground: oklch(0.985 0 0);
|
--sidebar-primary-foreground: oklch(0.985 0 0);
|
||||||
|
|
@ -31,84 +36,44 @@
|
||||||
--sidebar-accent-foreground: oklch(0.205 0 0);
|
--sidebar-accent-foreground: oklch(0.205 0 0);
|
||||||
--sidebar-border: oklch(0.922 0 0);
|
--sidebar-border: oklch(0.922 0 0);
|
||||||
--sidebar-ring: oklch(0.708 0 0);
|
--sidebar-ring: oklch(0.708 0 0);
|
||||||
--font-monospaced: "Jetbrains Mono", monospace;
|
--font-monospaced: "iA Writer Mono";
|
||||||
--font-sansserif: "Inter", sans-serif;
|
--font-sansserif: "iA Writer Quattro", sans-serif;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* .dark { */
|
@theme inline {
|
||||||
/* --background: oklch(0.145 0 0); */
|
--radius-sm: calc(var(--radius) - 4px);
|
||||||
/* --foreground: oklch(0.985 0 0); */
|
--radius-md: calc(var(--radius) - 2px);
|
||||||
/* --card: oklch(0.205 0 0); */
|
--radius-lg: var(--radius);
|
||||||
/* --card-foreground: oklch(0.985 0 0); */
|
--radius-xl: calc(var(--radius));
|
||||||
/* --popover: oklch(0.205 0 0); */
|
--color-background: var(--background);
|
||||||
/* --popover-foreground: oklch(0.985 0 0); */
|
--color-foreground: var(--foreground);
|
||||||
/* --primary: oklch(0.922 0 0); */
|
--color-card: var(--card);
|
||||||
/* --primary-foreground: oklch(0.205 0 0); */
|
--color-card-foreground: var(--card-foreground);
|
||||||
/* --secondary: oklch(0.269 0 0); */
|
--color-popover: var(--popover);
|
||||||
/* --secondary-foreground: oklch(0.985 0 0); */
|
--color-popover-foreground: var(--popover-foreground);
|
||||||
/* --muted: oklch(0.269 0 0); */
|
--color-primary: var(--primary);
|
||||||
/* --muted-foreground: oklch(0.708 0 0); */
|
--color-primary-foreground: var(--primary-foreground);
|
||||||
/* --accent: oklch(0.269 0 0); */
|
--color-secondary: var(--secondary);
|
||||||
/* --accent-foreground: oklch(0.985 0 0); */
|
--color-secondary-foreground: var(--secondary-foreground);
|
||||||
/* --destructive: oklch(0.704 0.191 22.216); */
|
--color-muted: var(--muted);
|
||||||
/* --border: oklch(1 0 0 / 10%); */
|
--color-muted-foreground: var(--muted-foreground);
|
||||||
/* --input: oklch(1 0 0 / 15%); */
|
--color-accent: var(--accent);
|
||||||
/* --ring: oklch(0.556 0 0); */
|
--color-accent-foreground: var(--accent-foreground);
|
||||||
/* --chart-1: oklch(0.488 0.243 264.376); */
|
--color-destructive: var(--destructive);
|
||||||
/* --chart-2: oklch(0.696 0.17 162.48); */
|
--color-border: var(--border);
|
||||||
/* --chart-3: oklch(0.769 0.188 70.08); */
|
--color-input: var(--input);
|
||||||
/* --chart-4: oklch(0.627 0.265 303.9); */
|
--color-ring: var(--ring);
|
||||||
/* --chart-5: oklch(0.645 0.246 16.439); */
|
--color-chart-1: var(--chart-1);
|
||||||
/* --sidebar: oklch(0.205 0 0); */
|
--color-chart-2: var(--chart-2);
|
||||||
/* --sidebar-foreground: oklch(0.985 0 0); */
|
--color-chart-3: var(--chart-3);
|
||||||
/* --sidebar-primary: oklch(0.488 0.243 264.376); */
|
--color-chart-4: var(--chart-4);
|
||||||
/* --sidebar-primary-foreground: oklch(0.985 0 0); */
|
--color-chart-5: var(--chart-5);
|
||||||
/* --sidebar-accent: oklch(0.269 0 0); */
|
--color-sidebar: var(--sidebar);
|
||||||
/* --sidebar-accent-foreground: oklch(0.985 0 0); */
|
--color-sidebar-foreground: var(--sidebar-foreground);
|
||||||
/* --sidebar-border: oklch(1 0 0 / 10%); */
|
--color-sidebar-primary: var(--sidebar-primary);
|
||||||
/* --sidebar-ring: oklch(0.556 0 0); */
|
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
||||||
/* } */
|
--color-sidebar-accent: var(--sidebar-accent);
|
||||||
|
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
||||||
.dark {
|
--color-sidebar-border: var(--sidebar-border);
|
||||||
--background: oklch(0.2 0 0);
|
--color-sidebar-ring: var(--sidebar-ring);
|
||||||
/* Softer dark gray instead of 0.145 */
|
|
||||||
--foreground: oklch(0.9 0 0);
|
|
||||||
/* Softer off-white instead of 0.985 */
|
|
||||||
--card: oklch(0.22 0 0);
|
|
||||||
/* Slightly lighter card */
|
|
||||||
--card-foreground: oklch(0.9 0 0);
|
|
||||||
/* Match foreground */
|
|
||||||
--popover: oklch(0.22 0 0);
|
|
||||||
--popover-foreground: oklch(0.9 0 0);
|
|
||||||
--primary: oklch(0.85 0 0);
|
|
||||||
/* Less harsh white */
|
|
||||||
--primary-foreground: oklch(0.22 0 0);
|
|
||||||
--secondary: oklch(0.3 0 0);
|
|
||||||
/* Lighter secondary */
|
|
||||||
--secondary-foreground: oklch(0.9 0 0);
|
|
||||||
--muted: oklch(0.3 0 0);
|
|
||||||
--muted-foreground: oklch(0.65 0 0);
|
|
||||||
/* Slightly softer */
|
|
||||||
--accent: #a89984;
|
|
||||||
|
|
||||||
--accent-foreground: oklch(0.9 0 0);
|
|
||||||
--destructive: oklch(0.704 0.191 22.216);
|
|
||||||
--border: oklch(1 0 0 / 15%);
|
|
||||||
/* Slightly more visible borders */
|
|
||||||
--input: oklch(1 0 0 / 18%);
|
|
||||||
--ring: oklch(0.556 0 0);
|
|
||||||
--chart-1: oklch(0.488 0.243 264.376);
|
|
||||||
--chart-2: oklch(0.696 0.17 162.48);
|
|
||||||
--chart-3: oklch(0.769 0.188 70.08);
|
|
||||||
--chart-4: oklch(0.627 0.265 303.9);
|
|
||||||
--chart-5: oklch(0.645 0.246 16.439);
|
|
||||||
--sidebar: oklch(0.22 0 0);
|
|
||||||
/* Match card */
|
|
||||||
--sidebar-foreground: oklch(0.9 0 0);
|
|
||||||
--sidebar-primary: oklch(0.488 0.243 264.376);
|
|
||||||
--sidebar-primary-foreground: oklch(0.9 0 0);
|
|
||||||
--sidebar-accent: oklch(0.3 0 0);
|
|
||||||
--sidebar-accent-foreground: oklch(0.9 0 0);
|
|
||||||
--sidebar-border: oklch(1 0 0 / 15%);
|
|
||||||
--sidebar-ring: oklch(0.556 0 0);
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
* {
|
|
||||||
/* border-color: var(--border);*/
|
|
||||||
outline-color: color-mix(in srgb, var(--ring) 50%, transparent);
|
|
||||||
}
|
|
||||||
|
|
||||||
html {
|
|
||||||
font-family: var(--font-sansserif);
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
background-color: var(--background);
|
|
||||||
color: var(--foreground);
|
|
||||||
}
|
|
||||||
|
|
||||||
button,
|
|
||||||
[type="button"],
|
|
||||||
[type="reset"],
|
|
||||||
[type="submit"],
|
|
||||||
.btn,
|
|
||||||
a[href]:not([aria-disabled="true"]),
|
|
||||||
[role="button"] {
|
|
||||||
cursor: pointer;
|
|
||||||
}
|
|
||||||
|
|
||||||
.shadow,
|
|
||||||
.shadow-sm,
|
|
||||||
.shadow-md,
|
|
||||||
.shadow-lg,
|
|
||||||
.shadow-xl,
|
|
||||||
.shadow-2xl,
|
|
||||||
[class*="shadow"] {
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
.card,
|
|
||||||
.dialog,
|
|
||||||
.popover,
|
|
||||||
.dropdown-menu {
|
|
||||||
box-shadow: none !important;
|
|
||||||
}
|
|
||||||
|
|
||||||
figure > img {
|
|
||||||
max-width: 600px;
|
|
||||||
min-width: 300px;
|
|
||||||
}
|
|
||||||
|
|
||||||
img.rounded-image {
|
|
||||||
max-width: auto;
|
|
||||||
min-width: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -1,40 +0,0 @@
|
||||||
@custom-variant dark (&:is(.dark *));
|
|
||||||
|
|
||||||
@theme inline {
|
|
||||||
--radius-sm: calc(var(--radius) - 4px);
|
|
||||||
--radius-md: calc(var(--radius) - 2px);
|
|
||||||
--radius-lg: var(--radius);
|
|
||||||
--radius-xl: calc(var(--radius));
|
|
||||||
--color-background: var(--background);
|
|
||||||
--color-foreground: var(--foreground);
|
|
||||||
--color-card: var(--card);
|
|
||||||
--color-card-foreground: var(--card-foreground);
|
|
||||||
--color-popover: var(--popover);
|
|
||||||
--color-popover-foreground: var(--popover-foreground);
|
|
||||||
--color-primary: var(--primary);
|
|
||||||
--color-primary-foreground: var(--primary-foreground);
|
|
||||||
--color-secondary: var(--secondary);
|
|
||||||
--color-secondary-foreground: var(--secondary-foreground);
|
|
||||||
--color-muted: var(--muted);
|
|
||||||
--color-muted-foreground: var(--muted-foreground);
|
|
||||||
--color-accent: var(--accent);
|
|
||||||
--color-accent-foreground: var(--accent-foreground);
|
|
||||||
--color-destructive: var(--destructive);
|
|
||||||
--color-border: var(--border);
|
|
||||||
--color-input: var(--input);
|
|
||||||
--color-ring: var(--ring);
|
|
||||||
--color-chart-1: var(--chart-1);
|
|
||||||
--color-chart-2: var(--chart-2);
|
|
||||||
--color-chart-3: var(--chart-3);
|
|
||||||
--color-chart-4: var(--chart-4);
|
|
||||||
--color-chart-5: var(--chart-5);
|
|
||||||
--color-sidebar: var(--sidebar);
|
|
||||||
--color-sidebar-foreground: var(--sidebar-foreground);
|
|
||||||
--color-sidebar-primary: var(--sidebar-primary);
|
|
||||||
--color-sidebar-primary-foreground: var(--sidebar-primary-foreground);
|
|
||||||
--color-sidebar-accent: var(--sidebar-accent);
|
|
||||||
--color-sidebar-accent-foreground: var(--sidebar-accent-foreground);
|
|
||||||
--color-sidebar-border: var(--sidebar-border);
|
|
||||||
--color-sidebar-ring: var(--sidebar-ring);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
@ -12,62 +12,65 @@ const BlogTemplate = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
|
<div className="container mx-auto p-4 grow">
|
||||||
{!post ? (
|
{!post ? (
|
||||||
<div>Loading...</div>
|
<div>Loading...</div>
|
||||||
) : (
|
) : (
|
||||||
<>
|
<article className="prose prose-lg max-w-none">
|
||||||
<div className="mb-5">
|
<header className="mb-6 pb-4">
|
||||||
<h2 className="text-3xl font-bold lg:text-3xl">{post?.title}</h2>
|
<h1 className="text-4xl font-semibold mb-4 leading-tight">
|
||||||
</div>
|
{post?.title}
|
||||||
<div className="flex md:flex-row md:justify-between flex-col mb-8">
|
</h1>
|
||||||
<span className="text-muted-foreground">
|
<div className="flex flex-wrap align-center gap-4 text-[#928374]">
|
||||||
|
<time datetime={convertDate(post?.date)} className="text-sm ">
|
||||||
{convertDate(post?.date)}
|
{convertDate(post?.date)}
|
||||||
</span>
|
</time>
|
||||||
<div className="flex gap-1 mt-3 md:mt-0">
|
<div className="flex flex-wrap gap-3 align-center">
|
||||||
{post?.tags?.map((tag, i) => (
|
{post?.tags?.map((tag, i) => (
|
||||||
<Badge asChild variant="secondary" className="">
|
<Link
|
||||||
<Link key={i} to={`/tags/${tag}`}>
|
className="text-primary text-sm underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
key={i}
|
||||||
|
to={`/tags/${tag}`}
|
||||||
|
>
|
||||||
{tag}
|
{tag}
|
||||||
</Link>
|
</Link>
|
||||||
</Badge>
|
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</header>
|
||||||
|
|
||||||
<div
|
<div
|
||||||
className="
|
className="
|
||||||
[&>h2]:text-xl [&>h2]:font-semibold [&>h2]:first:mt-0 [&>h2:not(:first-child)]:mt-8
|
[&>h2]:text-2xl [&>h2]:font-medium [&>h2]:my-4 [&>h2]:text-[#fabd2f]!
|
||||||
[&>h3]:text-xl [&>h3]:sm:text-1xl [&>h3]:font-semibold [&>h3:not(:first-child)]:mt-5
|
[&>h3]:text-xl [&>h3]:font-medium [&>h3]:my-4 [&>h3]:text-[#fabd2f]
|
||||||
[&>h4]:text-lg [&>h4]:sm:text-xl [&>h4]:font-semibold [&>h4:not(:first-child)]:mt-4
|
[&>h4]:text-lg [&>h4]:font-medium [&>h4]:my-4 [&>h4]:text-[#fabd2f]
|
||||||
[&>p]:leading-7 [&>p:not(:first-child)]:mt-4
|
[&>p]:leading-7 [&>p:not(:first-child)]:mt-4
|
||||||
[&>p+:is(h1,h2,h3,h4,h5,h6)]:mt-6
|
[&>p+:is(h1,h2,h3,h4,h5,h6)]:mt-6
|
||||||
[&>blockquote]:mt-4 [&>blockquote]:border-l-2 [&>blockquote]:pl-6 [&>blockquote]:text-muted-foreground
|
[&>blockquote]:mt-4 [&>blockquote]:border-l-2 [&>blockquote]:pl-6 [&>blockquote]:text-muted-foreground
|
||||||
[&>ul]:my-4 [&>ul]:ml-6 [&>ul]:list-disc [&>ul>li]:mt-2
|
[&>ul]:my-4 [&>ul]:ml-6 [&>ul]:list-disc [&>ul>li]:mt-2
|
||||||
[&>table]:w-full [&>table]:my-4
|
|
||||||
[&>table>thead>tr]:m-0 [&>table>thead>tr]:border-t [&>table>thead>tr]:p-0 [&>table>thead>tr:even]:bg-muted
|
|
||||||
[&>table>thead>tr>th]:border [&>table>thead>tr>th]:px-4 [&>table>thead>tr>th]:py-2 [&>table>thead>tr>th]:text-left [&>table>thead>tr>th]:font-bold [&>table>thead>tr>th[align=center]]:text-center [&>table>thead>tr>th[align=right]]:text-right
|
|
||||||
[&>table>tbody>tr]:m-0 [&>table>tbody>tr]:border-t [&>table>tbody>tr]:p-0 [&>table>tbody>tr:even]:bg-muted
|
|
||||||
[&>table>tbody>tr>td]:border [&>table>tbody>tr>td]:px-4 [&>table>tbody>tr>td]:py-2 [&>table>tbody>tr>td]:text-left [&>table>tbody>tr>td[align=center]]:text-center [&>table>tbody>tr>td[align=right]]:text-right
|
|
||||||
[&>code]:relative [&>code]:rounded [&>code]:bg-muted [&>code]:px-[0.3rem] [&>code]:py-[0.2rem] [&>code]:font-mono [&>code]:text-sm [&>code]:font-normal
|
|
||||||
[&>table>tbody>tr:nth-child(even)]:bg-muted
|
|
||||||
[&>table>thead>tr:nth-child(even)]:bg-muted
|
|
||||||
[&_table_code]:text-sm font-normal
|
|
||||||
[&>pre]:mt-6 [&>pre]:mb-6
|
[&>pre]:mt-6 [&>pre]:mb-6
|
||||||
[&>p+pre]:mt-6
|
[&>p+pre]:mt-6
|
||||||
[&>pre+p]:mt-6
|
[&>pre+p]:mt-6
|
||||||
[&>ul+pre]:mt-6
|
[&>ul+pre]:mt-6
|
||||||
[&>li]:leading-[1.6]
|
[&>li]:leading-[1.6]
|
||||||
[&_li_code]:relative [&_li_code]:rounded [&_li_code]:bg-muted [&_li_code]:px-[0.3rem] [&_li_code]:py-[0.2rem] [&_li_code]:font-mono [&_li_code]:text-sm [&_li_code]:font-normal
|
[&_li_code]:relative [&_li_code]:rounded [&_li_code]:bg-[#504945] [&_li_code]:px-[0.3rem] [&_li_code]:py-[0.2rem] [&_li_code]:font-mono [&_li_code]:text-sm [&_li_code]:font-normal
|
||||||
[&>p]:mt-6 [&>p]:leading-[1.6]
|
[&>code]:relative [&>code]:rounded [&>code]:bg-[#504945] [&>code]:px-[0.3rem] [&>code]:py-[0.2rem] [&>code]:font-mono [&>code]:text-sm [&>code]:font-normal
|
||||||
[&_a]:underline [&_a]:underline-offset-4 [&_a]:hover:text-muted-foreground
|
|
||||||
[&>figure]:w-full [&>figure]:flex [&>figure]:flex-col [&>figure]:items-center [&>figure]:justify-center [&>figure]:mb-6 [&>figure]:mx-auto
|
[&>figure]:w-full [&>figure]:flex [&>figure]:flex-col [&>figure]:items-center [&>figure]:justify-center [&>figure]:mb-6 [&>figure]:mx-auto
|
||||||
[&>figure>img]:w-full
|
[&>figure>img]:w-full
|
||||||
[&>figure>figcaption]:text-sm [&>figure>figcaption]:text-muted-foreground [&>figure>figcaption]:mt-3 [&>figure>figcaption]:text-center
|
[&>figure>figcaption]:text-sm [&>figure>figcaption]:text-[#bdae93] [&>figure>figcaption]:mt-3 [&>figure>figcaption]:text-center
|
||||||
[&>figure>figcaption>a]:text-primary [&>figure>figcaption>a:hover]:text-primary/80
|
[&>figure>figcaption>a]:text-primary [&>figure>figcaption>a:hover]:text-primary/80
|
||||||
|
[&_a]:underline [&_a]:underline-offset-3 [&_a]:hover:text-[#689d6a] [&_a]:text-primary
|
||||||
|
[&>table]:w-full [&>table]:my-4
|
||||||
|
[&>table>thead>tr]:m-0 [&>table>thead>tr]:border-t [&>table>thead>tr]:p-0 [&>table>thead>tr:even]:bg-muted
|
||||||
|
[&>table>thead>tr>th]:border [&>table>thead>tr>th]:px-4 [&>table>thead>tr>th]:py-2 [&>table>thead>tr>th]:text-left [&>table>thead>tr>th]:font-bold [&>table>thead>tr>th[align=center]]:text-center [&>table>thead>tr>th[align=right]]:text-right
|
||||||
|
[&>table>tbody>tr]:m-0 [&>table>tbody>tr]:border-t [&>table>tbody>tr]:p-0 [&>table>tbody>tr:even]:bg-muted
|
||||||
|
[&>table>tbody>tr>td]:border [&>table>tbody>tr>td]:px-4 [&>table>tbody>tr>td]:py-2 [&>table>tbody>tr>td]:text-left [&>table>tbody>tr>td[align=center]]:text-center [&>table>tbody>tr>td[align=right]]:text-right
|
||||||
"
|
"
|
||||||
dangerouslySetInnerHTML={{ __html: post?.html }}
|
dangerouslySetInnerHTML={{ __html: post?.html }}
|
||||||
/>
|
/>
|
||||||
</>
|
</article>
|
||||||
)}
|
)}
|
||||||
|
</div>
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,30 +1,82 @@
|
||||||
// @ts-nocheck
|
// @ts-nocheck
|
||||||
import { Header } from "@/components/Header"
|
|
||||||
import { ThemeProvider } from "@/context/ThemeProvider"
|
|
||||||
import { useTheme } from "@/context/ThemeProvider"
|
|
||||||
|
|
||||||
const MainTemplate = (props) => {
|
import gruvboxComputer from "../images/gruvbox-computer.svg"
|
||||||
|
import { Link } from "react-router"
|
||||||
|
const Header = () => {
|
||||||
return (
|
return (
|
||||||
<ThemeProvider defaultTheme="dark" storageKey="vite-ui-theme">
|
<header className="py-6">
|
||||||
<MainContent>{props.children}</MainContent>
|
<nav className="bg-sidebar container mx-auto justify-between flex gap-1">
|
||||||
</ThemeProvider>
|
<div className="scanlined">
|
||||||
|
<img src={gruvboxComputer} className="w-10" />
|
||||||
|
</div>
|
||||||
|
<ul class="flex space-x-4 px-4 py-2">
|
||||||
|
<li class="flex flex-col items-center justify-center">
|
||||||
|
<Link
|
||||||
|
class="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
to="/"
|
||||||
|
>
|
||||||
|
home
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
|
||||||
|
<li class="flex flex-col items-center justify-center">
|
||||||
|
<Link
|
||||||
|
class="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
to="/posts"
|
||||||
|
>
|
||||||
|
posts
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
<li class="flex flex-col items-center justify-center">
|
||||||
|
<Link
|
||||||
|
class="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
to="/about"
|
||||||
|
>
|
||||||
|
about
|
||||||
|
</Link>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</header>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
const MainContent = ({ children }) => {
|
const Footer = () => {
|
||||||
const { theme } = useTheme()
|
|
||||||
const classes =
|
|
||||||
theme === "light"
|
|
||||||
? "min-h-screen w-full flex flex-col overflow-x-hidden mb-15"
|
|
||||||
: "min-h-screen w-full flex flex-col overflow-x-hidden mb-15"
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={classes}>
|
<footer className="bg-sidebar container mx-auto px-4 mt-10 mb-8">
|
||||||
|
<nav>
|
||||||
|
<ul className="flex flex-row justify-start gap-4">
|
||||||
|
<li className="flex flex-col items-center justify-center">
|
||||||
|
<a
|
||||||
|
className="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
href="https://forgejo.systemsobscure.net/thomasabishop"
|
||||||
|
target="blank"
|
||||||
|
>
|
||||||
|
forgejo
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
<li className="">
|
||||||
|
<a
|
||||||
|
className="text-primary underline underline-offset-3 hover:text-[#689d6a]"
|
||||||
|
href="https://fosstodon.org/@systemsobscure"
|
||||||
|
target="blank"
|
||||||
|
>
|
||||||
|
fosstodon
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</nav>
|
||||||
|
</footer>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const MainTemplate = ({ children }) => {
|
||||||
|
return (
|
||||||
|
<div className="antialiased max-w-3xl mt-3 mx-auto text-[#fbf1c7] bg-[#282828] no-scanlines wrapper">
|
||||||
|
<main className="flex-auto min-w-0 mt-0 flex flex-col px-2 md:px-0">
|
||||||
<Header />
|
<Header />
|
||||||
<main className="flex-1 w-full px-2 md:px-4 flex justify-center pt-22">
|
<div>{children}</div>
|
||||||
<div className="w-full max-w-3xl lg:max-w-3xl xl:max-w-3xl px-2 md:px-4 md:py-3 py-0">
|
<Footer />
|
||||||
{children}
|
|
||||||
</div>
|
|
||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -12,7 +12,10 @@ const TagTemplate = () => {
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<MainTemplate>
|
<MainTemplate>
|
||||||
<PostListing title={`Posts tagged: #${tag}`} posts={filteredPosts} />
|
<div className="container mx-auto p-4">
|
||||||
|
<h1 className="h1 text-3xl text-[#b8bb26]!">{`Posts tagged: #${tag}`}</h1>
|
||||||
|
</div>
|
||||||
|
<PostListing title={null} posts={filteredPosts} />
|
||||||
</MainTemplate>
|
</MainTemplate>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -24,12 +24,19 @@ const days = [
|
||||||
]
|
]
|
||||||
|
|
||||||
const convertDate = (isoStamp: string) => {
|
const convertDate = (isoStamp: string) => {
|
||||||
const unixSeconds = new Date(isoStamp)
|
//const unixSeconds = new Date(isoStamp)
|
||||||
// const weekday = days[unixSeconds.getDay()]
|
//// const weekday = days[unixSeconds.getDay()]
|
||||||
const day = unixSeconds.getDate()
|
//const day = unixSeconds.getDate()
|
||||||
const month = months[unixSeconds.getMonth()]
|
//const month = months[unixSeconds.getMonth()]
|
||||||
const year = unixSeconds.getFullYear()
|
//const year = unixSeconds.getFullYear()
|
||||||
return `${day} ${month} ${year}`
|
//return `${day} ${month} ${year}`
|
||||||
|
|
||||||
|
console.log(isoStamp)
|
||||||
|
const date = new Date(isoStamp)
|
||||||
|
const year = date.getFullYear()
|
||||||
|
const month = String(date.getMonth() + 1).padStart(2, "0")
|
||||||
|
const day = String(date.getDate()).padStart(2, "0")
|
||||||
|
return `${year}-${month}-${day}`
|
||||||
}
|
}
|
||||||
|
|
||||||
export { convertDate }
|
export { convertDate }
|
||||||
|
|
|
||||||
0
test.txt
0
test.txt
Loading…
Add table
Reference in a new issue