feat: updated app pages

This commit is contained in:
2026-05-28 17:46:49 -07:00
parent 0fe7c10b1e
commit 8b6393795e
4 changed files with 292 additions and 95 deletions
+37 -30
View File
@@ -1,11 +1,25 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import { IconBrandLinkedinFilled, IconMailFilled } from "@tabler/icons-react"; import {
IconBrandLinkedinFilled,
IconMailFilled,
IconPhoneFilled,
} from "@tabler/icons-react";
import Hero from "@/components/content/Hero"; import Hero from "@/components/content/Hero";
import SectionLabel from "@/components/layout/SectionLabel"; import SectionLabel from "@/components/layout/SectionLabel";
import AccentLink from "@/components/navigation/AccentLink"; import AccentLink from "@/components/navigation/AccentLink";
import GiteaIcon from "@/components/icons/GiteaIcon"; import GiteaIcon from "@/components/icons/GiteaIcon";
import Tooltip from "@/components/ui/Tooltip"; import Tooltip from "@/components/ui/Tooltip";
import { colors, education, timeline } from "@/constants"; import CopyButton from "@/components/ui/CopyButton";
import {
about,
colors,
education,
email,
gitea,
linkedIn,
phone,
timeline,
} from "@/constants";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "About — Angel Mankel", title: "About — Angel Mankel",
@@ -18,23 +32,9 @@ export default function AboutPage() {
{/* Hero Section */} {/* Hero Section */}
<Hero label="About"> <Hero label="About">
<div className="space-y-5 text-[19px] leading-[1.65]"> <div className="space-y-5 text-[19px] leading-[1.65]">
<p> {about.map((paragraph) => (
I came up through healthcare IT help desk, then Salesforce <p key={paragraph}>{paragraph}</p>
administration, then software engineering. A path that taught me ))}
to translate between operations, design, and engineering without
losing the thread.
</p>
<p>
These days I write React and TypeScript for production, lead
component-library work, and spend a lot of time in Claude Code
custom slash commands, MCP integrations, agent workflows. The
AI-tooling layer is reshaping how I work across the stack.
</p>
<p>
Based in Arizona, looking for remote-US roles where shipping
frontend product and building AI tooling around it are both
first-class.
</p>
</div> </div>
</Hero> </Hero>
@@ -72,18 +72,23 @@ export default function AboutPage() {
{/* Social Links */} {/* Social Links */}
<nav aria-label="Contact" className="flex gap-5 text-neutral-300"> <nav aria-label="Contact" className="flex gap-5 text-neutral-300">
<Tooltip text="Email"> <CopyButton
<a value={email}
href="mailto:angelmankel@gmail.com" label="Copy email"
aria-label="Email" className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`}
className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`} >
> <IconMailFilled size={32} />
<IconMailFilled size={32} /> </CopyButton>
</a> <CopyButton
</Tooltip> value={phone}
label="Copy phone"
className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`}
>
<IconPhoneFilled size={32} />
</CopyButton>
<Tooltip text="LinkedIn"> <Tooltip text="LinkedIn">
<a <a
href="https://www.linkedin.com/in/angel-mankel-0616aa132/" href={linkedIn}
target="_blank" target="_blank"
rel="noreferrer" rel="noreferrer"
aria-label="LinkedIn" aria-label="LinkedIn"
@@ -94,7 +99,9 @@ export default function AboutPage() {
</Tooltip> </Tooltip>
<Tooltip text="Gitea"> <Tooltip text="Gitea">
<a <a
href="#" href={gitea}
target="_blank"
rel="noreferrer"
aria-label="Gitea" aria-label="Gitea"
className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`} className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`}
> >
+6 -6
View File
@@ -14,20 +14,20 @@ export default function HomePage() {
<p className="font-serif italic text-3xl leading-[1.3]"> <p className="font-serif italic text-3xl leading-[1.3]">
I&apos;m{" "} I&apos;m{" "}
<RotatingWord <RotatingWord
items={["a developer", "a creative", "a tinkerer"]} items={["a developer", "a creative", "a builder", "a tinkerer"]}
/> />
. .
</p> </p>
<p className="text-[19px] leading-[1.65]"> <p className="text-[19px] leading-[1.65]">
Building software people rely on and understanding what makes those I build software people actually use, and I care about the people on
people tick is what drives my greatest ambitions. the other side of it as much as the code.
</p> </p>
<p className="text-[19px] leading-[1.65]"> <p className="text-[19px] leading-[1.65]">
I&apos;m the kind of engineer who picks up whatever the problem needs and I&apos;ll pick up whatever a problem needs and keep digging until it
keeps digging into the code, and into the people it&apos;s meant for. works. The constant isn&apos;t a particular stack it&apos;s the
The throughline isn&apos;t a stack, it&apos;s the curiosity. curiosity that got me here.
</p> </p>
</div> </div>
+130
View File
@@ -0,0 +1,130 @@
import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import { IconArrowNarrowLeft, IconExternalLink } from "@tabler/icons-react";
import Hero from "@/components/content/Hero";
import SectionLabel from "@/components/layout/SectionLabel";
import { colors, projects } from "@/constants";
export function generateStaticParams() {
return projects.map((project) => ({ slug: project.slug }));
}
export async function generateMetadata({
params,
}: {
params: Promise<{ slug: string }>;
}): Promise<Metadata> {
const { slug } = await params;
const project = projects.find((p) => p.slug === slug);
if (!project) return { title: "Project — Angel Mankel" };
return {
title: `${project.title} — Angel Mankel`,
description: project.description,
};
}
export default async function ProjectPage({
params,
}: {
params: Promise<{ slug: string }>;
}) {
const { slug } = await params;
const project = projects.find((p) => p.slug === slug);
if (!project) notFound();
return (
<div className="space-y-14">
<Link
href="/projects"
className="inline-flex items-center gap-1 text-sm text-neutral-400 transition-colors hover:text-neutral-200"
>
<IconArrowNarrowLeft size={18} />
All projects
</Link>
<Hero label={project.title} subtitle={project.description}>
<div className="space-y-5">
<p className="text-md text-neutral-400">
{project.year} {project.stack.join(" · ")}
</p>
<a
href={project.liveUrl}
target="_blank"
rel="noreferrer"
className="inline-flex items-center gap-2 rounded-lg border border-neutral-700 px-4 py-2 text-[15px] font-medium transition-colors hover:border-neutral-500"
style={{ color: colors.accent }}
>
Open live app
<IconExternalLink size={18} />
</a>
</div>
</Hero>
{/* Screenshots */}
{project.screenshots.length > 0 && (
<section className="space-y-4">
{project.screenshots.map((shot) => (
<div
key={shot.src}
className="overflow-hidden rounded-xl border border-neutral-800"
>
<Image
src={shot.src}
alt={shot.alt}
width={1200}
height={750}
className="h-auto w-full"
/>
</div>
))}
</section>
)}
{/* Overview */}
<section className="space-y-4">
<SectionLabel label="Overview" />
<div className="space-y-4 text-[17px] leading-[1.65] text-neutral-200">
{project.overview.map((paragraph) => (
<p key={paragraph}>{paragraph}</p>
))}
</div>
</section>
{/* What I learned */}
<section className="space-y-4">
<SectionLabel label="What I learned" />
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200">
{project.learned.map((item) => (
<li
key={item}
className="before:mr-2 before:text-neutral-600 before:content-['']"
>
{item}
</li>
))}
</ul>
</section>
{/* What I'd do differently */}
<section className="space-y-4">
<SectionLabel label="What I'd do differently" />
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200">
{project.improvements.map((item) => (
<li
key={item}
className="before:mr-2 before:text-neutral-600 before:content-['']"
>
{item}
</li>
))}
</ul>
</section>
</div>
);
}
+119 -59
View File
@@ -1,77 +1,137 @@
import type { Metadata } from "next"; import type { Metadata } from "next";
import Hero from "@/components/content/Hero"; import Hero from "@/components/content/Hero";
import SectionLabel from "@/components/layout/SectionLabel"; import SectionLabel from "@/components/layout/SectionLabel";
import Collapsible from "@/components/ui/Collapsible";
import { hardware, editorAndShell, aiTools } from "@/constants";
import type { ToolGroup } from "@/constants/uses";
export const metadata: Metadata = { export const metadata: Metadata = {
title: "Uses — Angel Mankel", title: "Uses — Angel Mankel",
}; };
type Section = function ToolSection({
| { label: string; kind: "list"; items: string[] } label,
| { label: string; kind: "prose"; paragraphs: string[] }; groups,
}: {
const sections: Section[] = [ label: string;
{ groups: ToolGroup[];
label: "Hardware", }) {
kind: "list", return (
items: [ <section className="space-y-4">
"Custom desktop — i9-12900H, 32GB RAM, Intel Iris Xe", <SectionLabel label={label} />
"Nobara Linux 43 · KDE Plasma 6 · Wayland", <div className="space-y-3">
"Headphones / keyboard / monitor — placeholder", {groups.map((group) => (
], <Collapsible
}, key={group.label}
{ title={group.label}
label: "Editor & Shell", subtitle={`${group.items.length} items`}
kind: "list", >
items: [ <ul className="space-y-2 text-[14px]">
"VS Code with a personal extension pack — placeholder", {group.items.map((item) => (
"Bash / zsh — placeholder for shell", <li key={item.name} className="flex flex-col gap-0.5">
"JetBrains Mono as the editor font", <span className="text-neutral-100">{item.name}</span>
"Theme — placeholder", {item.description && (
], <span className="text-neutral-400">{item.description}</span>
}, )}
{ </li>
label: "AI Workflow", ))}
kind: "prose", </ul>
paragraphs: [ </Collapsible>
"Claude Code is the daily driver — custom slash commands, MCP servers wired into my editor, and agent workflows that handle the repeat work so I can stay in the design problem.", ))}
"Placeholder paragraph two — name a specific workflow and the time it saves.", </div>
], </section>
}, );
{ }
label: "Self-hosting",
kind: "list",
items: [
"Bare-metal home server, exposed to the internet through Traefik",
"This site lives on it — Next.js standalone build, no managed host",
"Other services — placeholder",
],
},
];
export default function UsesPage() { export default function UsesPage() {
return ( return (
<div className="space-y-14"> <div className="space-y-14">
<Hero label="Uses" subtitle="What I reach for daily." /> <Hero label="Uses" subtitle="The gear, software, and setup behind my work." />
{sections.map((section) => ( <section className="space-y-4">
<section key={section.label} className="space-y-4"> <SectionLabel label="Hardware" />
<SectionLabel label={section.label} />
{section.kind === "list" ? ( <div className="space-y-10">
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200"> {hardware.map((group) => (
{section.items.map((item) => ( <div key={group.label} className="space-y-4">
<li key={item}>{item}</li> <h3 className="text-xs font-semibold uppercase tracking-[0.18em] text-neutral-500">
))} {group.label}
</ul> </h3>
) : (
<div className="space-y-4 text-[15px] leading-[1.65] text-neutral-200"> <div className="space-y-3">
{section.paragraphs.map((p) => ( {group.machines.map((machine) => (
<p key={p}>{p}</p> <Collapsible
key={machine.name}
title={machine.name}
subtitle={machine.role}
>
<dl className="space-y-3 text-[14px]">
<div className="flex flex-col gap-1 sm:flex-row sm:gap-3">
<dt className="w-20 shrink-0 text-neutral-500">OS</dt>
<dd className="text-neutral-200">{machine.os}</dd>
</div>
<div className="flex flex-col gap-1 sm:flex-row sm:gap-3">
<dt className="w-20 shrink-0 text-neutral-500">Specs</dt>
<dd className="flex flex-wrap gap-1.5">
{machine.specs.map((spec) => (
<span
key={spec}
className="rounded-md border border-neutral-800 bg-neutral-900 px-2 py-0.5 text-[12px] text-neutral-300"
>
{spec}
</span>
))}
</dd>
</div>
{machine.services && (
<div className="flex flex-col gap-1 sm:flex-row sm:gap-3">
<dt className="w-20 shrink-0 text-neutral-500">
Running
</dt>
<dd>
<ul className="space-y-1 text-neutral-200">
{machine.services.map((service) => (
<li
key={service}
className="before:mr-2 before:text-neutral-600 before:content-['']"
>
{service}
</li>
))}
</ul>
</dd>
</div>
)}
</dl>
</Collapsible>
))} ))}
</div> </div>
)}
</section> {group.extras?.map((extra) => (
))} <Collapsible
key={extra.label}
title={extra.label}
subtitle={`${extra.items.length} items`}
>
<ul className="space-y-1.5 text-[14px] text-neutral-200">
{extra.items.map((item) => (
<li
key={item}
className="before:mr-2 before:text-neutral-600 before:content-['']"
>
{item}
</li>
))}
</ul>
</Collapsible>
))}
</div>
))}
</div>
</section>
<ToolSection label="Editor & Shell" groups={editorAndShell} />
<ToolSection label="AI Tools" groups={aiTools} />
</div> </div>
); );
} }