+
-
+
Message received.
-
- Thanks for reaching out — I'll get back to you shortly. No
- third-party form service in the middle; this went straight to
- Hadlock.
+
+ Thank you for reaching out. I will get back to you shortly. This
+ message went straight to Hadlock. No third-party form service handled
+ it.
@@ -127,7 +130,11 @@ export function ContactForm() {
}
return (
-
);
}
const inputClass =
- "w-full rounded-lg border border-white/10 bg-white/5 px-4 py-3 text-sm text-white placeholder:text-slate-500 transition focus:border-brand-400/60 focus:bg-white/10 focus:ring-2 focus:ring-brand-400/20 focus:outline-none";
+ "w-full border border-border bg-surface/40 px-4 py-3 text-sm text-text placeholder:text-faint transition focus:border-accent/60 focus:bg-surface/60 focus:ring-2 focus:ring-accent/20 focus:outline-none";
function Field({
label,
@@ -226,14 +226,16 @@ function Field({
}) {
return (
);
}
diff --git a/src/app/_components/mobile-nav.tsx b/src/app/_components/mobile-nav.tsx
new file mode 100644
index 0000000..04ecfcf
--- /dev/null
+++ b/src/app/_components/mobile-nav.tsx
@@ -0,0 +1,99 @@
+"use client";
+
+import { useEffect, useState } from "react";
+import { createPortal } from "react-dom";
+
+const links = [
+ { href: "#services", label: "Services" },
+ { href: "#approach", label: "Approach" },
+ { href: "#projects", label: "Projects" },
+ { href: "#name", label: "The name" },
+ { href: "#contact", label: "Contact" },
+];
+
+export function MobileNav() {
+ const [isOpen, setIsOpen] = useState(false);
+ const [isMounted, setIsMounted] = useState(false);
+
+ useEffect(() => {
+ setIsMounted(true);
+ }, []);
+
+ return (
+ <>
+
+
+ {isMounted &&
+ createPortal(
+
setIsOpen(false)}
+ aria-hidden="true"
+ />,
+ document.body,
+ )}
+
+
+ >
+ );
+}
+
+function MenuIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+function XIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/src/app/_components/theme-toggle.tsx b/src/app/_components/theme-toggle.tsx
new file mode 100644
index 0000000..79b0d6e
--- /dev/null
+++ b/src/app/_components/theme-toggle.tsx
@@ -0,0 +1,83 @@
+"use client";
+
+import { useEffect, useState } from "react";
+
+const storageKey = "hadlock_theme";
+
+const prefersLight = () =>
+ window.matchMedia("(prefers-color-scheme: light)").matches;
+
+export function ThemeToggle() {
+ const [isLight, setIsLight] = useState(false);
+
+ useEffect(() => {
+ const saved = window.localStorage.getItem(storageKey);
+ if (saved === "light" || saved === "dark") {
+ setIsLight(saved === "light");
+ return;
+ }
+ setIsLight(prefersLight());
+ }, []);
+
+ function toggle() {
+ const saved = window.localStorage.getItem(storageKey);
+ const next = !(saved === "light" || saved === "dark"
+ ? saved === "light"
+ : prefersLight());
+ setIsLight(next);
+ document.documentElement.classList.remove("light", "dark");
+ document.documentElement.classList.add(next ? "light" : "dark");
+ try {
+ window.localStorage.setItem(storageKey, next ? "light" : "dark");
+ } catch {}
+ }
+
+ return (
+
+ );
+}
+
+function SunIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
+
+function MoonIcon({ className }: { className?: string }) {
+ return (
+
+ );
+}
diff --git a/src/app/api/contact/route.ts b/src/app/api/contact/route.ts
index af9590b..cdd3311 100644
--- a/src/app/api/contact/route.ts
+++ b/src/app/api/contact/route.ts
@@ -12,7 +12,7 @@ const contactSchema = z.object({
.trim()
.min(10, "Tell me a little more about what you need")
.max(5000),
- // Honeypot: hidden from real users, tempting to bots. Left empty by humans.
+ // A honeypot. It is hidden from real users and tempting to bots. Humans leave it empty.
website: z.string().optional(),
});
diff --git a/src/app/api/trpc/[trpc]/route.ts b/src/app/api/trpc/[trpc]/route.ts
deleted file mode 100644
index f41a82e..0000000
--- a/src/app/api/trpc/[trpc]/route.ts
+++ /dev/null
@@ -1,34 +0,0 @@
-import { fetchRequestHandler } from "@trpc/server/adapters/fetch";
-import { type NextRequest } from "next/server";
-
-import { env } from "~/env";
-import { appRouter } from "~/server/api/root";
-import { createTRPCContext } from "~/server/api/trpc";
-
-/**
- * This wraps the `createTRPCContext` helper and provides the required context for the tRPC API when
- * handling a HTTP request (e.g. when you make requests from Client Components).
- */
-const createContext = async (req: NextRequest) => {
- return createTRPCContext({
- headers: req.headers,
- });
-};
-
-const handler = (req: NextRequest) =>
- fetchRequestHandler({
- endpoint: "/api/trpc",
- req,
- router: appRouter,
- createContext: () => createContext(req),
- onError:
- env.NODE_ENV === "development"
- ? ({ path, error }) => {
- console.error(
- `❌ tRPC failed on ${path ?? "
"}: ${error.message}`,
- );
- }
- : undefined,
- });
-
-export { handler as GET, handler as POST };
diff --git a/src/app/icon.svg b/src/app/icon.svg
index d8b123f..1a0f25f 100644
--- a/src/app/icon.svg
+++ b/src/app/icon.svg
@@ -1,10 +1,17 @@
-