feat: updated app pages
This commit is contained in:
+34
-27
@@ -1,11 +1,25 @@
|
||||
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 SectionLabel from "@/components/layout/SectionLabel";
|
||||
import AccentLink from "@/components/navigation/AccentLink";
|
||||
import GiteaIcon from "@/components/icons/GiteaIcon";
|
||||
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 = {
|
||||
title: "About — Angel Mankel",
|
||||
@@ -18,23 +32,9 @@ export default function AboutPage() {
|
||||
{/* Hero Section */}
|
||||
<Hero label="About">
|
||||
<div className="space-y-5 text-[19px] leading-[1.65]">
|
||||
<p>
|
||||
I came up through healthcare IT — help desk, then Salesforce
|
||||
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>
|
||||
{about.map((paragraph) => (
|
||||
<p key={paragraph}>{paragraph}</p>
|
||||
))}
|
||||
</div>
|
||||
</Hero>
|
||||
|
||||
@@ -72,18 +72,23 @@ export default function AboutPage() {
|
||||
|
||||
{/* Social Links */}
|
||||
<nav aria-label="Contact" className="flex gap-5 text-neutral-300">
|
||||
<Tooltip text="Email">
|
||||
<a
|
||||
href="mailto:angelmankel@gmail.com"
|
||||
aria-label="Email"
|
||||
<CopyButton
|
||||
value={email}
|
||||
label="Copy email"
|
||||
className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`}
|
||||
>
|
||||
<IconMailFilled size={32} />
|
||||
</a>
|
||||
</Tooltip>
|
||||
</CopyButton>
|
||||
<CopyButton
|
||||
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">
|
||||
<a
|
||||
href="https://www.linkedin.com/in/angel-mankel-0616aa132/"
|
||||
href={linkedIn}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label="LinkedIn"
|
||||
@@ -94,7 +99,9 @@ export default function AboutPage() {
|
||||
</Tooltip>
|
||||
<Tooltip text="Gitea">
|
||||
<a
|
||||
href="#"
|
||||
href={gitea}
|
||||
target="_blank"
|
||||
rel="noreferrer"
|
||||
aria-label="Gitea"
|
||||
className={`hover:text-neutral-200 rounded-md hover:bg-[${colors.accent}] p-1 transition-colors duration-200`}
|
||||
>
|
||||
|
||||
+6
-6
@@ -14,20 +14,20 @@ export default function HomePage() {
|
||||
<p className="font-serif italic text-3xl leading-[1.3]">
|
||||
I'm{" "}
|
||||
<RotatingWord
|
||||
items={["a developer", "a creative", "a tinkerer"]}
|
||||
items={["a developer", "a creative", "a builder", "a tinkerer"]}
|
||||
/>
|
||||
.
|
||||
</p>
|
||||
|
||||
<p className="text-[19px] leading-[1.65]">
|
||||
Building software people rely on — and understanding what makes those
|
||||
people tick — is what drives my greatest ambitions.
|
||||
I build software people actually use, and I care about the people on
|
||||
the other side of it as much as the code.
|
||||
</p>
|
||||
|
||||
<p className="text-[19px] leading-[1.65]">
|
||||
I'm the kind of engineer who picks up whatever the problem needs and
|
||||
keeps digging — into the code, and into the people it's meant for.
|
||||
The throughline isn't a stack, it's the curiosity.
|
||||
I'll pick up whatever a problem needs and keep digging until it
|
||||
works. The constant isn't a particular stack — it's the
|
||||
curiosity that got me here.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -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>
|
||||
);
|
||||
}
|
||||
+116
-56
@@ -1,77 +1,137 @@
|
||||
import type { Metadata } from "next";
|
||||
import Hero from "@/components/content/Hero";
|
||||
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 = {
|
||||
title: "Uses — Angel Mankel",
|
||||
};
|
||||
|
||||
type Section =
|
||||
| { label: string; kind: "list"; items: string[] }
|
||||
| { label: string; kind: "prose"; paragraphs: string[] };
|
||||
|
||||
const sections: Section[] = [
|
||||
{
|
||||
label: "Hardware",
|
||||
kind: "list",
|
||||
items: [
|
||||
"Custom desktop — i9-12900H, 32GB RAM, Intel Iris Xe",
|
||||
"Nobara Linux 43 · KDE Plasma 6 · Wayland",
|
||||
"Headphones / keyboard / monitor — placeholder",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "Editor & Shell",
|
||||
kind: "list",
|
||||
items: [
|
||||
"VS Code with a personal extension pack — placeholder",
|
||||
"Bash / zsh — placeholder for shell",
|
||||
"JetBrains Mono as the editor font",
|
||||
"Theme — placeholder",
|
||||
],
|
||||
},
|
||||
{
|
||||
label: "AI Workflow",
|
||||
kind: "prose",
|
||||
paragraphs: [
|
||||
"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.",
|
||||
],
|
||||
},
|
||||
{
|
||||
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",
|
||||
],
|
||||
},
|
||||
];
|
||||
function ToolSection({
|
||||
label,
|
||||
groups,
|
||||
}: {
|
||||
label: string;
|
||||
groups: ToolGroup[];
|
||||
}) {
|
||||
return (
|
||||
<section className="space-y-4">
|
||||
<SectionLabel label={label} />
|
||||
<div className="space-y-3">
|
||||
{groups.map((group) => (
|
||||
<Collapsible
|
||||
key={group.label}
|
||||
title={group.label}
|
||||
subtitle={`${group.items.length} items`}
|
||||
>
|
||||
<ul className="space-y-2 text-[14px]">
|
||||
{group.items.map((item) => (
|
||||
<li key={item.name} className="flex flex-col gap-0.5">
|
||||
<span className="text-neutral-100">{item.name}</span>
|
||||
{item.description && (
|
||||
<span className="text-neutral-400">{item.description}</span>
|
||||
)}
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</Collapsible>
|
||||
))}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export default function UsesPage() {
|
||||
return (
|
||||
<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 key={section.label} className="space-y-4">
|
||||
<SectionLabel label={section.label} />
|
||||
{section.kind === "list" ? (
|
||||
<ul className="space-y-2 text-[15px] leading-[1.65] text-neutral-200">
|
||||
{section.items.map((item) => (
|
||||
<li key={item}>{item}</li>
|
||||
<section className="space-y-4">
|
||||
<SectionLabel label="Hardware" />
|
||||
|
||||
<div className="space-y-10">
|
||||
{hardware.map((group) => (
|
||||
<div key={group.label} className="space-y-4">
|
||||
<h3 className="text-xs font-semibold uppercase tracking-[0.18em] text-neutral-500">
|
||||
{group.label}
|
||||
</h3>
|
||||
|
||||
<div className="space-y-3">
|
||||
{group.machines.map((machine) => (
|
||||
<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>
|
||||
) : (
|
||||
<div className="space-y-4 text-[15px] leading-[1.65] text-neutral-200">
|
||||
{section.paragraphs.map((p) => (
|
||||
<p key={p}>{p}</p>
|
||||
))}
|
||||
</dd>
|
||||
</div>
|
||||
)}
|
||||
</section>
|
||||
</dl>
|
||||
</Collapsible>
|
||||
))}
|
||||
</div>
|
||||
|
||||
{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>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user