diff --git a/components/portfolio/ClosingCta.tsx b/components/portfolio/ClosingCta.tsx new file mode 100644 index 0000000..38adb95 --- /dev/null +++ b/components/portfolio/ClosingCta.tsx @@ -0,0 +1,90 @@ +import Link from "next/link"; +import { contact } from "@/lib/portfolio"; + +const sectionLinks = [ + { label: "Work", href: "#work" }, + { label: "Capabilities", href: "#capabilities" }, + { label: "Operator", href: "#operator" }, + { label: "Contact", href: "#contact" }, +]; + +const elsewhereLinks = [ + { label: "Git", href: contact.git }, + { label: "LinkedIn", href: contact.linkedIn }, +]; + +export function ClosingCta() { + const year = new Date().getFullYear(); + + return ( + + ); +} diff --git a/components/portfolio/ContactTerminal.tsx b/components/portfolio/ContactTerminal.tsx new file mode 100644 index 0000000..1906512 --- /dev/null +++ b/components/portfolio/ContactTerminal.tsx @@ -0,0 +1,114 @@ +"use client"; + +import { useEffect, useRef, useState } from "react"; +import { contact } from "@/lib/portfolio"; + +type CopyTarget = "email" | "ssh"; +type CopyStatus = "idle" | "copied" | "failed"; + +const rows = [ + { label: "EMAIL", value: contact.email, kind: "copy" as const, target: "email" as CopyTarget }, + { label: "GIT", value: contact.git, kind: "link" as const }, + { label: "LINKEDIN", value: contact.linkedIn, kind: "link" as const }, + { label: "SSH", value: contact.ssh, kind: "copy" as const, target: "ssh" as CopyTarget }, +]; + +export function ContactTerminal() { + const [copyState, setCopyState] = useState>({ + email: "idle", + ssh: "idle", + }); + const resetTimers = useRef>>>({}); + + useEffect(() => { + const timers = resetTimers.current; + + return () => { + Object.values(timers).forEach((timer) => { + if (timer) { + clearTimeout(timer); + } + }); + }; + }, []); + + async function copyValue(target: CopyTarget, value: string) { + if (resetTimers.current[target]) { + clearTimeout(resetTimers.current[target]); + } + + try { + await navigator.clipboard.writeText(value); + setCopyState((current) => ({ ...current, [target]: "copied" })); + } catch { + setCopyState((current) => ({ ...current, [target]: "failed" })); + } + + resetTimers.current[target] = setTimeout(() => { + setCopyState((current) => ({ ...current, [target]: "idle" })); + }, 2000); + } + + return ( +
+
+
+

> open contact.channel

+ SECURE HANDOFF +
+ +
+
+

CONTACT TERMINAL

+

+ Send signal. +

+

+ Reach the operator directly for systems work, hardened infrastructure, or unusually interesting problems. +

+
+ +
+ {rows.map((row) => ( +
+

{row.label}

+ {row.kind === "copy" ? ( + + ) : ( + + {row.value} + + + )} +
+ ))} + +
+

AVAILABILITY

+

+

+
+
+
+
+
+ ); +}