diff --git a/.prettierrc b/.prettierrc
new file mode 100644
index 0000000..e69de29
diff --git a/.vscode/settings.json b/.vscode/settings.json
new file mode 100644
index 0000000..486e447
--- /dev/null
+++ b/.vscode/settings.json
@@ -0,0 +1,7 @@
+{
+ "editor.formatOnSave": true,
+ "editor.defaultFormatter": "esbenp.prettier-vscode",
+ "editor.codeActionsOnSave": {
+ "source.fixAll.eslint": "explicit"
+ }
+}
\ No newline at end of file
diff --git a/app/page copy 2.tsx b/app/page copy 2.tsx
deleted file mode 100644
index bfb3738..0000000
--- a/app/page copy 2.tsx
+++ /dev/null
@@ -1,128 +0,0 @@
-"use client";
-import Link from 'next/link';
-import { motion } from 'framer-motion';
-import { Globe, Smartphone, Server, Gamepad2, Activity } from 'lucide-react';
-
-export default function Home() {
- return (
-
-
-
-
-
- {/* Top Row: The Architect (Wide) */}
-
-
-
The Architect
-
- Bridging the gap between complex system architecture and fluid user experiences.
- I specialize in designing distributed web systems and cross-platform mobile apps
- with a relentless focus on performance, self-sovereign infrastructure, and automated delivery pipelines.
-
-
-
- {['#Architecture', '#SystemDesign', '#Automation', '#FullStack', '#Scalability'].map(tag => (
- {tag}
- ))}
-
-
-
- {/* Top Row: Hetzner Node (Narrow) */}
-
-
- {/* Middle Row: Web Systems */}
-
-
-
-
-
Web Systems
-
- Architecting distributed platforms with a focus on high-availability, API design, and containerized deployment.
-
-
-
- {['Next.js', 'Python', 'Node.js', 'Caddy', 'PostgreSQL'].map(tech => (
-
- {tech}
-
- ))}
-
-
-
-
- {/* Middle Row: Mobile Apps */}
-
-
-
-
-
Mobile Apps
-
- Building fluid, cross-platform experiences using reactive state management and native hardware integration.
-
-
-
- {['Android', 'iOS', 'Flutter', 'Riverpod', 'Stores'].map(tech => (
-
- {tech}
-
- ))}
-
-
-
-
- {/* Middle Row: Infrastructure (The New Card) */}
-
-
-
-
-
DevOps
-
- Managing self-hosted cloud nodes with automated CI/CD pipelines, secure proxying, and proactive monitoring.
-
-
-
- {['Docker', 'Woodpecker', 'Hetzner', 'Linux', 'Uptime'].map(tech => (
-
- {tech}
-
- ))}
-
-
-
-
- {/* Bottom Row: The Forge (Wide) */}
-
-
-
-
-
-
The Forge
-
Indie Game Dev & Creative Prototypes
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/app/page copy.tsx b/app/page copy.tsx
deleted file mode 100644
index 3b09234..0000000
--- a/app/page copy.tsx
+++ /dev/null
@@ -1,195 +0,0 @@
-"use client";
-import { motion } from "framer-motion";
-import { Server, Globe, Smartphone, Gamepad2, Activity } from "lucide-react";
-import Link from 'next/link';
-
-export default function Home() {
- return (
-
- {/* Header section */}
-
-
- {/* Bento Grid */}
-
-
- {/* About Me - Large Card */}
-
-
-
The Architect
-
- Bridging the gap between complex system architecture and fluid user experiences. I specialize in designing distributed web systems and cross-platform mobile apps with a relentless focus on performance, self-sovereign infrastructure, and automated delivery pipelines.
-
-
-
- #NextJS #Flutter #Typescript #Python #Node #Docker #Kubernetes #Serverless #CI/CD
-
-
-
- {/* Live Pulse Card */}
-
- {/* The Monitor Map (Easily editable) */}
- {(() => {
- const monitors = [
- { id: 2, name: "Datasaur" },
- { id: 6, name: "Audiobookshelf" },
- { id: 7, name: "Woodpecker CI" },
- { id: 8, name: "Forgejo Git" },
- { id: 9, name: "Server dashboard" },
- { id: 10, name: "Ratoong" },
- ];
-
- return (
- <>
- {/* Default View */}
-
-
-
-
-
System Status:
-
-
-
-
-
-
- {/* Hover View: Friendly Names */}
-
-
Service Registry
-
- {monitors.map((m) => (
-
-
{m.name}
-
-
-
-
-
- ))}
-
-
- >
- );
- })()}
-
-
- {/* Project One */}
-
-
- {/* Icon Container - Fixed size ensures it never disappears */}
-
-
-
-
-
-
Web Systems
-
- Architecting distributed platforms with a focus on high-availability, API design, and containerized deployment.
-
-
-
- {/* Tech Pips - Use 'flex-wrap' and 'gap-y' to handle multiline safely */}
-
- {['Next.js', 'Node.js', 'Python', 'PostgreSQL', 'Docker'].map((tech) => (
-
- {tech}
-
- ))}
-
-
- {/* Hover Indicator */}
-
-
-
-
- {/* Project Two */}
-
-
-
-
-
- Mobile Apps
-
- Building fluid, cross-platform experiences using reactive state management and native hardware integration.
-
-
- {/* Tech Pips */}
-
- {['Android', 'iOS', 'Flutter', 'Riverpod', 'Publishing'].map((tech) => (
-
- {tech}
-
- ))}
-
-
-
-
-
-
-
- {/* Game Teaser / The Lab */}
-
-
-
-
-
-
The Forge
-
Indie Game Dev & Prototypes
-
-
-
-
-
- {/* Deployment Footer */}
-
-
- );
-}
\ No newline at end of file
diff --git a/app/page.tsx b/app/page.tsx
index c75502c..6c6242c 100644
--- a/app/page.tsx
+++ b/app/page.tsx
@@ -1,10 +1,10 @@
"use client";
-import Link from 'next/link';
-import { motion } from 'framer-motion';
-import { Globe, Smartphone, Server, Gamepad2, Activity } from 'lucide-react';
-import { useState } from 'react';
-import MonitorCard from '@/components/MonitorCard';
+import Link from "next/link";
+import { motion } from "framer-motion";
+import { Globe, Smartphone, Server, Gamepad2, Activity } from "lucide-react";
+import { useState } from "react";
+import MonitorCard from "@/components/MonitorCard";
export default function Home() {
const [isHoveringMonitors, setIsHoveringMonitors] = useState(false);
@@ -12,83 +12,107 @@ export default function Home() {
return (
-
-
{/* Top Row Left: The Architect */}
- {/* Background Decoration: Subtle Grid or Blueprint */}
-
- {/* You could place a subtle SVG circuit or architectural grid here */}
-
+
- {/* Left: Bio & Tags */}
-
-
-
The Architect
-
- Bridging the gap between rigid regulatory requirements and fluid user experiences.
- I specialize in designing distributed systems and
- cross-platform mobile apps with a focus on
- automated delivery and high-integrity code.
-
+ {/* Description and tags */}
+
+
+
+ The Architect
+
+
+ Bridging the gap between rigid regulatory requirements and
+ fluid user experiences. I specialize in designing{" "}
+ distributed systems and
+
+ {" "}
+ cross-platform mobile apps
+ {" "}
+ with a focus on automated delivery and high-integrity code.
+
+
+
+
+ {[
+ "#Architecture",
+ "#Regulatory Compliance",
+ "#Agile Leadership",
+ "#DevOps",
+ ].map((tag) => (
+
+ {tag}
+
+ ))}
+
-
-
- {['#Architecture', '#Regulatory Compliance', '#Agile Leadership', '#DevOps'].map(tag => (
-
- {tag}
-
- ))}
+
+
+
+ {/* Technical details */}
+
+
+
+
+ Leadership
+
+
+ Tech Lead & Scrum Master. Orchestrating sprint cycles,
+ system design, and cross-functional team growth.
+
+
+
+
+
+ Integrity
+
+
+ Experienced in{" "}
+ High-Stakes Environments {" "}
+ (Medical/Regulatory), QMS, and Cyber Essentials.
+
+
+
+
+
+ Infrastructure
+
+
+ Kubernetes, GCP, and automated CI/CD pipelines.
+
+
+
- {/* Vertical Divider */}
-
-
- {/* Right: Seniority Specs */}
-
-
-
- Leadership
-
- Tech Lead & Scrum Master. Orchestrating sprint cycles, system design, and cross-functional team growth.
-
-
-
-
- Integrity
-
- Experienced in High-Stakes Environments (Medical/Regulatory), QMS, and Cyber Essentials.
-
-
-
-
- Infrastructure
-
- Kubernetes, GCP, and automated CI/CD pipelines.
-
-
-
-
-
-
-
- {/* Top Row Right: The Service Registry (Restored) */}
-
setIsHoveringMonitors(true)}
onMouseLeave={() => setIsHoveringMonitors(false)}
@@ -99,7 +123,7 @@ export default function Home() {
{/* Middle Row: Web Systems */}
-
@@ -107,22 +131,28 @@ export default function Home() {
Web Systems
- Architecting distributed platforms with a focus on high-availability and containerized deployment.
+ Architecting distributed platforms with a focus on
+ high-availability and containerized deployment.
- {['Next.js', 'Python', 'Node.js', 'Caddy', 'PostgreSQL'].map(tech => (
-
- {tech}
-
- ))}
+ {["Next.js", "Python", "Node.js", "Caddy", "PostgreSQL"].map(
+ (tech) => (
+
+ {tech}
+
+ ),
+ )}
{/* Middle Row: Mobile Apps */}
-
@@ -130,22 +160,28 @@ export default function Home() {
Mobile Apps
- Building fluid, cross-platform experiences using reactive state and native hardware integration.
+ Building fluid, cross-platform experiences using reactive
+ state and native hardware integration.
- {['Android', 'iOS', 'Flutter', 'Riverpod', 'Stores'].map(tech => (
-
- {tech}
-
- ))}
+ {["Android", "iOS", "Flutter", "Riverpod", "Stores"].map(
+ (tech) => (
+
+ {tech}
+
+ ),
+ )}
{/* Middle Row: DevOps */}
-
@@ -153,15 +189,21 @@ export default function Home() {
DevOps
- Managing self-hosted cloud nodes with automated CI/CD pipelines and proactive monitoring.
+ Managing self-hosted cloud nodes with automated CI/CD
+ pipelines and proactive monitoring.
- {['Docker', 'Woodpecker', 'Hetzner', 'Linux', 'Uptime'].map(tech => (
-
- {tech}
-
- ))}
+ {["Docker", "Woodpecker", "Hetzner", "Linux", "Uptime"].map(
+ (tech) => (
+
+ {tech}
+
+ ),
+ )}
@@ -173,33 +215,34 @@ export default function Home() {
The Forge
-
Indie Game Dev & Creative Prototypes
+
+ Indie Game Dev & Creative Prototypes
+
-
+
);
-}
\ No newline at end of file
+}
diff --git a/app/projects/[category]/[slug]/page copy.tsx b/app/projects/[category]/[slug]/page copy.tsx
deleted file mode 100644
index 2c10b18..0000000
--- a/app/projects/[category]/[slug]/page copy.tsx
+++ /dev/null
@@ -1,138 +0,0 @@
-"use client";
-
-import { use } from "react";
-import { motion } from "framer-motion";
-import Link from "next/link";
-import { ArrowLeft, ExternalLink, Github, ShieldCheck, Cpu, Users } from "lucide-react";
-import { PROJECT_REGISTRY } from "@/data/projects";
-import Mermaid from "@/components/Mermaid";
-import ProjectShowcase from "@/components/ProjectShowcase";
-import ImageCarousel from "@/components/ImageCarousel";
-import ReactMarkdown from 'react-markdown';
-
-
-
-export default function ProjectDetail({ params }: { params: Promise<{ category: string, slug: string }> }) {
- const { category, slug } = use(params);
- const project = PROJECT_REGISTRY.find((p) => p.slug === slug);
-
- if (!project) return
Project Not Found
;
-
- if (!project) return Project Log Not Found.
;
-
- return (
-
-
-
- {/* Navigation */}
-
-
Back to {category}
-
-
- {/* Header Section */}
-
-
- {project.title}
- {project.subtitle}
- {project.description}
-
-
-
-
- {/* Senior Stats Sidebar */}
-
-
-
-
-
-
My Role
-
{project.role}
-
-
-
-
-
-
Stack
-
{project.stack.join(", ")}
-
-
-
-
-
-
Impact
-
{project.metrics.join(" • ")}
-
-
-
-
-
-
-
-
- {/* Desktop Showcase View */}
-
-
- {/* Mobile Carousel View */}
-
-
-
-
-
- Interactive Gallery — Select or swipe to explore
-
-
-
- {/* SYSTEM ARCHITECTURE (New Mermaid Section) */}
-{project.mermaidChart && (
-
-
-
- TECHNICAL_ARCH // 01
-
-
-
System Architecture Log
-
-
-
-
-
-
-)}
-
- {/* Engineering Narrative */}
-{/* Updated Engineering Narrative Header */}
-
-
-
- PROJECT_LOG // 02
-
-
-
The Engineering Story
-
-
-
- {project.engineeringStory}
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/app/projects/[category]/[slug]/page.tsx b/app/projects/[category]/[slug]/page.tsx
index 8c56e9b..f44aa92 100644
--- a/app/projects/[category]/[slug]/page.tsx
+++ b/app/projects/[category]/[slug]/page.tsx
@@ -3,133 +3,175 @@
import { use } from "react";
import { motion } from "framer-motion";
import Link from "next/link";
-import { ArrowLeft, ExternalLink, Github, ShieldCheck, Cpu, Users } from "lucide-react";
+import {
+ ArrowLeft,
+ ExternalLink,
+ Github,
+ ShieldCheck,
+ Cpu,
+ Users,
+} from "lucide-react";
import { PROJECT_REGISTRY } from "@/data/projects";
import Mermaid from "@/components/Mermaid";
import ProjectShowcase from "@/components/ProjectShowcase";
import ImageCarousel from "@/components/ImageCarousel";
-import ReactMarkdown from 'react-markdown';
+import ReactMarkdown from "react-markdown";
-
-
-export default function ProjectDetail({ params }: { params: Promise<{ category: string, slug: string }> }) {
+export default function ProjectDetail({
+ params,
+}: {
+ params: Promise<{ category: string; slug: string }>;
+}) {
const { category, slug } = use(params);
const project = PROJECT_REGISTRY.find((p) => p.slug === slug);
if (!project) return Project Not Found
;
- if (!project) return Project Log Not Found.
;
+ if (!project)
+ return (
+ Project Log Not Found.
+ );
return (
-
{/* Navigation */}
-
+
Back to {category}
{/* Header Section */}
-
- {project.title}
- {project.subtitle}
- {project.description}
-
+
+
+ {project.title}
+
+
+ {project.subtitle}
+
+
+ {project.description}
+
+
- {/* Senior Stats Sidebar */}
-
+ {/* Stats Sidebar */}
+
-
My Role
+
+ My Role
+
{project.role}
-
Stack
-
{project.stack.join(", ")}
+
+ Stack
+
+
+ {project.stack.join(", ")}
+
-
Impact
-
{project.metrics.join(" • ")}
+
+ Impact
+
+
+ {project.metrics.join(" • ")}
+
-
- {/* Desktop Showcase View */}
-
+ {/* Desktop Showcase View */}
+
- {/* Mobile Carousel View */}
-
-
-
-
-
- Interactive Gallery — Select or swipe to explore
-
+ {/* Mobile Carousel View */}
+
+
+
+
+
+ Interactive Gallery — Select or swipe to explore
+
- {/* SYSTEM ARCHITECTURE (New Mermaid Section) */}
+ {/* Mermaid */}
{project.mermaidChart && (
-
+
-
-
+
+
System Architecture Log
-
-
+
+
-
-
-
+
+
-
+
)}
{/* Engineering Narrative */}
-{/* Updated Engineering Narrative Header */}
-
-
-
- PROJECT LOG // {project.storyLabel || "NARRATIVE"}
-
-
-
The Engineering Story
-
-
-
- {project.engineeringStory}
-
-
+
+
+
+ PROJECT LOG // {project.storyLabel || "NARRATIVE"}
+
+
+
+ The Engineering Story
+
+
+
+
+ {project.engineeringStory}
+
+
);
-}
\ No newline at end of file
+}
diff --git a/app/projects/[category]/page copy.tsx b/app/projects/[category]/page copy.tsx
deleted file mode 100644
index f8a1aca..0000000
--- a/app/projects/[category]/page copy.tsx
+++ /dev/null
@@ -1,120 +0,0 @@
-"use client";
-import { motion } from "framer-motion";
-import Link from "next/link";
-import { Globe, Smartphone, ArrowLeft } from "lucide-react";
-import { use } from "react";
-
-const categories = {
- web: {
- title: "Web Systems",
- icon: ,
- description: "Architecting scalable web applications and distributed systems.",
- projects: [
- {
- name: "Ratoong",
- detail: "Professional production platform.",
- stack: ["Node.js", "PostgreSQL", "Caddy"]
- },
- {
- name: "Datasaur",
- detail: "Full-stack data science pipeline.",
- stack: ["Python", "FastAPI", "Next.js"]
- }
- ]
- },
- mobile: {
- title: "Mobile Apps",
- icon: ,
- description: "Building cross-platform experiences with Flutter and native integrations.",
- projects: [
- {
- name: "Flutter App 1",
- detail: "Active Development - Coming Soon",
- stack: ["Flutter", "Dart", "Firebase"]
- },
- {
- name: "Flutter App 2",
- detail: "Internal R&D Prototype",
- stack: ["Flutter", "Riverpod", "SQLite"]
- }
- ]
- }
-};
-
-export default function CategoryPage({ params }: { params: Promise<{ category: string }> }) {
-
-
- const resolvedParams = use(params);
- const category = resolvedParams.category;
-
- const data = categories[category as keyof typeof categories];
-
- if (!data) return Category not found.
;
-
-
- if (!data) {
- return (
-
-
404: Category Not Found
-
Path: /projects/{category}
-
Return Home
-
- );
- }
-
- return (
-
-
- BACK TO DASHBOARD
-
-
-
-
- {data.icon}
-
{data.title}
-
- {data.description}
-
-
- {data.projects.map((project, index) => (
-
-
-
-
-
{project.name}
-
{project.detail}
-
-
- {project.stack.map(s => (
-
- {s}
-
- ))}
-
-
-
-
- ))}
-
-
-
- );
-}
\ No newline at end of file
diff --git a/app/projects/[category]/page.tsx b/app/projects/[category]/page.tsx
index 4ec78c4..5c54150 100644
--- a/app/projects/[category]/page.tsx
+++ b/app/projects/[category]/page.tsx
@@ -9,49 +9,58 @@ const CATEGORY_META = {
web: {
title: "Web Systems",
icon: ,
- description: "Architecting scalable web applications and distributed systems.",
+ description:
+ "Architecting scalable web applications and distributed systems.",
},
mobile: {
title: "Mobile Apps",
icon: ,
- description: "Building cross-platform experiences with Flutter and native integrations.",
+ description:
+ "Building cross-platform experiences with Flutter and native integrations.",
},
infrastructure: {
title: "DevOps & Infrastructure",
icon: ,
- description: "Self-hosted systems architecture and automated deployment pipelines.",
- }
+ description:
+ "Self-hosted systems architecture and automated deployment pipelines.",
+ },
};
-export default function CategoryPage({ params }: { params: Promise<{ category: string }> }) {
-
-
+export default function CategoryPage({
+ params,
+}: {
+ params: Promise<{ category: string }>;
+}) {
const resolvedParams = use(params);
const category = resolvedParams.category;
-// 1. Get metadata for the header
const meta = CATEGORY_META[category as keyof typeof CATEGORY_META];
- // 2. Filter the registry to find projects belonging to this category
- const filteredProjects = PROJECT_REGISTRY.filter(p => p.category === category);
+ const filteredProjects = PROJECT_REGISTRY.filter(
+ (p) => p.category === category,
+ );
if (!meta) {
return (
404: Category Not Found
- Return Home
+
+ Return Home
+
);
}
-
return (
-
+
BACK TO DASHBOARD
- {meta.title}
- {meta.description}
+
+ {meta.description}
+
{filteredProjects.map((project, index) => (
-
-
-
-
-
{project.title}
-
{project.description}
+
+
+
+
+ {project.title}
+
+
{project.description}
+
+
+ {project.stack.map((s) => (
+
+ {s}
+
+ ))}
+
-
- {project.stack.map(s => (
-
- {s}
-
- ))}
-
-
-
+
))}
);
-}
\ No newline at end of file
+}
diff --git a/components/ImageCarousel.tsx b/components/ImageCarousel.tsx
index d8c139d..7e64cd4 100644
--- a/components/ImageCarousel.tsx
+++ b/components/ImageCarousel.tsx
@@ -14,17 +14,19 @@ export default function ImageCarousel({ images }: GalleryProps) {
const imageIndex = Math.abs(page % images.length);
- const paginate = useCallback((newDirection: number) => {
- setPage([page + newDirection, newDirection]);
- }, [page]);
+ const paginate = useCallback(
+ (newDirection: number) => {
+ setPage([page + newDirection, newDirection]);
+ },
+ [page],
+ );
- // AUTO-PLAY LOGIC
useEffect(() => {
if (!isAutoPlaying) return;
const interval = setInterval(() => {
paginate(1);
- }, 5000); // 5 seconds is the "sweet spot" for technical analysis
+ }, 5000);
return () => clearInterval(interval);
}, [paginate, isAutoPlaying]);
@@ -43,10 +45,10 @@ export default function ImageCarousel({ images }: GalleryProps) {
};
return (
-
setIsAutoPlaying(false)} // Pause on hover
- onMouseLeave={() => setIsAutoPlaying(true)} // Resume when mouse leaves
+ onMouseEnter={() => setIsAutoPlaying(false)}
+ onMouseLeave={() => setIsAutoPlaying(true)}
>
setIsAutoPlaying(false)} // Kill auto-play on interaction
+ onDragStart={() => setIsAutoPlaying(false)}
onDragEnd={(e, { offset }) => {
const swipe = Math.abs(offset.x) > 50;
if (swipe) paginate(offset.x > 0 ? -1 : 1);
@@ -78,7 +80,7 @@ export default function ImageCarousel({ images }: GalleryProps) {
{
- setIsAutoPlaying(false); // Kill auto-play
+ setIsAutoPlaying(false);
paginate(-1);
}}
>
@@ -87,7 +89,7 @@ export default function ImageCarousel({ images }: GalleryProps) {
{
- setIsAutoPlaying(false); // Kill auto-play
+ setIsAutoPlaying(false);
paginate(1);
}}
>
@@ -95,9 +97,9 @@ export default function ImageCarousel({ images }: GalleryProps) {
- {/* Progress Bar (Visual Timer) */}
+ {/* Progress Bar */}
{isAutoPlaying && (
-
);
-}
\ No newline at end of file
+}
diff --git a/components/Mermaid.tsx b/components/Mermaid.tsx
index 2abb4bd..a03d2cc 100644
--- a/components/Mermaid.tsx
+++ b/components/Mermaid.tsx
@@ -18,7 +18,6 @@ export default function Mermaid({ chart }: { chart: string }) {
});
mermaid.contentLoaded();
- // Check if the rendered diagram is taller than 400px
if (contentRef.current) {
const height = contentRef.current.scrollHeight;
setNeedsExpansion(height > 400);
@@ -36,21 +35,25 @@ export default function Mermaid({ chart }: { chart: string }) {
>
{/* Legend */}
{chart}
- {/* The "Fade to Darkness" Overlay - only show if needs expansion */}
+ {/* The "Fade to Darkness" Overlay (when expansion is needed) */}
{needsExpansion && !isExpanded && (
- {/* Expand/Collapse Button - only show if needs expansion */}
+ {/* Expand/Collapse Button (when expansion is needed) */}
{needsExpansion && (
{
@@ -71,18 +74,25 @@ export default function Mermaid({ chart }: { chart: string }) {
setIsExpanded(!isExpanded);
}}
className={`absolute -bottom-5 left-1/2 -translate-x-1/2 z-30 flex items-center gap-2 px-5 py-2.5 rounded-full text-[10px] font-mono uppercase tracking-[0.2em] transition-all border shadow-xl
- ${isExpanded
- ? "bg-neutral-800 border-neutral-700 text-white"
- : "bg-neutral-900 border-neutral-800 text-neutral-500 group-hover:bg-neutral-800 group-hover:border-neutral-600 group-hover:text-white"
+ ${
+ isExpanded
+ ? "bg-neutral-800 border-neutral-700 text-white"
+ : "bg-neutral-900 border-neutral-800 text-neutral-500 group-hover:bg-neutral-800 group-hover:border-neutral-600 group-hover:text-white"
}`}
>
{isExpanded ? (
- <> Collapse Logic >
+ <>
+ {" "}
+ Collapse Logic{" "}
+ >
) : (
- <> Expand Architecture >
+ <>
+ {" "}
+ Expand Architecture{" "}
+ >
)}
)}
);
-}
\ No newline at end of file
+}
diff --git a/components/MonitorCard.tsx b/components/MonitorCard.tsx
index 0e47f85..a2b495f 100644
--- a/components/MonitorCard.tsx
+++ b/components/MonitorCard.tsx
@@ -1,16 +1,22 @@
-'use client';
+"use client";
-import React, { useState, useEffect } from 'react';
-import { motion, AnimatePresence } from 'framer-motion';
-import { Activity } from 'lucide-react';
+import React, { useState, useEffect } from "react";
+import { motion, AnimatePresence } from "framer-motion";
+import { Activity } from "lucide-react";
const MONITORS = [
- { id: 2, name: "Datasaur" }, { id: 6, name: "Audiobookshelf" },
- { id: 7, name: "Woodpecker CI" }, { id: 8, name: "Forgejo Git" },
- { id: 9, name: "Server dashboard" }, { id: 10, name: "Ratoong" },
- { id: 3, name: "Dozzle" }, { id: 12, name: "Observatory" },
- { id: 13, name: "Surf hub" }, { id: 11, name: "Anime list" },
- { id: 5, name: "Wiki" }, { id: 4, name: "Watchtower" },
+ { id: 2, name: "Datasaur" },
+ { id: 6, name: "Audiobookshelf" },
+ { id: 7, name: "Woodpecker CI" },
+ { id: 8, name: "Forgejo Git" },
+ { id: 9, name: "Server dashboard" },
+ { id: 10, name: "Ratoong" },
+ { id: 3, name: "Dozzle" },
+ { id: 12, name: "Observatory" },
+ { id: 13, name: "Surf hub" },
+ { id: 11, name: "Anime list" },
+ { id: 5, name: "Wiki" },
+ { id: 4, name: "Watchtower" },
];
const ITEMS_PER_PAGE = 6;
@@ -19,99 +25,111 @@ export default function MonitorRegistry({ isHovered }: { isHovered: boolean }) {
const [page, setPage] = useState(0);
const totalPages = Math.ceil(MONITORS.length / ITEMS_PER_PAGE);
-useEffect(() => {
- let interval: NodeJS.Timeout | null = null;
+ useEffect(() => {
+ let interval: NodeJS.Timeout | null = null;
- if (isHovered && totalPages > 1) {
- // Only start the interval if we are hovered
- interval = setInterval(() => {
- setPage((prev) => (prev + 1) % totalPages);
- }, 4000);
- }
-
- // This cleanup function runs whenever isHovered changes
- // or the component unmounts.
- return () => {
- if (interval) clearInterval(interval);
- // Move the reset here so it happens "after" the effect cycle
- if (!isHovered) {
- setPage(0);
+ if (isHovered && totalPages > 1) {
+ interval = setInterval(() => {
+ setPage((prev) => (prev + 1) % totalPages);
+ }, 4000);
}
- };
-}, [isHovered, totalPages]);
- const currentMonitors = MONITORS.slice(page * ITEMS_PER_PAGE, (page + 1) * ITEMS_PER_PAGE);
+ return () => {
+ if (interval) clearInterval(interval);
+ if (!isHovered) {
+ setPage(0);
+ }
+ };
+ }, [isHovered, totalPages]);
+
+ const currentMonitors = MONITORS.slice(
+ page * ITEMS_PER_PAGE,
+ (page + 1) * ITEMS_PER_PAGE,
+ );
return (
<>
{/* Default View */}
-
- {/* Header Section */}
-
-
- {/* Added "Server Specs" to fill space and match the style of "The Architect" */}
-
-
-
Architecture
-
linux/amd64
-
-
-
Provider
-
Hetzner Cloud
-
-
-
-
- {/* Hover View */}
- {/* Hover View: Automated Carousel */}
-
-
-
- Service Registry
-
-
-
- {/* Increased height slightly to 200px and removed 'justify-center' from parent */}
-
-
-
- {currentMonitors.map((m) => (
-
-
- {m.name}
-
-
{/* Scale badges slightly */}
-
-
+
+ {/* Header Section */}
+
+
+
+
+
+ Hetzner Node-01
+
+
+
+
SYS_STATUS:
+
+ Online
+
- ))}
-
-
-
-
+
+
+
+
+
+
+ Architecture
+
+
linux/amd64
+
+
+
+ Provider
+
+
Hetzner Cloud
+
+
+
+
+ {/* Hover View */}
+
+
+
+ Service Registry
+
+
+
+
+
+
+ {currentMonitors.map((m) => (
+
+
+ {m.name}
+
+
+
+
+
+
+ ))}
+
+
+
+
>
);
-}
\ No newline at end of file
+}
diff --git a/components/ProjectShowcase.tsx b/components/ProjectShowcase.tsx
index 3c14e9d..56e893f 100644
--- a/components/ProjectShowcase.tsx
+++ b/components/ProjectShowcase.tsx
@@ -8,7 +8,7 @@ export default function ProjectShowcase({ images }: { images: string[] }) {
return (
- {/* Large Featured Image (Left 9 Columns) */}
+ {/* Main Image */}
-
+
{/* Subtle Overlay Label */}
@@ -30,21 +30,25 @@ export default function ProjectShowcase({ images }: { images: string[] }) {
- {/* Thumbnail Column (Right 3 Columns) */}
+ {/* Thumbnail Column */}
{images.map((img, i) => (
setIndex(i)}
className={`relative flex-shrink-0 w-24 lg:w-full aspect-video rounded-xl border-2 transition-all overflow-hidden ${
- i === index
- ? "border-blue-500 ring-4 ring-blue-500/10"
+ i === index
+ ? "border-blue-500 ring-4 ring-blue-500/10"
: "border-neutral-800 opacity-40 hover:opacity-100"
}`}
>
-
+
{i === index && (
-
@@ -54,4 +58,4 @@ export default function ProjectShowcase({ images }: { images: string[] }) {
);
-}
\ No newline at end of file
+}
diff --git a/eslint.config.mjs b/eslint.config.mjs
index 05e726d..1330e05 100644
--- a/eslint.config.mjs
+++ b/eslint.config.mjs
@@ -1,18 +1,13 @@
import { defineConfig, globalIgnores } from "eslint/config";
import nextVitals from "eslint-config-next/core-web-vitals";
import nextTs from "eslint-config-next/typescript";
+import eslintConfigPrettier from "eslint-config-prettier";
const eslintConfig = defineConfig([
...nextVitals,
...nextTs,
- // Override default ignores of eslint-config-next.
- globalIgnores([
- // Default ignores of eslint-config-next:
- ".next/**",
- "out/**",
- "build/**",
- "next-env.d.ts",
- ]),
+ eslintConfigPrettier,
+ globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]),
]);
export default eslintConfig;
diff --git a/package-lock.json b/package-lock.json
index 2d18654..b63ddaa 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25,6 +25,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.4",
+ "eslint-config-prettier": "^10.1.8",
"react-markdown": "^10.1.0",
"tailwindcss": "^4.1.18",
"typescript": "^5"
@@ -4324,6 +4325,22 @@
"url": "https://github.com/sponsors/sindresorhus"
}
},
+ "node_modules/eslint-config-prettier": {
+ "version": "10.1.8",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-10.1.8.tgz",
+ "integrity": "sha512-82GZUjRS0p/jganf6q1rEO25VSoHH0hKPCTrgillPjdI/3bgBhAE1QzHrHTizjpRvy6pGAvKjDJtk2pF9NDq8w==",
+ "dev": true,
+ "license": "MIT",
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint-config-prettier"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
"node_modules/eslint-import-resolver-node": {
"version": "0.3.9",
"resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz",
diff --git a/package.json b/package.json
index 1df805c..48714cd 100644
--- a/package.json
+++ b/package.json
@@ -26,6 +26,7 @@
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "16.1.4",
+ "eslint-config-prettier": "^10.1.8",
"react-markdown": "^10.1.0",
"tailwindcss": "^4.1.18",
"typescript": "^5"