Files
hadlock.tech/src/app/page.tsx
T

761 lines
27 KiB
TypeScript

import Image from "next/image";
import Link from "next/link";
import { ContactForm } from "~/app/_components/contact-form";
import { MobileNav } from "~/app/_components/mobile-nav";
import { ThemeToggle } from "~/app/_components/theme-toggle";
const services = [
{
title: "Networks & Infrastructure",
body: "UniFi networking with wired links, wireless access, VLANs, and hardware that stays up when it counts.",
tag: "UniFi",
},
{
title: "Servers & Cloud",
body: "Local and cloud servers with web hosting, S3 storage, and backups tested by restore.",
tag: "Cloud",
},
{
title: "Databases",
body: "Designed, deployed, and operated under the stack, with backups, monitoring, and tested restores.",
tag: "Data",
},
{
title: "Web & Applications",
body: "Sites built and hosted from the marketing page to the ticket platform. When a site fails, one number gets the call.",
tag: "Web",
},
{
title: "Commerce & Ticketing",
body: "A ticket system built from scratch, plus a Shopify store running real event-day traffic, not a demo.",
tag: "Commerce",
},
{
title: "On-Site Operations",
body: "Event-day coverage at the counter, the gate, and wherever else the day requires. Most clients get this as part of the job.",
tag: "On-site",
},
];
export default function Home() {
return (
<div className="relative min-h-screen overflow-x-clip">
<Header />
<main>
<Hero />
<Services />
<Approach />
<Projects />
<NameStory />
<Contact />
</main>
<Footer />
</div>
);
}
function Wordmark() {
return (
<Link href="/" className="group flex shrink-0 items-center">
<img
src="/branding/logo_white.svg"
alt="Hadlock Technologies"
className="theme-logo-dark h-7 w-auto transition-opacity group-hover:opacity-75"
/>
<img
src="/branding/logo_blue.svg"
alt="Hadlock Technologies"
className="theme-logo-light h-7 w-auto transition-opacity group-hover:opacity-75"
/>
</Link>
);
}
function Header() {
return (
<header className="sticky top-0 z-50">
<div className="mx-auto max-w-6xl px-4 pt-2">
<div className="border-border bg-bg relative flex h-14 items-center justify-between border px-4 shadow-lg">
<Wordmark />
<nav className="text-text-secondary hidden items-center gap-6 text-sm font-medium md:flex">
<a href="#services" className="hover:text-text transition">
Services
</a>
<a href="#approach" className="hover:text-text transition">
Approach
</a>
<a href="#projects" className="hover:text-text transition">
Projects
</a>
<a href="#name" className="hover:text-text transition">
The name
</a>
</nav>
<div className="flex items-center gap-2">
<ThemeToggle />
<a
href="#contact"
className="bg-text text-bg px-4 py-2 text-sm font-semibold transition hover:opacity-80"
>
Get in touch
</a>
<MobileNav />
</div>
</div>
</div>
</header>
);
}
const capabilities = [
"Networks",
"Servers",
"Cloud",
"Storage",
"Databases",
"Web",
"Commerce",
];
function Hero() {
return (
<section className="relative isolate -mt-16 flex min-h-screen flex-col overflow-hidden">
{/* The Hadlock Pond image fills the section with a slight darkening filter */}
<div className="absolute inset-0 -z-20">
<Image
src="/hadlock-pond.jpg"
alt="Hadlock Pond at dawn in Fort Ann, New York"
fill
priority
sizes="100vw"
className="hero-photo object-cover"
/>
</div>
{/* Scrims darken the top for the nav and the left for the text. They fade the base into the page */}
<div className="from-scrim-b-from to-scrim-b-to absolute inset-0 -z-10 bg-gradient-to-b via-transparent" />
<div className="from-scrim-l-from via-scrim-l-mid to-scrim-l-end absolute inset-0 -z-10 bg-gradient-to-r" />
{/* Dark overlay, same treatment as the Racetix card, in dark mode */}
<div className="from-hero-overlay-start via-hero-overlay-mid to-hero-overlay-end absolute inset-0 -z-10 bg-gradient-to-r" />
<div className="mx-auto flex w-full max-w-6xl flex-1 flex-col items-start justify-center px-6 pt-32 pb-16">
<img
src="/branding/logo_white.svg"
alt="Hadlock Technologies"
className="theme-logo-dark h-24 w-auto"
/>
<img
src="/branding/logo_blue.svg"
alt="Hadlock Technologies"
className="theme-logo-light h-24 w-auto"
/>
<h1 className="text-text mt-7 max-w-4xl text-4xl font-semibold tracking-tight lowercase sm:text-6xl sm:leading-[1.05]">
Everything technical, <span className="text-accent">handled.</span>
</h1>
<p className="text-text-secondary mt-6 max-w-xl text-lg leading-relaxed">
The same stack powers clients across Long Island race tracks,
retailers, and web platforms. UniFi, cloud infrastructure, databases,
websites, ticketing, and commerce, built and operated by one person.
Hadlock Technologies is that work under one name.
</p>
<div className="mt-9 flex flex-wrap items-center gap-4">
<a
href="#contact"
className="bg-accent text-on-accent hover:bg-accent-hover px-6 py-3 text-sm font-semibold transition"
>
Start a conversation
</a>
<a
href="#services"
className="border-border-strong bg-surface/50 text-text hover:border-border-strong hover:bg-surface border px-6 py-3 text-sm font-semibold backdrop-blur-sm transition"
>
See what we do
</a>
</div>
</div>
{/* A capability strip, where DUNE puts its client logos */}
<div className="bg-bg/40 border-border relative border-t backdrop-blur-sm">
<div className="text-muted mx-auto flex max-w-6xl flex-wrap items-center gap-x-6 gap-y-2 px-6 py-5 text-xs tracking-widest uppercase">
<span className="text-faint">Working across</span>
{capabilities.map((c) => (
<span key={c} className="text-text-secondary">
{c}
</span>
))}
</div>
</div>
</section>
);
}
function Services() {
return (
<section id="services" className="scroll-mt-20">
<div className="mx-auto max-w-6xl px-6 py-24">
<SectionHeading
eyebrow="What we do"
title="One person. The whole stack."
description=" Most places use a network contractor, a hosting company, a web
agency, and a POS vendor. That is four invoices and four different
people to call when something breaks. Hadlock is all four."
/>
<div className="border-border bg-border mt-14 grid grid-cols-1 gap-px overflow-hidden border sm:grid-cols-2 lg:grid-cols-3">
{services.map((s) => (
<div
key={s.title}
className="group bg-surface hover:bg-surface-hover relative p-7 transition"
>
<div className="border-accent-border bg-accent-bg text-accent mb-4 inline-flex items-center border px-2.5 py-1 text-xs font-medium">
{s.tag}
</div>
<h3 className="text-text text-lg font-semibold">{s.title}</h3>
<p className="text-muted mt-2.5 text-sm leading-relaxed">
{s.body}
</p>
</div>
))}
</div>
</div>
</section>
);
}
function Approach() {
const points = [
{
title: "Full ownership",
body: "Everything from the network cable to the interface is built and owned in one place. Nothing gets lost in a handoff, because there is no handoff.",
},
{
title: "Hands-on, not hands-off",
body: "The person who designed the system is the person who fixes it, on-site when it matters. No ticket queue for an answer.",
},
{
title: "Built to stay up",
body: "Backups are tested. Monitoring alerts before a problem surfaces. The systems handle the busy night, not just an average Tuesday.",
},
];
return (
<section
id="approach"
className="bg-surface/40 border-border-faint scroll-mt-20 border-y"
>
<div className="mx-auto max-w-6xl px-6 py-24">
<div className="grid grid-cols-1 gap-14 lg:grid-cols-[1fr_1.2fr]">
<div>
<SectionHeading
eyebrow="The approach"
title="No title covers the job. There is not one."
description="The same person is the software engineer, the network admin, and the systems operator for every client. On a busy night, that person might work the counter too. The job is whatever keeps the place running."
/>
</div>
<div className="border-border bg-border grid gap-px overflow-hidden border">
{points.map((p) => (
<div key={p.title} className="bg-surface p-7">
<h3 className="text-text text-base font-semibold">{p.title}</h3>
<p className="text-muted mt-2 text-sm leading-relaxed">
{p.body}
</p>
</div>
))}
</div>
</div>
</div>
</section>
);
}
const racetixFeatures = [
{
title: "Multi-tenant by track",
body: "Each track runs its own catalog, prices, and gate.",
},
{
title: "QR gate scanning",
body: "The scanner app verifies tickets offline at the gate.",
},
{
title: "Apple & Google Wallet",
body: "Paid tickets go straight into the phone wallet.",
},
{
title: "Stripe payments",
body: "Stripe Connect runs checkout, refunds, and tax.",
},
];
const beenvoiceFeatures = [
{
title: "Invoices that stay organized",
body: "Draft line items, apply taxes, send a link, and export a polished PDF.",
},
{
title: "Built-in time clock",
body: "Track billable hours as you work, then pull them into an invoice without retyping.",
},
{
title: "Recurring invoices",
body: "Set up retainers once, then let beenvoice generate the next invoice on schedule.",
},
{
title: "Send and follow up",
body: "Email invoices from the app and keep status visible from draft through paid.",
},
];
const racehubFeatures = [
{
title: "Events & schedule",
body: "Race events, special events, and iCal feeds for every fan's calendar.",
},
{
title: "Standings & results",
body: "Points, lineups, and results served live from the Hotlap API.",
},
{
title: "News & media",
body: "Articles, announcements, and promotions published from the CMS.",
},
{
title: "Granular permissions",
body: "97+ permissions, role-based access, and a full audit trail.",
},
];
const hristudioFeatures = [
{
title: "Visual experiment designer",
body: "Drag-and-drop blocks build branching protocols. No code required.",
},
{
title: "Guided wizard interface",
body: "Step-by-step trial execution with every action logged in time.",
},
{
title: "Robot-agnostic",
body: "Design once, run on any robot — NAO, Pepper, or TurtleBot.",
},
{
title: "Automatic data capture",
body: "Synchronized multi-modal streams, timestamped and exportable.",
},
];
function Projects() {
return (
<section id="projects" className="scroll-mt-20">
<div className="mx-auto max-w-6xl px-6 py-24">
<SectionHeading
eyebrow="Projects"
title="Four products, built end to end."
description="Racetix sells tickets for short tracks. beenvoice invoices for freelancers and small businesses. RaceHub runs the official Riverhead Raceway site. HRIStudio makes human-robot interaction research reproducible. All four run in production, operated end to end."
/>
</div>
<a
href="https://racetix.us"
target="_blank"
rel="noreferrer"
className="group border-border relative isolate block overflow-hidden border-y"
>
<div className="absolute inset-0 -z-20">
<Image
src="/racetix-hero.webp"
alt="Race cars on a short track under the lights"
fill
sizes="100vw"
className="racetix-photo object-cover"
/>
</div>
<div className="from-racetix-overlay-start via-racetix-overlay-mid to-racetix-overlay-end absolute inset-0 -z-10 bg-gradient-to-r" />
<div className="bg-racetix-red/20 absolute -bottom-16 -left-16 h-64 w-64 blur-[100px]" />
<div className="relative mx-auto grid max-w-6xl gap-10 px-6 py-16 md:grid-cols-[1.1fr_0.9fr] md:py-24">
<div>
<img
src="/logo_white.svg"
alt="Racetix"
className="theme-logo-dark h-8 w-auto"
/>
<img
src="/logo_red.svg"
alt="Racetix"
className="theme-logo-light h-8 w-auto"
/>
<p className="text-text-secondary font-display mt-6 max-w-xl text-base leading-relaxed">
Multi-tenant ticketing for short tracks. It owns ticket products,
orders, payments, admission redemptions, and ticket email. Tracks
embed checkout in their own site through the versioned API.
</p>
<span className="text-racetix-red font-display mt-8 inline-flex items-center gap-2 text-sm font-bold tracking-widest uppercase transition group-hover:opacity-70">
Explore Racetix
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 transition-transform group-hover:translate-x-1"
aria-hidden="true"
>
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</span>
</div>
<div className="grid content-center gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-2">
{racetixFeatures.map((f) => (
<div key={f.title} className="flex items-start gap-3">
<span className="text-racetix-red mt-0.5 shrink-0">
<CheckIcon className="h-4 w-4" />
</span>
<div>
<h3 className="text-text font-display text-sm font-bold tracking-wide uppercase">
{f.title}
</h3>
<p className="text-text-secondary/80 font-display mt-1 text-xs leading-relaxed">
{f.body}
</p>
</div>
</div>
))}
</div>
</div>
</a>
<a
href="https://beenvoice.app"
target="_blank"
rel="noreferrer"
className="group border-border relative isolate block overflow-hidden border-y"
>
{/* beenvoice backdrop: hairline grid + soft animated blob */}
<div className="beenvoice-grid absolute inset-0 -z-20" />
<div className="beenvoice-blob absolute top-1/2 left-1/2 -z-20 h-[800px] w-[800px] -translate-x-1/2 -translate-y-1/2 rounded-full bg-[var(--beenvoice-blob)] blur-3xl" />
{/* Scrim keeps the text readable in both themes */}
<div className="from-bg/80 via-bg/35 absolute inset-0 -z-10 bg-gradient-to-r to-transparent" />
<div className="relative mx-auto grid max-w-6xl gap-10 px-6 py-16 md:grid-cols-[1.1fr_0.9fr] md:py-24">
<div>
<div className="text-text flex items-center font-mono text-2xl font-bold tracking-tight">
<span>$</span>
<span className="inline-block w-1" />
<span>been</span>
<span className="text-text/70">voice</span>
</div>
<h3 className="text-text mt-6 max-w-xl font-serif text-3xl font-semibold tracking-tight sm:text-4xl">
Run your freelance admin from one place.
</h3>
<p className="text-text-secondary font-app-sans mt-4 max-w-xl text-base leading-relaxed">
beenvoice manages clients, tracks time, sends invoices, and stays
on top of getting paid, without the weight of a full accounting
suite.
</p>
<span className="text-text mt-8 inline-flex items-center gap-2 font-mono text-sm font-bold tracking-widest uppercase transition group-hover:opacity-70">
Explore beenvoice
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 transition-transform group-hover:translate-x-1"
aria-hidden="true"
>
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</span>
</div>
<div className="grid content-center gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-2">
{beenvoiceFeatures.map((f) => (
<div key={f.title} className="flex items-start gap-3">
<span className="text-text mt-0.5 shrink-0">
<CheckIcon className="h-4 w-4" />
</span>
<div>
<h4 className="text-text font-serif text-sm font-semibold">
{f.title}
</h4>
<p className="text-text-secondary/80 font-app-sans mt-1 text-xs leading-relaxed">
{f.body}
</p>
</div>
</div>
))}
</div>
</div>
</a>
<a
href="https://riverheadraceway.com"
target="_blank"
rel="noreferrer"
className="group border-border relative isolate block overflow-hidden border-y"
>
{/* racehub backdrop: dark checkered band, stays dark in both themes */}
<div className="racehub-checkered absolute inset-0 -z-20" />
<div className="absolute inset-0 -z-10 bg-[radial-gradient(90%_90%_at_50%_50%,var(--racehub-red)/30,var(--racehub-red)/10_60%,transparent_85%)]" />
<div className="absolute inset-0 -z-10 bg-gradient-to-l from-black/70 to-transparent" />
<div className="relative mx-auto grid max-w-6xl gap-10 px-6 py-16 md:grid-cols-[1.1fr_0.9fr] md:py-24">
<div>
<img
src="/racehub/logo_white.svg"
alt="Riverhead Raceway"
className="h-9 w-auto"
/>
<h3 className="font-racehub mt-6 max-w-xl text-3xl font-bold tracking-tight text-white uppercase sm:text-4xl">
The official site for Riverhead Raceway.
</h3>
<p className="font-racehub mt-4 max-w-xl text-base leading-relaxed text-white/80">
RaceHub is the website and CMS for Long Island&apos;s only auto
racing venue. It publishes events, news, standings, and results,
with live 2026 race data served from the Hotlap API.
</p>
<span className="text-racehub-red font-racehub mt-8 inline-flex items-center gap-2 text-sm font-bold tracking-widest uppercase transition group-hover:opacity-70">
Visit Riverhead Raceway
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 transition-transform group-hover:translate-x-1"
aria-hidden="true"
>
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</span>
</div>
<div className="grid content-center gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-2">
{racehubFeatures.map((f) => (
<div key={f.title} className="flex items-start gap-3">
<span className="text-racehub-red mt-0.5 shrink-0">
<CheckIcon className="h-4 w-4" />
</span>
<div>
<h4 className="font-racehub text-sm font-bold tracking-wide text-white uppercase">
{f.title}
</h4>
<p className="font-racehub mt-1 text-xs leading-relaxed text-white/70">
{f.body}
</p>
</div>
</div>
))}
</div>
</div>
</a>
<a
href="https://github.com/soconnor0919/hristudio"
target="_blank"
rel="noreferrer"
className="group border-border relative isolate block overflow-hidden border-y"
>
{/* hristudio backdrop: teal dot grid, adapts to theme */}
<div className="hristudio-dots absolute inset-0 -z-20" />
<div className="bg-hristudio-teal/20 absolute -top-24 -left-24 h-96 w-96 blur-[120px]" />
<div className="from-hristudio-teal/10 via-hristudio-teal/5 absolute inset-0 -z-10 bg-gradient-to-r to-transparent" />
<div className="relative mx-auto grid max-w-6xl gap-10 px-6 py-16 md:grid-cols-[1.1fr_0.9fr] md:py-24">
<div>
<div className="flex items-center gap-2.5">
<span className="bg-hristudio-teal flex aspect-square h-8 items-center justify-center rounded-lg p-1.5">
<BotIcon className="h-5 w-5 text-white" />
</span>
<span className="text-text font-hristudio text-2xl font-bold tracking-tight">
HRI<span className="font-normal">Studio</span>
</span>
</div>
<h3 className="text-text font-hristudio mt-6 max-w-xl text-3xl font-bold tracking-tight sm:text-4xl">
Wizard-of-Oz studies, made scientific.
</h3>
<p className="text-text-secondary font-hristudio mt-4 max-w-xl text-base leading-relaxed">
HRIStudio is an open-source platform for human-robot interaction
research. Design experiments visually, run live trials with a
wizard, and capture synchronized data reproducible by default.
</p>
<span className="text-hristudio-teal font-hristudio mt-8 inline-flex items-center gap-2 text-sm font-bold tracking-widest uppercase transition group-hover:opacity-70">
Explore HRIStudio
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className="h-4 w-4 transition-transform group-hover:translate-x-1"
aria-hidden="true"
>
<path d="M5 12h14M12 5l7 7-7 7" />
</svg>
</span>
</div>
<div className="grid content-center gap-4 sm:grid-cols-2 md:grid-cols-1 lg:grid-cols-2">
{hristudioFeatures.map((f) => (
<div key={f.title} className="flex items-start gap-3">
<span className="text-hristudio-teal mt-0.5 shrink-0">
<CheckIcon className="h-4 w-4" />
</span>
<div>
<h4 className="text-text font-hristudio text-sm font-semibold tracking-wide">
{f.title}
</h4>
<p className="text-text-secondary/80 font-hristudio mt-1 text-xs leading-relaxed">
{f.body}
</p>
</div>
</div>
))}
</div>
</div>
</a>
</section>
);
}
function CheckIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2.5"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<path d="M5 13l4 4L19 7" />
</svg>
);
}
function BotIcon({ className }: { className?: string }) {
return (
<svg
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
className={className}
aria-hidden="true"
>
<rect x="4" y="8" width="16" height="12" rx="2" />
<path d="M12 8V4" />
<circle cx="12" cy="4" r="1" />
<path d="M2 14h2" />
<path d="M20 14h2" />
<path d="M9 13v2" />
<path d="M15 13v2" />
</svg>
);
}
function NameStory() {
return (
<section id="name" className="scroll-mt-20">
<div className="mx-auto max-w-3xl px-6 py-24 text-center">
<div className="text-accent font-mono text-sm font-semibold tracking-widest uppercase">
The name
</div>
<h2 className="text-text mt-3 text-3xl font-bold tracking-tight sm:text-4xl">
Named for still water in the Adirondacks.
</h2>
<p className="text-text-secondary mx-auto mt-4 max-w-xl text-base leading-relaxed">
The water at the top of this page is Hadlock Pond in Fort Ann, New
York. It is quiet, dependable, and built to last. This is the standard
held for infrastructure. You should not have to think about it for it
to work.
</p>
</div>
</section>
);
}
function Contact() {
return (
<section
id="contact"
className="bg-surface/50 border-border-faint scroll-mt-20 border-t"
>
<div className="mx-auto max-w-3xl px-6 py-24">
<div className="bg-surface border-border relative overflow-hidden border p-8 md:p-12">
<div className="absolute inset-0 -z-10 bg-[radial-gradient(50%_60%_at_50%_0%,var(--accent-glow),transparent_70%)]" />
<div className="mb-8 text-center">
<div className="text-accent font-mono text-sm font-semibold tracking-widest uppercase">
Get in touch
</div>
<h2 className="text-text mx-auto mt-3 max-w-2xl text-3xl font-bold tracking-tight sm:text-4xl">
Have something technical that needs an owner?
</h2>
</div>
<ContactForm />
</div>
</div>
</section>
);
}
function Footer() {
return (
<footer className="border-border-faint border-t">
<div className="mx-auto flex max-w-6xl flex-col items-center justify-between gap-4 px-6 py-10 sm:flex-row">
<Wordmark />
<p className="text-faint text-sm">
Hadlock Technologies LLC · New York · © {new Date().getFullYear()}
</p>
</div>
</footer>
);
}
function SectionHeading({
eyebrow,
title,
description,
display = false,
}: {
eyebrow: string;
title: string;
description: string;
display?: boolean;
}) {
return (
<div className="max-w-2xl">
<div
className={`text-accent text-sm font-semibold tracking-widest uppercase ${
display ? "font-display" : "font-mono"
}`}
>
{eyebrow}
</div>
<h2
className={`text-text mt-3 text-3xl font-bold tracking-tight sm:text-4xl ${
display ? "font-display uppercase" : ""
}`}
>
{title}
</h2>
<p className="text-text-secondary mt-4 text-base leading-relaxed">
{description}
</p>
</div>
);
}