/* sections.jsx — marquee, experience, why, included, process, faq, form */
/* ---------- simple line icons ---------- */
const Icon = {
target: "M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0-18 0 M12 12m-5 0a5 5 0 1 0 10 0a5 5 0 1 0-10 0 M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0-2 0",
shield: "M12 3l7 3v5c0 4.5-3 7.5-7 9-4-1.5-7-4.5-7-9V6l7-3z M9 12l2 2 4-4",
bolt: "M13 3L4 14h6l-1 7 9-11h-6l1-7z",
scale: "M4 20h16 M12 4v16 M12 4l-6 7h12l-6-7 M6 11a3 3 0 0 0 6 0 M12 11a3 3 0 0 0 6 0",
compass: "M12 12m-9 0a9 9 0 1 0 18 0a9 9 0 1 0-18 0 M15.5 8.5l-2 5-5 2 2-5 5-2z",
headset: "M4 13v-1a8 8 0 0 1 16 0v1 M4 13a2 2 0 0 1 2 2v2a2 2 0 0 1-4 0v-2 M20 13a2 2 0 0 0-2 2v2a2 2 0 0 0 4 0v-2 M18 19a4 4 0 0 1-4 3h-1"
};
function LineIcon({ d, size = 26 }) {
return (
{d.split(" M").map((seg, i) => )}
);
}
/* ---------- 02 — Marquee (fundido ao hero) ---------- */
function MarqueeSection() {
const platforms = [
{ name: "Shopify Plus", src: "assets/platforms/shopify-plus.png", h: 30 },
{ name: "VTEX", src: "assets/platforms/vtex.png", h: 40 },
{ name: "Nuvemshop", src: "assets/platforms/nuvemshop.png", h: 26 },
{ name: "Shopify", src: "assets/platforms/shopify.png", h: 34 },
{ name: "Tray", src: "assets/platforms/tray.png", h: 40 },
{ name: "Wake", src: "assets/platforms/wake.png", h: 38 }];
return (
);
}
/* ---------- 03 — Experiência (light, layout 2 colunas) ---------- */
const EXP_ICONS = {
star: "M12 3.2l2.6 5.7 6.2.6-4.7 4.1 1.4 6.1L12 16.9 6.5 19.7l1.4-6.1L3.2 9.5l6.2-.6z",
cart: "M3 4h2.2l2.3 11.2a1 1 0 0 0 1 .8h8.7a1 1 0 0 0 1-.78L20.5 8H6 M9.5 20a1 1 0 1 0 .01 0 M17 20a1 1 0 1 0 .01 0",
layers: "M12 3.3l8.5 4.6-8.5 4.6-8.5-4.6 8.5-4.6z M3.7 12.3l8.3 4.5 8.3-4.5 M3.7 16.4l8.3 4.5 8.3-4.5"
};
function ExperienceSection() {
const stats = [
{ icon: EXP_ICONS.star, to: 20, suffix: "+", label: "anos de experiência acumulada" },
{ icon: EXP_ICONS.cart, to: 200, suffix: "+", label: "projetos de E‑Commerce entregues" },
{ icon: EXP_ICONS.layers, to: 100, suffix: "+", label: "negócios acelerados" }];
return (
{/* ---- Coluna esquerda: mockup protagonista + partículas orbitando ---- */}
{/* glow roxo extremamente suave atrás do dispositivo */}
{/* partículas orbitando — camada atrás do mockup */}
{/* mockup — protagonista */}
{/* partículas orbitando — camada na frente (dá a volta nos dispositivos) */}
{/* ---- Coluna direita: conteúdo + cards ---- */}
Mais de 20 anos de experiência acumulada >}
sub={<>Já ajudamos centenas de negócios a estruturar e acelerar seus projetos de E‑Commerce com foco em alta performance.>} />
);
}
/* ---------- 04 — Por que STG (dark, 6 cards) ---------- */
function WhySection() {
const cards = [
{ icon: Icon.target, t: "Plataforma ideal", d: "Escolha estratégica da tecnologia certa para o momento e o tamanho da sua operação" },
{ icon: Icon.shield, t: "Migração segura", d: "Proteção total da operação durante a transição, sem perda de faturamento" },
{ icon: Icon.bolt, t: "Performance", d: "Projetos focados em velocidade, experiência e conversão em cada etapa" },
{ icon: Icon.scale, t: "Escalabilidade", d: "Estruturas preparadas para crescer junto com a demanda do negócio" },
{ icon: Icon.compass, t: "Visão estratégica", d: "Não entregamos apenas tecnologia entregamos uma operação pensada para o longo prazo" },
{ icon: Icon.headset, t: "Suporte real", d: "Acompanhamento próximo após a publicação, com gente que conhece o seu projeto" }];
return (
Tecnologia é meioOperação é o resultado >}
sub="Seis pilares que separam um site bonito de uma operação digital preparada para escalar" />
{cards.map((c, i) =>
{c.t}
{c.d}
)}
);
}
/* ---------- Suporte total (dark, acompanhamento humanizado) ---------- */
function SupportSection() {
return (
Suporte total
Acompanhamento personalizado durante o projeto
Nosso time acompanha você e sua equipe durante todo o processo de implantação do E‑Commerce, orientando sobre cada ponto importante para o sucesso do projeto, tirando dúvidas e oferecendo treinamento para a operação do painel administrativo incluindo cadastro de produtos, configuração de promoções e troca de banners.
);
}
/* ---------- Scroll-animation card (adapted from ContainerScroll / framer) ---------- */
function useScrollProgress(ref) {
const [p, setP] = useState(0);
useEffect(() => {
const el = ref.current;
if (!el) return;
if (matchMedia("(prefers-reduced-motion:reduce)").matches) {setP(1);return;}
let raf;
const upd = () => {
const r = el.getBoundingClientRect();
const vh = window.innerHeight || 1;
const prog = (vh - r.top) / (vh * 0.92);
setP(Math.min(1, Math.max(0, prog)));
};
const onScroll = () => {cancelAnimationFrame(raf);raf = requestAnimationFrame(upd);};
window.addEventListener("scroll", onScroll, { passive: true });
window.addEventListener("resize", onScroll);
upd();
return () => {window.removeEventListener("scroll", onScroll);window.removeEventListener("resize", onScroll);cancelAnimationFrame(raf);};
}, []);
return p;
}
function ContainerScroll({ titleComponent, children }) {
const ref = useRef(null);
const p = useScrollProgress(ref);
const [isMobile, setIsMobile] = useState(false);
useEffect(() => {
const ck = () => setIsMobile(window.innerWidth <= 768);
ck();
window.addEventListener("resize", ck);
return () => window.removeEventListener("resize", ck);
}, []);
const rotate = 20 * (1 - p);
const scale = isMobile ? 0.82 + 0.13 * p : 1.05 - 0.05 * p;
const translate = -40 * p;
return (
);
}
/* ---------- 05 — O que está incluso (scroll-animation screen) ---------- */
function IncludedSection() {
const items = [
["Homepage otimizada", "Carregamento rápido e layout inteligente"],
["Páginas de produto", "Estrutura pensada para conversão"],
["Checkout", "Fluxo curto, seguro e sem fricção"],
["Integrações", "ERP, pagamentos e logística conectados"],
["SEO On Page", "Site otimizado para o Google e IAs"],
["Pixel e GA4", "Rastreamento configurado e ativo"],
["Experiência do usuário", "Navegação intuitiva + busca inteligente"],
["Mobile First", "Ótima experiência pelo celular"]];
return (
O que está incluso
Tudo que seu projeto precisa para ir além
>}>
SUA OPERAÇÃO · TUDO INCLUSO
{["#ff5f57", "#febc2e", "#28c840"].map((c) => )}
{items.map(([t, d], i) =>
{e.currentTarget.style.borderColor = "rgba(170,0,238,.4)";e.currentTarget.style.background = "rgba(170,0,238,.06)";}}
onMouseLeave={(e) => {e.currentTarget.style.borderColor = "var(--border-dark)";e.currentTarget.style.background = "rgba(255,252,252,.03)";}}>
{d}
)}
);
}
/* ---------- 06 — Processo (timeline conectada) ---------- */
function ProcessSection() {
const steps = [
["01", "Arquitetura e Planejamento", "Levantamento de requisitos e mapeamento de integrações necessárias"],
["02", "Criação de Layouts (UX / UI)", "Produção de layouts totalmente responsivos com protótipos navegáveis"],
["03", "Implementação e Integração", "Desenvolvimento front-end e integração com meios de pagamento e entrega"],
["04", "Configuração e Treinamento", "Configuração das regras de negócio e treinamento para operação do painel administrativo"],
["05", "Testes e Publicação", "Homologação do projeto com testes de compra e publicação da loja (go-live)"],
["06", "Manutenção e Suporte", "Ajustes, correções, atualização de conteúdos e evolução contínua do site", "Opcional"]];
const tlRef = useRef(null);
const lineRef = useRef(null);
const rowsRef = useRef([]);
const lineTop = useRef(32);
const lineH = useRef(0);
const centersRel = useRef([]);
const [line, setLine] = useState({ top: 32, height: 0 });
// Scroll-linked timeline: the connector line grows from top to bottom in sync
// with scroll progress, and each step (marker + card) surges into view as the
// line front reaches it. Driven imperatively (rAF-free, timer/poll-backed) so
// it works even where time-based CSS is frozen.
useEffect(() => {
const tl = tlRef.current;
if (!tl) return;
const reduce = window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches;
const measure = () => {
const nodes = tl.querySelectorAll(".proc-node");
if (nodes.length < 2) return;
const rTop = tl.getBoundingClientRect().top;
const centers = [].map.call(nodes, (n) => {const r = n.getBoundingClientRect();return r.top - rTop + r.height / 2;});
const top = centers[0];
const H = Math.max(0, centers[centers.length - 1] - top);
lineTop.current = top;lineH.current = H;
centersRel.current = centers.map((c) => c - top);
setLine({ top, height: H });
apply();
};
const apply = () => {
const H = lineH.current;
if (reduce) {
if (lineRef.current) lineRef.current.style.transform = "scaleY(1)";
rowsRef.current.forEach((el) => {if (el) {el.style.opacity = "1";el.style.transform = "none";}});
return;
}
if (H <= 0) return;
const vh = window.innerHeight || 0;
const tlRect = tl.getBoundingClientRect();
const lineStartVY = tlRect.top + lineTop.current;
const anchorY = vh * 0.6; // growth front sits at 60% of the viewport
let filled = anchorY - lineStartVY; // px of line revealed so far
filled = Math.max(0, Math.min(H, filled));
const progress = filled / H;
if (lineRef.current) lineRef.current.style.transform = "scaleY(" + progress + ")";
const centers = centersRel.current;
const SPAN = 80;
rowsRef.current.forEach((el, i) => {
if (!el) return;
const c = centers[i] != null ? centers[i] : i / Math.max(1, steps.length - 1) * H;
// each step completes exactly as the line front reaches its marker
// center (surges over the preceding SPAN px) — so the LAST step, whose
// center sits at the very end of the line (filled caps at H), still
// reaches full opacity instead of being stuck at 0.
const t = filled <= 0 ? 0 : Math.max(0, Math.min(1, (filled - (c - SPAN)) / SPAN));
const e = 1 - Math.pow(1 - t, 3);
el.style.opacity = String(e);
el.style.transform = "translate3d(" + -26 * (1 - e) + "px,0,0)";
});
};
// initial hidden state (JS drives it; if JS never runs the rows stay at
// their default opacity:1, so nothing is ever permanently invisible)
if (!reduce) {
rowsRef.current.forEach((el) => {if (el) {el.style.opacity = "0";el.style.transform = "translate3d(-26px,0,0)";}});
if (lineRef.current) lineRef.current.style.transform = "scaleY(0)";
}
measure();
const mt = setTimeout(measure, 450);
window.addEventListener("resize", measure);
const io = new IntersectionObserver(() => apply(), { threshold: [0, 0.1, 0.3, 0.6, 0.9, 1] });
io.observe(tl);
window.addEventListener("scroll", apply, { passive: true });
const its = [setTimeout(apply, 80), setTimeout(apply, 320), setTimeout(apply, 720)];
const poll = setInterval(apply, 250);
apply();
return () => {
window.removeEventListener("resize", measure);
window.removeEventListener("scroll", apply);
io.disconnect();
clearTimeout(mt);its.forEach(clearTimeout);clearInterval(poll);
};
}, []);
return (
Um método claro, do briefing à publicação } eyebrowColor="rgb(170, 0, 238)" />
{/* connector line — z-index 0 (below markers + cards); only the scrolled portion shows */}
{steps.map(([n, t, d, badge], i) =>
rowsRef.current[i] = el} className="proc-row" style={{
position: "relative", display: "grid", gridTemplateColumns: "64px 1fr", gap: 26, alignItems: "flex-start",
paddingBottom: i === steps.length - 1 ? 0 : 22, willChange: "opacity, transform"
}}>
{n}
{t}
{badge && {badge} }
{d}
)}
);
}
/* ---------- 07 — FAQ (light accordion) ---------- */
function FaqItem({ q, a, open, onToggle }) {
const bodyRef = useRef(null);
return (
);
}
function FaqSection() {
const faqs = [
["Quanto tempo leva?", "Em média, 8 a 10 semanas, dependendo da maturidade e complexidade do projeto."],
["Vocês fazem migração?", "Sim. Migração de plataforma é uma das nossas especialidades, conduzida de forma controlada para proteger faturamento, SEO e histórico da operação."],
["Trabalham com quais plataformas?", "VTEX, Wake, Shopify Plus, Tray, Nuvemshop e Shopify. A escolha da plataforma é parte do trabalho estratégico."],
["Há suporte após a publicação?", "Sim. Damos suporte por 30 dias para quaisquer ajustes e correções que sejam necessários após o go-live do projeto."],
["O layout é personalizado?", "Sim. Cada operação é estruturada sob medida com um layout exclusivo ou um template personalizado com a identidade visual da marca."],
["Atendem operações complexas?", "Sim. Soluções híbridas (varejo e atacado), integrações com ERP, múltiplos canais, catálogos extensos e regras específicas de negócio são parte do nosso dia a dia."]];
const [open, setOpen] = useState(0);
return (
Ainda com dúvidas? >}
sub="As respostas para o que mais perguntam antes de começar um projeto." />
{faqs.map(([q, a], i) =>
setOpen(open === i ? -1 : i)} />
)}
);
}
/* ---------- 08 — Formulário final (dark) ---------- */
const SEGMENTOS = ["Moda e vestuário", "Beleza e cosméticos", "Saúde e suplementos", "Casa e decoração", "Móveis e acessórios", "Alimentos e bebidas", "Eletrônicos", "B2B / Atacado", "Serviços", "Outro"];
const QF_STEPS = [
{ k: "empresa", type: "text", q: "Qual o nome da sua empresa?", ph: "Nome da empresa", micro: "Assim já chegamos preparados para a conversa.", btn: "Continuar" },
{ k: "nome", type: "text", q: "Qual seu nome?", ph: "Digite seu nome", micro: "Queremos tornar nosso contato mais próximo.", btn: "Continuar" },
{ k: "whatsapp", type: "tel", q: "Qual o número do seu WhatsApp?", ph: "(00) 00000-0000", micro: "Retornaremos por esse número.", btn: "Continuar" },
{ k: "email", type: "email", q: "Qual é o seu e-mail?", ph: "voce@empresa.com", micro: "Utilizamos apenas para contato e envio de informações relevantes.", btn: "Finalizar" }];
function fmtWhats(v) {
const d = v.replace(/\D/g, "").slice(0, 11);
if (d.length <= 2) return d;
if (d.length <= 7) return `(${d.slice(0, 2)}) ${d.slice(2)}`;
return `(${d.slice(0, 2)}) ${d.slice(2, 7)}-${d.slice(7)}`;
}
const DDDS_BR = new Set([11,12,13,14,15,16,17,18,19,21,22,24,27,28,31,32,33,34,35,37,38,41,42,43,44,45,46,47,48,49,51,53,54,55,61,62,63,64,65,66,67,68,69,71,73,74,75,77,79,81,82,83,84,85,86,87,88,89,91,92,93,94,95,96,97,98,99]);
function celularValido(v) {
const d = (v || "").replace(/\D/g, "");
if (d.length !== 11) return false;
if (!DDDS_BR.has(Number(d.slice(0, 2)))) return false;
return d[2] === "9";
}
function ContactSection() {
// step: -1 = abertura · 0..4 = perguntas · 5 = final
const [step, setStep] = useState(-1);
const [form, setForm] = useState({ nome: "", whatsapp: "", email: "", empresa: "", segmento: "", segmentoOutro: "" });
const outroRef = useRef(null);
const [err, setErr] = useState("");
const [selOpen, setSelOpen] = useState(false);
const inputRef = useRef(null);
useEffect(() => {
if (step >= 0 && step < QF_STEPS.length) {
const t = setTimeout(() => inputRef.current && inputRef.current.focus(), 280);
return () => clearTimeout(t);
}
}, [step]);
useEffect(() => {setSelOpen(false);}, [step]);
const progress = step < 0 ? 0 : Math.min(step + 1, QF_STEPS.length) / QF_STEPS.length * 100;
const valid = (k, v) => {
if (k === "whatsapp") return celularValido(v);
if (k === "email") return /^[^@\s]+@[^@\s]+\.[^@\s]+$/.test(v);
if (k === "segmento") return v.trim().length > 0 && (v !== "Outro" || form.segmentoOutro.trim().length > 0);
return v.trim().length > 0;
};
const set = (k) => (e) => {
const v = k === "whatsapp" ? fmtWhats(e.target.value) : e.target.value;
setForm((f) => ({ ...f, [k]: v }));
if (err) setErr("");
};
const next = () => {
const cur = QF_STEPS[step];
if (!valid(cur.k, form[cur.k])) {
setErr(
cur.k === "whatsapp" ? "Informe um celular válido: DDD + 9 dígitos (ex: (11) 91234-5678)." :
cur.k === "email" ? "Informe um e-mail válido." :
cur.k === "segmento" && !form.segmento ? "Selecione um segmento." :
cur.k === "segmento" ? "Descreva o seu segmento." :
"Este campo é obrigatório.");
(cur.k === "segmento" && form.segmento === "Outro" ? outroRef.current : inputRef.current)?.focus();
return;
}
setErr("");
setStep((s) => s + 1);
};
const back = () => {setErr("");setStep((s) => Math.max(-1, s - 1));};
const onKey = (e) => {if (e.key === "Enter") {e.preventDefault();next();}};
const cur = step >= 0 && step < QF_STEPS.length ? QF_STEPS[step] : null;
return (
{/* Barra de progresso */}
{step >= 0 && step < QF_STEPS.length &&
}
{/* ---------- Abertura ---------- */}
{step === -1 &&
Próximo passo
Quer um orçamento sem compromisso?
Informe seus dados e nossa equipe entrará em contato
}
{/* ---------- Perguntas ---------- */}
{cur &&
Etapa {step + 1} de {QF_STEPS.length}
{cur.q}
{cur.type === "select" ?
setSelOpen((o) => !o)}
onKeyDown={(e) => {
if (e.key === "Enter" && !selOpen && form.segmento && form.segmento !== "Outro") {e.preventDefault();next();} else
if (e.key === "ArrowDown" || e.key === " ") {e.preventDefault();setSelOpen(true);} else
if (e.key === "Escape") setSelOpen(false);
}}>
{form.segmento || cur.ph}
{selOpen &&
{SEGMENTOS.map((s) =>
{setForm((f) => ({ ...f, segmento: s }));if (err) setErr("");setSelOpen(false);}}>
{s}
{form.segmento === s &&
}
)}
}
{form.segmento === "Outro" &&
}
:
}
{err || cur.micro}
}
{/* ---------- Final ---------- */}
{step === QF_STEPS.length &&
Recebemos suas informações.
Em breve, nossa equipe entrará em contato pelo WhatsApp.
}
);
}
/* ---------- Footer (minimal, não-corporativo) ---------- */
function Footer() {
const link = { color: "var(--text-dark-dim)", textDecoration: "none", display: "inline-flex", alignItems: "center", gap: 11, fontSize: ".95rem", transition: "color .25s" };
const hoverOn = (e) => e.currentTarget.style.color = "#aa00ee";
const hoverOff = (e) => e.currentTarget.style.color = "var(--text-dark-dim)";
const heading = { fontFamily: "Rubik", fontWeight: 700, fontSize: "1.02rem", color: "#aa00ee", marginBottom: 20, letterSpacing: "-.01em" };
return (
{/* Quem é a STG */}
Criamos, migramos e estruturamos operações de E‑Commerce com foco em performance e escala.
{/* Contato */}
{/* Siga-nos */}
Siga-nos
{ e.currentTarget.style.color = "#aa00ee"; e.currentTarget.style.borderColor = "rgba(170,0,238,.5)"; e.currentTarget.style.background = "rgba(170,0,238,.08)"; }}
onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dark)"; e.currentTarget.style.borderColor = "var(--border-dark-strong)"; e.currentTarget.style.background = "transparent"; }}>
{ e.currentTarget.style.color = "#aa00ee"; e.currentTarget.style.borderColor = "rgba(170,0,238,.5)"; e.currentTarget.style.background = "rgba(170,0,238,.08)"; }}
onMouseLeave={(e) => { e.currentTarget.style.color = "var(--text-dark)"; e.currentTarget.style.borderColor = "var(--border-dark-strong)"; e.currentTarget.style.background = "transparent"; }}>
);
}
Object.assign(window, {
MarqueeSection, ExperienceSection, WhySection, SupportSection, IncludedSection,
ProcessSection, FaqSection, ContactSection, Footer, LineIcon
});