From 50fadddbe335afa8ef990181b9e370fe82383c17 Mon Sep 17 00:00:00 2001 From: donny Date: Wed, 27 May 2026 19:52:41 -0700 Subject: [PATCH] chore: update components folder structure, add constants, update existing layout components, scaffold pages --- app/about/page.tsx | 86 +------------------ app/globals.css | 16 +--- app/layout.tsx | 18 ++-- app/page.tsx | 66 +++++++++++++- app/projects/page.tsx | 78 +---------------- app/uses/page.tsx | 10 +-- components/content/Hero.tsx | 11 +++ components/{ => content}/RotatingHeadline.tsx | 0 components/{ => content}/Testimonials.tsx | 25 ++++-- components/layout/Container.tsx | 18 ++-- components/layout/Footer.tsx | 15 ++-- components/layout/Header.tsx | 18 ++-- components/layout/Nav.tsx | 48 +++++++---- components/layout/SectionLabel.tsx | 13 +++ components/navigation/AccentLink.tsx | 11 +++ constants/colors.ts | 32 +++++++ 16 files changed, 228 insertions(+), 237 deletions(-) create mode 100644 components/content/Hero.tsx rename components/{ => content}/RotatingHeadline.tsx (100%) rename components/{ => content}/Testimonials.tsx (59%) create mode 100644 components/layout/SectionLabel.tsx create mode 100644 components/navigation/AccentLink.tsx create mode 100644 constants/colors.ts diff --git a/app/about/page.tsx b/app/about/page.tsx index 3f1c5d6..d0a1161 100644 --- a/app/about/page.tsx +++ b/app/about/page.tsx @@ -1,94 +1,12 @@ -// File-based routing: this file's location IS the URL. -// /app/about/page.tsx → https://yoursite.com/about -// -// Conventions: -// page.tsx = the route's UI -// layout.tsx = a wrapper shared across this folder + any nested routes -// loading.tsx = shown while the page is loading -// not-found.tsx = shown on 404 within this segment - +import Hero from "@/components/content/Hero"; import type { Metadata } from "next"; -import Link from "next/link"; -// Route-level metadata. Next reads this at build time and emits the -// correct , <meta description>, OG tags, etc. into <head>. -// The root layout (/app/layout.tsx) had its own metadata; this overrides -// the title for /about specifically. export const metadata: Metadata = { title: "About — Angel Mankel", - // description: - // "Frontend engineer with deep AI-tooling fluency. Currently at Village Medical.", }; -// This is a Server Component by default. It runs on the server, sends -// HTML to the browser, and ships zero JavaScript for this page itself. -// You only add "use client" at the top of a file when you need -// interactivity (useState, onClick, etc). -// -// Because it's a server component, you could `await fetch()` here, read -// a markdown file from disk, or query a DB directly — no API route needed. export default function AboutPage() { return ( - <main className="mx-auto max-w-2xl px-6 py-24"> - <h1 className="text-3xl font-semibold mb-12">About</h1> - - <section className="space-y-6 text-base leading-relaxed text-neutral-200"> - <p> - I came up through healthcare IT — help desk, then Salesforce admin, - then software engineer at Village Medical, where I lead React and - TypeScript redesigns of patient-facing apps and the internal - component library. - </p> - <p> - In parallel I've gone deep on AI-assisted development. Claude - Code, custom slash commands, MCP integrations — the tooling layer - that's reshaping how I work across the stack. - </p> - <p> - I'm looking for remote-US product-engineering roles where - shipping React is the day job and using and building AI tooling is - first-class. - </p> - </section> - - <section className="mt-16"> - <div className="text-xs uppercase tracking-wider text-neutral-500 mb-3"> - Timeline - </div> - <ul className="font-mono text-sm space-y-1.5 text-neutral-200"> - <li> - <span className="text-neutral-500 mr-3">2024 – now</span> - Village Medical · Software Engineer - </li> - <li> - <span className="text-neutral-500 mr-3">2022 – 2024</span> - Village Medical · Salesforce Admin - </li> - <li> - <span className="text-neutral-500 mr-3">2019 – 2022</span> - Gila River · Help Desk / IT - </li> - </ul> - </section> - - {/* next/link does client-side navigation between routes (no full - page reload) and prefetches the target route when the link - enters the viewport. Use it for any internal nav. For external - links and file downloads, plain <a> is fine. */} - <p className="mt-10 text-sm"> - <a href="/cv.pdf" className="underline underline-offset-4 text-amber-400"> - Download CV (PDF) - </a> - </p> - - <nav className="mt-16 pt-8 border-t border-neutral-800 flex gap-6 text-sm text-neutral-400"> - <Link href="/" className="hover:text-white"> - ← Back home - </Link> - <Link href="/projects" className="hover:text-white"> - See projects - </Link> - </nav> - </main> + <Hero label="About"/> ); } diff --git a/app/globals.css b/app/globals.css index a2dc41e..d66353f 100644 --- a/app/globals.css +++ b/app/globals.css @@ -1,26 +1,18 @@ @import "tailwindcss"; :root { - --background: #ffffff; - --foreground: #171717; + --background: #0a0a0a; + --foreground: #ededed; } @theme inline { --color-background: var(--background); --color-foreground: var(--foreground); - --font-sans: var(--font-geist-sans); - --font-mono: var(--font-geist-mono); -} - -@media (prefers-color-scheme: dark) { - :root { - --background: #0a0a0a; - --foreground: #ededed; - } + --font-sans: var(--font-inter); + --font-serif: var(--font-instrument-serif); } body { background: var(--background); color: var(--foreground); - font-family: Arial, Helvetica, sans-serif; } diff --git a/app/layout.tsx b/app/layout.tsx index 696927d..1c00a0f 100644 --- a/app/layout.tsx +++ b/app/layout.tsx @@ -1,22 +1,24 @@ import type { Metadata } from "next"; -import { Geist, Geist_Mono } from "next/font/google"; +import { Inter, Instrument_Serif } from "next/font/google"; import "./globals.css"; import Header from "@/components/layout/Header"; import Footer from "@/components/layout/Footer"; import Container from "@/components/layout/Container"; -const geistSans = Geist({ - variable: "--font-geist-sans", +const inter = Inter({ + variable: "--font-inter", subsets: ["latin"], }); -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", +const instrumentSerif = Instrument_Serif({ + variable: "--font-instrument-serif", subsets: ["latin"], + weight: "400", + style: ["normal", "italic"], }); export const metadata: Metadata = { - title: "Angel Mankel — Software Engineer" + title: "Angel Mankel — Software Engineer", }; export default function RootLayout({ @@ -27,9 +29,9 @@ export default function RootLayout({ return ( <html lang="en" - className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} + className={`${inter.variable} ${instrumentSerif.variable} h-full antialiased`} > - <body className="min-h-full flex flex-col"> + <body className="min-h-full flex flex-col font-sans"> <Header /> <Container>{children}</Container> <Footer /> diff --git a/app/page.tsx b/app/page.tsx index 450df1b..f162d24 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,3 +1,67 @@ +import Hero from "@/components/content/Hero"; +import Testimonials from "@/components/content/Testimonials"; +import SectionLabel from "@/components/layout/SectionLabel"; +import AccentLink from "@/components/navigation/AccentLink"; + +type Testimonial = { + name: string; + title: string; + avatar?: string; + quote: string; +}; + +const testimonialsData: Testimonial[] = [ + { + name: "Sarah Chen", + title: "Senior Software Engineer · Patient Platforms", + quote: + "Working with Angel was the first time I saw someone treat Claude Code as a real engineering tool instead of a toy. He’d built half a workflow before the rest of us figured out it was possible.", + }, + { + name: "Marcus Whitfield", + title: "Staff Frontend Engineer", + quote: + "He owned the component library end-to-end and ran the migration without breaking a single screen. Quiet shipper — you only notice his work when you go looking for it.", + }, + { + name: "Priya Anand", + title: "Product Designer", + quote: + "Angel is the rare engineer who actually reads designs and asks questions when something feels off. Half my polish budget got returned to me on the projects he was on.", + }, + { + name: "Daniel Okafor", + title: "Engineering Manager", + quote: + "He’s at his best when the problem doesn’t fit cleanly into one stack. I’d hand him an ambiguous brief and a week later he’d come back with a working prototype and a plan.", + }, +]; + export default function HomePage() { - return <p>Home</p>; + return ( + <div> + <Hero label="Angel Mankel"> + <div className="space-y-5"> + <p className="font-serif italic text-3xl leading-[1.3]"> + I’m a creator at heart. + </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. + </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. + </p> + </div> + </Hero> + <SectionLabel label="Selected Projects" /> + <AccentLink label="See all projects" link="/projects" /> + <SectionLabel label="What people say" /> + <Testimonials items={testimonialsData} /> + </div> + ); } diff --git a/app/projects/page.tsx b/app/projects/page.tsx index 33251bb..c29dada 100644 --- a/app/projects/page.tsx +++ b/app/projects/page.tsx @@ -1,84 +1,12 @@ -// File-based routing: this file's location IS the URL. -// /app/about/page.tsx → https://yoursite.com/about -// -// Conventions: -// page.tsx = the route's UI -// layout.tsx = a wrapper shared across this folder + any nested routes -// loading.tsx = shown while the page is loading -// not-found.tsx = shown on 404 within this segment - +import Hero from "@/components/content/Hero"; import type { Metadata } from "next"; -import Link from "next/link"; -// Route-level metadata. Next reads this at build time and emits the -// correct <title>, <meta description>, OG tags, etc. into <head>. -// The root layout (/app/layout.tsx) had its own metadata; this overrides -// the title for /work specifically. export const metadata: Metadata = { title: "Work — Angel Mankel", -// description: -// "Frontend engineer with deep AI-tooling fluency. Currently at Village Medical.", }; -// This is a Server Component by default. It runs on the server, sends -// HTML to the browser, and ships zero JavaScript for this page itself. -// You only add "use client" at the top of a file when you need -// interactivity (useState, onClick, etc). -// -// Because it's a server component, you could `await fetch()` here, read -// a markdown file from disk, or query a DB directly — no API route needed. -export default function WorkPage() { +export default function ProjectsPage() { return ( - <main className="mx-auto max-w-2xl px-6 py-24"> - <h1 className="text-3xl font-semibold mb-12">Work</h1> - - <section className="space-y-6 text-base leading-relaxed text-neutral-200"> - <p> - I came up through healthcare IT — help desk, then Salesforce admin, - then software engineer at Village Medical, where I lead React and - TypeScript redesigns of patient-facing apps and the internal - component library. - </p> - <p> - In parallel I've gone deep on AI-assisted development. Claude - Code, custom slash commands, MCP integrations — the tooling layer - that's reshaping how I work across the stack. - </p> - <p> - I'm looking for remote-US product-engineering roles where - shipping React is the day job and using and building AI tooling is - first-class. - </p> - </section> - - <section className="mt-16"> - <div className="text-xs uppercase tracking-wider text-neutral-500 mb-3"> - Timeline - </div> - <ul className="font-mono text-sm space-y-1.5 text-neutral-200"> - <li> - <span className="text-neutral-500 mr-3">2024 – now</span> - Village Medical · Software Engineer - </li> - <li> - <span className="text-neutral-500 mr-3">2022 – 2024</span> - Village Medical · Salesforce Admin - </li> - <li> - <span className="text-neutral-500 mr-3">2019 – 2022</span> - Gila River · Help Desk / IT - </li> - </ul> - </section> - - <nav className="mt-16 pt-8 border-t border-neutral-800 flex gap-6 text-sm text-neutral-400"> - <Link href="/" className="hover:text-white"> - ← Back home - </Link> - <Link href="/about" className="hover:text-white"> - See about - </Link> - </nav> - </main> + <Hero label="Projects" subtitle="Selected work, written as postmortems." /> ); } diff --git a/app/uses/page.tsx b/app/uses/page.tsx index 4ac6682..6e332a7 100644 --- a/app/uses/page.tsx +++ b/app/uses/page.tsx @@ -1,16 +1,12 @@ +import Hero from "@/components/content/Hero"; import type { Metadata } from "next"; -import Link from "next/link"; - export const metadata: Metadata = { +export const metadata: Metadata = { title: "Uses — Angel Mankel", }; export default function UsesPage() { return ( - <main className="mx-auto max-w-2xl px-6 py-24"> - <h1 className="text-3xl font-semibold mb-12">Uses</h1> - - test - </main> + <Hero label="Uses" subtitle="Tools and technologies I use in my work."/> ); } diff --git a/components/content/Hero.tsx b/components/content/Hero.tsx new file mode 100644 index 0000000..241a73b --- /dev/null +++ b/components/content/Hero.tsx @@ -0,0 +1,11 @@ +export default function Hero({ label, subtitle, children }: { label: string; subtitle?: string; children?: React.ReactNode }) { + return ( + <section className="space-y-8"> + <h1 className="font-serif text-[88px] leading-none tracking-tight"> + {label} + </h1> + {subtitle && <p className="text-xl text-neutral-400 text-muted">{subtitle}</p>} + {children} + </section> + ); +} diff --git a/components/RotatingHeadline.tsx b/components/content/RotatingHeadline.tsx similarity index 100% rename from components/RotatingHeadline.tsx rename to components/content/RotatingHeadline.tsx diff --git a/components/Testimonials.tsx b/components/content/Testimonials.tsx similarity index 59% rename from components/Testimonials.tsx rename to components/content/Testimonials.tsx index b482207..c261032 100644 --- a/components/Testimonials.tsx +++ b/components/content/Testimonials.tsx @@ -1,6 +1,7 @@ "use client"; -import { useState } from "react"; +import { useEffect, useState } from "react"; +import { IconPlayerPauseFilled, IconPlayerPlayFilled, IconPoint, IconPointFilled } from '@tabler/icons-react'; type Testimonial = { name: string; @@ -15,10 +16,21 @@ type TestimonialsProps = { export default function Testimonials({ items }: TestimonialsProps) { const [activeIndex, setActiveIndex] = useState(0); + const [isPlaying, setIsPlaying] = useState(true); const active = items[activeIndex]; if (!active) return null; + useEffect(() => { + if (items.length <= 1 || !isPlaying) return; + + const id = setInterval(() => { + setActiveIndex((i) => (i + 1) % items.length); + }, 5000); + + return () => clearInterval(id); + }, [items.length, isPlaying]); + return ( <section className="w-full"> {/* header: avatar + name + title */} @@ -30,26 +42,25 @@ export default function Testimonials({ items }: TestimonialsProps) { </div> </header> - {/* quote */} <blockquote>{active.quote}</blockquote> - {/* controls: pagination dots + play/pause */} <footer className="flex items-center justify-between"> - <div className="flex gap-2"> + <div className="flex mt-3"> {items.map((_, i) => ( <button key={i} onClick={() => setActiveIndex(i)} aria-label={`Go to testimonial ${i + 1}`} > - {/* TODO: dot styling */} - {i === activeIndex ? "●" : "○"} + {i === activeIndex ? <IconPointFilled /> : <IconPoint />} </button> ))} </div> {/* TODO: play/pause button + auto-rotate timer */} - <button aria-label="Pause">⏸</button> + <button onClick={() => setIsPlaying(!isPlaying)} aria-label={isPlaying ? "Pause" : "Play"}> + {isPlaying ? <IconPlayerPauseFilled /> : <IconPlayerPlayFilled />} + </button> </footer> </section> ); diff --git a/components/layout/Container.tsx b/components/layout/Container.tsx index e013e47..262a200 100644 --- a/components/layout/Container.tsx +++ b/components/layout/Container.tsx @@ -1,7 +1,11 @@ -export default function Container({ children }: { children: React.ReactNode }) { - return ( - <div className="p-3 bg-neutral-900 flex-1"> - {children} - </div> - ) -} \ No newline at end of file +export default function Container({ + children, +}: { + children: React.ReactNode; +}) { + return ( + <div className="flex-1 min-h-0 overflow-y-auto"> + <div className="mx-auto w-full max-w-2xl px-6 py-16">{children}</div> + </div> + ); +} diff --git a/components/layout/Footer.tsx b/components/layout/Footer.tsx index 13d8b3c..1904b7c 100644 --- a/components/layout/Footer.tsx +++ b/components/layout/Footer.tsx @@ -1,9 +1,8 @@ - - export default function Footer() { - return ( - <div className="border-t border-neutral-800 text-start p-6"> - Self-hosted · Traefik · Next.js - </div> - ) -} \ No newline at end of file + return ( + <footer className="mx-auto w-full max-w-2xl px-6 py-6 flex items-center justify-between border-t border-neutral-800 text-xs text-neutral-500"> + <span>© 2026 Angel Mankel</span> + <span>Self-hosted · Traefik · Next.js</span> + </footer> + ); +} diff --git a/components/layout/Header.tsx b/components/layout/Header.tsx index 4b61d27..5e83453 100644 --- a/components/layout/Header.tsx +++ b/components/layout/Header.tsx @@ -3,16 +3,14 @@ import Nav from "./Nav"; export default function Header() { return ( - <header className="border-b border-neutral-800"> - <div className="p-6 justify-start flex items-center flex gap-5"> - <Link - href="/" - className="text-2xl font-semibold text-neutral-100 hover:text-white" - > - Angel Mankel - </Link> - <Nav /> - </div> + <header className="mx-auto w-full max-w-2xl px-6 py-5 flex items-center justify-between"> + <Link + href="/" + className="text-sm font-semibold text-neutral-100 hover:text-white" + > + Angel Mankel + </Link> + <Nav /> </header> ); } diff --git a/components/layout/Nav.tsx b/components/layout/Nav.tsx index 17399c4..06a767d 100644 --- a/components/layout/Nav.tsx +++ b/components/layout/Nav.tsx @@ -1,24 +1,36 @@ -import Link from "next/link"; +"use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; const navLinks = [ - { href: "/projects", label: "Projects" }, - { href: "/about", label: "About" }, - { href: "/uses", label: "Uses" }, + { href: "/projects", label: "projects" }, + { href: "/about", label: "about" }, + { href: "/uses", label: "uses" }, ]; export default function Nav() { - return ( - <nav className="flex gap-6 font-mono text-xs text-neutral-400"> - {navLinks.map((link) => ( - <Link - key={link.href} - href={link.href} - className="hover:text-white" - > - {link.label} - </Link> - ))} - </nav> - ) -} \ No newline at end of file + const pathname = usePathname(); + + return ( + <nav className="flex gap-6 text-[13px]"> + {navLinks.map((link) => { + const isActive = + pathname === link.href || pathname.startsWith(`${link.href}/`); + return ( + <Link + key={link.href} + href={link.href} + className={ + isActive + ? "text-neutral-100" + : "text-neutral-500 hover:text-neutral-100" + } + > + {link.label} + </Link> + ); + })} + </nav> + ); +} diff --git a/components/layout/SectionLabel.tsx b/components/layout/SectionLabel.tsx new file mode 100644 index 0000000..3dbe9f4 --- /dev/null +++ b/components/layout/SectionLabel.tsx @@ -0,0 +1,13 @@ +import { IconPointFilled } from '@tabler/icons-react'; +import { colors } from '@/constants/colors'; + +export default function SectionLabel({ label }: { label: string }) { + return ( + <div> + <h2 className="text-sm font-semibold uppercase tracking-wide text-neutral-400 flex gap-1 items-center"> + <IconPointFilled color={colors.accent}/> + {label} + </h2> + </div> + ); +} diff --git a/components/navigation/AccentLink.tsx b/components/navigation/AccentLink.tsx new file mode 100644 index 0000000..71d686c --- /dev/null +++ b/components/navigation/AccentLink.tsx @@ -0,0 +1,11 @@ +import { IconArrowNarrowRight } from '@tabler/icons-react'; +import { colors } from '@/constants/colors'; + +export default function AccentLink({ link, label }: { link: string, label: string }) { + return ( + <a href={link} className='flex gap-1 items-center'> + <p style={{ color: colors.accent }}>{label}</p> + <IconArrowNarrowRight color={colors.accent} /> + </a> + ); +} diff --git a/constants/colors.ts b/constants/colors.ts new file mode 100644 index 0000000..664f3c7 --- /dev/null +++ b/constants/colors.ts @@ -0,0 +1,32 @@ +// Neutrals +const bg = "#0a0a0a"; +const bgElevated = "#1c1c1c"; +const border = "#2e2e2e"; +const textMuted = "#6b6b6b"; +const text = "#ededed"; + +// Accent +const accent = "#d4a24a"; + +// Hover states +const bgElevatedHover = "#262626"; +const borderHover = "#3d3d3d"; +const textMutedHover = "#9a9a9a"; +const textHover = "#ffffff"; +const accentHover = "#e6b966"; + +export const colors = { + bg, + bgElevated, + border, + textMuted, + text, + accent, + hover: { + bgElevated: bgElevatedHover, + border: borderHover, + textMuted: textMutedHover, + text: textHover, + accent: accentHover, + }, +};