#!/usr/bin/env node // Renders crawlable static pages from services-data.js + projects-data.js: // /services/index.html, /services//index.html, /projects/index.html, // /projects//index.html, plus sitemap.xml. Node stdlib only. import fs from "node:fs"; import path from "node:path"; import vm from "node:vm"; const ROOT = path.dirname(new URL(import.meta.url).pathname); const SITE = "https://wilddragon.net"; const TODAY = new Date().toISOString().slice(0, 10); const win = { __R: (p) => "/" + p }; vm.runInNewContext(fs.readFileSync(path.join(ROOT, "projects-data.js"), "utf8"), { window: win, __R: win.__R }); vm.runInNewContext(fs.readFileSync(path.join(ROOT, "services-data.js"), "utf8"), { window: win }); const { PROJECTS, SERVICE_PAGES, SERVICE_AREAS } = win; const esc = (s) => String(s).replace(/[&<>"]/g, (c) => ({ "&": "&", "<": "<", ">": ">", '"': """ }[c])); const abs = (p) => (p.startsWith("http") ? p : SITE + (p.startsWith("/") ? p : "/" + p)); const write = (rel, html) => { const f = path.join(ROOT, rel); fs.mkdirSync(path.dirname(f), { recursive: true }); fs.writeFileSync(f, html); }; const NAV = `
`; const FOOTER = `
`; // ponytail: page-local CSS instead of touching styles.css — the SPA never renders these pages. const STYLE = ``; function page({ url, title, description, image, ld, body, breadcrumb }) { const crumbs = { "@type": "BreadcrumbList", itemListElement: breadcrumb.map(([name, item], i) => ({ "@type": "ListItem", position: i + 1, name, item: abs(item) })) }; return ` ${esc(title)} ${STYLE} ${NAV} ${body} ${FOOTER} `; } const BUSINESS_ID = SITE + "/#organization"; const projectCard = (p) => `${esc(p.client)}${esc(p.title)}`; // ── service pages ────────────────────────────────────────────────────────── for (const s of SERVICE_PAGES) { const url = `/services/${s.slug}/`; const related = s.related.map((slug) => PROJECTS.find((p) => p.slug === slug)).filter(Boolean); const ld = [ { "@type": "Service", "@id": abs(url) + "#service", name: s.title, description: s.metaDescription, url: abs(url), provider: { "@id": BUSINESS_ID }, areaServed: SERVICE_AREAS.map((n) => ({ "@type": "Place", name: n })), serviceType: s.title, hasOfferCatalog: { "@type": "OfferCatalog", name: s.title, itemListElement: s.services.map(([n, d]) => ({ "@type": "Offer", itemOffered: { "@type": "Service", name: n, description: d } })) } }, { "@type": "FAQPage", mainEntity: s.faq.map(([q, a]) => ({ "@type": "Question", name: q, acceptedAnswer: { "@type": "Answer", text: a } })) }, ]; const body = `
${esc(s.eyebrow)}

${esc(s.title)}

${esc(s.lede)}

Request a consultation

${s.body.map((p) => `

${esc(p)}

`).join("")}

What's included

${s.services.map(([n, d]) => `

${esc(n)}

${esc(d)}

`).join("")}

Service area

${SERVICE_AREAS.map(esc).join(" · ")}

Frequently asked questions

${s.faq.map(([q, a]) => `
${esc(q)}
${esc(a)}
`).join("")}
${related.length ? `

Related projects

` : ""}
`; write(url + "index.html", page({ url, title: s.metaTitle, description: s.metaDescription, image: s.hero, ld, body, breadcrumb: [["Home", "/"], ["Services", "/services/"], [s.title, url]] })); } // ── project pages ────────────────────────────────────────────────────────── for (const p of PROJECTS) { const url = `/projects/${p.slug}/`; const desc = p.summary.length > 158 ? p.summary.slice(0, 155).replace(/\s+\S*$/, "") + "…" : p.summary; const ld = [{ "@type": "Article", "@id": abs(url) + "#article", headline: p.title, description: p.summary, image: abs(p.thumbnail), url: abs(url), author: { "@id": SITE + "/#person" }, publisher: { "@id": BUSINESS_ID }, about: { "@type": "Organization", name: p.client }, keywords: [p.category, ...(p.technologies || [])].join(", ") }]; const body = `
${esc(p.title)}
${esc(p.category)} · ${esc(p.year)}

${esc(p.title)}

${esc(p.summary)}

${p.description.map((t) => `

${esc(t)}

`).join("")}
${p.highlights ? `

Highlights

    ${p.highlights.map((h) => `
  • ${esc(h)}
  • `).join("")}
` : ""} ${p.scope ? `

Scope

${p.scope.map((t) => `${esc(t)}`).join("")}
` : ""} ${p.technologies ? `

Technologies

${p.technologies.map((t) => `${esc(t)}`).join("")}
` : ""}
`; write(url + "index.html", page({ url, title: `${p.title} | Case Study | Wild Dragon`, description: desc, image: p.thumbnail, ld, body, breadcrumb: [["Home", "/"], ["Projects", "/projects/"], [p.title, url]] })); } // ── index pages ──────────────────────────────────────────────────────────── write("services/index.html", page({ url: "/services/", title: "AV & Broadcast Integration Services | Washington DC | Wild Dragon", description: "Commercial AV installation, broadcast systems integration, LED video walls, and conference room AV — designed, installed, and commissioned in Washington, DC and the DMV.", image: "images/wild-dragon-logo.jpg", ld: [], breadcrumb: [["Home", "/"], ["Services", "/services/"]], body: `
Services · Washington, DC

AV & broadcast integration services

Design, installation, and commissioning for commercial AV and broadcast facilities across the DMV.

` })); write("projects/index.html", page({ url: "/projects/", title: "Broadcast & AV Integration Case Studies | Wild Dragon", description: "Case studies: broadcast facilities, cloud production, XR stages, and AV integration for the Washington Commanders, BetMGM, CVS/Aetna, UBS, Monumental Sports, Intuit, and Red Sands.", image: "images/photos/production-switcher.jpg", ld: [], breadcrumb: [["Home", "/"], ["Projects", "/projects/"]], body: `
Selected work

Case studies

Facilities designed, integrated, and commissioned by Wild Dragon.

` })); // ── sitemap ──────────────────────────────────────────────────────────────── const urls = [["/", "1.0"], ["/services/", "0.9"], ...SERVICE_PAGES.map((s) => [`/services/${s.slug}/`, "0.9"]), ["/projects/", "0.7"], ...PROJECTS.map((p) => [`/projects/${p.slug}/`, "0.7"])]; write("sitemap.xml", ` ${urls.map(([u, pr]) => ` ${abs(u)}${TODAY}${pr}`).join("\n")} `); console.log(`built ${SERVICE_PAGES.length} service + ${PROJECTS.length} project pages, sitemap ${urls.length} urls`);