<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="icon" href="https://afterdarklabs.io/logo_assets/favicon.ico">
<link rel="icon" type="image/svg+xml" href="https://afterdarklabs.io/logo_assets/badge.svg">
<link rel="icon" type="image/png" sizes="32x32" href="https://afterdarklabs.io/logo_assets/favicon-32.png">
<link rel="icon" type="image/png" sizes="16x16" href="https://afterdarklabs.io/logo_assets/favicon-16.png">
<link rel="apple-touch-icon" sizes="180x180" href="https://afterdarklabs.io/logo_assets/apple-touch-icon.png">
<link rel="manifest" href="https://afterdarklabs.io/site.webmanifest">
<title>South Florida Website Design and Development | After Dark Labs</title>
<meta name="description" content="Florida website design and development from After Dark Labs. Custom business websites, Shopify stores, apps, SEO, hosting, and integrations. Free demo in 24 hours.">
<meta name="robots" content="index,follow">
<link rel="canonical" href="https://afterdarklabs.io/">
<meta property="og:title" content="Website Design and Development | After Dark Labs Florida">
<meta property="og:description" content="Florida website design and development for businesses that need custom websites, Shopify stores, mobile apps, internal tools, SEO, hosting, and integrations.">
<meta property="og:image" content="https://afterdarklabs.io/twitter-card.png?v=3">
<meta property="og:image:width" content="1200">
<meta property="og:image:height" content="630">
<meta property="og:image:alt" content="After Dark Labs websites and tools that help businesses stand out">
<meta property="og:type" content="website">
<meta property="og:url" content="https://afterdarklabs.io/">
<meta property="og:site_name" content="After Dark Labs">
<meta name="twitter:card" content="summary_large_image">
<meta name="twitter:title" content="Website Design and Development | After Dark Labs Florida">
<meta name="twitter:description" content="Florida website design and development for custom websites, Shopify stores, apps, SEO, hosting, and integrations.">
<meta name="twitter:image" content="https://afterdarklabs.io/twitter-card.png?v=3">
<meta name="twitter:image:alt" content="After Dark Labs websites and tools that help businesses stand out">
<link rel="stylesheet" href="https://afterdarklabs.io/index.tailwind.css?v=4">
<style>
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=Space+Grotesk:wght@500;600;700&family=Syne:wght@500;600;700;800&display=swap');
:root {
--neon: #ccff00;
--page-bg: #17212b;
--page-bg-rgb: 23, 33, 43;
}
html { scroll-behavior: smooth; }
body {
background: var(--page-bg);
font-family: 'Inter', system-ui, sans-serif;
}
.heading { font-family: 'Space Grotesk', sans-serif; }
.logo-font { font-family: 'Syne', sans-serif; }
.hero-bg { background: var(--page-bg); }
.bg-black,
.bg-zinc-950 {
background-color: var(--page-bg) !important;
}
.bg-black\/90 {
background-color: rgba(var(--page-bg-rgb), 0.92) !important;
}
.neon-text {
position: relative;
isolation: isolate;
display: inline-flex;
align-items: baseline;
font: inherit;
letter-spacing: inherit;
line-height: inherit;
color: var(--neon);
white-space: nowrap;
}
.neon-text::before {
content: "";
position: absolute;
inset: -0.24em -0.42em -0.18em;
background: radial-gradient(ellipse at 50% 58%, rgba(204,255,0,0.38), rgba(204,255,0,0.16) 42%, transparent 74%);
filter: blur(0.18em);
opacity: 0.9;
pointer-events: none;
z-index: -2;
}
.neon-letter,
.neon-gap {
position: relative;
display: inline-block;
font: inherit;
letter-spacing: inherit;
line-height: inherit;
}
.neon-letter {
color: #ccff00;
-webkit-text-fill-color: currentColor;
-webkit-text-stroke: 0.006em rgba(235,255,120,0.58);
filter:
drop-shadow(0 0 0.012em rgba(235,255,120,0.54))
drop-shadow(0 0 0.045em rgba(204,255,0,0.72))
drop-shadow(0 0 0.105em rgba(204,255,0,0.28))
drop-shadow(0 0 0.24em rgba(204,255,0,0.16));
text-shadow:
0 0 0.026em rgba(235,255,120,0.38),
0 0 0.095em rgba(204,255,0,0.42);
}
.neon-letter::after {
content: attr(data-letter);
position: absolute;
inset: 0;
color: transparent;
-webkit-text-fill-color: transparent;
-webkit-text-stroke: 0.018em rgba(4,8,10,0.46);
transform: translate(0.01em, 0.012em);
opacity: 0.45;
z-index: -1;
}
.neon-letter--flicker {
animation: neon-letter-flicker var(--flicker-duration, 12s) steps(1, end) infinite;
animation-delay: var(--flicker-delay, 0s);
}
.neon-gap {
width: 0.18em;
}
.hero-content {
position: relative;
z-index: 2;
}
.hero-vignette {
position: absolute;
inset: 0;
background:
radial-gradient(ellipse 80% 70% at 50% 50%, transparent 42%, rgba(var(--page-bg-rgb),0.54) 100%),
linear-gradient(to bottom, rgba(var(--page-bg-rgb),0.22) 0%, transparent 24%, transparent 78%, rgba(var(--page-bg-rgb),0.34) 100%);
pointer-events: none;
z-index: 1;
}
@keyframes neon-letter-flicker {
0%, 4.8%, 5.4%, 5.9%, 37.5%, 38%, 38.6%, 100% {
opacity: 1;
-webkit-text-stroke-color: rgba(235,255,120,0.58);
filter:
drop-shadow(0 0 0.012em rgba(235,255,120,0.54))
drop-shadow(0 0 0.045em rgba(204,255,0,0.72))
drop-shadow(0 0 0.105em rgba(204,255,0,0.28))
drop-shadow(0 0 0.24em rgba(204,255,0,0.16));
}
5%, 37.8% {
opacity: 0.08;
-webkit-text-stroke-color: rgba(204,255,0,0.18);
filter: none;
}
5.7%, 38.3% {
opacity: 0.38;
-webkit-text-stroke-color: rgba(204,255,0,0.36);
filter:
drop-shadow(0 0 0.012em rgba(204,255,0,0.34))
drop-shadow(0 0 0.036em rgba(204,255,0,0.20));
}
}
@media (prefers-reduced-motion: reduce) {
.neon-letter,
.neon-letter--flicker {
animation: none;
}
}
nav a[data-nav-link][aria-current="page"] {
color: #ccff00;
font-weight: 600;
}
.work-pagination {
display: none;
align-items: center;
justify-content: center;
flex-shrink: 0;
}
.work-pagination-dot {
display: grid;
width: 1.75rem;
height: 1.75rem;
place-items: center;
border-radius: 9999px;
color: rgba(255,255,255,0.32);
}
.work-pagination-dot::before {
content: "";
width: 0.425rem;
height: 0.425rem;
border-radius: inherit;
background: currentColor;
transition: color 160ms ease, transform 160ms ease, box-shadow 160ms ease;
}
.work-pagination-dot:hover {
color: rgba(255,255,255,0.72);
}
.work-pagination-dot[aria-current="true"] {
color: var(--neon);
}
.work-pagination-dot[aria-current="true"]::before {
transform: scale(1.2);
box-shadow: 0 0 0.55rem rgba(204,255,0,0.32);
}
.work-pagination-dot:focus-visible {
outline: 2px solid var(--neon);
outline-offset: -3px;
}
@media (min-width: 768px) {
.work-pagination {
display: flex;
}
#work-scroller {
padding-inline: max(1.5rem, calc((100% - 64rem) / 2 + 1.5rem));
scroll-padding-inline: max(1.5rem, calc((100% - 64rem) / 2 + 1.5rem));
}
#work-scroller > article {
width: 38rem;
}
}
</style>
<script type="application/ld+json">
{
"@context": "https://schema.org",
"@graph": [
{
"@type": "Organization",
"@id": "https://afterdarklabs.io/#organization",
"name": "After Dark Labs",
"url": "https://afterdarklabs.io/",
"logo": "https://afterdarklabs.io/logo_assets/icon-512.png",
"founder": {
"@type": "Person",
"name": "Harrison Erd",
"url": "https://harrisonerd.com/"
},
"areaServed": {
"@type": "Country",
"name": "United States"
},
"description": "After Dark Labs provides South Florida website design and development for custom business websites, Shopify stores, mobile apps, internal tools, SEO foundations, hosting setup, logo design, backend APIs, and integrations."
},
{
"@type": "WebSite",
"@id": "https://afterdarklabs.io/#website",
"url": "https://afterdarklabs.io/",
"name": "After Dark Labs",
"publisher": {
"@id": "https://afterdarklabs.io/#organization"
},
"description": "After Dark Labs provides website design and development for custom websites, Shopify stores, mobile apps, and business tools."
},
{
"@type": "Service",
"@id": "https://afterdarklabs.io/#custom-web-app-service",
"name": "Custom website, Shopify store, and mobile app development",
"provider": {
"@id": "https://afterdarklabs.io/#organization"
},
"areaServed": {
"@type": "Country",
"name": "United States"
},
"serviceType": [
"Business website development",
"Shopify store development",
"Mobile app development",
"Internal tool development",
"SEO and hosting setup"
],
"url": "https://afterdarklabs.io/"
}
]
}
</script>
</head>
<body class="bg-black text-white overflow-x-hidden">
<nav class="fixed top-0 z-50 w-full bg-black/90 backdrop-blur-lg">
<div class="mx-auto max-w-7xl px-4 py-4 sm:px-6 lg:py-5">
<div class="flex items-center justify-between gap-3 lg:hidden">
<button type="button" id="mobile-nav-toggle" class="flex min-w-0 items-center gap-2 rounded-2xl p-1.5 pr-3 text-left text-white transition-colors hover:bg-white/10 focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00]" aria-controls="mobile-nav-menu" aria-expanded="false" aria-label="Open navigation menu">
<img src="https://afterdarklabs.io/logo_assets/mark.svg" width="48" height="48" alt="" aria-hidden="true">
<span class="min-w-0">
<span class="logo-font block truncate text-xl font-semibold tracking-tighter">After Dark</span>
<span class="logo-font -mt-1 block text-xs tracking-[3px] text-[#ccff00]">LABS</span>
</span>
</button>
<a href="https://calendly.com/harrison-afterdarklabs/30min" target="_blank" rel="noopener noreferrer" class="shrink-0 rounded-2xl bg-[#ccff00] px-4 py-3 text-sm font-semibold text-black transition-colors hover:bg-white">Get a Free Demo</a>
</div>
<div class="hidden items-center justify-between lg:flex">
<a href="https://afterdarklabs.io/" class="flex items-center gap-3">
<img src="https://afterdarklabs.io/logo_assets/mark.svg" width="48" height="48" alt="" aria-hidden="true">
<div>
<span class="logo-font block text-2xl font-semibold tracking-tighter">After Dark</span>
<span class="logo-font -mt-1 block text-sm tracking-[3px] text-[#ccff00]">LABS</span>
</div>
</a>
<div class="flex items-center gap-6">
<div class="flex items-center gap-7 text-sm font-medium">
<a href="https://afterdarklabs.io/#work" class="transition-colors hover:text-[#ccff00]" data-nav-link>Portfolio</a>
<a href="https://afterdarklabs.io/#services" class="transition-colors hover:text-[#ccff00]" data-nav-link>Services</a>
</div>
<a href="https://calendly.com/harrison-afterdarklabs/30min" target="_blank" rel="noopener noreferrer" class="rounded-2xl bg-[#ccff00] px-6 py-3 font-semibold text-black transition-colors hover:bg-white">Get a Free Demo</a>
</div>
</div>
<div id="mobile-nav-menu" class="hidden lg:hidden">
<div class="mt-4 grid gap-2 border-t border-white/10 pt-4 text-sm font-semibold">
<a href="https://afterdarklabs.io/#hero" class="rounded-2xl px-4 py-3 text-gray-200 transition-colors hover:bg-white/10 hover:text-[#ccff00]" data-nav-link>Home</a>
<a href="https://afterdarklabs.io/#work" class="rounded-2xl px-4 py-3 text-gray-200 transition-colors hover:bg-white/10 hover:text-[#ccff00]" data-nav-link>Portfolio</a>
<a href="https://afterdarklabs.io/#services" class="rounded-2xl px-4 py-3 text-gray-200 transition-colors hover:bg-white/10 hover:text-[#ccff00]" data-nav-link>Services</a>
</div>
</div>
</div>
</nav>
<main>
<section id="hero" class="hero-bg min-h-screen flex items-center pt-20" style="position:relative; overflow:hidden;">
<div class="hero-vignette"></div>
<div class="hero-content mx-auto grid w-full max-w-7xl gap-10 px-6 py-32 text-left lg:grid-cols-[1.3fr_0.7fr] lg:items-end">
<div>
<h1 class="heading text-7xl font-bold leading-none tracking-tighter md:text-8xl lg:text-7xl xl:text-8xl">
Websites and tools that help businesses<br><span class="neon-text" aria-hidden="true"><span class="neon-letter neon-letter--flicker" data-letter="s" style="--flicker-delay:-1.1s; --flicker-duration:11s;">s</span><span class="neon-letter" data-letter="t">t</span><span class="neon-letter neon-letter--flicker" data-letter="a" style="--flicker-delay:-5.6s; --flicker-duration:13s;">a</span><span class="neon-letter" data-letter="n">n</span><span class="neon-letter neon-letter--flicker" data-letter="d" style="--flicker-delay:-8.4s; --flicker-duration:15s;">d</span><span class="neon-gap"></span><span class="neon-letter" data-letter="o">o</span><span class="neon-letter neon-letter--flicker" data-letter="u" style="--flicker-delay:-3.3s; --flicker-duration:12s;">u</span><span class="neon-letter" data-letter="t">t</span></span><span class="sr-only">stand out</span>
</h1>
</div>
<div class="mx-auto flex max-w-xl flex-col items-start gap-8 lg:mx-0">
<div class="flex flex-wrap gap-5 justify-start">
<a href="https://calendly.com/harrison-afterdarklabs/30min" target="_blank" rel="noopener noreferrer" class="px-8 py-5 bg-[#ccff00] hover:bg-white text-black font-semibold rounded-3xl text-lg transition-all flex items-center gap-3 group">
Get a Free Demo
</a>
</div>
</div>
</div>
</section>
<section id="work" class="scroll-mt-24 border-t border-white/10 bg-black">
<div class="py-20 md:py-24">
<div class="mx-auto mb-10 flex max-w-5xl items-end justify-between gap-6 px-6">
<h2 class="heading text-4xl font-semibold tracking-tighter md:text-6xl">Recent projects</h2>
<div id="work-pagination" class="work-pagination" role="group" aria-label="Choose a portfolio project"></div>
</div>
<div id="work-scroller" class="no-scrollbar flex snap-x snap-mandatory gap-5 overflow-x-auto scroll-smooth px-6 pb-2 scroll-px-6">
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://miadruck.com" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Mia Druck website">
<img src="https://afterdarklabs.io/miadruck.webp?v=2" width="1200" height="681" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Mia Druck real estate website project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Mia Druck • Website • Real estate</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://gardenfreshwholesale.com" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Garden Fresh Wholesale website">
<img src="https://afterdarklabs.io/gardenfresh.webp?v=2" width="1200" height="702" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Garden Fresh Wholesale ecommerce web app project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Garden Fresh Wholesale • Business Website • SEO</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://coralvibefl.com" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Coral Vibe website">
<img src="https://afterdarklabs.io/coralvibe.webp?v=1" width="1200" height="678" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Shopify store for Coral Vibe Florida project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Coral Vibe • Shopify • E-Commerce</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://invoiceotter.com" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Invoice Otter website">
<img src="https://afterdarklabs.io/invoiceotter.webp?v=1" width="1200" height="673" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Invoice Otter SaaS project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Invoice Otter • SaaS • Subscriptions</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://vegy.app/" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Vegy website">
<img src="https://afterdarklabs.io/vegy.webp?v=1" width="1200" height="702" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Vegy wholesale ordering platform project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Vegy • SaaS • Wholesale ordering</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://prima.vegy.app/" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit Prima Produce website">
<img src="https://afterdarklabs.io/prima.webp?v=1" width="1200" height="702" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="Prima Produce business website project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">Prima Produce • Business Website • Produce logistics</p>
</div>
</article>
<article class="w-[82vw] shrink-0 snap-start overflow-hidden rounded-3xl bg-zinc-950 sm:w-[28rem] md:w-[38rem]">
<a href="https://954boatlifts.com" target="_blank" rel="noopener noreferrer" class="block w-full overflow-hidden rounded-t-3xl text-left focus:outline-none focus-visible:ring-2 focus-visible:ring-[#ccff00] focus-visible:ring-offset-2 focus-visible:ring-offset-[#17212b]" aria-label="Visit 954 Boat Lifts website">
<img src="https://afterdarklabs.io/954boatlifts.webp?v=1" width="1200" height="696" loading="lazy" decoding="async" class="aspect-video w-full object-cover" alt="954 Boat Lifts marine services website project by After Dark Labs">
</a>
<div class="p-6">
<p class="text-sm font-medium text-[#ccff00]">954 Boat Lifts • Business Website • Marine services</p>
</div>
</article>
</div>
</div>
</section>
<section id="services" class="scroll-mt-24 border-t border-white/10 bg-zinc-950">
<div class="mx-auto max-w-5xl px-6 py-20 md:py-24">
<h2 class="heading text-4xl font-semibold leading-none tracking-tighter md:text-6xl">Technology built around your business</h2>
<div class="mt-10">
<div class="grid gap-6 sm:grid-cols-2 md:gap-7">
<div class="px-6 py-7 md:px-7 md:py-8">
<h3 class="text-lg font-semibold leading-tight text-[#ccff00]">Websites & E-commerce</h3>
<p class="mt-3 text-base leading-7 text-gray-300">Custom websites, website redesigns, Shopify stores & subscription platforms</p>
</div>
<div class="px-6 py-7 md:px-7 md:py-8">
<h3 class="text-lg font-semibold leading-tight text-[#ccff00]">Apps & Custom Platforms</h3>
<p class="mt-3 text-base leading-7 text-gray-300">iOS/Android apps, customer portals, internal tools, APIs & SaaS MVPs</p>
</div>
<div class="px-6 py-7 md:px-7 md:py-8">
<h3 class="text-lg font-semibold leading-tight text-[#ccff00]">Branding & Marketing</h3>
<p class="mt-3 text-base leading-7 text-gray-300">Logo design, brand identity, local search/AI rankings, lead capture & social media marketing</p>
</div>
<div class="px-6 py-7 md:px-7 md:py-8">
<h3 class="text-lg font-semibold leading-tight text-[#ccff00]">Infrastructure & AI</h3>
<p class="mt-3 text-base leading-7 text-gray-300">Hosting, domains, business email, voice AI, LLM integrations, & intelligent automation</p>
</div>
</div>
<p class="mt-7 max-w-3xl text-sm leading-6 text-gray-400">Simple business websites typically range from $500–$2,000 as a one-time fee. Shopify stores, apps, and custom tools are quoted based on scope. Hosting is included for most service business websites, which means no monthly website fees.</p>
</div>
</div>
</section>
</main>
<footer class="border-t border-white/10 bg-black py-16">
<div class="mx-auto max-w-7xl px-6 text-center">
<a href="https://afterdarklabs.io/" class="mb-6 inline-flex items-center justify-center gap-3">
<img src="https://afterdarklabs.io/logo_assets/mark-animated.svg" width="48" height="48" alt="" aria-hidden="true">
<span class="min-w-0 text-left">
<span class="logo-font block text-3xl font-semibold tracking-tighter">After Dark</span>
<span class="logo-font -mt-1 block text-sm tracking-[3px] text-[#ccff00]">LABS</span>
</span>
</a>
<p class="text-gray-500">© 2026 <a href="https://harrisonerd.com/" class="transition-colors hover:text-[#ccff00]">Harrison Erd</a> • Pompano Beach, FL
<br><br><a href="mailto:[email protected]" class="transition-colors hover:text-[#ccff00]" style="margin-top:50px;">[email protected]</a></p>
</div>
</footer>
<script>
(function () {
const toggle = document.getElementById('mobile-nav-toggle');
const menu = document.getElementById('mobile-nav-menu');
if (!toggle || !menu) return;
function setMenuOpen(isOpen) {
menu.classList.toggle('hidden', !isOpen);
toggle.setAttribute('aria-expanded', String(isOpen));
toggle.setAttribute('aria-label', isOpen ? 'Close navigation menu' : 'Open navigation menu');
}
toggle.addEventListener('click', () => {
setMenuOpen(toggle.getAttribute('aria-expanded') !== 'true');
});
menu.querySelectorAll('a[data-nav-link]').forEach(link => {
link.addEventListener('click', () => setMenuOpen(false));
});
window.addEventListener('keydown', event => {
if (event.key === 'Escape') setMenuOpen(false);
});
window.addEventListener('resize', () => {
if (window.innerWidth >= 1024) setMenuOpen(false);
});
})();
(function () {
const scroller = document.getElementById('work-scroller');
const pagination = document.getElementById('work-pagination');
if (!scroller || !pagination) return;
const cards = Array.from(scroller.querySelectorAll(':scope > article'));
if (!cards.length) return;
const reduceMotion = window.matchMedia('(prefers-reduced-motion: reduce)');
const dots = cards.map((card, index) => {
const dot = document.createElement('button');
const projectName = card.querySelector('.p-6 p')?.textContent.split('•')[0].trim() || `project ${index + 1}`;
dot.type = 'button';
dot.className = 'work-pagination-dot';
dot.setAttribute('aria-label', `Show ${projectName} project`);
dot.setAttribute('aria-controls', 'work-scroller');
dot.addEventListener('click', () => {
const maxScroll = scroller.scrollWidth - scroller.clientWidth;
const target = Math.min(cards[index].offsetLeft - cards[0].offsetLeft, maxScroll);
scroller.scrollTo({
left: Math.max(0, target),
behavior: reduceMotion.matches ? 'auto' : 'smooth'
});
});
pagination.appendChild(dot);
return dot;
});
let updateFrame = 0;
function updatePagination() {
updateFrame = 0;
const maxScroll = scroller.scrollWidth - scroller.clientWidth;
let activeIndex = 0;
let closestDistance = Infinity;
cards.forEach((card, index) => {
const position = Math.min(card.offsetLeft - cards[0].offsetLeft, maxScroll);
const distance = Math.abs(scroller.scrollLeft - Math.max(0, position));
if (distance < closestDistance) {
closestDistance = distance;
activeIndex = index;
}
});
dots.forEach((dot, index) => {
const isActive = index === activeIndex;
dot.toggleAttribute('aria-current', isActive);
if (isActive) dot.setAttribute('aria-current', 'true');
dot.tabIndex = isActive ? 0 : -1;
});
}
function schedulePaginationUpdate() {
if (!updateFrame) updateFrame = window.requestAnimationFrame(updatePagination);
}
pagination.addEventListener('keydown', event => {
const currentIndex = dots.indexOf(document.activeElement);
if (currentIndex < 0) return;
let nextIndex = currentIndex;
if (event.key === 'ArrowLeft') nextIndex = Math.max(0, currentIndex - 1);
if (event.key === 'ArrowRight') nextIndex = Math.min(dots.length - 1, currentIndex + 1);
if (event.key === 'Home') nextIndex = 0;
if (event.key === 'End') nextIndex = dots.length - 1;
if (nextIndex === currentIndex && !['Home', 'End'].includes(event.key)) return;
event.preventDefault();
dots[nextIndex].focus();
dots[nextIndex].click();
});
scroller.addEventListener('scroll', schedulePaginationUpdate, { passive: true });
window.addEventListener('resize', schedulePaginationUpdate);
updatePagination();
})();
(function () {
const activeClasses = ['text-[#ccff00]', 'font-semibold'];
const navLinks = Array.from(document.querySelectorAll('nav a[data-nav-link]'));
if (!navLinks.length) return;
const sections = Array.from(document.querySelectorAll('main section[id]'))
.map(element => ({ element, href: `#${element.id}` }));
function setActive(href) {
navLinks.forEach(link => {
const isActive = new URL(link.href).hash === href;
link.classList.toggle(activeClasses[0], isActive);
link.classList.toggle(activeClasses[1], isActive);
if (isActive) {
link.setAttribute('aria-current', 'page');
} else {
link.removeAttribute('aria-current');
}
});
}
function updateActiveNav() {
const offset = window.innerHeight * 0.35;
let currentHref = '';
for (const section of sections) {
if (section.element.getBoundingClientRect().top <= offset) {
currentHref = section.href;
}
}
setActive(currentHref);
}
function activeFromHash() {
return sections.some(section => section.href === window.location.hash)
? window.location.hash
: '';
}
setActive(activeFromHash());
requestAnimationFrame(updateActiveNav);
window.addEventListener('load', updateActiveNav);
window.addEventListener('scroll', updateActiveNav, { passive: true });
window.addEventListener('hashchange', () => {
setActive(activeFromHash());
requestAnimationFrame(updateActiveNav);
});
})();
</script>
</body>
</html>