From 1ff0d61a19fce08bf8244ebd226ba3baa825b3f7 Mon Sep 17 00:00:00 2001 From: GeorgeWebberley Date: Fri, 30 Jan 2026 12:40:25 +0100 Subject: [PATCH] Added prettier auto-format and cleaned up comments --- .prettierrc | 0 .vscode/settings.json | 7 + app/page copy 2.tsx | 128 --------- app/page copy.tsx | 195 -------------- app/page.tsx | 259 +++++++++++-------- app/projects/[category]/[slug]/page copy.tsx | 138 ---------- app/projects/[category]/[slug]/page.tsx | 164 +++++++----- app/projects/[category]/page copy.tsx | 120 --------- app/projects/[category]/page.tsx | 102 +++++--- components/ImageCarousel.tsx | 30 ++- components/Mermaid.tsx | 44 ++-- components/MonitorCard.tsx | 208 ++++++++------- components/ProjectShowcase.tsx | 20 +- eslint.config.mjs | 11 +- package-lock.json | 17 ++ package.json | 1 + 16 files changed, 509 insertions(+), 935 deletions(-) create mode 100644 .prettierrc create mode 100644 .vscode/settings.json delete mode 100644 app/page copy 2.tsx delete mode 100644 app/page copy.tsx delete mode 100644 app/projects/[category]/[slug]/page copy.tsx delete mode 100644 app/projects/[category]/page copy.tsx 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 ( -
-
-

George W.

-

Senior Full Stack Engineer & Tech Lead

-
- -
- - {/* 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) */} -
-
-
- Hetzner Node-01 -
-
- Status: UP - -
-
- - {/* 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 */} -
-

George W.

-

Senior Full Stack Engineer & Tech Lead

-
- - {/* 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 */} -
-
-
-
- Hetzner Node-01 -
-
-

System Status:

- Overall Status -
-
- -
- - {/* Hover View: Friendly Names */} -
-

Service Registry

-
- {monitors.map((m) => ( -
- {m.name} -
- up - ms -
-
- ))} -
-
- - ); - })()} - - - {/* 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 */} -
-
-
-

Pipeline Status

- Build Status -
-
-

Engine: Next.js 15 (Standalone)

-
- -
-
-

- Deploy: {process.env.NEXT_PUBLIC_APP_VERSION || 'v1.0.0-dev'} -

-
-
-
- ); -} \ 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 (
-

George W.

-

Senior Full Stack Engineer & Tech Lead

+

+ Senior Full Stack Engineer & Tech Lead +

- + Git - + LinkedIn
- {/* 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 +

-
-
-
-

Pipeline Status

- Build Status +
+
+
+

Pipeline Status

+ Build Status +
+
+

Engine: Next.js 15 (Standalone)

-
-

Engine: Next.js 15 (Standalone)

-
- -
-
-

- Deploy: {process.env.NEXT_PUBLIC_APP_VERSION || 'v1.0.0-dev'} -

-
-
-
+
+
+

+ Deploy: {process.env.NEXT_PUBLIC_APP_VERSION || "v1.0.0-dev"} +

+
+
+
); -} \ 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}

- -
- {project.liveUrl && ( - - Launch Site - - )} - - {project?.repoUrl && ( - - View Source - - )} -
-
- - {/* 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} +

+
{project.liveUrl && ( - + Launch Site )} - + {project?.repoUrl && ( - + View Source )}
- {/* 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) {
- {/* 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 */}
-
-
- Traffic Flow +
+
+ + Traffic Flow + +
+
+
+ + Service Node + +
-
-
- Service Node -
-
{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 && ( )}
); -} \ 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 */} -
-
-
-
- Hetzner Node-01 -
-
-

SYS_STATUS:

- Online -
-
- -
- - {/* 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 */} - up - ms +
+ {/* Header Section */} +
+
+
+
+ + Hetzner Node-01 + +
+
+

SYS_STATUS:

+ + Online +
- ))} - - -
-
+ +
+ +
+
+

+ Architecture +

+

linux/amd64

+
+
+

+ Provider +

+

Hetzner Cloud

+
+
+
+ + {/* Hover View */} +
+
+

+ Service Registry +

+
+ +
+ + + {currentMonitors.map((m) => ( +
+ + {m.name} + +
+ up + ms +
+
+ ))} +
+
+
+
); -} \ 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) => (
); -} \ 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"