/* global React */ const { useState, useEffect, useRef, useMemo } = React; // ---------- Lucide icon helper ---------- function Icon({ name, size = 18, className = "", strokeWidth = 1.75 }) { const ref = useRef(null); useEffect(() => { if (window.lucide && ref.current) { ref.current.innerHTML = ""; const el = document.createElement("i"); el.setAttribute("data-lucide", name); el.setAttribute("width", String(size)); el.setAttribute("height", String(size)); el.setAttribute("stroke-width", String(strokeWidth)); ref.current.appendChild(el); window.lucide.createIcons({ attrs: { width: size, height: size, "stroke-width": strokeWidth } }); } }, [name, size, strokeWidth]); return ; } // ---------- Brand mark used in nav + modals ---------- function LogoMark({ size = 28 }) { return ( SUN TECH); } // ---------- Decorative orbital background ---------- function OrbitalBackdrop() { return (
{/* center orbit ring */}
{/* cyan glow (sun replaced by tech aura) */}
{/* signature star — echoes the logo */}
); } // ---------- Pricing rendering ---------- function PriceBlock({ pricing }) { if (pricing.kind === "quote") { return (
Valor Sob Consulta {pricing.label && {pricing.label} }
); } if (pricing.kind === "fixed") { return (
{pricing.value}
{pricing.caption}
); } // tiered return (
{pricing.heading &&
{pricing.heading}
} {pricing.tiers.map((t, i) =>
{t.label} {t.value}
)} {pricing.note &&
{pricing.note}
}
); } // ---------- Glossary term tooltip ---------- // Tooltip renders into a portal at document.body so it escapes any // `overflow: hidden` / `transform` / stacking-context clipping from ancestors. function Term({ term, display }) { const def = (window.GLOSSARY || {})[term]; const [open, setOpen] = useState(false); const [pos, setPos] = useState({ x: 0, y: 0, flipDown: false }); const triggerRef = useRef(null); const computePosition = React.useCallback(() => { if (!triggerRef.current) return; const r = triggerRef.current.getBoundingClientRect(); const flipDown = r.top < 160; const x = r.left + r.width / 2; const y = flipDown ? r.bottom + 8 : r.top - 8; setPos({ x, y, flipDown }); }, []); // While open: track scroll/resize/wheel so the tooltip follows the trigger. useEffect(() => { if (!open) return; computePosition(); const onScroll = () => computePosition(); window.addEventListener('scroll', onScroll, true); window.addEventListener('resize', onScroll); return () => { window.removeEventListener('scroll', onScroll, true); window.removeEventListener('resize', onScroll); }; }, [open, computePosition]); // Close on outside click and ESC useEffect(() => { if (!open) return; const onDoc = (e) => { if (triggerRef.current && !triggerRef.current.contains(e.target)) setOpen(false); }; const onKey = (e) => {if (e.key === "Escape") setOpen(false);}; document.addEventListener('mousedown', onDoc); document.addEventListener('keydown', onKey); return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); }; }, [open]); if (!def) { return {display || term}; } const tooltip = open && ReactDOM.createPortal(
{term}
{def}
, document.body ); return ( setOpen(true)} onMouseLeave={() => setOpen(false)}> {e.stopPropagation();setOpen((o) => !o);}} onKeyDown={(e) => {if (e.key === 'Enter' || e.key === ' ') {e.preventDefault();setOpen((o) => !o);}}} className="cursor-help inline-flex items-baseline gap-[3px] border-b border-dotted border-cy-400/55 hover:border-cy-300 transition-colors"> {display || term} {tooltip} ); } // Walk a string and wrap every glossary term in a element. // Returns either the original string (no matches) or an array of strings+elements. function renderWithTerms(text) { if (typeof text !== 'string' || !window.GLOSSARY) return text; const keys = Object.keys(window.GLOSSARY).sort((a, b) => b.length - a.length); if (keys.length === 0) return text; const escaped = keys.map((k) => k.replace(/[.*+?^${}()|[\]\\]/g, '\\$&')); const re = new RegExp('(' + escaped.join('|') + ')', 'gi'); const wordChar = /[\w\u00C0-\u017F]/; const parts = []; let last = 0; let m; while ((m = re.exec(text)) !== null) { const before = text[m.index - 1]; const after = text[m.index + m[0].length]; // Skip if embedded inside a longer word (e.g. 'API' inside 'APIzer') if (before && wordChar.test(before) || after && wordChar.test(after)) continue; if (m.index > last) parts.push(text.slice(last, m.index)); const canonical = keys.find((k) => k.toLowerCase() === m[0].toLowerCase()) || m[0]; parts.push(); last = m.index + m[0].length; } if (parts.length === 0) return text; if (last < text.length) parts.push(text.slice(last)); return parts; } // ---------- Service card ---------- function ServiceCard({ svc, onQuote, variant = "default" }) { const accent = { software: { ring: "rgba(26,182,240,.40)", chip: "bg-cy-400/10 text-cy-200 border-cy-400/30", dot: "#1AB6F0", icon: "Code2" }, redes: { ring: "rgba(26,182,240,.35)", chip: "bg-cy-400/10 text-cy-100 border-cy-400/25", dot: "#3FC4FA", icon: "Network" }, telecom: { ring: "rgba(124,218,254,.45)", chip: "bg-cy-200/10 text-cy-100 border-cy-200/25", dot: "#7CDAFE", icon: "RadioTower" }, smart: { ring: "rgba(217,225,236,.30)", chip: "bg-white/5 text-ice-200 border-white/15", dot: "#D9E1EC", icon: "Home" }, suporte: { ring: "rgba(217,225,236,.22)", chip: "bg-white/5 text-ice-200 border-white/10", dot: "#AEB9C8", icon: "Wrench" } }[svc.category]; return (
{/* corner accent line */}
{/* header row */}
{svc.code}
{svc.badge}
{svc.sla && {svc.sla} }
{/* title + description */}

