wilddragon-site/components.jsx
Zachary Gaetano 2104388ea1 Rebuild from Claude Design bundle: single-page React (no build step)
Replaces the Next.js app with the standalone bundle exported from
claude.ai/design — same content/SEO, but the site is now a flat static
directory: index.html + a few .jsx/.js files + images/, plus a
fully self-contained single-file build at standalone.html.

Why: the previous Next.js repo had .next/ and node_modules/ committed
(~460MB) and edits required a rebuild loop. The design bundle lets
content/layout tweaks ship by editing data.js, projects-data.js,
components.jsx, or styles.css directly.

- index.html        single-page entry (React 18 + Babel via unpkg CDN)
- styles.css        all styles
- data.js           site copy
- projects-data.js  case studies
- components.jsx    sections + project overlay
- tweaks-panel.jsx  in-page Tweaks panel
- llms.txt          machine-readable site summary
- images/           photos and logos referenced by relative path
- standalone.html   single-file portable build (all assets inlined)
2026-05-12 04:25:33 +00:00

865 lines
44 KiB
JavaScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* global React */
const { useState, useEffect, useRef, Fragment } = React;
// ─── Icons ───────────────────────────────────────────────────────────────────
const Icon = ({ size = 16, sw = 1.75, className = "", children }) => (
<svg xmlns="http://www.w3.org/2000/svg" width={size} height={size} viewBox="0 0 24 24"
fill="none" stroke="currentColor" strokeWidth={sw} strokeLinecap="round" strokeLinejoin="round" className={className}>
{children}
</svg>
);
const ChevronDown = (p) => <Icon size={16} {...p}><polyline points="6 9 12 15 18 9"/></Icon>;
const Menu = (p) => <Icon size={24} {...p}><line x1="4" x2="20" y1="6" y2="6"/><line x1="4" x2="20" y1="12" y2="12"/><line x1="4" x2="20" y1="18" y2="18"/></Icon>;
const X = (p) => <Icon size={24} {...p}><path d="M18 6 6 18"/><path d="m6 6 12 12"/></Icon>;
const ArrowRight = (p) => <Icon size={16} {...p}><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></Icon>;
const ArrowLeft = (p) => <Icon size={16} {...p}><path d="m12 19-7-7 7-7"/><path d="M19 12H5"/></Icon>;
const ArrowUpRight = (p) => <Icon size={14} {...p}><path d="M7 17 17 7"/><path d="M7 7h10v10"/></Icon>;
const Mail = (p) => <Icon size={16} {...p}><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></Icon>;
const MapPin = (p) => <Icon size={16} {...p}><path d="M20 10c0 7-8 13-8 13s-8-6-8-13a8 8 0 0 1 16 0Z"/><circle cx="12" cy="10" r="3"/></Icon>;
const Linkedin = (p) => <Icon size={16} {...p}><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6z"/><rect width="4" height="12" x="2" y="9"/><circle cx="4" cy="4" r="2"/></Icon>;
const ExternalLink = (p) => <Icon size={13} {...p}><path d="M15 3h6v6"/><path d="M10 14 21 3"/><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/></Icon>;
const Radio = (p) => <Icon {...p}><path d="M4.9 19.1C1 15.2 1 8.8 4.9 4.9"/><path d="M7.8 16.2c-2.3-2.3-2.3-6.1 0-8.5"/><circle cx="12" cy="12" r="2"/><path d="M16.2 7.8c2.3 2.3 2.3 6.1 0 8.5"/><path d="M19.1 4.9C23 8.8 23 15.2 19.1 19.1"/></Icon>;
const Tv = (p) => <Icon {...p}><rect width="20" height="15" x="2" y="7" rx="2" ry="2"/><polyline points="17 2 12 7 7 2"/></Icon>;
const Cloud = (p) => <Icon {...p}><path d="M17.5 19a4.5 4.5 0 1 0-1.41-8.775A6.5 6.5 0 1 0 7 19h10.5z"/></Icon>;
const Cable = (p) => <Icon {...p}><path d="M4 9a2 2 0 0 1-2-2V5h6v2a2 2 0 0 1-2 2Z"/><path d="M3 5V3"/><path d="M7 5V3"/><path d="M19 15V6.5a3.5 3.5 0 0 0-7 0v11a3.5 3.5 0 0 1-7 0V9"/><path d="M17 21v-2"/><path d="M21 21v-2"/><path d="M22 19v-2a2 2 0 0 0-2-2h-2v4Z"/></Icon>;
const Ruler = (p) => <Icon {...p}><path d="M21.3 15.3a2.4 2.4 0 0 1 0 3.4l-2.6 2.6a2.4 2.4 0 0 1-3.4 0L2.7 8.7a2.41 2.41 0 0 1 0-3.4l2.6-2.6a2.41 2.41 0 0 1 3.4 0Z"/><path d="m14.5 12.5 2-2"/><path d="m11.5 9.5 2-2"/><path d="m8.5 6.5 2-2"/><path d="m17.5 15.5 2-2"/></Icon>;
const Wrench = (p) => <Icon {...p}><path d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"/></Icon>;
const MonitorCheck = (p) => <Icon {...p}><path d="m9 10 2 2 4-4"/><rect width="20" height="14" x="2" y="3" rx="2"/><path d="M12 17v4"/><path d="M8 21h8"/></Icon>;
const Headset = (p) => <Icon {...p}><path d="M3 11h3a2 2 0 0 1 2 2v3a2 2 0 0 1-2 2H3v-7a9 9 0 0 1 18 0v7h-3a2 2 0 0 1-2-2v-3a2 2 0 0 1 2-2h3"/></Icon>;
const Camera = (p) => <Icon {...p}><path d="M14.5 4h-5L7 7H4a2 2 0 0 0-2 2v9a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V9a2 2 0 0 0-2-2h-3l-2.5-3z"/><circle cx="12" cy="13" r="3"/></Icon>;
const Film = (p) => <Icon {...p}><rect width="18" height="18" x="3" y="3" rx="2"/><path d="M7 3v18"/><path d="M3 7.5h4"/><path d="M3 12h18"/><path d="M3 16.5h4"/><path d="M17 3v18"/><path d="M17 7.5h4"/><path d="M17 16.5h4"/></Icon>;
const Package = (p) => <Icon {...p}><path d="m7.5 4.27 9 5.15"/><path d="M21 8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73l7 4a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16Z"/><path d="m3.3 7 8.7 5 8.7-5"/><path d="M12 22V12"/></Icon>;
const Monitor = (p) => <Icon {...p}><rect width="20" height="14" x="2" y="3" rx="2"/><line x1="8" x2="16" y1="21" y2="21"/><line x1="12" x2="12" y1="17" y2="21"/></Icon>;
// ─── Reveal wrapper ──────────────────────────────────────────────────────────
function Reveal({ children, delay = 0, className = "" }) {
const ref = useRef(null);
const [visible, setVisible] = useState(false);
useEffect(() => {
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting) {
setTimeout(() => setVisible(true), delay);
obs.unobserve(e.target);
}
}, { threshold: 0.1, rootMargin: "0px 0px -60px 0px" });
if (ref.current) obs.observe(ref.current);
return () => obs.disconnect();
}, [delay]);
return <div ref={ref} className={`reveal ${visible ? "visible" : ""} ${className}`}>{children}</div>;
}
// ─── Cursor follower ─────────────────────────────────────────────────────────
function CursorDot() {
const ref = useRef(null);
useEffect(() => {
const onMove = (e) => {
if (ref.current) {
ref.current.style.transform = `translate(${e.clientX}px, ${e.clientY}px) translate(-50%, -50%)`;
}
document.body.dataset.cursor = "on";
};
window.addEventListener("mousemove", onMove);
return () => window.removeEventListener("mousemove", onMove);
}, []);
return <div ref={ref} className="cursor-dot"></div>;
}
// ─── Page-load curtain ───────────────────────────────────────────────────────
function Curtain() {
const [gone, setGone] = useState(false);
useEffect(() => { const t = setTimeout(() => setGone(true), 1300); return () => clearTimeout(t); }, []);
if (gone) return null;
return <div className="curtain"></div>;
}
// ─── NAVIGATION ──────────────────────────────────────────────────────────────
function Navigation() {
const [scrolled, setScrolled] = useState(false);
const [open, setOpen] = useState(false);
useEffect(() => {
const onScroll = () => setScrolled(window.scrollY > 50);
window.addEventListener("scroll", onScroll);
return () => window.removeEventListener("scroll", onScroll);
}, []);
const links = [{href:"#about",label:"About"},{href:"#projects",label:"Projects"},{href:"#on-set",label:"On Set"},{href:"#contact",label:"Contact"}];
return (
<nav className={`nav ${scrolled ? "scrolled" : ""}`}>
<div className="nav-inner">
<a href="#" className="nav-logo">
<img src={__R("images/dragon-mark.png")} alt="Wild Dragon"/>
<span>Wild Dragon</span>
</a>
<div className="nav-links">
{links.map(l => <a key={l.href} href={l.href}>{l.label}</a>)}
</div>
<button className="nav-toggle" onClick={() => setOpen(!open)} aria-label="Toggle menu">
{open ? <X/> : <Menu/>}
</button>
</div>
<div className={`nav-mobile ${open ? "open" : ""}`}>
{links.map(l => <a key={l.href} href={l.href} onClick={()=>setOpen(false)}>{l.label}</a>)}
</div>
</nav>
);
}
// ─── HERO ────────────────────────────────────────────────────────────────────
const HERO_ROLES = [
"Broadcast Systems Integration",
"Production Facility Design",
"IP & Cloud Production",
"Extended Reality Stages",
];
function Hero({ ctaLabel }) {
const [loaded, setLoaded] = useState(false);
const [idx, setIdx] = useState(0);
const [text, setText] = useState("");
const [typing, setTyping] = useState(true);
useEffect(() => { const t = setTimeout(() => setLoaded(true), 100); return () => clearTimeout(t); }, []);
useEffect(() => {
const role = HERO_ROLES[idx];
if (typing) {
if (text.length < role.length) {
const t = setTimeout(() => setText(role.slice(0, text.length + 1)), 50);
return () => clearTimeout(t);
} else {
const t = setTimeout(() => setTyping(false), 2500);
return () => clearTimeout(t);
}
} else {
if (text.length > 0) {
const t = setTimeout(() => setText(text.slice(0, -1)), 25);
return () => clearTimeout(t);
} else {
setIdx((i) => (i + 1) % HERO_ROLES.length);
setTyping(true);
}
}
}, [text, typing, idx]);
const aniBase = (d) => ({transition:`opacity .9s ${d}s, transform .9s ${d}s`, opacity: loaded?1:0, transform: loaded?"translateY(0)":"translateY(24px)"});
return (
<section className="hero grain">
<div className="hero-bg"><img src={__R("images/photos/production-switcher.jpg")} alt="Production switcher in a broadcast control room"/></div>
<div className="hero-grid"></div>
<div className="hero-fade-v"></div>
<div className="hero-fade-h"></div>
<div className="hero-corner tl">// 38.9°N · 77.0°W</div>
<div className="hero-corner tr">EST. 2024 · DC</div>
<div className="hero-corner bl">REC · LIVE</div>
<div className="hero-corner br"> 01 / 09</div>
<div className="hero-content">
<div className="hero-logo" style={aniBase(0)}>
<img src={__R("images/wild-dragon-logo.png")} alt="Wild Dragon — Zachary Gaetano"/>
</div>
<div className="hero-tag" style={aniBase(.15)}>
{text}<span className="caret">|</span>
</div>
<h1 style={aniBase(.3)}>
Zachary<br/><span className="last">Gaetano</span>
</h1>
<div className="hero-rule" style={{transition:"opacity 1s .4s, width 1s .4s", opacity: loaded?1:0, width: loaded?"6rem":0}}></div>
<p className="hero-desc" style={aniBase(.5)}>
Designing and integrating broadcast production facilities for sports, corporate, aerospace, and financial organizations.
</p>
<div className="hero-ctas" style={aniBase(.65)}>
<a href="#projects" className="btn btn-primary">{ctaLabel || "View Projects"}</a>
<a href="mailto:zgaetano@wilddragon.net" className="btn btn-ghost-dark">Get in Touch</a>
</div>
</div>
<a href="#clients" className="scroll-indicator" style={{transition:"opacity 1s .8s", opacity: loaded?1:0}}>
<span>Scroll</span>
<ChevronDown/>
</a>
</section>
);
}
// ─── CLIENTS — marquee or static ─────────────────────────────────────────────
const CLIENTS = [
{ name:"Broadcast Management Group", logo:__R("images/clients/bmg.png") },
{ name:"AVI-SPL", logo:__R("images/clients/avispl.png") },
{ name:"NBC Sports", logo:__R("images/clients/nbc-sports.png") },
{ name:"Washington Commanders", logo:__R("images/clients/commanders.png") },
{ name:"CVS / Aetna", logo:__R("images/clients/cvs.png") },
{ name:"UBS", logo:__R("images/clients/ubs.png") },
{ name:"BetMGM", logo:__R("images/clients/betmgm.svg") },
{ name:"Intuit", logo:__R("images/clients/intuit.png") },
{ name:"Monumental Sports", logo:__R("images/clients/monumental.svg") },
{ name:"COSM", logo:__R("images/clients/cosm.svg") },
{ name:"Red Sands", logo:__R("images/clients/redsands.svg") },
];
function Clients({ marquee }) {
return (
<section id="clients" className="clients" aria-label="Clients">
<div className="container">
<Reveal>
<p className="clients-label"> Trusted by </p>
</Reveal>
</div>
{marquee ? (
<div className="marquee">
<div className="marquee-track">
{[...CLIENTS, ...CLIENTS].map((c, i) => (
<div className="marquee-item" key={i} title={c.name}>
<img src={c.logo} alt={c.name}/>
</div>
))}
</div>
</div>
) : (
<div className="container">
<Reveal>
<div className="clients-static">
{CLIENTS.map((c) => (
<div className="marquee-item" key={c.name} title={c.name}>
<img src={c.logo} alt={c.name}/>
</div>
))}
</div>
</Reveal>
</div>
)}
</section>
);
}
// ─── PARTNERS ────────────────────────────────────────────────────────────────
const PARTNERS = [
{ name:"THOR Broadcast", logo:__R("images/clients/thor-logo.png") },
{ name:"Ross Video", logo:__R("images/clients/ross-video.png") },
{ name:"Filmtools", logo:__R("images/clients/filmtools.png") },
{ name:"Forecast Consoles", logo:__R("images/clients/forecast.png") },
{ name:"vMix by Studio Coast", logo:__R("images/clients/vmix.png") },
{ name:"RED Digital Cinema", logo:__R("images/partners/red-digital.png") },
];
function Partners() {
return (
<section className="partners">
<div className="container">
<Reveal>
<p>// Technology Partners</p>
<div className="partners-grid">
{PARTNERS.map(p => (
<div className="partner-logo" title={p.name} key={p.name}>
<img src={p.logo} alt={p.name}/>
</div>
))}
</div>
</Reveal>
</div>
</section>
);
}
// ─── ABOUT ───────────────────────────────────────────────────────────────────
const CAPS = [
{ icon: Radio, title:"Systems Design", desc:"End-to-end broadcast facility design including signal flow engineering, equipment specification, and architectural documentation." },
{ icon: Cable, title:"Integration", desc:"Full system integration from rack builds and wiring to commissioning, testing, and operator training." },
{ icon: Cloud, title:"Cloud & IP", desc:"Cloud-native production architectures, SMPTE ST 2110, NDI, SRT, and hybrid on-prem/cloud workflows." },
{ icon: Tv, title:"Broadcast Engineering", desc:"Live production engineering, RF systems, video routing, and real-time broadcast operations." },
];
function About() {
return (
<section id="about" className="about">
<div className="container">
<Reveal>
<div className="about-intro">
<div className="section-counter"><span className="num">01</span><span className="bar"></span><span>About</span></div>
<h2 className="section-title">From live production to<br/>systems architecture.</h2>
<div className="body" style={{marginTop:"2rem"}}>
<p>I&apos;m a broadcast engineer and systems integrator based in the Washington DC area, with a career that spans both sides of the industry production and infrastructure.</p>
<p>My background in live production as a 1st AC, DIT, Camera Operator, and Trinity Operator gives me an operator&apos;s perspective that most systems designers don&apos;t have. I build facilities that work the way production teams actually need them to not just the way engineers imagine they will.</p>
<p>Today, my focus is broadcast systems integration. As a principal systems designer with <a className="inline" href="https://broadcastmgmt.com" target="_blank" rel="noopener noreferrer">Broadcast Management Group</a>, I design production facilities for organizations including the <strong>Washington Commanders</strong>, <strong>CVS/Aetna</strong>, <strong>UBS</strong>, <strong>BetMGM</strong>, <strong>Intuit</strong>, and <strong>Monumental Sports</strong> engineering control rooms, studios, XR stages, and IP-based workflows across sports, corporate, financial services, aerospace, and defense.</p>
</div>
</div>
</Reveal>
<div className="cap-grid">
{CAPS.map((c, i) => (
<Reveal key={c.title} delay={i*100}>
<div className="cap">
<div className="cap-icon"><c.icon size={20} sw={1.5}/></div>
<h3>{c.title}</h3>
<p>{c.desc}</p>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
// ─── STATS — dark band ───────────────────────────────────────────────────────
const STATS = [
{ v:10, suf:"+", label:"Years in Broadcast", desc:"Production & engineering" },
{ v:8, suf:"+", label:"Major Facilities", desc:"Designed & integrated" },
{ v:5900, suf:"+", label:"End Users Served", desc:"Global content delivery" },
{ v:5, suf:"", label:"Industry Verticals", desc:"Sports, corporate, financial, aerospace, defense" },
];
function AnimatedNumber({ value, suffix }) {
const [n, setN] = useState(0);
const ref = useRef(null);
const done = useRef(false);
useEffect(() => {
const obs = new IntersectionObserver(([e]) => {
if (e.isIntersecting && !done.current) {
done.current = true;
const dur = 2000, steps = 60, inc = value/steps;
let cur = 0;
const t = setInterval(() => {
cur += inc;
if (cur >= value) { setN(value); clearInterval(t); }
else setN(Math.floor(cur));
}, dur/steps);
}
}, { threshold: .3 });
if (ref.current) obs.observe(ref.current);
return () => obs.disconnect();
}, [value]);
const display = n >= 1000 && value >= 1000 ? `${(n/1000).toFixed(1).replace(/\.0$/,"")}k` : n;
return <span ref={ref} className="tabular-nums">{display}<span className="suf">{suffix}</span></span>;
}
function Stats() {
return (
<section className="stats grain dark-section">
<div className="container">
<div className="stats-grid">
{STATS.map((s, i) => (
<Reveal key={s.label} delay={i*80}>
<div>
<div className="stat-value"><AnimatedNumber value={s.v} suffix={s.suf}/></div>
<p className="stat-label">{s.label}</p>
<p className="stat-desc">{s.desc}</p>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
// ─── SERVICES — timeline ─────────────────────────────────────────────────────
const SERVICES = [
{ icon: Ruler, phase:"01", title:"Design", desc:"Signal flow engineering, system architecture, and equipment specification. Every facility starts with documentation that defines how signals move and how operators interact." },
{ icon: Wrench, phase:"02", title:"Integrate", desc:"Full system builds from rack fabrication and cable infrastructure through to final termination and labeling — clean builds technicians can troubleshoot and maintain for years." },
{ icon: MonitorCheck, phase:"03", title:"Commission", desc:"End-to-end system testing, calibration, and performance verification. Every signal path tested, every failover validated, every workflow documented before handoff." },
{ icon: Headset, phase:"04", title:"Support", desc:"Operator training, documentation packages, and ongoing technical support. Systems designed with the people who use them in mind — not just the engineers who build them." },
];
function Services() {
return (
<section id="services" className="services">
<div className="container">
<Reveal>
<div style={{maxWidth:"48rem", marginBottom:"4rem"}}>
<div className="section-counter"><span className="num">02</span><span className="bar"></span><span>Process</span></div>
<h2 className="section-title" style={{marginBottom:"1.25rem"}}>From blueprint to broadcast.</h2>
<p className="section-lede">A complete lifecycle approach to broadcast facility design and systems integration. Every project follows a disciplined process so systems work flawlessly when the red light goes on.</p>
</div>
</Reveal>
<div className="svc-timeline">
<div className="svc-line"></div>
<div className="svc-grid">
{SERVICES.map((s, i) => (
<Reveal key={s.title} delay={i*100}>
<div className="svc">
<div className="svc-node">{s.phase}</div>
<div className="svc-icon"><s.icon size={20} sw={1.5}/></div>
<h3>{s.title}</h3>
<p>{s.desc}</p>
</div>
</Reveal>
))}
</div>
</div>
</div>
</section>
);
}
// ─── PROJECTS — featured + grid ──────────────────────────────────────────────
function pickScopeChips(scopes) {
return (scopes || []).slice(0, 3);
}
function Projects({ onOpen }) {
const list = window.PROJECTS || [];
const [featured, ...rest] = list;
return (
<section id="projects" className="projects" aria-label="Selected projects">
<div className="container">
<Reveal>
<div className="projects-head">
<div>
<div className="section-counter"><span className="num">03</span><span className="bar"></span><span>Projects</span></div>
<h2 className="section-title">Selected work.</h2>
</div>
<p className="lede">Broadcast facility design and systems integration for enterprise, sports, aerospace, and financial services clients.</p>
</div>
</Reveal>
{featured && (
<Reveal>
<a className="proj-featured" href={`#/projects/${featured.slug}`} onClick={(e)=>{e.preventDefault(); onOpen(featured.slug);}}>
<img src={featured.thumbnail} alt={`${featured.client}${featured.category}`}/>
<div className="proj-featured-body">
<div className="proj-featured-meta">
<span className="badge">Featured</span>
<span className="yr">{featured.category} · {featured.year}</span>
</div>
<h3>{featured.client}</h3>
<p>{featured.summary}</p>
<span className="cta">View case study <ArrowRight/></span>
</div>
</a>
</Reveal>
)}
<div className="proj-grid">
{rest.map((p, i) => (
<Reveal key={p.slug} delay={i*60}>
<a className="proj-card"
href={`#/projects/${p.slug}`}
onClick={(e)=>{e.preventDefault(); onOpen(p.slug);}}>
<img src={p.thumbnail} alt={`${p.client}${p.category}`}/>
<div className="proj-card-overlay"></div>
<div className="proj-arrow"><ArrowUpRight/></div>
<div className="proj-card-body">
<div className="proj-card-meta">
<span className="cat">{p.category}</span>
<span className="sep">/</span>
<span className="yr">{p.year}</span>
</div>
<h3>{p.client}</h3>
<p className="summary">{p.summary}</p>
<div className="scope-chips">
{pickScopeChips(p.scope).map(s => <span key={s}>{s}</span>)}
</div>
</div>
</a>
</Reveal>
))}
</div>
</div>
</section>
);
}
// ─── TECH — spec sheet ───────────────────────────────────────────────────────
const TECH_CATS = [
{ name:"Routing & Switching", items:["Ross Ultrix","Blackmagic ATEM","Evertz EQX","Panasonic Kairos"] },
{ name:"All-In-One Production", items:["vMix","Vizrt","AMPP"] },
{ name:"Signal Transport", items:["SMPTE 2110","NDI","SRT","SDI","Dante","MADI","JPEG XS"] },
{ name:"Infrastructure", items:["IP Networking","Fiber Infrastructure","KVM Systems","UPS & Power","Cooling & Ventilation"] },
{ name:"Display & XR", items:["LED Processing","Unreal Engine","Camera Tracking","Projection","Color Management"] },
];
function TechStack() {
return (
<section className="tech grain dark-section">
<div className="container">
<Reveal>
<div style={{maxWidth:"48rem", marginBottom:"3rem"}}>
<div className="section-counter"><span className="num">04</span><span className="bar"></span><span>Technology</span></div>
<h2 className="section-title" style={{marginBottom:"1.25rem"}}>Tools of the trade.</h2>
<p className="section-lede">Deep experience across the full broadcast technology stack from traditional SDI infrastructure to modern IP and cloud-native production platforms.</p>
</div>
</Reveal>
<div className="tech-table">
{TECH_CATS.map((c, i) => (
<Reveal key={c.name} delay={i*60}>
<div className="tech-row">
<h3>{c.name}</h3>
<div className="items">
{c.items.map(it => <span key={it}>{it}</span>)}
</div>
</div>
</Reveal>
))}
</div>
</div>
</section>
);
}
// ─── ON SET ──────────────────────────────────────────────────────────────────
const REEL_URL = "https://player.vimeo.com/video/225920311?h=0&badge=0&autopause=0&player_id=0&app_id=58479";
const CREDITS = [
{ t:"Bethesda Fallout Day", r:"1st AC", f:"Corporate / Event", y:"2025" },
{ t:"ZeniMax Elder Scrolls Online Update", r:"1st AC", f:"Corporate", y:"2025" },
{ t:"Palo Alto Ad", r:"Arri Trinity Operator", f:"Commercial", y:"2025" },
{ t:"Street Fighter DLC Ad", r:"Arri Trinity Operator", f:"Commercial", y:"2025" },
{ t:"Chase Small Business Promo", r:"Arri Trinity Operator", f:"Commercial", y:"2025" },
{ t:"ZeniMax Elder Scrolls Online Update", r:"1st AC", f:"Corporate", y:"2024" },
{ t:"Joe Vogel For Congress", r:"Camera Op / Trinity Op", f:"Political", y:"2024" },
{ t:"I Am a Champion", r:"Camera Op / Trinity Op", f:"Sports", y:"2023" },
{ t:"Washington Commanders Schedule Release", r:"Director of Photography", f:"Sports", y:"2023" },
{ t:"Stephen Sharer MIDNIGHT", r:"DP / Trinity Operator", f:"Narrative", y:"2023" },
{ t:"Stephen Sharer YOU and I", r:"DP / Trinity Operator", f:"Narrative", y:"2023" },
{ t:"A Chocolate Lens", r:"Camera Operator", f:"Narrative", y:"2023" },
{ t:"Capital One: Pride Month", r:"Director of Photography", f:"Corporate", y:"2021" },
{ t:"Tafon Nchukwi: My Goal Is to Be UFC Champion", r:"Camera Operator", f:"Sports Doc", y:"2020" },
{ t:"Tinder", r:"Director of Photography", f:"Commercial", y:"2020" },
{ t:"Conewago", r:"DP / Editor", f:"Documentary", y:"2020" },
{ t:"In Memoriam", r:"Director of Photography", f:"Documentary", y:"2019" },
];
const GEAR = [
{ cat:"Cinema Cameras", icon: Camera, items:["RED Komodo-X","RED DSMC3 V-Raptor-X 8K VV + 6K S35 Dual-Format (Canon RF)"] },
{ cat:"Lenses", icon: Film, items:["Mamiya 645 Sekor C PL 7 Lens Set","Mamiya 645 Sekor C 35mm","Mamiya 645 Sekor C 110mm","Mamiya 645 Sekor C 150mm","16-30mm / 28-75mm / 75-180mm T2.9 Zoom Set"] },
{ cat:"Monitoring & Transmission", icon: Monitor, items:["SmallHD DSMC3 7\"","SmallHD Cine7 with Bolt 6 1500RX","Teradek Bolt 6 Max XT Kit (TX + RX)","DJI SDR Transmission"] },
{ cat:"Camera Support", icon: Package, items:["Arri Trinity — Certified Owner & Operator","Dana Dolly Kit (8 / 4ft Speedrail, 2× Matthews Stands)","DJI Focus Pro All In One"] },
];
function OnSet() {
return (
<section id="on-set" className="onset">
<div className="container onset-stack">
<Reveal>
<div className="onset-intro">
<div className="section-counter"><span className="num">05</span><span className="bar"></span><span>On Set</span></div>
<h2 className="section-title">Trinity Operator &amp; DIT, <span className="light">Washington DC.</span></h2>
<p className="section-lede" style={{marginTop:"1.25rem"}}>Certified Arri Trinity owner and operator with RED camera packages and specialized live production gear from run-and-gun documentary to multi-camera sports and political campaigns. Available for commercial, corporate, sports, and narrative work throughout the DMV.</p>
</div>
</Reveal>
<Reveal>
<div>
<p className="eyebrow">Reel</p>
<div className="reel-frame">
<iframe src={REEL_URL} allow="autoplay; fullscreen; picture-in-picture" allowFullScreen title="Zachary Gaetano — Showreel"></iframe>
</div>
</div>
</Reveal>
<Reveal>
<div>
<p className="eyebrow">Selected Credits</p>
<div className="credits-list">
{CREDITS.map((c, i) => (
<div key={i} className="credit-row">
<span className="t">{c.t}</span>
<span className="r">{c.r}</span>
<span className="f">{c.f}</span>
<span className="y">{c.y}</span>
</div>
))}
</div>
</div>
</Reveal>
<div>
<Reveal><p className="eyebrow">Kit</p></Reveal>
<div className="gear-grid">
{GEAR.map((g, i) => (
<Reveal key={g.cat} delay={i*100}>
<div className="gear-card">
<div className="cap-icon" style={{marginBottom:"1rem"}}><g.icon size={20} sw={1.5}/></div>
<h3>{g.cat}</h3>
<ul>{g.items.map(it => <li key={it}>{it}</li>)}</ul>
</div>
</Reveal>
))}
</div>
</div>
</div>
</section>
);
}
// ─── CONTACT ─────────────────────────────────────────────────────────────────
function Contact() {
return (
<section id="contact" className="contact">
<div className="container">
<Reveal>
<div className="contact-head">
<div className="section-rule center"></div>
<p className="eyebrow center">// Contact</p>
<h2 className="section-title">Let&apos;s create something.</h2>
<p className="lede" style={{marginTop:"1.25rem"}}>Whether you&apos;re planning a new broadcast facility, upgrading existing infrastructure, or exploring cloud and IP-based production workflows, I&apos;d love to discuss your project.</p>
</div>
</Reveal>
<div className="contact-grid">
<Reveal delay={0}><a className="contact-card" href="mailto:zgaetano@wilddragon.net">
<div className="icon"><Mail size={16} sw={1.5}/></div>
<span className="label">Email</span>
<span className="val">zgaetano@wilddragon.net</span>
</a></Reveal>
<Reveal delay={80}><a className="contact-card" href="https://www.linkedin.com/in/zachary-gaetano-05962386/" target="_blank" rel="noopener noreferrer">
<div className="icon"><Linkedin size={16} sw={1.5}/></div>
<span className="label">LinkedIn</span>
<span className="val">Zachary Gaetano</span>
</a></Reveal>
<Reveal delay={160}><div className="contact-card">
<div className="icon"><MapPin size={16} sw={1.5}/></div>
<span className="label">Location</span>
<span className="val">Washington, DC Area</span>
</div></Reveal>
</div>
<Reveal delay={200}>
<div className="cta-banner grain">
<div className="cta-banner-grid"></div>
<div className="inner">
<h3>Ready to start your next project?</h3>
<p>From initial concept through commissioning and training, I bring a complete lifecycle approach to every facility I design.</p>
<a href="mailto:zgaetano@wilddragon.net" className="btn btn-primary">Start a Conversation <ArrowRight/></a>
</div>
</div>
</Reveal>
</div>
</section>
);
}
// ─── FOOTER ──────────────────────────────────────────────────────────────────
function Footer() {
const sections = [
{ title:"Navigation", links:[{l:"About",h:"#about"},{l:"Process",h:"#services"},{l:"Projects",h:"#projects"},{l:"On Set",h:"#on-set"},{l:"Contact",h:"#contact"}] },
{ title:"Expertise", links:[{l:"Systems Design",h:"#about"},{l:"IP & Cloud Production",h:"#about"},{l:"Broadcast Integration",h:"#about"},{l:"XR / Virtual Production",h:"#projects"}] },
];
return (
<footer className="footer">
<div className="container">
<div className="footer-top">
<div>
<div className="footer-brand">
<img src={__R("images/dragon-mark.png")} alt="Wild Dragon"/>
<span>Wild Dragon</span>
</div>
<p className="about-line">Broadcast systems integration and production facility design for sports, corporate, financial services, and defense organizations.</p>
<div className="footer-socials">
<a href="https://www.linkedin.com/in/zachary-gaetano-05962386/" target="_blank" rel="noopener noreferrer" aria-label="LinkedIn"><Linkedin size={14}/></a>
<a href="mailto:zgaetano@wilddragon.net" aria-label="Email"><Mail size={14}/></a>
</div>
</div>
{sections.map(s => (
<div className="footer-col" key={s.title}>
<h4>{s.title}</h4>
<ul>{s.links.map(l => <li key={l.l}><a href={l.h}>{l.l}</a></li>)}</ul>
</div>
))}
</div>
<div className="footer-bottom">
<p>© {new Date().getFullYear()} Zachary Gaetano. All rights reserved.</p>
<p>Washington, DC Area</p>
</div>
</div>
</footer>
);
}
// ─── PROJECT OVERLAY — editorial case study ──────────────────────────────────
function ProjectOverlay({ slug, onClose, onOpen }) {
const projects = window.PROJECTS;
const project = projects.find(p => p.slug === slug);
useEffect(() => {
const onKey = (e) => { if (e.key === "Escape") onClose(); };
window.addEventListener("keydown", onKey);
document.body.style.overflow = "hidden";
return () => { window.removeEventListener("keydown", onKey); document.body.style.overflow = ""; };
}, [onClose]);
const maskRef = useRef(null);
useEffect(() => { if (maskRef.current) maskRef.current.scrollTop = 0; }, [slug]);
if (!project) return null;
const idx = projects.findIndex(p => p.slug === slug);
const total = projects.length;
const prev = idx > 0 ? projects[idx-1] : projects[total-1];
const next = idx < total - 1 ? projects[idx+1] : projects[0];
const num = String(idx+1).padStart(2,"0");
const totalStr = String(total).padStart(2,"0");
// First description paragraph used as a lede, rest as body
const [lede, ...rest] = project.description;
return (
<div className="po-mask" ref={maskRef} onClick={(e)=>{ if(e.target===maskRef.current) onClose(); }}>
<button className="po-close" onClick={onClose} aria-label="Close"><X size={20}/></button>
<div className="po">
<nav className="po-nav">
<div className="po-nav-inner">
<a className="po-back" href="#projects" onClick={(e)=>{e.preventDefault(); onClose();}}>
<ArrowLeft size={15}/> All projects
</a>
<span className="po-counter"><span className="n">{num}</span><span className="d">/</span><span className="t">{totalStr}</span></span>
<span className="po-brand">Wild Dragon</span>
</div>
</nav>
{/* === HERO === */}
<section className="po-hero">
<div className="po-hero-bg">
<img src={project.thumbnail} alt=""/>
<div className="po-hero-grid"></div>
<div className="po-hero-fade"></div>
</div>
<div className="container po-hero-inner">
<div className="po-hero-meta">
<span className="po-cat">{project.category}</span>
<span className="po-dot"></span>
<span className="po-yr">{project.year}</span>
</div>
<h1 className="po-title">{project.client}</h1>
<p className="po-lede">{project.summary}</p>
<div className="po-hero-rule"></div>
<div className="po-hero-foot">
<span className="po-corner-l">// {project.title || project.client}</span>
<span className="po-corner-r">Case Study · {num}/{totalStr}</span>
</div>
</div>
</section>
{/* === FACTS BAR === */}
<section className="po-facts">
<div className="container">
<div className="po-facts-grid">
<div><span className="lbl">Client</span><span className="val">{project.client}</span></div>
<div><span className="lbl">Category</span><span className="val">{project.category}</span></div>
<div><span className="lbl">Year</span><span className="val">{project.year}</span></div>
<div><span className="lbl">Role</span><span className="val">{(project.scope && project.scope[0]) || "Systems Design"}</span></div>
</div>
</div>
</section>
{/* === LEDE / OPENING === */}
{lede && (
<section className="po-section po-opening">
<div className="container po-opening-grid">
<div className="po-opening-label">
<span className="num">01</span>
<span className="bar"></span>
<span>Overview</span>
</div>
<p className="po-opening-text">{lede}</p>
</div>
</section>
)}
{/* === FEATURE IMAGE === */}
<div className="po-feature">
<div className="po-feature-inner">
<img src={project.thumbnail} alt={`${project.client}${project.category}`}/>
<div className="po-feature-cap">
<span>{project.client} · {project.category}</span>
<span>{project.year}</span>
</div>
</div>
</div>
{/* === BODY + SIDECAR === */}
<section className="po-section po-body">
<div className="container po-body-grid">
<div className="po-body-main">
<div className="po-body-label">
<span className="num">02</span>
<span className="bar"></span>
<span>The build</span>
</div>
<div className="po-body-prose">
{rest.map((p, i) => <p key={i}>{p}</p>)}
</div>
{project.highlights && project.highlights.length > 0 && (
<>
<div className="po-body-label" style={{marginTop:"4.5rem"}}>
<span className="num">03</span>
<span className="bar"></span>
<span>Highlights</span>
</div>
<ul className="po-hl">
{project.highlights.map((h, i) => (
<li key={i}>
<span className="hl-n">{String(i+1).padStart(2,"0")}</span>
<span className="hl-t">{h}</span>
</li>
))}
</ul>
</>
)}
</div>
<aside className="po-sidecar">
<div className="po-spec">
<div className="po-spec-row">
<span className="k">Scope</span>
<div className="v">
{project.scope.map(s => <span key={s} className="chip">{s}</span>)}
</div>
</div>
<div className="po-spec-row">
<span className="k">Technology</span>
<div className="v">
{project.technologies.map(t => <span key={t} className="chip chip-accent">{t}</span>)}
</div>
</div>
</div>
</aside>
</div>
</section>
{/* === CLOSING CTA === */}
<section className="po-cta">
<div className="container">
<div className="po-cta-inner grain">
<div>
<p className="po-cta-eyebrow">// Next step</p>
<h3>Planning a similar build?</h3>
<p className="po-cta-desc">Same engineering discipline, end to end design, integration, commissioning, training, support.</p>
</div>
<a href="mailto:zgaetano@wilddragon.net" className="btn btn-primary">Start a conversation <ArrowRight/></a>
</div>
</div>
</section>
{/* === RELATED + PREV/NEXT === */}
<section className="po-related">
<div className="container">
<div className="po-related-head">
<span className="po-related-label">// Other case studies</span>
</div>
<div className="po-related-grid">
{projects.filter(p => p.slug !== project.slug).slice(0, 3).map(p => (
<a key={p.slug} className="po-related-card"
href={`#/projects/${p.slug}`}
onClick={(e)=>{e.preventDefault(); onOpen(p.slug);}}>
<div className="img"><img src={p.thumbnail} alt={p.client}/></div>
<div className="meta">
<span className="cat">{p.category}</span>
<span className="yr">{p.year}</span>
</div>
<h4>{p.client}</h4>
<span className="go">View case study <ArrowRight size={13}/></span>
</a>
))}
</div>
</div>
</section>
<section className="po-prevnext">
<div className="po-prevnext-grid">
<a className="prev" href={`#/projects/${prev.slug}`} onClick={(e)=>{e.preventDefault(); onOpen(prev.slug);}}>
<ArrowLeft size={20}/>
<div className="text">
<div className="label">Previous</div>
<div className="name">{prev.client}</div>
</div>
</a>
<a className="next" href={`#/projects/${next.slug}`} onClick={(e)=>{e.preventDefault(); onOpen(next.slug);}}>
<div className="text">
<div className="label">Next</div>
<div className="name">{next.client}</div>
</div>
<ArrowRight size={20}/>
</a>
</div>
</section>
<footer className="po-foot">
<div className="po-foot-inner">
<a className="brand" href="#" onClick={(e)=>{e.preventDefault(); onClose();}}>Wild Dragon</a>
<p>© {new Date().getFullYear()} Zachary Gaetano</p>
</div>
</footer>
</div>
</div>
);
}
Object.assign(window, {
Navigation, Hero, Clients, Partners, About, Stats, Services, Projects,
TechStack, OnSet, Contact, Footer, ProjectOverlay, CursorDot, Curtain,
});