Build the full site from the copy decks

Adds About, Contact, Services (index + 8) and Industries (index + 7),
generated from structured JSON converted out of the .docx copy decks
by scripts/convert-docs.py. Shared header with dropdowns, footer,
contact form (mailto until a backend exists), FAQ/breadcrumb JSON-LD,
sitemap and robots.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Alejandro Veras
2026-10-08 22:54:50 -04:00
parent 99a48fac2b
commit b8cf7cfbcb
42 changed files with 11488 additions and 151 deletions
+58 -19
View File
@@ -1,13 +1,23 @@
"use client";
import Link from "next/link";
import { usePathname } from "next/navigation";
import { useEffect, useState } from "react";
import Logo from "./logo";
import { navLinks } from "@/content/home";
import { mainNav } from "@/lib/routes";
export default function SiteHeader() {
const pathname = usePathname();
const [scrolled, setScrolled] = useState(false);
const [hidden, setHidden] = useState(false);
const [open, setOpen] = useState(false);
const [menuPath, setMenuPath] = useState(pathname);
// Close the mobile menu whenever the route changes.
if (menuPath !== pathname) {
setMenuPath(pathname);
setOpen(false);
}
useEffect(() => {
let lastY = window.scrollY;
@@ -30,6 +40,7 @@ export default function SiteHeader() {
return () => window.removeEventListener("keydown", onKey);
}, [open]);
const isActive = (href: string) => pathname === href || pathname.startsWith(href + "/");
const classes = ["site-header", scrolled || open ? "is-scrolled" : "", hidden && !open ? "is-hidden" : ""]
.filter(Boolean)
.join(" ");
@@ -40,15 +51,39 @@ export default function SiteHeader() {
<div className="wrap bar">
<Logo />
<nav className="nav" aria-label="Primary">
{navLinks.map((l) => (
<a key={l.href} href={l.href}>
{l.label}
</a>
))}
{mainNav.map((l) =>
l.children ? (
<div key={l.href} className="nav-group">
<Link href={l.href} className={isActive(l.href) ? "is-active" : undefined}>
{l.label}
</Link>
<div className="nav-panel">
<div className="nav-panel-inner">
<Link href={l.href} className="nav-panel-all">
All {l.label} <span aria-hidden="true">→</span>
</Link>
<ul>
{l.children.map((c) => (
<li key={c.href}>
<Link href={c.href} className={pathname === c.href ? "is-active" : undefined}>
{c.label}
</Link>
</li>
))}
</ul>
</div>
</div>
</div>
) : (
<Link key={l.href} href={l.href} className={isActive(l.href) ? "is-active" : undefined}>
{l.label}
</Link>
),
)}
</nav>
<a href="#contact" className="btn btn-solid header-cta">
<Link href="/contact" className="btn btn-solid header-cta">
Book a Strategy Call <span className="arrow">→</span>
</a>
</Link>
<button
type="button"
className="menu-toggle"
@@ -62,24 +97,28 @@ export default function SiteHeader() {
</button>
</div>
</header>
<div id="mobile-menu" className={`mobile-menu${open ? " is-open" : ""}`} aria-hidden={!open}>
<div id="mobile-menu" className={`mobile-menu${open ? " is-open" : ""}`} aria-hidden={!open} inert={!open}>
<ul>
{navLinks.map((l) => (
{mainNav.map((l) => (
<li key={l.href}>
<a href={l.href} onClick={() => setOpen(false)} tabIndex={open ? 0 : -1}>
<Link href={l.href}>
{l.label} <span aria-hidden="true">→</span>
</a>
</Link>
{l.children && (
<div className="mobile-sub">
{l.children.map((c) => (
<Link key={c.href} href={c.href}>
{c.label}
</Link>
))}
</div>
)}
</li>
))}
</ul>
<a
href="#contact"
className="btn btn-solid"
onClick={() => setOpen(false)}
tabIndex={open ? 0 : -1}
>
<Link href="/contact" className="btn btn-solid">
Book a Strategy Call <span className="arrow">→</span>
</a>
</Link>
</div>
</>
);