diff --git a/app/forge/page.tsx b/app/forge/page.tsx index 0b26d93..877fc70 100644 --- a/app/forge/page.tsx +++ b/app/forge/page.tsx @@ -1,79 +1,31 @@ -"use client"; -import { Hammer, History, Target, Code2 } from "lucide-react"; +import ForgeUI from "@/components/ForgeUI"; import PageLayout from "@/components/PageLayout"; +import { getAllForgeProjects } from "@/lib/git"; +import { Hammer } from "lucide-react"; + +export default async function ForgePage() { + const projects = await getAllForgeProjects(); -export default function ForgePage() { return (
-

- TheForge +

+ The Forge

-

-
- The Forge is where I document my current engineering focus. This space - reflects active builds, experimental prototypes and gives a taste of - my next releases. +

+ The Forge is my active development logs, providing a glimpse into my + current projects and future potential releases.

- {/* Main Project Card */} -
-
-
-

- Project: PixelPals -

-
- - Build Phase: Alpha 0.1 - - - Engine: Godot / GDScript - -
-
-
- - Active Focus - -
-
- -

- Exploring procedural generation and state-machine-driven AI behaviors. - The goal is to bridge my experience in systems architecture with - real-time game loops and interactive storytelling. -

- - {/* Milestones / Changelog */} -
-

- Development_Log -

-
    -
  • - - 2026-02-02 - -

    - Implemented dynamic weather patterns using custom shaders. -

    -
  • -
  • - - 2026-01-28 - -

    - Optimized asset loading pipeline for faster initial scene entry. -

    -
  • -
-
-
+
+ {projects.map((project) => ( + + ))} +
); } diff --git a/components/ForgeUI copy.tsx b/components/ForgeUI copy.tsx new file mode 100644 index 0000000..df6e063 --- /dev/null +++ b/components/ForgeUI copy.tsx @@ -0,0 +1,131 @@ +"use client"; + +import Image from "next/image"; +import { History, ExternalLink, Cpu } from "lucide-react"; +import { ForgeProject } from "@/types"; + +export default function ForgeUI({ project }: { project: ForgeProject }) { + return ( +
+ {/* Upper Section: Hero Area */} +
+
+
+
+

+ {project.projectName} +

+ +
+ {/* Version Badge */} + + {project.currentVersion} + + + {/* Engine Tag */} + + {project.engine} + + + {/* Status Indicator (Now part of the tag row) */} +
+ + + + + ACTIVE STREAM +
+
+
+ + {project.externalLink && ( + + Project Details + + )} +
+ +

+ {project.description} +

+
+ + {project.imagePath && ( +
+
+ {`${project.projectName} +
+
+
+ )} +
+ + {/* Technical Highlights */} +
+ {project.highlights.map((tag) => ( + + {`// ${tag}`} + + ))} +
+ + {/* Lower Section: Logs with Gradient Fade */} +
+
+ Dev_Log +
+ + {/* This container handles the fade effect */} +
+ {project.changelog.map((entry) => ( +
+
+
+ + {entry.version} + + + {entry.date} + +
+
    + {entry.changes.map((change, idx) => ( +
  • + {change} +
  • + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/components/ForgeUI.tsx b/components/ForgeUI.tsx new file mode 100644 index 0000000..aeb3dd6 --- /dev/null +++ b/components/ForgeUI.tsx @@ -0,0 +1,136 @@ +"use client"; + +import Image from "next/image"; +import { History, ExternalLink, Cpu } from "lucide-react"; +import { ForgeProject } from "@/types"; + +export default function ForgeUI({ project }: { project: ForgeProject }) { + return ( +
+ {/* Upper Section: Hero Area */} +
+ {" "} + {/* Added pt-8 here to replace the p-8 pt-0 conflict */} +
+
+
+

+ {project.projectName} +

+ +
+ + {project.currentVersion} + + + + {project.engine} + + +
+ + + + + ACTIVE STREAM +
+
+
+ + {project.externalLink && ( + + Project Details + + )} +
+ +

+ {project.description} +

+
+ {project.imagePath && ( +
+
+ {`${project.projectName} +
+
+
+ )} +
+ + {/* Technical Highlights */} +
+ {project.highlights.map((tag) => ( + + {`// ${tag}`} + + ))} +
+ + {/* Lower Section: Logs */} +
+
+ Dev_Log +
+ +
+ {project.changelog.map((entry) => ( +
+ {/* The Version Indicator Circle */} +
+ +
+ + {entry.version} + + + {entry.date} + +
+ + {/* Individual Changes with Bullet Points */} +
    + {entry.changes.map((change, idx) => ( +
  • + {/* The Bullet Character */} + + • + + {change} +
  • + ))} +
+
+ ))} +
+
+
+ ); +} diff --git a/data/forge.ts b/data/forge.ts index b9ecc09..ae947d7 100644 --- a/data/forge.ts +++ b/data/forge.ts @@ -1,26 +1,21 @@ -import { ForgeProject } from "@/types"; +import { ForgeProjectMetadata } from "@/types"; -export const ACTIVE_BUILD: ForgeProject = { - id: "pixelpals", - name: "PixelPals", - status: "Alpha", - engine: "Godot 4.x", - description: - "A mobile-first creature collection game focusing on procedural behaviors and lightweight state-management.", - highlights: [ - "State-machine AI", - "Custom Shader Pipelines", - "Mobile-optimized Loops", - ], - externalLink: "/projects/pixelpals", // Link to your internal portfolio or a dedicated site - changelog: [ - { - date: "2026-02-03", - update: "Refined touch-input latency for mobile devices.", - }, - { - date: "2026-01-25", - update: "Integrated local SQLite database for creature persistence.", - }, - ], -}; +export const FORGE_PROJECTS: ForgeProjectMetadata[] = [ + { + id: "pixelpals", + projectName: "PixelPals", + repoName: "pixel-pals", + status: "Alpha", + engine: "Flutter / Firebase", + imagePath: "/forge/pixel-pals.png", + description: + "A GPS-driven, cooperative creature collection game built to encourage exercise. Features turn-based tactical combat, class-based progression, and AI-generated companion art based on player descriptions.", + highlights: [ + "GPS / Mapbox Integration", + "Generative AI (Pet Synthesis)", + "Real-time Firebase Sync", + "Turn-based Battle Engine", + ], + externalLink: "https://pixelpals.app", + }, +]; diff --git a/lib/git.ts b/lib/git.ts new file mode 100644 index 0000000..95ab0d4 --- /dev/null +++ b/lib/git.ts @@ -0,0 +1,91 @@ +import { FORGE_PROJECTS } from "@/data/forge"; +import { ChangelogEntry, ForgeProject } from "@/types"; +import "server-only"; + +export async function getAllForgeProjects(): Promise { + // We use Promise.all to fetch all changelogs in parallel for speed + return Promise.all( + FORGE_PROJECTS.map(async (metadata) => { + const changelog = await getPrivateChangelog(metadata.repoName); + const latestVersion = + changelog.length > 0 ? changelog[0].version : "0.0.0"; + + return { + ...metadata, + currentVersion: `${latestVersion}_${metadata.status}`, + changelog: changelog, + }; + }), + ); +} + +export async function getPrivateChangelog( + repoName: string, +): Promise { + const repoOwner = "georgew"; + const filePath = "changelog.md"; + const url = `https://git.georgew.dev/api/v1/repos/${repoOwner}/${repoName}/contents/${filePath}`; + const headers = { + headers: { + Authorization: `token ${process.env.FORGEJO_TOKEN}`, + Accept: "application/vnd.forgejo.raw", + }, + next: { revalidate: 3600 }, + }; + + try { + const response = await fetch(url, headers); + + if (!response.ok) { + return [ + { + version: "v0.0.0", + date: new Date().toISOString().split("T")[0], + changes: ["Documentation currently synchronizing with the Forge..."], + }, + ]; + } + + const data = await response.json(); + + // 2. Forgejo returns the content as a base64 string + // We need to decode it to UTF-8 + const markdownText = Buffer.from(data.content, "base64").toString("utf-8"); + + return parseMarkdown(markdownText); + } catch (error) { + console.error("Git Fetch Error:", error); + return []; + } +} + +// Simple parser for your ## [Version] - YYYY-MM-DD format +function parseMarkdown(text: string): ChangelogEntry[] { + // Split by "## " at the start of a line to isolate version blocks + const sections = text.split(/\n(?=## )/g); + + return sections + .map((section) => { + const lines = section + .split("\n") + .map((l) => l.trim()) + .filter(Boolean); + if (lines.length === 0) return null; + + // Match the pattern: ## [Version] - YYYY-MM-DD + const headerMatch = lines[0].match(/## \[(.*?)\] - (.*)/); + if (!headerMatch) return null; + + const version = headerMatch[1]; + const date = headerMatch[2]; + + // Collect all lines starting with "- " anywhere in this version block + const changes = lines + .filter((line) => line.startsWith("- ")) + .map((line) => line.replace("- ", "")); + + return { version, date, changes }; + }) + .filter((entry): entry is ChangelogEntry => entry !== null) + .slice(0, 3); +} diff --git a/public/forge/pixel-pals.png b/public/forge/pixel-pals.png new file mode 100644 index 0000000..247437a Binary files /dev/null and b/public/forge/pixel-pals.png differ diff --git a/types/index.ts b/types/index.ts index 93c1690..496e741 100644 --- a/types/index.ts +++ b/types/index.ts @@ -50,11 +50,31 @@ export interface LabService { export interface ForgeProject { id: string; - name: string; status: "Alpha" | "Beta" | "R&D"; engine: string; description: string; + imagePath?: string; highlights: string[]; externalLink?: string; - changelog: { date: string; update: string }[]; + projectName: string; + currentVersion: string; // Dynamically parsed + changelog: ChangelogEntry[]; // Dynamically fetched +} + +export interface ForgeProjectMetadata { + id: string; + projectName: string; + repoName: string; + status: "Alpha" | "Beta" | "R&D"; + engine: string; + imagePath?: string; + description: string; + highlights: string[]; + externalLink?: string; +} + +export interface ChangelogEntry { + version: string; + date: string; + changes: string[]; }