Skip to content
Merged
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
37 changes: 17 additions & 20 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 2 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,13 +34,12 @@
"clsx": "2.1.1",
"framer-motion": "14.9.0",
"lucide-react": "1.67.0",
"next": "16.4.4",
"next": "16.4.5",
"next-mdx-remote": "6.0.0",
"next-themes": "0.4.6",
"react": "19.3.1",
"react-dom": "19.3.1",
"react-hook-form": "7.91.0",
"react-icons": "5.7.0",
"resend": "6.37.0",
"sonner": "2.0.8",
"tailwind-merge": "3.7.0",
Expand All @@ -52,7 +51,7 @@
"devDependencies": {
"@biomejs/biome": "2.6.4",
"@chromatic-com/storybook": "5.6.0",
"@next/bundle-analyzer": "16.4.4",
"@next/bundle-analyzer": "16.4.5",
"@storybook/addon-a11y": "10.6.5",
"@storybook/addon-docs": "10.6.5",
"@storybook/addon-links": "10.6.5",
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/a11y/hero.a11y.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { act, render } from "@testing-library/react";
import axe from "axe-core";
import { describe, expect, it } from "vitest";
import { Hero } from "@/components/sections/Hero";
Expand All @@ -11,7 +11,7 @@ async function runAxe(node: HTMLElement) {

describe("Hero accessibility", () => {
it("has no critical violations", async () => {
const { container } = render(<Hero />);
const { container } = await act(async () => render(<Hero />));
const results = await runAxe(container);
if (results.violations.length > 0) {
// Log details for debugging
Expand Down
4 changes: 2 additions & 2 deletions src/__tests__/a11y/sections.a11y.test.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { render } from "@testing-library/react";
import { act, render } from "@testing-library/react";
import axe from "axe-core";
import { describe, expect, it } from "vitest";

Expand Down Expand Up @@ -29,7 +29,7 @@ const components = [
describe("Sections accessibility", () => {
components.forEach(([label, Comp]) => {
it(`${label} has no critical violations`, async () => {
const { container } = render(<Comp />);
const { container } = await act(async () => render(<Comp />));
// Run axe sequentially by awaiting each run
await new Promise((resolve) => setTimeout(resolve, 100));
const results = await runAxe(container);
Expand Down
23 changes: 0 additions & 23 deletions src/app/api/vitals/route.ts

This file was deleted.

2 changes: 1 addition & 1 deletion src/app/projects/[slug]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import type { Metadata } from "next";
import Image from "next/image";
import Link from "next/link";
import { notFound } from "next/navigation";
import { SiGithub as Github } from "react-icons/si";
import { projects } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { MdxContent } from "@/components/layout/MdxContent";
import { ProjectJsonLd } from "@/components/seo/ProjectJsonLd";
import { Badge } from "@/components/ui/Badge";
Expand Down
13 changes: 0 additions & 13 deletions src/app/web-vitals.ts

This file was deleted.

16 changes: 16 additions & 0 deletions src/components/icons/GithubIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { SVGProps } from "react";

export function GithubIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
focusable="false"
{...props}
>
<path d="M12 .297c-6.63 0-12 5.373-12 12 0 5.303 3.438 9.8 8.205 11.385.6.113.82-.258.82-.577 0-.285-.01-1.04-.015-2.04-3.338.724-4.042-1.61-4.042-1.61C4.422 18.07 3.633 17.7 3.633 17.7c-1.087-.744.084-.729.084-.729 1.205.084 1.838 1.236 1.838 1.236 1.07 1.835 2.809 1.305 3.495.998.108-.776.417-1.305.76-1.605-2.665-.3-5.466-1.332-5.466-5.93 0-1.31.465-2.38 1.235-3.22-.135-.303-.54-1.523.105-3.176 0 0 1.005-.322 3.3 1.23.96-.267 1.98-.399 3-.405 1.02.006 2.04.138 3 .405 2.28-1.552 3.285-1.23 3.285-1.23.645 1.653.24 2.873.12 3.176.765.84 1.23 1.91 1.23 3.22 0 4.61-2.805 5.625-5.475 5.92.42.36.81 1.096.81 2.22 0 1.606-.015 2.896-.015 3.286 0 .315.21.69.825.57C20.565 22.092 24 17.592 24 12.297c0-6.627-5.373-12-12-12" />
</svg>
);
}
16 changes: 16 additions & 0 deletions src/components/icons/InstagramIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { SVGProps } from "react";

export function InstagramIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 24 24"
fill="currentColor"
aria-hidden="true"
focusable="false"
{...props}
>
<path d="M7.0301.084c-1.2768.0602-2.1487.264-2.911.5634-.7888.3075-1.4575.72-2.1228 1.3877-.6652.6677-1.075 1.3368-1.3802 2.127-.2954.7638-.4956 1.6365-.552 2.914-.0564 1.2775-.0689 1.6882-.0626 4.947.0062 3.2586.0206 3.6671.0825 4.9473.061 1.2765.264 2.1482.5635 2.9107.308.7889.72 1.4573 1.388 2.1228.6679.6655 1.3365 1.0743 2.1285 1.38.7632.295 1.6361.4961 2.9134.552 1.2773.056 1.6884.069 4.9462.0627 3.2578-.0062 3.668-.0207 4.9478-.0814 1.28-.0607 2.147-.2652 2.9098-.5633.7889-.3086 1.4578-.72 2.1228-1.3881.665-.6682 1.0745-1.3378 1.3795-2.1284.2957-.7632.4966-1.636.552-2.9124.056-1.2809.0692-1.6898.063-4.948-.0063-3.2583-.021-3.6668-.0817-4.9465-.0607-1.2797-.264-2.1487-.5633-2.9117-.3084-.7889-.72-1.4568-1.3876-2.1228C21.2982 1.33 20.628.9208 19.8378.6165 19.074.321 18.2017.1197 16.9244.0645 15.6471.0093 15.236-.005 11.977.0014 8.718.0076 8.31.0215 7.0301.0839m.1402 21.6932c-1.17-.0509-1.8053-.2453-2.2287-.408-.5606-.216-.96-.4771-1.3819-.895-.422-.4178-.6811-.8186-.9-1.378-.1644-.4234-.3624-1.058-.4171-2.228-.0595-1.2645-.072-1.6442-.079-4.848-.007-3.2037.0053-3.583.0607-4.848.05-1.169.2456-1.805.408-2.2282.216-.5613.4762-.96.895-1.3816.4188-.4217.8184-.6814 1.3783-.9003.423-.1651 1.0575-.3614 2.227-.4171 1.2655-.06 1.6447-.072 4.848-.079 3.2033-.007 3.5835.005 4.8495.0608 1.169.0508 1.8053.2445 2.228.408.5608.216.96.4754 1.3816.895.4217.4194.6816.8176.9005 1.3787.1653.4217.3617 1.056.4169 2.2263.0602 1.2655.0739 1.645.0796 4.848.0058 3.203-.0055 3.5834-.061 4.848-.051 1.17-.245 1.8055-.408 2.2294-.216.5604-.4763.96-.8954 1.3814-.419.4215-.8181.6811-1.3783.9-.4224.1649-1.0577.3617-2.2262.4174-1.2656.0595-1.6448.072-4.8493.079-3.2045.007-3.5825-.006-4.848-.0608M16.953 5.5864A1.44 1.44 0 1 0 18.39 4.144a1.44 1.44 0 0 0-1.437 1.4424M5.8385 12.012c.0067 3.4032 2.7706 6.1557 6.173 6.1493 3.4026-.0065 6.157-2.7701 6.1506-6.1733-.0065-3.4032-2.771-6.1565-6.174-6.1498-3.403.0067-6.156 2.771-6.1496 6.1738M8 12.0077a4 4 0 1 1 4.008 3.9921A3.9996 3.9996 0 0 1 8 12.0077" />
</svg>
);
}
16 changes: 16 additions & 0 deletions src/components/icons/LinkedinIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import type { SVGProps } from "react";

export function LinkedinIcon(props: SVGProps<SVGSVGElement>) {
return (
<svg
xmlns="http://www.w3.org/2000/svg"
viewBox="0 0 448 512"
fill="currentColor"
aria-hidden="true"
focusable="false"
{...props}
>
<path d="M416 32H31.9C14.3 32 0 46.5 0 64.3v383.4C0 465.5 14.3 480 31.9 480H416c17.6 0 32-14.5 32-32.3V64.3c0-17.8-14.4-32.3-32-32.3zM135.4 416H69V202.2h66.5V416zm-33.2-243c-21.3 0-38.5-17.3-38.5-38.5S80.9 96 102.2 96c21.2 0 38.5 17.3 38.5 38.5 0 21.3-17.2 38.5-38.5 38.5zm282.1 243h-66.4V312c0-24.8-.5-56.7-34.5-56.7-34.6 0-39.9 27-39.9 54.9V416h-66.4V202.2h63.7v29.2h.9c8.9-16.8 30.6-34.5 62.9-34.5 67.2 0 79.7 44.3 79.7 101.9V416z" />
</svg>
);
}
2 changes: 1 addition & 1 deletion src/components/layout/ClientFooter.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import { ArrowUp, Heart, Mail } from "lucide-react";
import Link from "next/link";
import { useEffect, useId, useLayoutEffect, useRef, useState } from "react";
import { SiGithub as Github } from "react-icons/si";
import { bio } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { Button } from "@/components/ui/Button";
import { usePrefersReducedMotion } from "@/hooks/usePrefersReducedMotion";
import { useScroll } from "@/hooks/useScroll";
Expand Down
2 changes: 1 addition & 1 deletion src/components/layout/ProjectList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@ import { ArrowUpRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { useState } from "react";
import { SiGithub as Github } from "react-icons/si";
import { projects } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";

Expand Down
4 changes: 2 additions & 2 deletions src/components/layout/SocialLink.tsx
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
"use client";

import type { IconType } from "react-icons";
import type { ComponentType, SVGProps } from "react";
import { cn } from "@/lib/utils";

// Define the props for our reusable component
interface SocialLinkProps {
href: string;
name: string;
icon: IconType;
icon: ComponentType<SVGProps<SVGSVGElement>>;
colorClass: string;
}

Expand Down
9 changes: 5 additions & 4 deletions src/components/sections/Hero.data.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import { FileText, Mail } from "lucide-react";
import type { IconType } from "react-icons";
import { FaLinkedin as Linkedin } from "react-icons/fa";
import { SiGithub as Github, SiInstagram as Instagram } from "react-icons/si";
import type { ComponentType, SVGProps } from "react";
import { bio } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { InstagramIcon as Instagram } from "@/components/icons/InstagramIcon";
import { LinkedinIcon as Linkedin } from "@/components/icons/LinkedinIcon";

export const HERO_TAGLINES = [
"Systems Engineer & Toolmaker",
Expand All @@ -22,7 +23,7 @@ export const HERO_STACK_RIBBONS = [
export type HeroSocialLink = {
href: string;
name: string;
icon: IconType;
icon: ComponentType<SVGProps<SVGSVGElement>>;
colorClass: string;
};

Expand Down
2 changes: 1 addition & 1 deletion src/components/sections/ProjectsPreview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ import { ArrowUpRight } from "lucide-react";
import dynamic from "next/dynamic";
import Image from "next/image";
import Link from "next/link";
import { SiGithub as Github } from "react-icons/si";
import { projects } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { useSectionMotion } from "@/hooks/useSectionMotion";
Expand Down
2 changes: 1 addition & 1 deletion src/components/sections/ProjectsPreviewAnimated.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ import { motion } from "framer-motion";
import { ArrowUpRight } from "lucide-react";
import Image from "next/image";
import Link from "next/link";
import { SiGithub as Github } from "react-icons/si";
import { projects } from "#velite";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { Badge } from "@/components/ui/Badge";
import { Button } from "@/components/ui/Button";
import { ROUTES } from "@/lib/routes";
Expand Down
2 changes: 1 addition & 1 deletion src/components/ui/DropdownMenu.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {
Users,
} from "lucide-react";
import * as React from "react";
import { SiGithub as Github } from "react-icons/si";
import { GithubIcon as Github } from "@/components/icons/GithubIcon";
import { Button } from "@/components/ui/Button"; // Confirmed: File is Button with capital B
import {
DropdownMenu,
Expand Down
1 change: 0 additions & 1 deletion tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
"isolatedModules": true,
"jsx": "react-jsx",
"incremental": true,
"allowSyntheticDefaultImports": true,
"paths": {
"#velite": ["./.velite"],
"#velite/*": ["./.velite/*"],
Expand Down
2 changes: 1 addition & 1 deletion vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ export default defineConfig({
provider: "v8",
reportsDirectory: "coverage",
reporter: ["text", "json", "html"],
thresholds: { lines: 0.7, branches: 0.6, functions: 0.65, statements: 0.7 },
thresholds: { lines: 0.84, branches: 0.73, functions: 0.82, statements: 0.83 },
},
projects: [
{
Expand Down
9 changes: 9 additions & 0 deletions vitest.setup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,15 @@ Object.defineProperty(globalThis, "IntersectionObserver", {
value: IntersectionObserver,
});

// Mock HTMLCanvasElement.getContext for jsdom (it has none) so the canvas-backed scenes don't
// spam "Not implemented: HTMLCanvasElement's getContext() method" on every test.
if (typeof HTMLCanvasElement !== "undefined") {
Object.defineProperty(HTMLCanvasElement.prototype, "getContext", {
configurable: true,
value: () => null,
});
}

// Mock window.matchMedia for usePrefersReducedMotion.
//
// Force `reduce` to match so tests take the calm path deterministically, but answer
Expand Down
Loading