Updated changelog versioning
This commit is contained in:
parent
8369d59310
commit
af8c720ba0
|
|
@ -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 (
|
||||
<PageLayout backLink="/" maxWidth="5xl">
|
||||
<header className="mb-20">
|
||||
<div className="flex items-center gap-3 mb-4">
|
||||
<Hammer className="text-orange-500 animate-pulse" size={24} />
|
||||
<h1 className="text-4xl font-bold tracking-tighter text-white uppercase">
|
||||
TheForge
|
||||
<h1 className="text-4xl font-bold tracking-tighter text-white uppercase font-mono">
|
||||
The Forge
|
||||
</h1>
|
||||
</div>
|
||||
<p className="text-neutral-500 max-w-2xl leading-relaxed text-sm font-mono">
|
||||
<br />
|
||||
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.
|
||||
<p className="text-neutral-500 max-w-2xl text-sm font-mono leading-relaxed">
|
||||
The Forge is my active development logs, providing a glimpse into my
|
||||
current projects and future potential releases.
|
||||
</p>
|
||||
</header>
|
||||
|
||||
{/* Main Project Card */}
|
||||
<section className="bg-neutral-900/40 border border-neutral-800 rounded-2xl p-8 mb-16">
|
||||
<div className="flex flex-wrap items-center justify-between gap-4 mb-8">
|
||||
<div>
|
||||
<h2 className="text-2xl font-bold text-white mb-2 italic">
|
||||
Project: PixelPals
|
||||
</h2>
|
||||
<div className="flex gap-4">
|
||||
<span className="text-[10px] text-orange-500 font-bold uppercase tracking-[0.2em]">
|
||||
Build Phase: Alpha 0.1
|
||||
</span>
|
||||
<span className="text-[10px] text-neutral-600 font-bold uppercase tracking-[0.2em]">
|
||||
Engine: Godot / GDScript
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div className="px-4 py-2 bg-orange-500/10 border border-orange-500/20 rounded-full">
|
||||
<span className="text-[10px] text-orange-500 font-bold uppercase tracking-widest">
|
||||
Active Focus
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p className="text-neutral-400 text-sm leading-relaxed mb-8">
|
||||
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.
|
||||
</p>
|
||||
|
||||
{/* Milestones / Changelog */}
|
||||
<div className="space-y-6">
|
||||
<h3 className="flex items-center gap-2 text-xs font-bold text-neutral-500 uppercase tracking-widest border-b border-neutral-800 pb-2">
|
||||
<History size={14} /> Development_Log
|
||||
</h3>
|
||||
<ul className="space-y-4">
|
||||
<li className="flex gap-4">
|
||||
<span className="text-xs text-neutral-600 font-mono mt-1 shrink-0">
|
||||
2026-02-02
|
||||
</span>
|
||||
<p className="text-xs text-neutral-400">
|
||||
Implemented dynamic weather patterns using custom shaders.
|
||||
</p>
|
||||
</li>
|
||||
<li className="flex gap-4">
|
||||
<span className="text-xs text-neutral-600 font-mono mt-1 shrink-0">
|
||||
2026-01-28
|
||||
</span>
|
||||
<p className="text-xs text-neutral-400">
|
||||
Optimized asset loading pipeline for faster initial scene entry.
|
||||
</p>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</section>
|
||||
<div className="space-y-24">
|
||||
{projects.map((project) => (
|
||||
<ForgeUI key={project.id} project={project} />
|
||||
))}
|
||||
</div>
|
||||
</PageLayout>
|
||||
);
|
||||
}
|
||||
|
|
|
|||
131
components/ForgeUI copy.tsx
Normal file
131
components/ForgeUI copy.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="bg-neutral-900/40 border border-neutral-800 rounded-3xl p-8 pt-0 mb-12 overflow-hidden">
|
||||
{/* Upper Section: Hero Area */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6">
|
||||
<div className="lg:col-span-8 flex flex-col justify-center">
|
||||
<div className="flex flex-wrap justify-between items-start gap-6 mb-6">
|
||||
<div>
|
||||
<h2 className="text-4xl font-bold text-white mb-4 tracking-tighter">
|
||||
{project.projectName}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
{/* Version Badge */}
|
||||
<span className="px-3 py-1 bg-orange-500/10 border border-orange-500/20 text-orange-500 text-[10px] font-bold uppercase tracking-widest rounded-full">
|
||||
{project.currentVersion}
|
||||
</span>
|
||||
|
||||
{/* Engine Tag */}
|
||||
<span className="px-3 py-1 bg-neutral-800 text-neutral-400 text-[10px] font-bold uppercase tracking-widest rounded-full flex items-center gap-2">
|
||||
<Cpu size={12} /> {project.engine}
|
||||
</span>
|
||||
|
||||
{/* Status Indicator (Now part of the tag row) */}
|
||||
<div className="flex items-center gap-2 text-[9px] font-mono text-green-500/60 ml-1">
|
||||
<span className="relative flex h-1.5 w-1.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-500"></span>
|
||||
</span>
|
||||
ACTIVE STREAM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{project.externalLink && (
|
||||
<a
|
||||
href={project.externalLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-xs font-bold text-white bg-neutral-800 hover:bg-neutral-700 transition-colors px-4 py-2 rounded-xl shrink-0"
|
||||
>
|
||||
Project Details <ExternalLink size={14} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-neutral-400 text-base leading-relaxed max-w-2xl">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
{project.imagePath && (
|
||||
<div className="lg:col-span-4 flex justify-center lg:justify-end items-center">
|
||||
<div className="relative w-full aspect-square max-w-[280px] group">
|
||||
<Image
|
||||
src={project.imagePath}
|
||||
alt={`${project.projectName} Feature`}
|
||||
fill
|
||||
className="object-contain drop-shadow-[0_0_40px_rgba(249,115,22,0.15)] transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-orange-500/10 blur-[80px] rounded-full -z-10 opacity-40 group-hover:opacity-60 transition-opacity" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Technical Highlights */}
|
||||
<div className="flex flex-wrap gap-2 mb-12">
|
||||
{project.highlights.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-[10px] text-neutral-600 font-mono uppercase tracking-tighter border border-neutral-800 px-3 py-1 rounded"
|
||||
>
|
||||
{`// ${tag}`}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Lower Section: Logs with Gradient Fade */}
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-2 text-xs font-bold text-neutral-500 uppercase tracking-widest border-b border-neutral-800 pb-2">
|
||||
<History size={14} /> Dev_Log
|
||||
</div>
|
||||
|
||||
{/* This container handles the fade effect */}
|
||||
<div
|
||||
className="space-y-8 relative"
|
||||
style={{
|
||||
maskImage:
|
||||
"linear-gradient(to bottom, black 60%, transparent 100%)",
|
||||
WebkitMaskImage:
|
||||
"linear-gradient(to bottom, black 60%, transparent 100%)",
|
||||
}}
|
||||
>
|
||||
{project.changelog.map((entry) => (
|
||||
<div
|
||||
key={entry.version}
|
||||
className="relative pl-8 border-l border-neutral-800"
|
||||
>
|
||||
<div className="absolute -left-1.5 top-1.5 w-3 h-3 bg-neutral-950 border border-neutral-700 rounded-full" />
|
||||
<div className="flex items-baseline gap-4 mb-3">
|
||||
<span className="text-orange-500 font-mono text-sm font-bold leading-none">
|
||||
{entry.version}
|
||||
</span>
|
||||
<span className="text-neutral-600 font-mono text-xs leading-none">
|
||||
{entry.date}
|
||||
</span>
|
||||
</div>
|
||||
<ul className="space-y-2.5">
|
||||
{entry.changes.map((change, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="text-neutral-400 text-sm leading-relaxed max-w-4xl"
|
||||
>
|
||||
{change}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
136
components/ForgeUI.tsx
Normal file
136
components/ForgeUI.tsx
Normal file
|
|
@ -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 (
|
||||
<div className="bg-neutral-900/40 border border-neutral-800 rounded-3xl p-8 pt-0 mb-12 overflow-hidden">
|
||||
{/* Upper Section: Hero Area */}
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-6 pt-4">
|
||||
{" "}
|
||||
{/* Added pt-8 here to replace the p-8 pt-0 conflict */}
|
||||
<div className="lg:col-span-8 flex flex-col justify-center">
|
||||
<div className="flex flex-wrap justify-between items-start gap-6 mb-6">
|
||||
<div>
|
||||
<h2 className="text-4xl font-bold text-white mb-4 tracking-tighter">
|
||||
{project.projectName}
|
||||
</h2>
|
||||
|
||||
<div className="flex flex-wrap items-center gap-3">
|
||||
<span className="px-3 py-1 bg-orange-500/10 border border-orange-500/20 text-orange-500 text-[10px] font-bold uppercase tracking-widest rounded-full">
|
||||
{project.currentVersion}
|
||||
</span>
|
||||
|
||||
<span className="px-3 py-1 bg-neutral-800 text-neutral-400 text-[10px] font-bold uppercase tracking-widest rounded-full flex items-center gap-2">
|
||||
<Cpu size={12} /> {project.engine}
|
||||
</span>
|
||||
|
||||
<div className="flex items-center gap-2 text-[9px] font-mono text-green-500/60 ml-1">
|
||||
<span className="relative flex h-1.5 w-1.5">
|
||||
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
|
||||
<span className="relative inline-flex rounded-full h-1.5 w-1.5 bg-green-500"></span>
|
||||
</span>
|
||||
ACTIVE STREAM
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{project.externalLink && (
|
||||
<a
|
||||
href={project.externalLink}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
className="flex items-center gap-2 text-xs font-bold text-white bg-neutral-800 hover:bg-neutral-700 transition-colors px-4 py-2 rounded-xl shrink-0"
|
||||
>
|
||||
Project Details <ExternalLink size={14} />
|
||||
</a>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<p className="text-neutral-400 text-base leading-relaxed max-w-2xl">
|
||||
{project.description}
|
||||
</p>
|
||||
</div>
|
||||
{project.imagePath && (
|
||||
<div className="lg:col-span-4 flex justify-center lg:justify-end items-center">
|
||||
<div className="relative w-full aspect-square max-w-[280px] group">
|
||||
<Image
|
||||
src={project.imagePath}
|
||||
alt={`${project.projectName} Feature`}
|
||||
fill
|
||||
className="object-contain drop-shadow-[0_0_40px_rgba(249,115,22,0.15)] transition-transform duration-700 group-hover:scale-110"
|
||||
/>
|
||||
<div className="absolute inset-0 bg-orange-500/10 blur-[80px] rounded-full -z-10 opacity-40 group-hover:opacity-60 transition-opacity" />
|
||||
</div>
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
|
||||
{/* Technical Highlights */}
|
||||
<div className="flex flex-wrap gap-2 mb-12 mt-6">
|
||||
{project.highlights.map((tag) => (
|
||||
<span
|
||||
key={tag}
|
||||
className="text-[10px] text-neutral-600 font-mono uppercase tracking-tighter border border-neutral-800 px-3 py-1 rounded"
|
||||
>
|
||||
{`// ${tag}`}
|
||||
</span>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{/* Lower Section: Logs */}
|
||||
<div className="space-y-8">
|
||||
<div className="flex items-center gap-2 text-xs font-bold text-neutral-500 uppercase tracking-widest border-b border-neutral-800 pb-2">
|
||||
<History size={14} /> Dev_Log
|
||||
</div>
|
||||
|
||||
<div
|
||||
className="space-y-10 relative pb-4" // Increased space between version blocks
|
||||
style={{
|
||||
maskImage:
|
||||
"linear-gradient(to bottom, black 60%, transparent 100%)",
|
||||
WebkitMaskImage:
|
||||
"linear-gradient(to bottom, black 60%, transparent 100%)",
|
||||
}}
|
||||
>
|
||||
{project.changelog.map((entry) => (
|
||||
<div
|
||||
key={entry.version}
|
||||
className="ml-1.5 relative pl-10 border-l border-neutral-800" // Increased pl-8 to pl-10 to fix clipping
|
||||
>
|
||||
{/* The Version Indicator Circle */}
|
||||
<div className="absolute -left-1.5 top-1 w-3 h-3 bg-neutral-950 border border-neutral-700 rounded-full" />
|
||||
|
||||
<div className="flex items-baseline gap-4 mb-4">
|
||||
<span className="text-orange-500 font-mono text-sm font-bold leading-none">
|
||||
{entry.version}
|
||||
</span>
|
||||
<span className="text-neutral-600 font-mono text-[10px] uppercase tracking-wider leading-none">
|
||||
{entry.date}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
{/* Individual Changes with Bullet Points */}
|
||||
<ul className="space-y-3">
|
||||
{entry.changes.map((change, idx) => (
|
||||
<li
|
||||
key={idx}
|
||||
className="text-neutral-400 text-sm leading-relaxed max-w-4xl flex items-start gap-3"
|
||||
>
|
||||
{/* The Bullet Character */}
|
||||
<span className="text-orange-500/40 mt-1.5 shrink-0 text-[10px]">
|
||||
•
|
||||
</span>
|
||||
<span>{change}</span>
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
|
@ -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",
|
||||
},
|
||||
];
|
||||
|
|
|
|||
91
lib/git.ts
Normal file
91
lib/git.ts
Normal file
|
|
@ -0,0 +1,91 @@
|
|||
import { FORGE_PROJECTS } from "@/data/forge";
|
||||
import { ChangelogEntry, ForgeProject } from "@/types";
|
||||
import "server-only";
|
||||
|
||||
export async function getAllForgeProjects(): Promise<ForgeProject[]> {
|
||||
// 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<ChangelogEntry[]> {
|
||||
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);
|
||||
}
|
||||
BIN
public/forge/pixel-pals.png
Normal file
BIN
public/forge/pixel-pals.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 205 KiB |
|
|
@ -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[];
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue