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.
This commit is contained in:
Alejandro Veras
2026-10-08 22:54:50 -04:00
parent ecfc86447b
commit 7b3bfb51e1
42 changed files with 11488 additions and 151 deletions
+30 -5
View File
@@ -2,23 +2,48 @@
import { useEffect } from "react";
/** Adds `is-in` to every `[data-reveal]` / `[data-reveal-group]` element as it scrolls into view. */
const SELECTOR = "[data-reveal], [data-reveal-group]";
/**
* Marks `[data-reveal]` / `[data-reveal-group]` elements with `data-in` as they scroll into view.
* Render it as the last child of a page: its effect then runs only after the page's markup has
* hydrated, so touching those elements can't cause a hydration mismatch.
*/
export default function RevealObserver() {
useEffect(() => {
const targets = document.querySelectorAll<HTMLElement>("[data-reveal], [data-reveal-group]");
const io = new IntersectionObserver(
(entries) => {
for (const entry of entries) {
if (entry.isIntersecting) {
entry.target.classList.add("is-in");
(entry.target as HTMLElement).dataset.in = "";
io.unobserve(entry.target);
}
}
},
{ rootMargin: "0px 0px -12% 0px", threshold: 0.12 },
);
targets.forEach((el) => io.observe(el));
return () => io.disconnect();
const watch = (root: ParentNode) =>
root.querySelectorAll<HTMLElement>(SELECTOR).forEach((el) => {
if (!("in" in el.dataset)) io.observe(el);
});
watch(document);
const mo = new MutationObserver((records) => {
for (const r of records) {
r.addedNodes.forEach((n) => {
if (n instanceof HTMLElement) {
if (n.matches(SELECTOR) && !("in" in n.dataset)) io.observe(n);
watch(n);
}
});
}
});
mo.observe(document.body, { childList: true, subtree: true });
return () => {
io.disconnect();
mo.disconnect();
};
}, []);
return null;