Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
45 changes: 43 additions & 2 deletions app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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 }>) {
Expand All @@ -64,6 +101,10 @@ export default function RootLayout({
<body
className={`${syne.variable} ${dmMono.variable} ${instrumentSerif.variable} antialiased`}
>
<script
type="application/ld+json"
dangerouslySetInnerHTML={{ __html: JSON.stringify(personJsonLd) }}
/>
<ThemeProvider
attribute="class"
defaultTheme="dark"
Expand Down
11 changes: 10 additions & 1 deletion app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,21 +2,31 @@ import { Navbar } from "@/components/layout/Navbar";
import { Footer } from "@/components/layout/Footer";
import { About } from "@/components/sections/About";
import { Contact } from "@/components/sections/Contact";
import { Experience } from "@/components/sections/Experience";
import { Hero } from "@/components/sections/Hero";
import { Marquee } from "@/components/sections/Marquee";
import { Projects } from "@/components/sections/Projects";
import { Skills } from "@/components/sections/Skills";
import { Writing } from "@/components/sections/Writing";
import { CommandPalette } from "@/components/ui/CommandPalette";
import { GitHubStats } from "@/components/ui/GitHubStats";
import { ScrollProgress } from "@/components/ui/ScrollProgress";

export default function HomePage() {
return (
<div className="min-h-dvh bg-bg text-text">
<ScrollProgress />
<CommandPalette />
<Navbar />
<main id="main" role="main">
<Hero />
<Marquee />
<div className="container-x -mt-2 mb-4">
<GitHubStats />
</div>
<About />
<Skills />
<Experience />
<Projects />
<Writing />
<Contact />
Expand All @@ -25,4 +35,3 @@ export default function HomePage() {
</div>
);
}

17 changes: 17 additions & 0 deletions components/layout/Navbar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -68,6 +73,18 @@ export function Navbar() {
</nav>

<div className="flex items-center gap-4">
<button
type="button"
onClick={openPalette}
className="hidden items-center gap-2 rounded-xs border border-border bg-surface/70 px-3 py-1.5 font-mono text-[11px] uppercase tracking-[0.18em] text-muted transition-colors hover:border-accent/60 hover:text-accent md:inline-flex"
aria-label="Open command palette"
>
<span aria-hidden>⌕</span>
Search
<kbd className="rounded-xs border border-border px-1.5 py-0.5 text-[10px] text-text/90">
⌘K
</kbd>
</button>
<div className="hidden md:flex">
<ThemeToggle />
</div>
Expand Down
27 changes: 14 additions & 13 deletions components/sections/About.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,23 +36,24 @@ export function About() {

<div className="mt-7 space-y-5 text-[15px] leading-8 text-muted">
<p>
I&apos;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&apos;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.
</p>
<p>
Professionally, I work as an IT Support Specialist in the Oil &amp; Gas sector, so I&apos;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&apos;m an IT Support Specialist across two Oil &amp; Gas companies, so
I&apos;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.
</p>
<p>
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&apos;ve shipped{" "}
<span className="text-text">seven production web apps</span> across fintech,
e-commerce, edtech, HR, and marketplaces, contributed{" "}
<span className="text-text">600+ commits</span> 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.
</p>
</div>

Expand Down
69 changes: 69 additions & 0 deletions components/sections/Experience.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import { RevealOnScroll } from "@/components/ui/RevealOnScroll";
import { experience } from "@/lib/data";
import type { ExperienceItem } from "@/lib/types";

const kindLabel: Record<ExperienceItem["kind"], string> = {
work: "Employment",
build: "Product",
community: "Community",
};

export function Experience() {
return (
<section id="experience" className="section-y border-t border-border/60">
<div className="container-x">
<RevealOnScroll>
<div className="accent-line label">Experience</div>
<h2 className="mt-6 font-display text-4xl font-extrabold tracking-[-0.06em] text-text sm:text-5xl">
Where I&rsquo;ve{" "}
<span className="font-serifItalic italic text-text/90">put in the work.</span>
</h2>
<p className="mt-4 max-w-2xl text-[15px] leading-8 text-muted">
Two concurrent IT roles keeping oil &amp; gas systems alive by day, seven shipped
products by night, and a 500+ member community I built from zero.
</p>
</RevealOnScroll>

<div className="mt-12 grid gap-0">
{experience.map((item, i) => (
<RevealOnScroll key={`${item.org}-${item.role}`} delay={i * 0.05}>
<article className="group relative grid gap-4 border-t border-border py-8 md:grid-cols-[220px_1fr] md:gap-10">
<span
aria-hidden
className="absolute left-0 top-[-1px] h-px w-0 bg-accent transition-all duration-500 group-hover:w-full"
/>
<div>
<div className="font-mono text-[11px] uppercase tracking-[0.22em] text-accent">
{item.period}
</div>
<div className="mt-2 font-mono text-[11px] uppercase tracking-[0.22em] text-muted">
{item.location}
</div>
<div className="mt-3 inline-flex rounded-xs border border-border bg-surface px-2.5 py-1 font-mono text-[10px] uppercase tracking-[0.22em] text-muted">
{kindLabel[item.kind]}
</div>
</div>

<div>
<h3 className="font-display text-2xl font-bold tracking-[-0.04em] text-text">
{item.role}
<span className="text-muted"> · </span>
<span className="text-accent">{item.org}</span>
</h3>
<ul className="mt-4 space-y-2.5">
{item.points.map((p, idx) => (
<li key={idx} className="flex gap-3 text-[14px] leading-7 text-muted">
<span aria-hidden className="mt-2.5 h-1 w-1 flex-shrink-0 rounded-full bg-accent/70" />
<span>{p}</span>
</li>
))}
</ul>
</div>
</article>
</RevealOnScroll>
))}
</div>
</div>
</section>
);
}
75 changes: 31 additions & 44 deletions components/sections/Hero.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -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)",
Expand Down Expand Up @@ -126,7 +126,7 @@ export function Hero() {
</a>
</motion.div>

<motion.div variants={item} className="mt-10 flex flex-wrap gap-4">
<motion.div variants={item} className="mt-10 flex flex-wrap items-center gap-4">
<Link
href="https://github.com/Lingz450"
target="_blank"
Expand All @@ -145,6 +145,18 @@ export function Hero() {
>
LinkedIn &#8599;
</Link>
<button
type="button"
onClick={() => 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
<kbd className="rounded-xs border border-border px-1.5 py-0.5 text-[10px] text-text">
⌘K
</kbd>
to explore
</button>
</motion.div>
</motion.div>

Expand All @@ -154,51 +166,26 @@ export function Hero() {
transition={{ duration: 0.8, ease, delay: 0.25 }}
className="relative"
>
<div className="rounded-sm border border-border bg-surface p-6 shadow-[0_20px_60px_rgba(0,0,0,0.55)]">
<div className="flex items-center gap-4">
<AvatarImage />
<div>
<div className="font-display text-[18px] font-bold tracking-[-0.03em] text-text">
Abass Ibrahim
<Terminal />

<div className="mt-4 grid grid-cols-3 gap-3">
{[
{ k: "3+", v: "Years" },
{ k: "7", v: "Live Apps" },
{ k: "500+", v: "Community" },
].map((s) => (
<div
key={s.v}
className="rounded-xs border border-border bg-surface px-4 py-3 text-center"
>
<div className="font-display text-[22px] font-extrabold tracking-[-0.04em] text-text">
{s.k}
</div>
<div className="font-mono text-[12px] uppercase tracking-[0.22em] text-muted">
Lagos State, Nigeria
<div className="mt-0.5 font-mono text-[10px] uppercase tracking-[0.22em] text-muted">
{s.v}
</div>
</div>
</div>

<div className="mt-6 grid grid-cols-2 gap-3">
{[
{ k: "3+ Years", v: "Experience" },
{ k: "20+ Stack", v: "Tools & Tech" },
{ k: "6+ Domains", v: "Roles" },
{ k: "\u221e Drive", v: "Always Learning" },
].map((s) => (
<div
key={s.k}
className="rounded-xs border border-border bg-bg/30 px-4 py-3"
>
<div className="font-display text-[18px] font-extrabold tracking-[-0.04em] text-text">
{s.k}
</div>
<div className="mt-0.5 font-mono text-[11px] uppercase tracking-[0.22em] text-muted">
{s.v}
</div>
</div>
))}
</div>

<div className="mt-6 inline-flex items-center gap-3 rounded-xs border border-border bg-bg/30 px-4 py-2">
<motion.span
className="h-2.5 w-2.5 rounded-full bg-accent"
animate={{ scale: [1, 1.4, 1], opacity: [1, 0.5, 1] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
aria-hidden
/>
<span className="font-mono text-[12px] uppercase tracking-[0.22em] text-text/90">
Open to Opportunities
</span>
</div>
))}
</div>
</motion.div>
</div>
Expand Down
Loading