{svc.title}

{renderWithTerms(svc.short)}

{/* deliverables */}
    {svc.deliverables.map((d, i) =>
  • {renderWithTerms(d)}
  • )}
{/* tags */}
{svc.tags.map((t, i) => {renderWithTerms(t)} )}
{/* footer: price stacked above full-width CTA */}
); } // ---------- Quote modal ---------- function QuoteModal({ service, onClose }) { const [form, setForm] = useState({ nome: "", empresa: "", whatsapp: "", demanda: "" }); const [state, setState] = useState("idle"); // idle | sending | success | error const firstRef = useRef(null); useEffect(() => { if (service) setTimeout(() => firstRef.current?.focus(), 80); const onKey = (e) => {if (e.key === "Escape") onClose();}; document.addEventListener("keydown", onKey); return () => document.removeEventListener("keydown", onKey); }, [service, onClose]); if (!service) return null; const onSubmit = (e) => { e.preventDefault(); if (!form.nome || !form.whatsapp) return; setState("sending"); // Build a structured WhatsApp message and open it in a new tab. const lines = [ `Olá, equipe SUN TECH! Vim pelo site solicitar orçamento:`, ``, `Serviço: ${service.code} — ${service.title}`, `Nome: ${form.nome}`]; if (form.empresa) lines.push(`Empresa: ${form.empresa}`); lines.push(`WhatsApp: ${form.whatsapp}`); if (form.demanda) { lines.push(``, `Detalhes:`, form.demanda); } const phone = typeof window !== 'undefined' && window.WHATSAPP_PHONE || "5534999999999"; const url = `https://wa.me/${phone}?text=${encodeURIComponent(lines.join("\n"))}`; window.open(url, "_blank", "noopener,noreferrer"); setState("success"); }; return (
{/* overlay */}
{/* top accent */}
{service.code} · Solicitação de orçamento

{service.title}

{state !== "success" ?
setForm({ ...form, nome: e.target.value })} className="modal-input" placeholder="João Silva" /> setForm({ ...form, empresa: e.target.value })} className="modal-input" placeholder="ACME Ltda." />
setForm({ ...form, whatsapp: e.target.value })} className="modal-input" placeholder="(34) 9 0000-0000" />