diff --git a/app/layout.tsx b/app/layout.tsx
index b6d4ad8..710a8b9 100644
--- a/app/layout.tsx
+++ b/app/layout.tsx
@@ -25,15 +25,22 @@ const instrumentSerif = Instrument_Serif({
});
export const metadata: Metadata = {
- title: "Abass Ibrahim | IT Specialist & Full-Stack Developer",
+ metadataBase: new URL("https://abassibrahim.xyz"),
+ title: "Abass Ibrahim | Full-Stack Developer & IT Specialist",
description:
- "Portfolio of Abass Ibrahim — IT Support Specialist, Full-Stack Developer, UI/UX Designer, and Web3 Builder based in Lagos, Nigeria.",
+ "Portfolio of Abass Ibrahim — Full-Stack Developer, IT Support Specialist, and Web3 builder in Lagos, Nigeria. Seven shipped production apps across fintech, e-commerce, edtech, and HR.",
+ applicationName: "Abass Ibrahim — Portfolio",
+ authors: [{ name: "Abass Ibrahim", url: "https://abassibrahim.xyz" }],
+ creator: "Abass Ibrahim",
+ alternates: { canonical: "https://abassibrahim.xyz" },
keywords: [
"Abass Ibrahim",
"Lagos developer",
"Nigeria web developer",
"IT Support Oil Gas",
"Full-Stack React Next.js",
+ "Next.js TypeScript developer",
+ "Web3 builder Nigeria",
],
openGraph: {
title: "Abass Ibrahim | IT Specialist & Full-Stack Developer",
@@ -56,6 +63,36 @@ export const viewport: Viewport = {
initialScale: 1,
};
+const personJsonLd = {
+ "@context": "https://schema.org",
+ "@type": "Person",
+ name: "Abass Ibrahim",
+ jobTitle: "Full-Stack Developer & IT Support Specialist",
+ url: "https://abassibrahim.xyz",
+ email: "mailto:abassibrahim591@gmail.com",
+ address: {
+ "@type": "PostalAddress",
+ addressLocality: "Lagos",
+ addressCountry: "NG",
+ },
+ sameAs: [
+ "https://github.com/Lingz450",
+ "https://www.linkedin.com/in/abass-ibrahim-devv",
+ "https://hashnode.com/@ghost69",
+ "https://medium.com/@Ghost69",
+ ],
+ knowsAbout: [
+ "React",
+ "Next.js",
+ "TypeScript",
+ "Tailwind CSS",
+ "Node.js",
+ "PostgreSQL",
+ "Web3",
+ "IT Support",
+ ],
+};
+
export default function RootLayout({
children,
}: Readonly<{ children: React.ReactNode }>) {
@@ -64,6 +101,10 @@ export default function RootLayout({
+
+
+
+
+
+
+
@@ -25,4 +35,3 @@ export default function HomePage() {
);
}
-
diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx
index 7952c4c..1aae98b 100644
--- a/components/layout/Navbar.tsx
+++ b/components/layout/Navbar.tsx
@@ -13,10 +13,15 @@ import { ThemeToggle } from "@/components/ui/ThemeToggle";
const navLinks = [
{ href: "#about", label: "About" },
{ href: "#skills", label: "Skills" },
+ { href: "#experience", label: "Experience" },
{ href: "#projects", label: "Projects" },
{ href: "#writing", label: "Writing" },
];
+function openPalette() {
+ window.dispatchEvent(new Event("open-command-palette"));
+}
+
function scrollToHash(hash: string) {
const id = hash.replace("#", "");
const el = document.getElementById(id);
@@ -68,6 +73,18 @@ export function Navbar() {
+
+ ⌕
+ Search
+
+ ⌘K
+
+
diff --git a/components/sections/About.tsx b/components/sections/About.tsx
index b750acd..5d89e07 100644
--- a/components/sections/About.tsx
+++ b/components/sections/About.tsx
@@ -36,23 +36,24 @@ export function About() {
- I'm Abass Ibrahim, based in Lagos State, Nigeria. My work lives at
- the intersection of clean interface design, dependable engineering, and calm
- problem-solving under real-world constraints. I enjoy building products that feel
- sharp, intentional, and fast — the kind of UI that earns trust in the first few
- seconds.
+ I'm Abass Ibrahim, a self-taught full-stack developer based in Lagos, Nigeria.
+ My work lives at the intersection of clean interface design, dependable engineering,
+ and calm problem-solving under real-world constraints — the kind of UI that earns
+ trust in the first few seconds and holds up in production.
- Professionally, I work as an IT Support Specialist in the Oil & Gas sector, so I'm
- wired for uptime, clarity, and systems thinking. That background shapes how I
- approach software: consistent patterns, strong defaults, readable flows, and the
- discipline to ship features without breaking what already works.
+ By day I'm an IT Support Specialist across two Oil & Gas companies, so
+ I'm wired for uptime, clarity, and systems thinking. That shapes how I write
+ software: consistent patterns, strong defaults, readable flows, and the discipline
+ to ship features without breaking what already works.
- On the builder side, I lean frontend while staying comfortable across the stack —
- from Next.js and APIs to databases and automation. I design in Figma only, and I
- write about the craft on Hashnode and Medium: lessons from building, debugging, and
- making complex ideas easier to work with.
+ Over the last few years I've shipped{" "}
+ seven production web apps across fintech,
+ e-commerce, edtech, HR, and marketplaces, contributed{" "}
+ 600+ commits to my own projects in the last year
+ alone, and grew a 500+ member trading community from zero. I lean frontend, stay
+ comfortable across the stack, and write about the craft on Hashnode and Medium.
diff --git a/components/sections/Experience.tsx b/components/sections/Experience.tsx
new file mode 100644
index 0000000..1acf7da
--- /dev/null
+++ b/components/sections/Experience.tsx
@@ -0,0 +1,69 @@
+import { RevealOnScroll } from "@/components/ui/RevealOnScroll";
+import { experience } from "@/lib/data";
+import type { ExperienceItem } from "@/lib/types";
+
+const kindLabel: Record
= {
+ work: "Employment",
+ build: "Product",
+ community: "Community",
+};
+
+export function Experience() {
+ return (
+
+
+
+ Experience
+
+ Where I’ve{" "}
+ put in the work.
+
+
+ Two concurrent IT roles keeping oil & gas systems alive by day, seven shipped
+ products by night, and a 500+ member community I built from zero.
+
+
+
+
+ {experience.map((item, i) => (
+
+
+
+
+
+ {item.period}
+
+
+ {item.location}
+
+
+ {kindLabel[item.kind]}
+
+
+
+
+
+ {item.role}
+ ·
+ {item.org}
+
+
+ {item.points.map((p, idx) => (
+
+
+ {p}
+
+ ))}
+
+
+
+
+ ))}
+
+
+
+ );
+}
diff --git a/components/sections/Hero.tsx b/components/sections/Hero.tsx
index 2c1f359..f3fc993 100644
--- a/components/sections/Hero.tsx
+++ b/components/sections/Hero.tsx
@@ -3,7 +3,7 @@
import Link from "next/link";
import { motion } from "framer-motion";
import { AnimatedText } from "@/components/ui/AnimatedText";
-import { AvatarImage } from "@/components/ui/AvatarImage";
+import { Terminal } from "@/components/ui/Terminal";
const roles = [
"IT Support Specialist (Oil & Gas)",
@@ -126,7 +126,7 @@ export function Hero() {
-
+
LinkedIn ↗
+ window.dispatchEvent(new Event("open-command-palette"))}
+ className="inline-flex items-center gap-2 rounded-xs border border-border bg-surface/60 px-3 py-1.5 font-mono text-[11px] uppercase tracking-[0.18em] text-muted transition-colors hover:border-accent/60 hover:text-accent"
+ aria-label="Open command palette"
+ >
+ Press
+
+ ⌘K
+
+ to explore
+
@@ -154,51 +166,26 @@ export function Hero() {
transition={{ duration: 0.8, ease, delay: 0.25 }}
className="relative"
>
-
-
-
-
-
- Abass Ibrahim
+
+
+
+ {[
+ { k: "3+", v: "Years" },
+ { k: "7", v: "Live Apps" },
+ { k: "500+", v: "Community" },
+ ].map((s) => (
+
+
+ {s.k}
-
- Lagos State, Nigeria
+
+ {s.v}
-
-
-
- {[
- { k: "3+ Years", v: "Experience" },
- { k: "20+ Stack", v: "Tools & Tech" },
- { k: "6+ Domains", v: "Roles" },
- { k: "\u221e Drive", v: "Always Learning" },
- ].map((s) => (
-
-
- {s.k}
-
-
- {s.v}
-
-
- ))}
-
-
-
-
-
- Open to Opportunities
-
-
+ ))}
diff --git a/components/ui/CommandPalette.tsx b/components/ui/CommandPalette.tsx
new file mode 100644
index 0000000..47ac580
--- /dev/null
+++ b/components/ui/CommandPalette.tsx
@@ -0,0 +1,276 @@
+"use client";
+
+import * as React from "react";
+import { AnimatePresence, motion } from "framer-motion";
+import { useTheme } from "next-themes";
+import { projects, socials } from "@/lib/data";
+
+type Command = {
+ id: string;
+ label: string;
+ hint: string;
+ group: "Navigate" | "Projects" | "Connect" | "General";
+ keywords?: string;
+ run: () => void;
+};
+
+function scrollToId(id: string) {
+ const el = document.getElementById(id);
+ if (el) el.scrollIntoView({ behavior: "smooth", block: "start" });
+}
+
+function openExternal(href: string) {
+ window.open(href, "_blank", "noopener,noreferrer");
+}
+
+/**
+ * ⌘K / Ctrl+K command palette — the primary keyboard interface for the site.
+ * Also opens on a custom `open-command-palette` window event (fired by the navbar button).
+ */
+export function CommandPalette() {
+ const [open, setOpen] = React.useState(false);
+ const [query, setQuery] = React.useState("");
+ const [active, setActive] = React.useState(0);
+ const { setTheme, resolvedTheme } = useTheme();
+ const listRef = React.useRef
(null);
+
+ const commands = React.useMemo(() => {
+ const nav: Command[] = [
+ { id: "nav-top", label: "Go to Top", hint: "home", group: "Navigate", run: () => window.scrollTo({ top: 0, behavior: "smooth" }) },
+ { id: "nav-about", label: "About", hint: "#about", group: "Navigate", run: () => scrollToId("about") },
+ { id: "nav-skills", label: "Skills & Stack", hint: "#skills", group: "Navigate", run: () => scrollToId("skills") },
+ { id: "nav-experience", label: "Experience", hint: "#experience", group: "Navigate", run: () => scrollToId("experience") },
+ { id: "nav-projects", label: "Projects", hint: "#projects", group: "Navigate", run: () => scrollToId("projects") },
+ { id: "nav-writing", label: "Writing", hint: "#writing", group: "Navigate", run: () => scrollToId("writing") },
+ { id: "nav-contact", label: "Contact", hint: "#contact", group: "Navigate", run: () => scrollToId("contact") },
+ ];
+
+ const proj: Command[] = projects.map((p) => {
+ const live = p.links.find((l) => l.label === "Live Demo");
+ const title = p.title.split("—")[0].trim();
+ return {
+ id: `proj-${p.number}`,
+ label: `Open ${title}`,
+ hint: p.domain ?? "project",
+ group: "Projects" as const,
+ keywords: `${p.title} ${p.tags.join(" ")} ${p.domain ?? ""}`,
+ run: () => openExternal(live?.href ?? p.links[0]?.href ?? "#"),
+ };
+ });
+
+ const connect: Command[] = [
+ ...socials.map((s) => ({
+ id: `social-${s.id}`,
+ label: `Open ${s.label}`,
+ hint: s.short,
+ group: "Connect" as const,
+ run: () => openExternal(s.href),
+ })),
+ {
+ id: "email",
+ label: "Copy Email",
+ hint: "clipboard",
+ group: "Connect",
+ keywords: "mail contact reach",
+ run: () => {
+ void navigator.clipboard?.writeText("abassibrahim591@gmail.com");
+ },
+ },
+ { id: "cv", label: "Download CV", hint: "pdf", group: "Connect", run: () => openExternal("/Abass_Ibrahim_CV.pdf") },
+ ];
+
+ const general: Command[] = [
+ {
+ id: "theme",
+ label: resolvedTheme === "dark" ? "Switch to Light Mode" : "Switch to Dark Mode",
+ hint: "theme",
+ group: "General",
+ keywords: "dark light toggle appearance",
+ run: () => setTheme(resolvedTheme === "dark" ? "light" : "dark"),
+ },
+ ];
+
+ return [...nav, ...proj, ...connect, ...general];
+ }, [resolvedTheme, setTheme]);
+
+ const filtered = React.useMemo(() => {
+ const q = query.trim().toLowerCase();
+ if (!q) return commands;
+ return commands.filter((c) =>
+ `${c.label} ${c.hint} ${c.keywords ?? ""}`.toLowerCase().includes(q),
+ );
+ }, [commands, query]);
+
+ const close = React.useCallback(() => {
+ setOpen(false);
+ setQuery("");
+ setActive(0);
+ }, []);
+
+ // Global open triggers: ⌘K / Ctrl+K + custom event.
+ React.useEffect(() => {
+ const onKey = (e: KeyboardEvent) => {
+ if ((e.metaKey || e.ctrlKey) && e.key.toLowerCase() === "k") {
+ e.preventDefault();
+ setOpen((o) => !o);
+ }
+ };
+ const onEvent = () => setOpen(true);
+ window.addEventListener("keydown", onKey);
+ window.addEventListener("open-command-palette", onEvent);
+ return () => {
+ window.removeEventListener("keydown", onKey);
+ window.removeEventListener("open-command-palette", onEvent);
+ };
+ }, []);
+
+ // Keep the active row visible + clamp when the filtered list changes.
+ React.useEffect(() => {
+ setActive((a) => Math.min(a, Math.max(filtered.length - 1, 0)));
+ }, [filtered.length]);
+
+ React.useEffect(() => {
+ if (!open) return;
+ const row = listRef.current?.querySelector(`[data-index="${active}"]`);
+ row?.scrollIntoView({ block: "nearest" });
+ }, [active, open]);
+
+ const onListKey = (e: React.KeyboardEvent) => {
+ if (e.key === "ArrowDown") {
+ e.preventDefault();
+ setActive((a) => (a + 1) % Math.max(filtered.length, 1));
+ } else if (e.key === "ArrowUp") {
+ e.preventDefault();
+ setActive((a) => (a - 1 + filtered.length) % Math.max(filtered.length, 1));
+ } else if (e.key === "Enter") {
+ e.preventDefault();
+ const cmd = filtered[active];
+ if (cmd) {
+ cmd.run();
+ close();
+ }
+ } else if (e.key === "Escape") {
+ e.preventDefault();
+ close();
+ }
+ };
+
+ let runningIndex = -1;
+ const groups: Command["group"][] = ["Navigate", "Projects", "Connect", "General"];
+
+ return (
+
+ {open ? (
+
+
+
+
+
+ >_
+
+ {/* eslint-disable-next-line jsx-a11y/no-autofocus */}
+ {
+ setQuery(e.target.value);
+ setActive(0);
+ }}
+ placeholder="Type a command or search…"
+ className="w-full bg-transparent py-4 font-mono text-[14px] text-text placeholder:text-muted/70 outline-none"
+ aria-label="Command search"
+ />
+
+ Esc
+
+
+
+
+ {filtered.length === 0 ? (
+
+ No matches for “{query}”
+
+ ) : (
+ groups.map((group) => {
+ const items = filtered.filter((c) => c.group === group);
+ if (items.length === 0) return null;
+ return (
+
+
+ {group}
+
+ {items.map((c) => {
+ runningIndex += 1;
+ const idx = runningIndex;
+ const isActive = idx === active;
+ return (
+
setActive(idx)}
+ onClick={() => {
+ c.run();
+ close();
+ }}
+ className={`flex w-full items-center justify-between gap-4 px-4 py-2.5 text-left transition-colors ${
+ isActive ? "bg-accent/10" : "bg-transparent"
+ }`}
+ >
+
+
+
+ {c.label}
+
+
+
+ {c.hint}
+
+
+ );
+ })}
+
+ );
+ })
+ )}
+
+
+
+
+ ↑↓ navigate
+ ↵ select
+
+ abass · command palette
+
+
+
+ ) : null}
+
+ );
+}
diff --git a/components/ui/GitHubStats.tsx b/components/ui/GitHubStats.tsx
new file mode 100644
index 0000000..246daa6
--- /dev/null
+++ b/components/ui/GitHubStats.tsx
@@ -0,0 +1,117 @@
+"use client";
+
+import * as React from "react";
+
+type Stats = {
+ repos: number;
+ followers: number;
+ following: number;
+};
+
+const GH_USER = "Lingz450";
+
+// Sensible fallbacks so the UI never renders empty if the API is rate-limited.
+const FALLBACK: Stats = { repos: 19, followers: 22, following: 237 };
+
+function useCountUp(target: number, run: boolean, duration = 900) {
+ const [value, setValue] = React.useState(0);
+ React.useEffect(() => {
+ if (!run) return;
+ let raf = 0;
+ const start = performance.now();
+ const tick = (now: number) => {
+ const t = Math.min((now - start) / duration, 1);
+ const eased = 1 - Math.pow(1 - t, 3);
+ setValue(Math.round(eased * target));
+ if (t < 1) raf = requestAnimationFrame(tick);
+ };
+ raf = requestAnimationFrame(tick);
+ return () => cancelAnimationFrame(raf);
+ }, [target, run, duration]);
+ return value;
+}
+
+/** Live GitHub stat strip — fetches public profile data client-side, no auth. */
+export function GitHubStats() {
+ const [stats, setStats] = React.useState(null);
+
+ React.useEffect(() => {
+ let cancelled = false;
+ fetch(`https://api.github.com/users/${GH_USER}`)
+ .then((r) => (r.ok ? r.json() : Promise.reject(new Error("gh"))))
+ .then((d: { public_repos: number; followers: number; following: number }) => {
+ if (!cancelled) {
+ setStats({
+ repos: d.public_repos,
+ followers: d.followers,
+ following: d.following,
+ });
+ }
+ })
+ .catch(() => {
+ if (!cancelled) setStats(FALLBACK);
+ });
+ return () => {
+ cancelled = true;
+ };
+ }, []);
+
+ const ready = stats !== null;
+ const data = stats ?? FALLBACK;
+
+ const items: { label: string; value: number; suffix?: string }[] = [
+ { label: "Public Repos", value: data.repos, suffix: "+" },
+ { label: "Followers", value: data.followers },
+ { label: "Following", value: data.following },
+ { label: "Commits / yr", value: 600, suffix: "+" },
+ ];
+
+ return (
+
+
+
+
+
+
+
+
+ github.com/{GH_USER}
+
+
+
+ {ready ? "live" : "syncing"}
+
+
+
+ {items.map((it) => (
+
+ ))}
+
+
+ );
+}
+
+function StatCell({
+ label,
+ value,
+ suffix,
+ run,
+}: {
+ label: string;
+ value: number;
+ suffix?: string;
+ run: boolean;
+}) {
+ const shown = useCountUp(value, run);
+ return (
+
+
+ {shown}
+ {suffix ? {suffix} : null}
+
+
+ {label}
+
+
+ );
+}
diff --git a/components/ui/ProjectCard.tsx b/components/ui/ProjectCard.tsx
index b46bd7e..a0bc71f 100644
--- a/components/ui/ProjectCard.tsx
+++ b/components/ui/ProjectCard.tsx
@@ -45,6 +45,26 @@ export function ProjectCard({
{project.number}
+ {(project.domain || project.year || project.status) && (
+
+ {project.domain ? {project.domain} : null}
+ {project.year ? (
+ <>
+
+ /
+
+ {project.year}
+ >
+ ) : null}
+ {project.status ? (
+
+
+ {project.status}
+
+ ) : null}
+
+ )}
+
{project.tags.map((t) => (
{t}
diff --git a/components/ui/ScrollProgress.tsx b/components/ui/ScrollProgress.tsx
new file mode 100644
index 0000000..8e6a53b
--- /dev/null
+++ b/components/ui/ScrollProgress.tsx
@@ -0,0 +1,21 @@
+"use client";
+
+import { motion, useScroll, useSpring } from "framer-motion";
+
+/** Thin accent progress bar pinned to the top of the viewport. */
+export function ScrollProgress() {
+ const { scrollYProgress } = useScroll();
+ const scaleX = useSpring(scrollYProgress, {
+ stiffness: 120,
+ damping: 30,
+ restDelta: 0.001,
+ });
+
+ return (
+
+ );
+}
diff --git a/components/ui/Terminal.tsx b/components/ui/Terminal.tsx
new file mode 100644
index 0000000..07a2b0d
--- /dev/null
+++ b/components/ui/Terminal.tsx
@@ -0,0 +1,132 @@
+"use client";
+
+import * as React from "react";
+
+type Line =
+ | { kind: "cmd"; text: string }
+ | { kind: "out"; text: string; accent?: boolean };
+
+const SCRIPT: Line[] = [
+ { kind: "cmd", text: "whoami" },
+ { kind: "out", text: "Abass Ibrahim — Full-Stack Developer & IT Specialist", accent: true },
+ { kind: "cmd", text: "cat stack.txt" },
+ { kind: "out", text: "next.js · typescript · tailwind · node · postgres · prisma" },
+ { kind: "cmd", text: "ls ./shipped" },
+ { kind: "out", text: "7 production apps · fintech · e-commerce · edtech · hr" },
+ { kind: "cmd", text: "status --now" },
+ { kind: "out", text: "● available for work · Lagos (GMT+1) · remote-ready", accent: true },
+];
+
+/**
+ * Typewriter terminal that plays a short scripted session, then blinks a caret.
+ * Respects prefers-reduced-motion by rendering the full transcript instantly.
+ */
+export function Terminal() {
+ const [visible, setVisible] = React.useState
(0);
+ const [typed, setTyped] = React.useState("");
+ const [done, setDone] = React.useState(false);
+
+ React.useEffect(() => {
+ const reduce =
+ typeof window !== "undefined" &&
+ window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+ if (reduce) {
+ setVisible(SCRIPT.length);
+ setDone(true);
+ return;
+ }
+
+ let cancelled = false;
+ let idx = 0;
+
+ const playLine = () => {
+ if (cancelled || idx >= SCRIPT.length) {
+ if (!cancelled) setDone(true);
+ return;
+ }
+ const line = SCRIPT[idx];
+ if (line.kind === "out") {
+ setVisible(idx + 1);
+ setTyped("");
+ idx += 1;
+ window.setTimeout(playLine, 320);
+ return;
+ }
+ // Typewriter for command lines.
+ let char = 0;
+ const type = () => {
+ if (cancelled) return;
+ char += 1;
+ setTyped(line.text.slice(0, char));
+ if (char < line.text.length) {
+ window.setTimeout(type, 34);
+ } else {
+ setVisible(idx + 1);
+ setTyped("");
+ idx += 1;
+ window.setTimeout(playLine, 180);
+ }
+ };
+ type();
+ };
+
+ const start = window.setTimeout(playLine, 500);
+ return () => {
+ cancelled = true;
+ window.clearTimeout(start);
+ };
+ }, []);
+
+ const currentIsCmd =
+ visible < SCRIPT.length && SCRIPT[visible]?.kind === "cmd" && typed.length > 0;
+
+ return (
+
+
+
+
+
+
+
+
+ abass@lagos — zsh
+
+
+
+
+ {SCRIPT.slice(0, visible).map((line, i) =>
+ line.kind === "cmd" ? (
+
+ ❯
+ {line.text}
+
+ ) : (
+
+ {line.text}
+
+ ),
+ )}
+
+ {currentIsCmd ? (
+
+ ❯
+
+ {typed}
+
+
+
+ ) : null}
+
+ {done ? (
+
+ ❯
+
+
+ ) : null}
+
+
+ );
+}
diff --git a/lib/data.ts b/lib/data.ts
index 778cf6a..d36aaeb 100644
--- a/lib/data.ts
+++ b/lib/data.ts
@@ -1,4 +1,10 @@
-import type { Project, SkillCategory, Social, WritingPlatform } from "./types";
+import type {
+ ExperienceItem,
+ Project,
+ SkillCategory,
+ Social,
+ WritingPlatform,
+} from "./types";
export const socials: Social[] = [
{
@@ -72,10 +78,66 @@ export const skillCategories: SkillCategory[] = [
},
];
+export const experience: ExperienceItem[] = [
+ {
+ role: "IT Officer",
+ org: "Metropole Petroleum",
+ period: "2024 — Present",
+ location: "Lagos, Nigeria",
+ kind: "work",
+ points: [
+ "Manage day-to-day IT operations across the company, troubleshooting hardware, network, and software issues to maintain uptime for business-critical systems.",
+ "Provide technical support across departments, resolving incidents quickly and minimising operational downtime.",
+ "Maintain user accounts, devices, and access controls, ensuring secure and reliable workflows.",
+ "Document recurring incidents and drive improvements to internal IT processes.",
+ ],
+ },
+ {
+ role: "IT Assistant",
+ org: "Bono Energy Group",
+ period: "2024 — Present",
+ location: "Lagos, Nigeria · Concurrent role",
+ kind: "work",
+ points: [
+ "Support IT infrastructure, end-user devices, and software installations across the company.",
+ "Assist with system maintenance, diagnostics, and resolution of technical issues raised by staff.",
+ ],
+ },
+ {
+ role: "Independent Web Developer",
+ org: "Self-Taught & Freelance",
+ period: "2022 — Present",
+ location: "Remote · Lagos, Nigeria",
+ kind: "build",
+ points: [
+ "Designed, built, and shipped seven production web applications spanning e-commerce, education, fintech, HR, and marketplace domains — all currently live.",
+ "Lead full-stack development across React, Next.js, TypeScript, Tailwind, and Node.js, with backends on PostgreSQL, Prisma, MongoDB, Supabase, and Firebase.",
+ "Implemented multi-role authentication with distinct credential types (matric-number, email/password) for production use.",
+ "Built Web3 tooling and automation bots that streamline daily community workflows.",
+ "Deploy and maintain production sites on Vercel and Netlify with custom domains.",
+ ],
+ },
+ {
+ role: "Founder & Crypto Trading Educator",
+ org: "Independent",
+ period: "2021 — Present",
+ location: "Online · 500+ active members",
+ kind: "community",
+ points: [
+ "Founded and grew a 500+ member community focused on crypto trading, Web3 fundamentals, and risk management.",
+ "Designed and delivered structured trading curricula, mentorship programmes, and educational content.",
+ "Translated complex Web3 concepts into beginner-friendly material — communication skills that carry directly into technical writing and product work.",
+ ],
+ },
+];
+
export const projects: Project[] = [
{
number: "01",
featured: true,
+ domain: "Fintech / SaaS",
+ year: "2025",
+ status: "Live",
title: "The Thesis Desk — Crypto Trading Command Center",
description:
"A full-stack trading command center built for the 500+ member crypto community I run. Features live price feeds for BTC, ETH, SOL, BNB, and XRP, P&L tracking, signal review, daily journaling with streak accountability, weekly discipline scoring, and a calculator suite. Includes role-based access with an admin panel and a focused 'Today's Trade Plan' workflow that turns the journal into sharper execution.",
@@ -87,6 +149,9 @@ export const projects: Project[] = [
},
{
number: "02",
+ domain: "Education",
+ year: "2025",
+ status: "Live",
title: "The Helping Tribe Academy — School Management Platform",
description:
"A complete school management system for a Counselling & Positive Psychology program. Three role-based login portals (Student, Facilitator, Admin) with separate matric-number and email authentication flows, plus a public application form for prospective students. Designed for clarity and speed, with distinct UX paths per role.",
@@ -98,6 +163,9 @@ export const projects: Project[] = [
},
{
number: "03",
+ domain: "E-Commerce",
+ year: "2025",
+ status: "Live",
title: "Wearables Atelier — Premium Nigerian Womenswear E-Commerce",
description:
"An editorial e-commerce site for a premium Nigerian womenswear brand — Iro & Buba, Aso Oke, Kaftan, Boubou, Turbans, and Jewellery. Multi-category navigation, ready-to-wear and custom-order flows, wholesale support, sale management, and a 'Now Trending' module with live product pricing. Built mobile-first with a strong magazine-style design language.",
@@ -109,6 +177,9 @@ export const projects: Project[] = [
},
{
number: "04",
+ domain: "E-Commerce",
+ year: "2024",
+ status: "Live",
title: "Fàdè (9thluxe) — Luxury Perfume E-Commerce",
description:
"A luxury perfume storefront with curated collections, drops, an editorial journal, persistent cart, wishlist, and full dark/light mode. Designed mobile-first with a slow-luxury feel — clean typography, generous whitespace, and a calm interaction model. Built end-to-end with Next.js, TypeScript, and Tailwind.",
@@ -120,6 +191,9 @@ export const projects: Project[] = [
},
{
number: "05",
+ domain: "Marketplace",
+ year: "2024",
+ status: "Live",
title: "Aureo — Trust-First Hiring Marketplace",
description:
"A hiring marketplace where candidates build verified profiles with trust scores and skill listings, while employers post transparent opportunities. Built around the idea that hiring should reward proof and clear communication, not noise.",
@@ -131,6 +205,9 @@ export const projects: Project[] = [
},
{
number: "06",
+ domain: "HR Tech / SaaS",
+ year: "2025",
+ status: "Live",
title: "Atlas HR — Global HR, Compliance & Payroll Platform",
description:
"A full-stack HR SaaS for teams that hire across borders — Nigeria, India, the UK, and the US. Features an AI Compliance Sandbox that answers HR questions and builds ready workflows, an interactive cost & risk calculator per country, automated onboarding/termination flows, country-aware document generation, and a programmatic knowledge base with templates and guides.",
@@ -142,6 +219,9 @@ export const projects: Project[] = [
},
{
number: "07",
+ domain: "Fintech",
+ year: "2025",
+ status: "Live",
title: "AirtimeVault — Airtime-to-Cash Conversion Platform",
description:
"A Nigerian fintech platform that converts MTN, Airtel, Glo, and 9mobile airtime into wallet funds in under 30 minutes. Includes tiered conversion rates, bill payments, P2P transfers, a virtual Naira card, referral bonuses, KYC verification, and 24/7 automated fraud monitoring — all from a single secure wallet.",
diff --git a/lib/types.ts b/lib/types.ts
index 6858f53..228943e 100644
--- a/lib/types.ts
+++ b/lib/types.ts
@@ -26,6 +26,18 @@ export interface Project {
tags: string[];
links: ProjectLink[];
featured?: boolean;
+ domain?: string; // e.g. "Fintech", "E-Commerce"
+ year?: string; // e.g. "2025"
+ status?: "Live" | "In Progress" | "Archived";
+}
+
+export interface ExperienceItem {
+ role: string;
+ org: string;
+ period: string;
+ location: string;
+ kind: "work" | "build" | "community";
+ points: string[];
}
export interface WritingPlatform {