aboutsummaryrefslogtreecommitdiff
path: root/components
diff options
context:
space:
mode:
Diffstat (limited to 'components')
-rw-r--r--components/footer.tsx20
-rw-r--r--components/header.tsx42
-rw-r--r--components/map-wrapper.tsx10
-rw-r--r--components/map.tsx70
-rw-r--r--components/mdx-content.tsx12
-rw-r--r--components/noise.tsx8
-rw-r--r--components/panda.tsx9
-rw-r--r--components/post-card.tsx20
-rw-r--r--components/projects-grid.tsx51
-rw-r--r--components/theme-provider.tsx11
-rw-r--r--components/tools-grid.tsx56
-rw-r--r--components/ui/button.tsx58
-rw-r--r--components/ui/card.tsx92
13 files changed, 459 insertions, 0 deletions
diff --git a/components/footer.tsx b/components/footer.tsx
new file mode 100644
index 0000000..b78b8b3
--- /dev/null
+++ b/components/footer.tsx
@@ -0,0 +1,20 @@
+import Link from 'next/link'
+import { Panda } from './panda'
+
+export function Footer() {
+ return (
+ <footer className="mx-auto max-w-[var(--site-width)] py-4">
+ <div className="flex items-center justify-between">
+ <div className="flex gap-4">
+ <Link
+ href="/privacy"
+ className="text-muted-foreground/60 transition-colors hover:text-foreground"
+ >
+ Privacy
+ </Link>
+ </div>
+ <Panda />
+ </div>
+ </footer>
+ )
+}
diff --git a/components/header.tsx b/components/header.tsx
new file mode 100644
index 0000000..79cb0c0
--- /dev/null
+++ b/components/header.tsx
@@ -0,0 +1,42 @@
+import Link from "next/link"
+import { PawPrint, Github, Mail } from "lucide-react"
+import { Button } from "@/components/ui/button"
+
+export function Header() {
+ return (
+ <header className="mx-auto flex w-full max-w-[var(--site-width)] items-center justify-between gap-5 py-4">
+ <div className="flex items-center gap-6">
+ <Link href="/" className="font-bold text-xl">
+ <PawPrint className="hover:rotate-12 transition-transform" />
+ </Link>
+ <nav className="flex gap-6">
+ <Link
+ href="/blog"
+ className="text-muted-foreground hover:text-foreground transition-colors"
+ >
+ Blog
+ </Link>
+ <Link
+ href="/projects"
+ className="text-muted-foreground hover:text-foreground transition-colors"
+ >
+ Projects
+ </Link>
+ </nav>
+ </div>
+
+ <div className="flex items-center gap-2">
+ <Button asChild variant="ghost" size="icon">
+ <Link href="https://github.com/schererleander" target="_blank" aria-label="Github">
+ <Github className="h-4 w-4" />
+ </Link>
+ </Button>
+ <Button asChild variant="ghost" size="icon">
+ <Link href="mailto:leander@schererleander.de" aria-label="Contact">
+ <Mail className="h-4 w-4" />
+ </Link>
+ </Button>
+ </div>
+ </header>
+ )
+}
diff --git a/components/map-wrapper.tsx b/components/map-wrapper.tsx
new file mode 100644
index 0000000..2407702
--- /dev/null
+++ b/components/map-wrapper.tsx
@@ -0,0 +1,10 @@
+
+"use client"
+
+import dynamic from 'next/dynamic'
+
+const Map = dynamic(() => import('./map'))
+
+export default function MapWrapper() {
+ return <Map />
+}
diff --git a/components/map.tsx b/components/map.tsx
new file mode 100644
index 0000000..8584c76
--- /dev/null
+++ b/components/map.tsx
@@ -0,0 +1,70 @@
+
+"use client"
+
+import { useRef } from 'react';
+import MapGL, { Marker, MapRef } from 'react-map-gl/maplibre';
+import maplibregl from 'maplibre-gl';
+import 'maplibre-gl/dist/maplibre-gl.css';
+
+const KARLSRUHE: { longitude: number, latitude: number } = { longitude: 8.4037, latitude: 49.0069 };
+
+export default function Map() {
+ const mapRef = useRef<MapRef>(null);
+
+ const onMapLoad = () => {
+ mapRef.current?.flyTo({
+ center: [KARLSRUHE.longitude, KARLSRUHE.latitude],
+ zoom: 10,
+ speed: 0.5,
+ curve: 1,
+ essential: true
+ });
+ };
+
+ return (
+ <div
+ className="relative h-[350px] rounded-xl overflow-hidden border border-border z-0 bg-background"
+ style={{
+ maskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
+ WebkitMaskImage: "linear-gradient(to bottom, black 50%, transparent 100%)",
+ }}
+ >
+ <MapGL
+ ref={mapRef}
+ mapLib={maplibregl}
+ onLoad={onMapLoad}
+ initialViewState={{
+ longitude: KARLSRUHE.longitude,
+ latitude: KARLSRUHE.latitude,
+ zoom: 2
+ }}
+ style={{ width: "100%", height: "100%" }}
+ mapStyle={{
+ version: 8,
+ sources: {
+ "carto-dark": {
+ type: "raster",
+ tiles: ["https://a.basemaps.cartocdn.com/dark_all/{z}/{x}/{y}.png"],
+ tileSize: 256,
+ attribution: '&copy; <a href="http://www.openstreetmap.org/copyright">OpenStreetMap</a>, &copy; <a href="https://carto.com/attributions">CARTO</a>',
+ },
+ },
+ layers: [
+ {
+ id: "carto-dark-layer",
+ type: "raster",
+ source: "carto-dark",
+ minzoom: 0,
+ maxzoom: 22,
+ },
+ ],
+ }}
+ attributionControl={false}
+ >
+ <Marker longitude={KARLSRUHE.longitude} latitude={KARLSRUHE.latitude} color="red">
+ <div style={{width:'12px',height:'12px',background:'var(--background)',borderRadius:'50%',border:'2px solid var(--foreground)',boxShadow:'0 0 10px rgba(0,0,0,0.5)'}} />
+ </Marker>
+ </MapGL>
+ </div>
+ );
+}
diff --git a/components/mdx-content.tsx b/components/mdx-content.tsx
new file mode 100644
index 0000000..d93ba3d
--- /dev/null
+++ b/components/mdx-content.tsx
@@ -0,0 +1,12 @@
+'use client'
+
+import { useMDXComponent } from 'next-contentlayer2/hooks'
+
+interface MDXContentProps {
+ code: string
+}
+
+export function MDXContent({ code }: MDXContentProps) {
+ const Component = useMDXComponent(code)
+ return <Component />
+}
diff --git a/components/noise.tsx b/components/noise.tsx
new file mode 100644
index 0000000..3e29b66
--- /dev/null
+++ b/components/noise.tsx
@@ -0,0 +1,8 @@
+
+export function Noise() {
+ return (
+ <div
+ className="fixed top-0 left-0 w-full h-full pointer-events-none z-50 noise"
+ />
+ );
+}
diff --git a/components/panda.tsx b/components/panda.tsx
new file mode 100644
index 0000000..ee9328c
--- /dev/null
+++ b/components/panda.tsx
@@ -0,0 +1,9 @@
+export function Panda() {
+ return (
+ <label className="text-sm text-muted-foreground/60 transition-colors hover:text-foreground cursor-pointer select-none">
+ <input type="checkbox" className="peer sr-only" />
+ <span className="peer-checked:hidden">ʕっ•ᴥ•ʔっ</span>
+ <span className="hidden peer-checked:inline">ʕ•ᴥ•ʔ</span>
+ </label>
+ )
+}
diff --git a/components/post-card.tsx b/components/post-card.tsx
new file mode 100644
index 0000000..4990188
--- /dev/null
+++ b/components/post-card.tsx
@@ -0,0 +1,20 @@
+import { type Post } from "contentlayer/generated"
+import { format, parseISO } from "date-fns"
+import Link from "next/link"
+
+export function PostCard(post: Post) {
+ return (
+ <div className="mb-8">
+ <Link href={post.url} className="no-underline">
+ <h2 className="mb-1.5 mt-3 text-xl font-bold text-foreground">
+ {post.title}
+ </h2>
+ </Link>
+ <div className="flex gap-1.5 font-medium text-muted-foreground/60">
+ <time dateTime={post.date} title={format(parseISO(post.date), 'yyyy/MM/dd')}>
+ {format(parseISO(post.date), 'do MMM yyyy')}
+ </time>
+ </div>
+ </div>
+ )
+}
diff --git a/components/projects-grid.tsx b/components/projects-grid.tsx
new file mode 100644
index 0000000..9ccb0fb
--- /dev/null
+++ b/components/projects-grid.tsx
@@ -0,0 +1,51 @@
+import { Card, CardDescription, CardHeader, CardTitle } from "@/components/ui/card";
+import Link from "next/link";
+
+interface Project {
+ title: string;
+ description: string;
+ githubUrl: string;
+}
+
+const projects: Project[] = [
+ {
+ title: "Boilerplate",
+ description: "A comprehensive starter template for modern web development projects.",
+ githubUrl: "https://github.com/schererleander/boilerplate",
+ },
+ {
+ title: "Quiz Website",
+ description: "An interactive quiz platform built to test your knowledge.",
+ githubUrl: "https://github.com/schererleander/quiz",
+ },
+ {
+ title: "Space Invaders",
+ description: "A classic arcade game clone recreated with modern web technologies.",
+ githubUrl: "https://github.com/schererleander/spaceinvaders",
+ },
+ {
+ title: "Specula",
+ description: "A mirror project or reflection tool (Description inferred from name).",
+ githubUrl: "https://github.com/schererleander/specula",
+ },
+];
+
+export function ProjectsGrid() {
+ return (
+ <section>
+ <div className="grid grid-cols-2 md:grid-cols-2 gap-6">
+ {projects.map((project) => (
+ <Link href={project.githubUrl} key={project.title} target="_blank" className="block group h-full">
+ <Card className="h-full transition-all duration-300 bg-card/50 hover:bg-card/80 shadow-none p-0 gap-0 overflow-hidden">
+ <div className="w-full h-40 bg-muted animate-pulse" />
+ <CardHeader className="p-6">
+ <CardTitle className="group-hover:text-primary transition-colors">{project.title}</CardTitle>
+ <CardDescription className="text-muted-foreground/60 group-hover:text-muted-foreground transition-colors">{project.description}</CardDescription>
+ </CardHeader>
+ </Card>
+ </Link>
+ ))}
+ </div>
+ </section>
+ );
+}
diff --git a/components/theme-provider.tsx b/components/theme-provider.tsx
new file mode 100644
index 0000000..6a1ffe4
--- /dev/null
+++ b/components/theme-provider.tsx
@@ -0,0 +1,11 @@
+"use client"
+
+import * as React from "react"
+import { ThemeProvider as NextThemesProvider } from "next-themes"
+
+export function ThemeProvider({
+ children,
+ ...props
+}: React.ComponentProps<typeof NextThemesProvider>) {
+ return <NextThemesProvider {...props}>{children}</NextThemesProvider>
+}
diff --git a/components/tools-grid.tsx b/components/tools-grid.tsx
new file mode 100644
index 0000000..b0cdfa6
--- /dev/null
+++ b/components/tools-grid.tsx
@@ -0,0 +1,56 @@
+import { Card } from "@/components/ui/card";
+import { GitGraph, Coffee, Terminal, Box, Snowflake, Code } from 'lucide-react';
+
+const tools = [
+ {
+ name: 'Git',
+ icon: GitGraph,
+ color: 'group-hover:text-[#F05032]'
+ },
+ {
+ name: 'Java',
+ icon: Coffee,
+ color: 'group-hover:text-[#5382a1]'
+ },
+ {
+ name: 'Python',
+ icon: Code,
+ color: 'group-hover:text-[#FFE873]'
+ },
+ {
+ name: 'Nix',
+ icon: Snowflake,
+ color: 'group-hover:text-[#5277C3]'
+ },
+ {
+ name: 'Docker',
+ icon: Box,
+ color: 'group-hover:text-[#2496ED]'
+ },
+ {
+ name: 'Linux',
+ icon: Terminal,
+ color: 'group-hover:text-[#FCC624]'
+ },
+];
+
+export function ToolsGrid() {
+ return (
+ <div className="grid grid-cols-3 md:grid-cols-6 gap-4 w-full max-w-4xl mx-auto">
+ {tools.map((tool) => (
+ <Card
+ key={tool.name}
+ className="group flex flex-col items-center justify-center gap-2 p-4 bg-card/50 hover:bg-card/80 transition-all duration-300 shadow-none"
+ >
+ <tool.icon
+ className={`w-6 h-6 text-muted-foreground transition-colors duration-300 ${tool.color}`}
+ strokeWidth={1.5}
+ />
+ <span className="text-xs font-medium text-muted-foreground/60 group-hover:text-muted-foreground transition-colors">
+ {tool.name}
+ </span>
+ </Card>
+ ))}
+ </div>
+ );
+}
diff --git a/components/ui/button.tsx b/components/ui/button.tsx
new file mode 100644
index 0000000..3ffbfed
--- /dev/null
+++ b/components/ui/button.tsx
@@ -0,0 +1,58 @@
+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-[color,box-shadow] disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg:not([class*='size-'])]:size-4 [&_svg]:shrink-0 ring-ring/10 dark:ring-ring/20 dark:outline-ring/40 outline-ring/50 focus-visible:ring-4 focus-visible:outline-1 aria-invalid:focus-visible:ring-0",
+ {
+ variants: {
+ variant: {
+ default:
+ "bg-primary text-primary-foreground shadow-sm hover:bg-primary/90",
+ destructive:
+ "bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90",
+ outline:
+ "border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground",
+ secondary:
+ "bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80",
+ ghost: "hover:bg-accent hover:text-accent-foreground",
+ link: "text-primary underline-offset-4 hover:underline",
+ },
+ size: {
+ default: "h-9 px-4 py-2",
+ sm: "h-8 rounded-md px-3 text-xs",
+ lg: "h-10 rounded-md px-8",
+ icon: "h-9 w-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 }
diff --git a/components/ui/card.tsx b/components/ui/card.tsx
new file mode 100644
index 0000000..681ad98
--- /dev/null
+++ b/components/ui/card.tsx
@@ -0,0 +1,92 @@
+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-2 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,
+}