Add theme system, project banners, mobile nav, and hero logo
- System-first theming via light-dark() with explicit .light/.dark overrides - Project banners: Racetix, beenvoice, RaceHub, HRIStudio - Mobile navigation with portaled blur backdrop - Hero logo placement and page copy polish - Remove tRPC, migrate contact flow to direct API route
This commit is contained in:
+524
-161
@@ -2,55 +2,51 @@ 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 networks designed, deployed, and managed end to end — wired, wireless, VLANs, and the hardware that has to stay up.",
|
||||
body: "UniFi networking with wired links, wireless access, VLANs, and hardware that stays up when it counts.",
|
||||
tag: "UniFi",
|
||||
},
|
||||
{
|
||||
title: "Servers & Cloud",
|
||||
body: "Provisioning and running local and cloud servers — web servers, object storage (S3), and automated backups that actually restore.",
|
||||
body: "Local and cloud servers with web hosting, S3 storage, and backups tested by restore.",
|
||||
tag: "Cloud",
|
||||
},
|
||||
{
|
||||
title: "Databases",
|
||||
body: "Design, deployment, and day-to-day operation. Reliable data, backed up, monitored, and recoverable when it counts.",
|
||||
body: "Designed, deployed, and operated under the stack, with backups, monitoring, and tested restores.",
|
||||
tag: "Data",
|
||||
},
|
||||
{
|
||||
title: "Web & Applications",
|
||||
body: "Marketing sites to production web apps — built, hosted, and maintained by the same person who runs the servers under them.",
|
||||
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: "Shopify storefronts plus a custom ticket-sales platform built from scratch to handle real event-day volume.",
|
||||
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: "When it's race day, whatever it takes to keep things moving — up to and including working the counter and selling tires.",
|
||||
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",
|
||||
},
|
||||
];
|
||||
|
||||
const stats = [
|
||||
{ value: "5+ yrs", label: "Running the stack at Riverhead Raceway" },
|
||||
{ value: "1 partner", label: "From the wall jack to the checkout button" },
|
||||
{ value: "24/7", label: "Systems built to stay online" },
|
||||
];
|
||||
|
||||
export default function Home() {
|
||||
return (
|
||||
<div className="relative min-h-screen overflow-x-hidden">
|
||||
<div className="relative min-h-screen overflow-x-clip">
|
||||
<Header />
|
||||
<main>
|
||||
<Hero />
|
||||
<Stats />
|
||||
<Services />
|
||||
<Approach />
|
||||
<Projects />
|
||||
<NameStory />
|
||||
<Contact />
|
||||
</main>
|
||||
@@ -59,86 +55,54 @@ export default function Home() {
|
||||
);
|
||||
}
|
||||
|
||||
// Reflected-"H" monogram: an H standing on a waterline, mirrored below like a
|
||||
// reflection on still water — a nod to Hadlock Pond.
|
||||
function Mark({ className }: { className?: string }) {
|
||||
return (
|
||||
<svg viewBox="0 0 32 32" fill="none" className={className} aria-hidden="true">
|
||||
<g
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
>
|
||||
<path d="M10 7v8M22 7v8M10 11h12" />
|
||||
</g>
|
||||
<line
|
||||
x1="6"
|
||||
y1="16.5"
|
||||
x2="26"
|
||||
y2="16.5"
|
||||
stroke="currentColor"
|
||||
strokeWidth="1.5"
|
||||
strokeLinecap="round"
|
||||
opacity="0.5"
|
||||
/>
|
||||
<g
|
||||
stroke="currentColor"
|
||||
strokeWidth="2.5"
|
||||
strokeLinecap="round"
|
||||
fill="none"
|
||||
opacity="0.3"
|
||||
>
|
||||
<path d="M10 18v6M22 18v6" />
|
||||
</g>
|
||||
</svg>
|
||||
);
|
||||
}
|
||||
|
||||
function Wordmark() {
|
||||
return (
|
||||
<Link href="/" className="group flex items-center gap-2.5">
|
||||
<span className="flex h-8 w-8 items-center justify-center rounded-md border border-brand-400/40 bg-brand-400/10 text-brand-300 transition group-hover:border-brand-400/70">
|
||||
<Mark className="h-5 w-5" />
|
||||
</span>
|
||||
<span className="text-[15px] font-semibold tracking-tight text-white">
|
||||
Hadlock<span className="text-brand-400"> Technologies</span>
|
||||
</span>
|
||||
<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="absolute inset-x-0 top-0 z-50">
|
||||
<div className="mx-auto flex h-20 max-w-6xl items-center justify-between px-6">
|
||||
<Wordmark />
|
||||
<nav className="hidden items-center gap-1 rounded-full border border-white/10 bg-white/5 p-1 text-sm text-slate-200 backdrop-blur-md md:flex">
|
||||
<a
|
||||
href="#services"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
Services
|
||||
</a>
|
||||
<a
|
||||
href="#approach"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
Approach
|
||||
</a>
|
||||
<a
|
||||
href="#name"
|
||||
className="rounded-full px-4 py-1.5 transition hover:bg-white/10 hover:text-white"
|
||||
>
|
||||
The name
|
||||
</a>
|
||||
</nav>
|
||||
<a
|
||||
href="#contact"
|
||||
className="rounded-full bg-white px-4 py-2 text-sm font-semibold text-ink-950 transition hover:bg-slate-200"
|
||||
>
|
||||
Get in touch
|
||||
</a>
|
||||
<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>
|
||||
);
|
||||
@@ -156,8 +120,8 @@ const capabilities = [
|
||||
|
||||
function Hero() {
|
||||
return (
|
||||
<section className="relative isolate flex min-h-screen flex-col overflow-hidden">
|
||||
{/* Hadlock Pond, full-bleed with a slight darkening filter */}
|
||||
<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"
|
||||
@@ -165,52 +129,60 @@ function Hero() {
|
||||
fill
|
||||
priority
|
||||
sizes="100vw"
|
||||
className="object-cover brightness-[0.68] saturate-[0.95]"
|
||||
className="hero-photo object-cover"
|
||||
/>
|
||||
</div>
|
||||
{/* Scrims: darken top for the nav, left for the text, and fade the base into the page */}
|
||||
<div className="absolute inset-0 -z-10 bg-gradient-to-b from-ink-950/70 via-transparent to-ink-950" />
|
||||
<div className="absolute inset-0 -z-10 bg-gradient-to-r from-ink-950/85 via-ink-950/30 to-transparent" />
|
||||
{/* 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 justify-center px-6 pt-32 pb-16">
|
||||
<div className="inline-flex w-fit items-center gap-2 rounded-full border border-white/15 bg-white/5 px-3.5 py-1.5 text-xs font-medium text-slate-200 backdrop-blur-sm">
|
||||
<span className="h-1.5 w-1.5 rounded-full bg-brand-400" />
|
||||
Full-stack technical partner · Long Island, NY
|
||||
</div>
|
||||
<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-16 w-auto"
|
||||
/>
|
||||
<img
|
||||
src="/branding/logo_blue.svg"
|
||||
alt="Hadlock Technologies"
|
||||
className="theme-logo-light h-16 w-auto"
|
||||
/>
|
||||
|
||||
<h1 className="mt-7 max-w-4xl font-mono text-4xl font-semibold tracking-tight text-white uppercase sm:text-6xl sm:leading-[1.05]">
|
||||
Everything technical,{" "}
|
||||
<span className="text-brand-400">handled.</span>
|
||||
<h1 className="text-text mt-7 max-w-4xl text-4xl font-semibold tracking-tight uppercase sm:text-6xl sm:leading-[1.05]">
|
||||
Everything technical, <span className="text-accent">handled.</span>
|
||||
</h1>
|
||||
|
||||
<p className="mt-6 max-w-xl text-lg leading-relaxed text-slate-300">
|
||||
The networks, servers, and software that keep a business running —
|
||||
designed, built, and maintained under one accountable name, instead of
|
||||
five vendors pointing at each other.
|
||||
<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="rounded-full bg-brand-400 px-6 py-3 text-sm font-semibold text-ink-950 transition hover:bg-brand-300"
|
||||
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="rounded-full border border-white/20 bg-white/5 px-6 py-3 text-sm font-semibold text-white backdrop-blur-sm transition hover:border-white/40 hover:bg-white/10"
|
||||
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>
|
||||
|
||||
{/* Capability strip — where DUNE puts its client logos */}
|
||||
<div className="relative border-t border-white/10 bg-ink-950/40 backdrop-blur-sm">
|
||||
<div className="mx-auto flex max-w-6xl flex-wrap items-center gap-x-6 gap-y-2 px-6 py-5 font-mono text-xs tracking-widest text-slate-400 uppercase">
|
||||
<span className="text-slate-500">Working across</span>
|
||||
{/* 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-slate-300">
|
||||
<span key={c} className="text-text-secondary">
|
||||
{c}
|
||||
</span>
|
||||
))}
|
||||
@@ -220,42 +192,29 @@ function Hero() {
|
||||
);
|
||||
}
|
||||
|
||||
function Stats() {
|
||||
return (
|
||||
<section className="border-y border-white/5 bg-ink-900/50">
|
||||
<div className="mx-auto grid max-w-6xl grid-cols-1 divide-y divide-white/5 px-6 sm:grid-cols-3 sm:divide-x sm:divide-y-0">
|
||||
{stats.map((s) => (
|
||||
<div key={s.label} className="px-2 py-8 sm:px-6">
|
||||
<div className="text-2xl font-bold text-white">{s.value}</div>
|
||||
<div className="mt-1 text-sm text-slate-400">{s.label}</div>
|
||||
</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 partner. The whole stack."
|
||||
description="Most businesses stitch together a network vendor, a hosting company, a web agency, and a point-of-sale provider. Hadlock is all of it — and the same hands that wire the network ship the app on top of it."
|
||||
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="mt-14 grid grid-cols-1 gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10 sm:grid-cols-2 lg:grid-cols-3">
|
||||
<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 relative bg-ink-900 p-7 transition hover:bg-ink-850"
|
||||
className="group bg-surface hover:bg-surface-hover relative p-7 transition"
|
||||
>
|
||||
<div className="mb-4 inline-flex items-center rounded-md border border-brand-400/30 bg-brand-400/10 px-2.5 py-1 text-xs font-medium text-brand-300">
|
||||
<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-lg font-semibold text-white">{s.title}</h3>
|
||||
<p className="mt-2.5 text-sm leading-relaxed text-slate-400">
|
||||
<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>
|
||||
@@ -270,39 +229,37 @@ function Approach() {
|
||||
const points = [
|
||||
{
|
||||
title: "Full ownership",
|
||||
body: "From the physical layer up to the interface. Nothing gets lost in the handoff between vendors, because there is no handoff.",
|
||||
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 it is the person who fixes it — on-site when it matters, no ticket queue between you and a resolution.",
|
||||
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 that restore, monitoring that warns you first, and systems sized for the busy day, not the average one.",
|
||||
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="scroll-mt-20 border-y border-white/5 bg-ink-900/40"
|
||||
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="A titleless everything-engineer, on your side."
|
||||
description="Software engineer, network admin, systems operator, and — on the right day — the person selling tires at the counter. The job is whatever keeps the operation running."
|
||||
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="grid gap-px overflow-hidden rounded-2xl border border-white/10 bg-white/10">
|
||||
<div className="border-border bg-border grid gap-px overflow-hidden border">
|
||||
{points.map((p) => (
|
||||
<div key={p.title} className="bg-ink-900 p-7">
|
||||
<h3 className="text-base font-semibold text-white">
|
||||
{p.title}
|
||||
</h3>
|
||||
<p className="mt-2 text-sm leading-relaxed text-slate-400">
|
||||
<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>
|
||||
@@ -314,21 +271,417 @@ function Approach() {
|
||||
);
|
||||
}
|
||||
|
||||
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'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="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
<div className="text-accent font-mono text-sm font-semibold tracking-widest uppercase">
|
||||
The name
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
<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="mx-auto mt-4 max-w-xl text-base leading-relaxed text-slate-300">
|
||||
<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 — quiet, dependable, and built to last. It's a good standard
|
||||
for infrastructure: you shouldn't have to think about it for it to
|
||||
be working.
|
||||
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>
|
||||
@@ -339,16 +692,16 @@ function Contact() {
|
||||
return (
|
||||
<section
|
||||
id="contact"
|
||||
className="scroll-mt-20 border-t border-white/5 bg-ink-900/50"
|
||||
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="relative overflow-hidden rounded-3xl border border-white/10 bg-gradient-to-br from-ink-850 to-ink-900 p-8 md:p-12">
|
||||
<div className="absolute inset-0 -z-10 bg-[radial-gradient(50%_60%_at_50%_0%,rgba(45,212,191,0.12),transparent_70%)]" />
|
||||
<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="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
<div className="text-accent font-mono text-sm font-semibold tracking-widest uppercase">
|
||||
Get in touch
|
||||
</div>
|
||||
<h2 className="mx-auto mt-3 max-w-2xl text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
<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>
|
||||
@@ -361,10 +714,10 @@ function Contact() {
|
||||
|
||||
function Footer() {
|
||||
return (
|
||||
<footer className="border-t border-white/5">
|
||||
<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-sm text-slate-500">
|
||||
<p className="text-faint text-sm">
|
||||
Hadlock Technologies LLC · New York · © {new Date().getFullYear()}
|
||||
</p>
|
||||
</div>
|
||||
@@ -376,20 +729,30 @@ function SectionHeading({
|
||||
eyebrow,
|
||||
title,
|
||||
description,
|
||||
display = false,
|
||||
}: {
|
||||
eyebrow: string;
|
||||
title: string;
|
||||
description: string;
|
||||
display?: boolean;
|
||||
}) {
|
||||
return (
|
||||
<div className="max-w-2xl">
|
||||
<div className="font-mono text-sm font-semibold tracking-widest text-brand-400 uppercase">
|
||||
<div
|
||||
className={`text-accent text-sm font-semibold tracking-widest uppercase ${
|
||||
display ? "font-display" : "font-mono"
|
||||
}`}
|
||||
>
|
||||
{eyebrow}
|
||||
</div>
|
||||
<h2 className="mt-3 text-3xl font-bold tracking-tight text-white sm:text-4xl">
|
||||
<h2
|
||||
className={`text-text mt-3 text-3xl font-bold tracking-tight sm:text-4xl ${
|
||||
display ? "font-display uppercase" : ""
|
||||
}`}
|
||||
>
|
||||
{title}
|
||||
</h2>
|
||||
<p className="mt-4 text-base leading-relaxed text-slate-300">
|
||||
<p className="text-text-secondary mt-4 text-base leading-relaxed">
|
||||
{description}
|
||||
</p>
|
||||
</div>
|
||||
|
||||
Reference in New Issue
Block a user