diff --git a/public/factor-e-icon.svg b/public/factor-e-icon.svg new file mode 100644 index 00000000..36e76533 --- /dev/null +++ b/public/factor-e-icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/public/favicon copy.svg b/public/favicon copy.svg new file mode 100644 index 00000000..f4e96bb3 --- /dev/null +++ b/public/favicon copy.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/public/glimpse-icon.svg b/public/glimpse-icon.svg new file mode 100644 index 00000000..6fb27e19 --- /dev/null +++ b/public/glimpse-icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/public/good-morning-icon.png b/public/good-morning-icon.png new file mode 100644 index 00000000..15585b23 Binary files /dev/null and b/public/good-morning-icon.png differ diff --git a/public/good-morning-icon.svg b/public/good-morning-icon.svg new file mode 100644 index 00000000..15585b23 Binary files /dev/null and b/public/good-morning-icon.svg differ diff --git a/public/images/factor-e/debug-overlay.gif b/public/images/factor-e/debug-overlay.gif new file mode 100644 index 00000000..09a8e06c Binary files /dev/null and b/public/images/factor-e/debug-overlay.gif differ diff --git a/public/images/factor-e/pixel-art.png b/public/images/factor-e/pixel-art.png new file mode 100644 index 00000000..61661c66 Binary files /dev/null and b/public/images/factor-e/pixel-art.png differ diff --git a/public/images/factor-e/place-destroy.gif b/public/images/factor-e/place-destroy.gif new file mode 100644 index 00000000..48c5e284 Binary files /dev/null and b/public/images/factor-e/place-destroy.gif differ diff --git a/public/images/factor-e/world-gen.gif b/public/images/factor-e/world-gen.gif new file mode 100644 index 00000000..f49e43fc Binary files /dev/null and b/public/images/factor-e/world-gen.gif differ diff --git a/public/images/glimpse/comments.png b/public/images/glimpse/comments.png new file mode 100644 index 00000000..7b3f4d32 Binary files /dev/null and b/public/images/glimpse/comments.png differ diff --git a/public/images/glimpse/crop.png b/public/images/glimpse/crop.png new file mode 100644 index 00000000..a72dc053 Binary files /dev/null and b/public/images/glimpse/crop.png differ diff --git a/public/images/glimpse/feed.png b/public/images/glimpse/feed.png new file mode 100644 index 00000000..1331f516 Binary files /dev/null and b/public/images/glimpse/feed.png differ diff --git a/public/images/glimpse/profile.png b/public/images/glimpse/profile.png new file mode 100644 index 00000000..c9f0d510 Binary files /dev/null and b/public/images/glimpse/profile.png differ diff --git a/public/images/glimpse/search.png b/public/images/glimpse/search.png new file mode 100644 index 00000000..e6ed33a2 Binary files /dev/null and b/public/images/glimpse/search.png differ diff --git a/public/images/glimpse/settings.png b/public/images/glimpse/settings.png new file mode 100644 index 00000000..5e76c9b5 Binary files /dev/null and b/public/images/glimpse/settings.png differ diff --git a/public/images/good-morning/create-notice.png b/public/images/good-morning/create-notice.png new file mode 100644 index 00000000..908213f0 Binary files /dev/null and b/public/images/good-morning/create-notice.png differ diff --git a/public/images/good-morning/login-with-google.png b/public/images/good-morning/login-with-google.png new file mode 100644 index 00000000..088a5c1e Binary files /dev/null and b/public/images/good-morning/login-with-google.png differ diff --git a/public/images/good-morning/me.png b/public/images/good-morning/me.png new file mode 100644 index 00000000..73278e76 Binary files /dev/null and b/public/images/good-morning/me.png differ diff --git a/public/images/good-morning/no-notice.png b/public/images/good-morning/no-notice.png new file mode 100644 index 00000000..a31bf943 Binary files /dev/null and b/public/images/good-morning/no-notice.png differ diff --git a/public/images/good-morning/notice.png b/public/images/good-morning/notice.png new file mode 100644 index 00000000..ca2dc2d4 Binary files /dev/null and b/public/images/good-morning/notice.png differ diff --git a/public/images/good-morning/partner-pairing.png b/public/images/good-morning/partner-pairing.png new file mode 100644 index 00000000..4d796986 Binary files /dev/null and b/public/images/good-morning/partner-pairing.png differ diff --git a/public/images/mizu/card-details.png b/public/images/mizu/card-details.png new file mode 100644 index 00000000..a40b4f6d Binary files /dev/null and b/public/images/mizu/card-details.png differ diff --git a/public/images/mizu/card-fighter.png b/public/images/mizu/card-fighter.png new file mode 100644 index 00000000..40529fd0 Binary files /dev/null and b/public/images/mizu/card-fighter.png differ diff --git a/public/images/mizu/card.png b/public/images/mizu/card.png new file mode 100644 index 00000000..6d948b74 Binary files /dev/null and b/public/images/mizu/card.png differ diff --git a/public/images/mizu/collection1.png b/public/images/mizu/collection1.png new file mode 100644 index 00000000..9fe5c625 Binary files /dev/null and b/public/images/mizu/collection1.png differ diff --git a/public/images/mizu/collection2.png b/public/images/mizu/collection2.png new file mode 100644 index 00000000..fe3ac85d Binary files /dev/null and b/public/images/mizu/collection2.png differ diff --git a/public/images/mizu/complete-trade.png b/public/images/mizu/complete-trade.png new file mode 100644 index 00000000..d0066c00 Binary files /dev/null and b/public/images/mizu/complete-trade.png differ diff --git a/public/images/mizu/current-trade.png b/public/images/mizu/current-trade.png new file mode 100644 index 00000000..0c20e08a Binary files /dev/null and b/public/images/mizu/current-trade.png differ diff --git a/public/images/mizu/forage-design.png b/public/images/mizu/forage-design.png new file mode 100644 index 00000000..4dc152d6 Binary files /dev/null and b/public/images/mizu/forage-design.png differ diff --git a/public/images/mizu/forage-locations.png b/public/images/mizu/forage-locations.png new file mode 100644 index 00000000..220ab541 Binary files /dev/null and b/public/images/mizu/forage-locations.png differ diff --git a/public/images/mizu/forage.png b/public/images/mizu/forage.png new file mode 100644 index 00000000..483690be Binary files /dev/null and b/public/images/mizu/forage.png differ diff --git a/public/images/mizu/inventory.png b/public/images/mizu/inventory.png new file mode 100644 index 00000000..417a5588 Binary files /dev/null and b/public/images/mizu/inventory.png differ diff --git a/public/images/mizu/pack-planning.png b/public/images/mizu/pack-planning.png new file mode 100644 index 00000000..b0991159 Binary files /dev/null and b/public/images/mizu/pack-planning.png differ diff --git a/public/images/mizu/quests-planning.png b/public/images/mizu/quests-planning.png new file mode 100644 index 00000000..a262251e Binary files /dev/null and b/public/images/mizu/quests-planning.png differ diff --git a/public/images/mizu/quests.png b/public/images/mizu/quests.png new file mode 100644 index 00000000..7b22567b Binary files /dev/null and b/public/images/mizu/quests.png differ diff --git a/public/images/mizu/update-planning.png b/public/images/mizu/update-planning.png new file mode 100644 index 00000000..456a47d2 Binary files /dev/null and b/public/images/mizu/update-planning.png differ diff --git a/public/images/prayerbud/create-network.png b/public/images/prayerbud/create-network.png new file mode 100644 index 00000000..6517abc9 Binary files /dev/null and b/public/images/prayerbud/create-network.png differ diff --git a/public/images/prayerbud/dashboard.png b/public/images/prayerbud/dashboard.png new file mode 100644 index 00000000..5bc5378e Binary files /dev/null and b/public/images/prayerbud/dashboard.png differ diff --git a/public/images/prayerbud/home.png b/public/images/prayerbud/home.png new file mode 100644 index 00000000..00156cac Binary files /dev/null and b/public/images/prayerbud/home.png differ diff --git a/public/images/prayerbud/post-login.png b/public/images/prayerbud/post-login.png new file mode 100644 index 00000000..5e2c0cc4 Binary files /dev/null and b/public/images/prayerbud/post-login.png differ diff --git a/public/images/prayerbud/prayer-card.png b/public/images/prayerbud/prayer-card.png new file mode 100644 index 00000000..b87ceb0c Binary files /dev/null and b/public/images/prayerbud/prayer-card.png differ diff --git a/public/images/prayerbud/pre-login.png b/public/images/prayerbud/pre-login.png new file mode 100644 index 00000000..bd6368a4 Binary files /dev/null and b/public/images/prayerbud/pre-login.png differ diff --git a/public/images/prayerbud/welcome-to-network.png b/public/images/prayerbud/welcome-to-network.png new file mode 100644 index 00000000..b17ea874 Binary files /dev/null and b/public/images/prayerbud/welcome-to-network.png differ diff --git a/public/images/wiskatron/1.png b/public/images/wiskatron/1.png new file mode 100644 index 00000000..88fc20eb Binary files /dev/null and b/public/images/wiskatron/1.png differ diff --git a/public/images/wiskatron/2.png b/public/images/wiskatron/2.png new file mode 100644 index 00000000..a17cd458 Binary files /dev/null and b/public/images/wiskatron/2.png differ diff --git a/public/images/wiskatron/3.png b/public/images/wiskatron/3.png new file mode 100644 index 00000000..f29f5feb Binary files /dev/null and b/public/images/wiskatron/3.png differ diff --git a/public/images/wiskatron/4.png b/public/images/wiskatron/4.png new file mode 100644 index 00000000..7e8a80bf Binary files /dev/null and b/public/images/wiskatron/4.png differ diff --git a/public/images/wiskatron/5.png b/public/images/wiskatron/5.png new file mode 100644 index 00000000..c85a6aca Binary files /dev/null and b/public/images/wiskatron/5.png differ diff --git a/public/images/wiskatron/6.png b/public/images/wiskatron/6.png new file mode 100644 index 00000000..0148b6ba Binary files /dev/null and b/public/images/wiskatron/6.png differ diff --git a/public/mizu-icon.svg b/public/mizu-icon.svg new file mode 100644 index 00000000..95cd5faa --- /dev/null +++ b/public/mizu-icon.svg @@ -0,0 +1,48 @@ + + + + + + + mizu + + + + + + + + + + + + + + \ No newline at end of file diff --git a/public/prayerbud-icon.svg b/public/prayerbud-icon.svg new file mode 100644 index 00000000..08ab6c40 --- /dev/null +++ b/public/prayerbud-icon.svg @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/public/watercooler-icon.svg b/public/watercooler-icon.svg new file mode 100644 index 00000000..cbd81415 --- /dev/null +++ b/public/watercooler-icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/public/wiskatron-icon.svg b/public/wiskatron-icon.svg new file mode 100644 index 00000000..e94dfb3f --- /dev/null +++ b/public/wiskatron-icon.svg @@ -0,0 +1,8 @@ + + + + + + + \ No newline at end of file diff --git a/src/App.tsx b/src/App.tsx index 3f516635..ae8ad486 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,7 +1,7 @@ +import { ProjectListItem } from "@/components/ProjectListItem"; +import { type ProjectEntry, projectList, projects } from "@/projects"; import { Link, Route, Routes, useParams } from "react-router-dom"; -import { ThemeToggle } from "@/components/theme-toggle"; -import { cn } from "@/lib/utils"; -import { type ProjectMetadata, projectList, projects } from "@/projects"; +import { ThemeToggle } from "./components/theme-toggle"; function App() { return ( @@ -16,20 +16,28 @@ function App() { export default App; function Home() { + const isDevMode = import.meta.env.VITE_PUBLIC_DEV === "1"; + const sortedProjects: ProjectEntry[] = [...projectList].sort( + (a, b) => + parseDate(b.metadata.date).getTime() - + parseDate(a.metadata.date).getTime(), + ); + return (
-

Oliver Bryan

- +

Oliver Bryan

-
- {projectList.map((project) => ( +
+ {sortedProjects.map((project) => ( ))}
+
); } @@ -45,77 +53,50 @@ function ProjectRoute() { function NotFound() { return (
-

Not found

- +

Not found

+ Go home
); } -type ProjectListItemProps = { - metadata: ProjectMetadata; - isDevMode?: boolean; - isHidden?: boolean; -}; +function parseDate(dateStr: string): Date { + const lower = dateStr.toLowerCase(); -function ProjectListItem({ - metadata, - isDevMode = false, - isHidden = false, -}: ProjectListItemProps) { - const tags = metadata.tags ? [...metadata.tags].sort() : []; - const isDevHidden = isDevMode && isHidden; + if (lower.includes("q1")) return new Date("2023-01-01"); + if (lower.includes("q2")) return new Date("2023-04-01"); + if (lower.includes("q3")) return new Date("2023-07-01"); + if (lower.includes("q4")) return new Date("2023-10-01"); - return ( - -
-
- {metadata.image ? ( - {`${metadata.title} - ) : ( -
- )} -
-
-

- {metadata.title} -

-

- {metadata.description} -

- {tags.length > 0 ? ( -
- {tags.map((tag) => ( - - {tag} - - ))} -
- ) : null} -
-
-
-

- {metadata.date} -

-
- - ); + const months: Record = { + january: 0, + february: 1, + march: 2, + april: 3, + may: 4, + june: 5, + july: 6, + august: 7, + september: 8, + october: 9, + november: 10, + december: 11, + }; + + for (const [monthName, monthIndex] of Object.entries(months)) { + if (lower.includes(monthName)) { + const yearMatch = dateStr.match(/\b(20\d{2})\b/); + if (yearMatch) { + return new Date(Number.parseInt(yearMatch[1], 10), monthIndex, 1); + } + } + } + + const yearMatch = dateStr.match(/\b(20\d{2})\b/); + if (yearMatch) { + return new Date(Number.parseInt(yearMatch[1], 10), 0, 1); + } + + return new Date(0); } diff --git a/src/components/ProjectListItem.tsx b/src/components/ProjectListItem.tsx new file mode 100644 index 00000000..e3649167 --- /dev/null +++ b/src/components/ProjectListItem.tsx @@ -0,0 +1,64 @@ +import { cn } from "@/lib/utils"; +import type { ProjectMetadata } from "@/projects"; +import { Link } from "react-router-dom"; + +export function ProjectListItem({ + metadata, + isDevMode = false, +}: { + metadata: ProjectMetadata; + isDevMode?: boolean; +}) { + const tags = metadata.tags ? [...metadata.tags].sort() : []; + if (metadata.hidden && !isDevMode) return null; + + return ( + +
+
+ {metadata.image ? ( + {`${metadata.title} + ) : ( +
+ )} +
+
+

+ {metadata.title} +

+

{metadata.description}

+ {tags.length > 0 ? ( +
+ {tags.map((tag) => ( + + {tag} + + ))} +
+ ) : null} +
+
+
+

+ {metadata.date} +

+
+ + ); +} diff --git a/src/components/theme-toggle.tsx b/src/components/theme-toggle.tsx index ad7eb6ea..664d9f40 100644 --- a/src/components/theme-toggle.tsx +++ b/src/components/theme-toggle.tsx @@ -1,8 +1,8 @@ -import { Moon, Sun } from "@nsmr/pixelart-react"; import { useTheme } from "@/components/theme-provider"; import { Button } from "@/components/ui/button"; +import { Moon, Sun } from "@nsmr/pixelart-react"; -function ThemeToggle() { +export function ThemeToggle() { const { resolvedTheme, setTheme } = useTheme(); const isDark = resolvedTheme === "dark"; @@ -16,5 +16,3 @@ function ThemeToggle() { ); } - -export { ThemeToggle }; diff --git a/src/projects/factor-e/index.tsx b/src/projects/factor-e/index.tsx new file mode 100644 index 00000000..cc95c312 --- /dev/null +++ b/src/projects/factor-e/index.tsx @@ -0,0 +1,104 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "factor-e", + description: + "Isometric factory sandbox prototype in C++/raylib with procedural worlds, tile building, inventory & tools.", + date: "August 2025", + slug: "factor-e", + image: "/factor-e-icon.svg", + github: "https://github.com/hex248/factor-e", + hidden: false, + tags: ["Game", "C++", "OpenGL", "CMake", "Pixel Art"], + type: "personal", +}; + +export function FactorEProject() { + return ( + +

+ "factor-e" is an isometric factory sandbox prototype I built to learn + C++ and{" "} + + raylib + + . Inspired by Minecraft and{" "} + + Terrafactor + + , it explores tile-based building, inventory management and procedural + world generation. +

+ +
+
+

+ Key features +

+
    +
  • Isometric rendering with my own pixel art
  • +
  • Procedural world generation using Perlin noise
  • +
  • Simple tile place/destroy loop
  • +
  • Basic inventory and tool system
  • +
  • Dev/debug overlay
  • +
  • Cross-platform builds (Windows + Linux)
  • +
  • + Status: active prototype +
  • +
+
+ +
+

+ Technologies +

+
    +
  • C++
  • +
  • raylib (OpenGL)
  • +
  • CMake
  • +
  • Perlin noise generation
  • +
  • Aseprite
  • +
  • Engine-less game development
  • +
+
+
+ +
+

Demo

+
+ + + + +
+
+
+ ); +} diff --git a/src/projects/flackie/index.tsx b/src/projects/flackie/index.tsx new file mode 100644 index 00000000..51335b8a --- /dev/null +++ b/src/projects/flackie/index.tsx @@ -0,0 +1,77 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "flackie", + description: + "A portable FLAC player built with C++ and Python for Raspberry Pi. Custom UI, hardware controls, e-ink display, and a 3D printed case.", + date: "October 2025", + slug: "flackie", + image: "/flackie-icon.svg", + github: "https://github.com/hex248/flackie", + hidden: true, + tags: [ + "Raspberry Pi", + "Python", + "C++", + "CMake", + "Electronics", + "Pillow", + "Image Generation", + ], + type: "personal", +}; + +export function FlackieProject() { + return ( + +

+ "flackie" is a portable FLAC music player I built using a Raspberry Pi + Zero 2 W, a small e-ink display, and some physical buttons. The device + features a custom Python UI for browsing and playing FLAC files. The + case was designed in CAD and 3D printed to house all the components + neatly. +

+ +
+
+

+ Key features +

+
    +
  • Portable design with a compact form factor
  • +
  • Custom Python UI for easy navigation
  • +
  • Physical buttons for playback control
  • +
  • 3D printed case
  • +
  • Supports FLAC audio playback
  • +
+
+ +
+

+ Technologies +

+
    +
  • C++
  • +
  • CMake
  • +
  • Python
  • +
  • Pillow
  • +
  • Raspberry Pi Zero 2 W
  • +
  • E-ink display
  • +
  • 3D printing
  • +
+
+
+ +
+

Pictures

+
+ + + + +
+
+
+ ); +} diff --git a/src/projects/glimpse/index.tsx b/src/projects/glimpse/index.tsx new file mode 100644 index 00000000..2bd3f7fc --- /dev/null +++ b/src/projects/glimpse/index.tsx @@ -0,0 +1,104 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "glimpse", + description: "Simple social media app inspired by early Instagram.", + date: "May 2025", + slug: "glimpse", + image: "/glimpse-icon.svg", + url: "https://glimpse.ob248.com", + github: "https://github.com/hex248/glimpse", + hidden: false, + tags: [ + "Web", + "React", + "TypeScript", + "PostgreSQL", + "Blob Storage", + "Databases", + "OAuth2", + ], + type: "personal", +}; + +export function GlimpseProject() { + return ( + +

+ "glimpse" is a full-stack social app for sharing photos with friends and + building real community. Early Instagram and tumblr were huge + inspirations, no influencers and brands, just keeping up with your + friends and family. Sign in with Google, and immediately access a + dynamic feed, view and comment on posts. Choose your profile colour, and + enable push notifications for new posts, comments, and friend requests. +

+
+
+

+ Key features +

+
    +
  • Photo uploads with caption and cropping function
  • +
  • User profiles with customisable colour themes
  • +
  • Dynamic, server-rendered feed of friends' photos
  • +
  • Commenting on posts
  • +
  • User search
  • +
  • Push notifications
  • +
+
+ +
+

+ Technologies +

+
    +
  • Next.js + TypeScript
  • +
  • Prisma ORM + PostgreSQL
  • +
  • Tailwind CSS
  • +
  • Google OAuth with NextAuth.js
  • +
  • Web Push API
  • +
  • Next.js server-side rendering and API routes
  • +
  • Progressive Web App (PWA)
  • +
+
+
+ +
+

Screenshots

+
+ + + + + + +
+
+
+ ); +} diff --git a/src/projects/good-morning/index.tsx b/src/projects/good-morning/index.tsx new file mode 100644 index 00000000..af1f7b26 --- /dev/null +++ b/src/projects/good-morning/index.tsx @@ -0,0 +1,106 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "good morning!", + description: + "An app for couples or friends to share daily notices with songs and photos", + date: "October 2025", + slug: "good-morning", + image: "/good-morning-icon.png", + url: "https://gm.ob248.com", + github: "https://github.com/hex248/good-morning", + hidden: false, + tags: [ + "Web", + "React", + "TypeScript", + "Go", + "PostgreSQL", + "AWS S3", + "Databases", + "OAuth2", + "Spotify API", + ], + type: "personal", +}; + +export function GoodMorningProject() { + return ( + +

+ "good morning!" is a web app I built to help couples or friends share + daily notices, songs, and photos with each other. It features a simple + and intuitive interface for sending and receiving messages, along with + support for photo attachments. The app is built with React and + TypeScript on the frontend, and Go with PostgreSQL on the backend. Media + files are stored securely using Cloudflare R2 (AWS S3). +

+ +
+
+

+ Key features +

+
    +
  • Create daily notices with photos and Spotify songs
  • +
  • Simple user interface
  • +
  • Google OAuth2 authentication for user accounts
  • +
+
+ +
+

+ Technologies +

+
    +
  • React
  • +
  • TypeScript
  • +
  • Go
  • +
  • PostgreSQL
  • +
  • Cloudflare R2 (AWS S3)
  • +
  • Spotify API
  • +
  • OAuth2 Authentication
  • +
  • Progressive Web App (PWA)
  • +
+
+
+ +
+

Demo

+
+ + + + + + +
+
+
+ ); +} diff --git a/src/projects/index.ts b/src/projects/index.ts index 32f2cf10..65d2aade 100644 --- a/src/projects/index.ts +++ b/src/projects/index.ts @@ -1,5 +1,20 @@ import type { ComponentType } from "react"; +import { FactorEProject, metadata as factorEMetadata } from "./factor-e"; +import { FlackieProject, metadata as flackieMetadata } from "./flackie"; +import { GlimpseProject, metadata as glimpseMetadata } from "./glimpse"; +import { + GoodMorningProject, + metadata as goodMorningMetadata, +} from "./good-morning"; +import { MizuProject, metadata as mizuMetadata } from "./mizu"; +import { PrayerbudProject, metadata as prayerbudMetadata } from "./prayerbud"; +import { ShleepProject, metadata as shleepMetadata } from "./shleep"; import { SprintProject, metadata as sprintMetadata } from "./sprint"; +import { + WatercoolerProject, + metadata as watercoolerMetadata, +} from "./watercooler"; +import { WiskatronProject, metadata as wiskatronMetadata } from "./wiskatron"; export type ProjectMetadata = { title: string; @@ -20,10 +35,46 @@ export type ProjectEntry = { }; export const projects = { + [factorEMetadata.slug]: { + metadata: factorEMetadata, + Component: FactorEProject, + }, + [flackieMetadata.slug]: { + metadata: flackieMetadata, + Component: FlackieProject, + }, + [glimpseMetadata.slug]: { + metadata: glimpseMetadata, + Component: GlimpseProject, + }, + [goodMorningMetadata.slug]: { + metadata: goodMorningMetadata, + Component: GoodMorningProject, + }, + [mizuMetadata.slug]: { + metadata: mizuMetadata, + Component: MizuProject, + }, + [prayerbudMetadata.slug]: { + metadata: prayerbudMetadata, + Component: PrayerbudProject, + }, + [shleepMetadata.slug]: { + metadata: shleepMetadata, + Component: ShleepProject, + }, [sprintMetadata.slug]: { metadata: sprintMetadata, Component: SprintProject, }, + [watercoolerMetadata.slug]: { + metadata: watercoolerMetadata, + Component: WatercoolerProject, + }, + [wiskatronMetadata.slug]: { + metadata: wiskatronMetadata, + Component: WiskatronProject, + }, } satisfies Record; export const projectList = Object.values(projects); diff --git a/src/projects/mizu/index.tsx b/src/projects/mizu/index.tsx new file mode 100644 index 00000000..0fdc5778 --- /dev/null +++ b/src/projects/mizu/index.tsx @@ -0,0 +1,134 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "MIZU", + description: + "A discord bot card trading and collection game. (Currently inactive, 4000+ players) ", + date: "2021 - 2024", + slug: "mizu", + image: "/mizu-icon.svg", + hidden: false, + tags: [ + "Node.js", + "TypeScript", + "PostgreSQL", + "AWS S3", + "Discord API", + "Database", + ], + type: "personal", +}; + +export function MizuProject() { + return ( + +

+ I led a four-person team to create MIZU, a popular anime trading card + game on Discord. In this role, I was responsible for the full lifecycle + of the application: designing the core architecture, building the + application with Node.js and TypeScript, and deploying it on a + self-managed VPS. We successfully scaled to serve over 4,000 players. + Although MIZU is no longer active, it was a significant experience in + leading a team and scaling a live application. +

+ +
+
+

+ Technologies +

+
    +
  • Node.js
  • +
  • TypeScript
  • +
  • Express.js
  • +
  • Discord.js
  • +
  • PostgreSQL
  • +
  • AWS S3
  • +
+
+
+ +
+

Gameplay

+
+ + + + + + + + + + +
+
+ +
+

+ Pre-Production +

+
+ + + + + +
+
+
+ ); +} diff --git a/src/projects/prayerbud/index.tsx b/src/projects/prayerbud/index.tsx new file mode 100644 index 00000000..203f8809 --- /dev/null +++ b/src/projects/prayerbud/index.tsx @@ -0,0 +1,100 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "PrayerBud", + description: + "A faith-based social platform facilitating sharing of support and prayers within communities.", + date: "February 2025 - Present", + slug: "prayerbud", + image: "/prayerbud-icon.svg", + url: "https://prayerbud.co.uk", + hidden: false, + tags: ["Web", "React", "TypeScript", "PostgreSQL", "OAuth2", "Databases"], + type: "professional", +}; + +export function PrayerbudProject() { + return ( + +
+

+ Pray Together and Grow Together: Join a diverse community of + individuals from around the world who are passionate about prayer and + spiritual growth. Create and share prayer requests with your PrayerBud + community who are ready to offer support, encouragement, and heartfelt + prayers. +

+

+ For prayer teams or churches, the app offers a streamlined way to + manage and organise prayer requests, ensuring that no request goes + unnoticed. +

+
+ +
+
+

+ Key features +

+
    +
  • Create and manage prayer networks
  • +
  • Manage prayer communities
  • +
  • Intimate engagement with friends and family
  • +
  • Admin dashboard for managing users and user content
  • +
  • Responsive design for mobile and desktop
  • +
+
+ +
+

+ Technologies +

+
    +
  • Next.js
  • +
  • React
  • +
  • TypeScript
  • +
  • PostgreSQL
  • +
  • Node.js
  • +
+
+
+ +
+

Screenshots

+
+ + + + + + +
+
+
+ ); +} diff --git a/src/projects/shared/Demo.tsx b/src/projects/shared/Demo.tsx index d9643fce..ac8a7189 100644 --- a/src/projects/shared/Demo.tsx +++ b/src/projects/shared/Demo.tsx @@ -1,5 +1,5 @@ -import type { ReactNode } from "react"; import { cn } from "@/lib/utils"; +import type { ReactNode } from "react"; type DemoProps = { image: string; @@ -13,8 +13,7 @@ export function Demo({ image, title, type = "plain", children }: DemoProps) {
{title} -
+
{title} {children}
diff --git a/src/projects/shared/ProjectPage.tsx b/src/projects/shared/ProjectPage.tsx index 1f00d528..02f23b50 100644 --- a/src/projects/shared/ProjectPage.tsx +++ b/src/projects/shared/ProjectPage.tsx @@ -1,5 +1,5 @@ -import type { ReactNode } from "react"; import type { ProjectMetadata } from "@/projects"; +import type { ReactNode } from "react"; type ProjectPageProps = { metadata: ProjectMetadata; @@ -13,7 +13,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) {
-

+

{metadata.title}

{metadata.image ? ( @@ -23,7 +23,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) { className="w-24 h-24 rounded mb-2" /> ) : ( -
+
)}
@@ -40,7 +40,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) {
-

+

{metadata.date} {metadata.github ? ( <> @@ -50,7 +50,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) { href={metadata.github} target="_blank" rel="noopener noreferrer" - className="text-ayu-green-500 hover:underline" + className="text-green-500 hover:underline" > Source Code @@ -63,7 +63,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) { {tags.map((tag: string) => ( {tag} @@ -73,7 +73,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) {

{children}
-

+

Oliver Bryan - {metadata.date} {metadata.github ? ( <> @@ -83,7 +83,7 @@ export function ProjectPage({ metadata, children }: ProjectPageProps) { href={metadata.github} target="_blank" rel="noopener noreferrer" - className="text-ayu-green-500 hover:underline" + className="text-green-500 hover:underline" > Source Code diff --git a/src/projects/shleep/index.tsx b/src/projects/shleep/index.tsx new file mode 100644 index 00000000..1fbaee18 --- /dev/null +++ b/src/projects/shleep/index.tsx @@ -0,0 +1,51 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "Shleep", + description: + "A couch co-op base defense game where you protect a sleepign child from nightmares.", + date: "February - June 2023", + slug: "shleep", + image: "/shleep-icon.svg", + url: "https://bigbootstudio.itch.io/shleep", + hidden: true, + tags: ["Unity", "C#", "HLSL", "Shader Graph", "Visual Effects Graph"], + type: "personal", +}; + +export function ShleepProject() { + return ( + +

+ Shleep is a couch co-op base defense game where you can build towers to + help aid you and your party to protect a sleeping child from nightmares. +

+ +
+

+ Technologies +

+
    +
  • Unity
  • +
  • C#
  • +
  • HLSL
  • +
  • Shader Graph
  • +
  • Visual Effects Graph
  • +
+
+ +
+

Screenshots

+
+ + + + + + +
+
+ + ); +} diff --git a/src/projects/sprint/index.tsx b/src/projects/sprint/index.tsx index 93966c8c..e44925b2 100644 --- a/src/projects/sprint/index.tsx +++ b/src/projects/sprint/index.tsx @@ -36,8 +36,8 @@ export function SprintProject() {

-
-

+
+

Key features

    @@ -54,8 +54,8 @@ export function SprintProject() {
-
-

+
+

Technologies

    @@ -70,9 +70,7 @@ export function SprintProject() {
-

- Architecture -

+

Architecture

Sprint uses a monorepo structure with three packages: a shared package containing database schemas and types, a Bun.serve API with Drizzle @@ -82,9 +80,7 @@ export function SprintProject() {

-

- Screenshots -

+

Screenshots

+

watercooler description here

+ +
+
+

+ Key features +

+
    +
  • feature1
  • +
  • + Status: active prototype +
  • +
+
+ +
+

+ Technologies +

+
    +
  • LiveKit (WebRTC)
  • +
  • Next.js + TypeScript
  • +
  • Prisma ORM + PostgreSQL
  • +
  • Tailwind CSS
  • +
  • Google OAuth with NextAuth.js
  • +
  • Next.js server-side rendering and API routes
  • +
+
+
+ +
+

Screenshots

+
+ + + + +
+
+ + ); +} diff --git a/src/projects/wiskatron/index.tsx b/src/projects/wiskatron/index.tsx new file mode 100644 index 00000000..715aca42 --- /dev/null +++ b/src/projects/wiskatron/index.tsx @@ -0,0 +1,64 @@ +import { Demo } from "@/projects/shared/Demo"; +import { ProjectPage } from "@/projects/shared/ProjectPage"; + +export const metadata = { + title: "Wiskatron", + description: "Spotify listening activity with dynamic visuals", + date: "February 2024", + slug: "wiskatron", + image: "/wiskatron-icon.svg", + github: "https://github.com/hex248/wiskatron", + hidden: false, + tags: ["Web", "React", "TypeScript", "Spotify API", "OAuth2"], + type: "personal", +}; + +export function WiskatronProject() { + return ( + +

+ Spotify listening activity web app with dynamic visuals, built with + Next.js. +

+ +
+
+

+ Key features +

+
    +
  • Live fetch from Spotify API
  • +
  • OAuth 2.0 authentication
  • +
  • Dynamic colour palette extraction
  • +
  • Smooth song transitions
  • +
+
+ +
+

+ Technologies +

+
    +
  • Next.js + TypeScript
  • +
  • Spotify API
  • +
  • OAuth 2.0 with fastify
  • +
  • Next.js server-side rendering and API routes
  • +
  • Colour palette extraction with node-vibrant
  • +
+
+
+ +
+

Screenshots

+
+ + + + + + +
+
+
+ ); +}