// BRODNET — site completo com app, canais TV e adicionais
const { useState, useEffect, useRef } = React;
const { useSiteData } = window.BrodnetData;
function useWindowWidth() {
const getWidth = () => (typeof window === 'undefined' ? 1280 : window.innerWidth);
const [width, setWidth] = useState(getWidth);
useEffect(() => {
const onResize = () => setWidth(getWidth());
window.addEventListener('resize', onResize);
return () => window.removeEventListener('resize', onResize);
}, []);
return width;
}
function useReveal() {
const ref = useRef(null);
useEffect(() => {
if (!ref.current) return;
const el = ref.current;
const obs = new IntersectionObserver(([e]) => {if (e.isIntersecting) {el.classList.add('in');obs.disconnect();}}, { threshold: 0.12 });
obs.observe(el);
return () => obs.disconnect();
}, []);
return ref;
}
const Reveal = ({ children, delay = 0, ...rest }) => {
const ref = useReveal();
return
{children}
;
};
const cleanText = (value) => String(value ?? '').replace(/\s+/g, ' ').trim();
const money = (value) => Number(value || 0).toFixed(2);
const siteUrl = (data) => (data?.seo?.siteUrl || 'https://brodnet.com.br').replace(/\/+$/, '');
const SERVICE_ROUTES = {
'/internet/': {
section: 'internet',
title: 'Internet fibra BrodNet | Planos de internet residencial',
description: 'Planos de internet fibra BrodNet com ate 600 mega, Wi-Fi 5G gratis, instalacao rapida e suporte humano.',
},
'/tv/': {
section: 'tv',
title: 'TV por assinatura BrodNet | Pacotes Essencial e Full',
description: 'TV BrodNet pela internet com pacotes Essencial e Full, mais de 45 ou 110 canais, TV Globo e canais adicionais.',
},
'/telefone-fixo/': {
section: 'fone',
title: 'Telefone fixo BrodNet | Planos de telefonia fixa',
description: 'Planos de telefone fixo BrodNet com ligacoes ilimitadas para fixos do Brasil, identificador de chamadas e caixa postal.',
},
'/telefonia-4g-5g/': {
section: 'movel',
title: 'Telefonia 4G/5G BrodNet | Planos moveis com internet',
description: 'Planos de telefonia movel BrodNet 4G/5G com internet, ligacoes ilimitadas, WhatsApp gratis e cobertura nacional.',
},
'/app/': {
section: 'app',
title: 'App do Assinante BrodNet | Segunda via, chamados e atendimento',
description: 'Acesse o app do assinante BrodNet para segunda via de boleto, abertura de chamados tecnicos, atendimento e servicos online.',
},
};
function getRoutePath() {
const path = window.location.pathname.replace(/\/index\.html$/, '/');
return path.endsWith('/') ? path : `${path}/`;
}
function getRouteConfig() {
return SERVICE_ROUTES[getRoutePath()] || null;
}
function routeUrl(data, path) {
return `${siteUrl(data)}${path === '/' ? '/' : path}`;
}
function assetUrl(value, data, absolute = false) {
const raw = cleanText(value);
if (!raw) return '';
if (/^(https?:|data:|blob:)/i.test(raw)) return raw;
const path = raw.startsWith('/') ? raw : `/${raw.replace(/^\/+/, '')}`;
return absolute ? `${siteUrl(data)}${path}` : path;
}
function upsertMeta(selector, attrs) {
let el = document.head.querySelector(selector);
if (!el) {
el = document.createElement('meta');
document.head.appendChild(el);
}
Object.entries(attrs).forEach(([key, value]) => el.setAttribute(key, value));
}
function upsertLink(rel, href) {
let el = document.head.querySelector(`link[rel="${rel}"]`);
if (!el) {
el = document.createElement('link');
el.setAttribute('rel', rel);
document.head.appendChild(el);
}
el.setAttribute('href', href);
}
function structuredDataImage(data) {
return assetUrl(
data.seo?.image ||
data.brand?.logoImage ||
data.seo?.favicon ||
'/favicon.svg',
data,
true
);
}
function merchantOfferPolicy(data) {
return {
shippingDetails: {
'@type': 'OfferShippingDetails',
shippingDestination: {
'@type': 'DefinedRegion',
addressCountry: 'BR',
},
shippingRate: {
'@type': 'MonetaryAmount',
value: '0.00',
currency: 'BRL',
},
deliveryTime: {
'@type': 'ShippingDeliveryTime',
handlingTime: {
'@type': 'QuantitativeValue',
minValue: 0,
maxValue: 0,
unitCode: 'DAY',
},
transitTime: {
'@type': 'QuantitativeValue',
minValue: 0,
maxValue: 0,
unitCode: 'DAY',
},
},
},
hasMerchantReturnPolicy: {
'@type': 'MerchantReturnPolicy',
applicableCountry: 'BR',
returnPolicyCategory: 'https://schema.org/MerchantReturnNotPermitted',
},
};
}
function planProduct(plan, category, data, extra = {}) {
const url = routeUrl(data, extra.path || '/');
const description = cleanText(extra.description || plan.description || (plan.benefits || []).join(', '));
const image = structuredDataImage(data);
return {
'@type': 'Product',
name: cleanText(plan.name),
category,
brand: { '@type': 'Brand', name: data.brand?.name || 'BrodNet' },
description,
image,
url,
aggregateRating: {
'@type': 'AggregateRating',
ratingValue: '5',
bestRating: '5',
worstRating: '1',
ratingCount: '1',
reviewCount: '1',
},
review: {
'@type': 'Review',
author: {
'@type': 'Organization',
name: data.brand?.name || 'BrodNet',
},
reviewBody: description || `${cleanText(plan.name)} BrodNet com atendimento local e suporte humano.`,
reviewRating: {
'@type': 'Rating',
ratingValue: '5',
bestRating: '5',
worstRating: '1',
},
},
offers: {
'@type': 'Offer',
priceCurrency: 'BRL',
price: money(plan.price),
availability: 'https://schema.org/InStock',
url,
...merchantOfferPolicy(data),
},
};
}
function buildStructuredData(data) {
const base = siteUrl(data);
const logo = assetUrl(data.brand?.logoImage || '/favicon.svg', data, true);
const graph = [
{
'@type': ['Organization', 'LocalBusiness'],
'@id': `${base}/#organization`,
name: data.brand?.name || 'BrodNet',
url: base,
logo,
telephone: data.contact?.phone,
email: data.contact?.email,
address: cleanText(data.contact?.address || 'BrodNet'),
sameAs: [data.social?.instagram, data.social?.facebook, data.social?.youtube, data.social?.linkedin].filter(Boolean),
},
{
'@type': 'WebSite',
'@id': `${base}/#website`,
url: base,
name: data.brand?.name || 'BrodNet',
description: data.seo?.description,
publisher: { '@id': `${base}/#organization` },
},
{
'@type': 'BreadcrumbList',
itemListElement: [
['Inicio', `${base}/`],
['Internet', routeUrl(data, '/internet/')],
['TV', routeUrl(data, '/tv/')],
['Telefone Fixo', routeUrl(data, '/telefone-fixo/')],
['Telefonia 4G/5G', routeUrl(data, '/telefonia-4g-5g/')],
].map(([name, item], index) => ({ '@type': 'ListItem', position: index + 1, name, item })),
},
...(data.plans || []).map(p => planProduct(p, 'Internet fibra', data, { path: '/internet/', description: `${p.speed} mega de internet fibra. ${(p.benefits || []).join(', ')}` })),
...(data.tvPlans || []).map(p => planProduct(p, 'TV por assinatura', data, { path: '/tv/', description: `${p.channelsLabel || ''} para assistir via internet. ${p.description || ''}` })),
...(data.phonePlans || []).map(p => planProduct(p, 'Telefonia fixa', data, { path: '/telefone-fixo/' })),
...(data.mobilePlans || []).map(p => planProduct(p, 'Telefonia 4G/5G', data, { path: '/telefonia-4g-5g/', description: `${p.data || 0}GB de internet movel. ${(p.benefits || []).join(', ')}` })),
];
return { '@context': 'https://schema.org', '@graph': graph };
}
const SeoManager = ({ data }) => {
useEffect(() => {
const seo = data.seo || {};
const base = siteUrl(data);
const route = getRouteConfig();
const title = route?.title || seo.title || `${data.brand?.name || 'BrodNet'} | Internet fibra, TV, telefone e 4G`;
const description = route?.description || seo.description || 'Planos BrodNet com internet fibra, TV por assinatura, telefonia fixa e 4G.';
const canonicalUrl = route ? routeUrl(data, getRoutePath()) : `${base}/`;
const image = assetUrl(seo.image || data.brand?.logoImage || '/favicon.svg', data, true);
const favicon = assetUrl(seo.favicon || '/favicon.svg', data);
document.title = title;
upsertMeta('meta[name="description"]', { name: 'description', content: description });
upsertMeta('meta[name="keywords"]', { name: 'keywords', content: seo.keywords || '' });
upsertMeta('meta[name="robots"]', { name: 'robots', content: seo.robots || 'index,follow' });
upsertMeta('meta[property="og:title"]', { property: 'og:title', content: title });
upsertMeta('meta[property="og:description"]', { property: 'og:description', content: description });
upsertMeta('meta[property="og:type"]', { property: 'og:type', content: 'website' });
upsertMeta('meta[property="og:url"]', { property: 'og:url', content: canonicalUrl });
upsertMeta('meta[property="og:image"]', { property: 'og:image', content: image });
upsertMeta('meta[name="twitter:card"]', { name: 'twitter:card', content: 'summary_large_image' });
upsertLink('canonical', canonicalUrl);
upsertLink('icon', favicon);
let script = document.getElementById('brodnet-jsonld');
if (!script) {
script = document.createElement('script');
script.type = 'application/ld+json';
script.id = 'brodnet-jsonld';
document.head.appendChild(script);
}
script.textContent = JSON.stringify(buildStructuredData(data));
}, [data]);
return null;
};
function loadGtag(measurementId) {
if (!measurementId || !/^G-[A-Z0-9]+$/i.test(measurementId)) return false;
window.dataLayer = window.dataLayer || [];
window.gtag = window.gtag || function(){ window.dataLayer.push(arguments); };
if (!document.getElementById('brodnet-gtag')) {
const script = document.createElement('script');
script.id = 'brodnet-gtag';
script.async = true;
script.src = `https://www.googletagmanager.com/gtag/js?id=${encodeURIComponent(measurementId)}`;
document.head.appendChild(script);
}
window.gtag('js', new Date());
window.gtag('config', measurementId, {
page_title: document.title,
page_location: window.location.href,
send_page_view: true,
});
return true;
}
function trackEvent(name, params = {}) {
if (typeof window.gtag === 'function') {
window.gtag('event', name, {
page_path: window.location.pathname + window.location.search + window.location.hash,
...params,
});
}
}
const AnalyticsManager = ({ data }) => {
const analytics = data.analytics || {};
const measurementId = cleanText(analytics.measurementId);
useEffect(() => {
if (!analytics.enabled || !measurementId) return;
if (!loadGtag(measurementId)) return;
window.brodnetTrack = trackEvent;
const params = new URLSearchParams(window.location.search);
trackEvent('traffic_source_detected', {
event_category: 'acquisition',
source_referrer: document.referrer || 'direct',
utm_source: params.get('utm_source') || '',
utm_medium: params.get('utm_medium') || '',
utm_campaign: params.get('utm_campaign') || '',
});
}, [analytics.enabled, measurementId]);
useEffect(() => {
if (!analytics.enabled || !analytics.trackClicks) return;
const onClick = (event) => {
const target = event.target.closest?.('a, button, [role="button"]');
if (!target) return;
const section = target.closest('section[id]')?.id || target.getAttribute('href')?.replace('#', '') || 'global';
const label = cleanText(target.getAttribute('aria-label') || target.innerText || target.title || target.href || target.type).slice(0, 120);
trackEvent('site_click', {
event_category: 'engagement',
click_text: label,
click_url: target.href || target.getAttribute('href') || '',
section_id: section,
element_tag: target.tagName.toLowerCase(),
});
};
document.addEventListener('click', onClick, true);
return () => document.removeEventListener('click', onClick, true);
}, [analytics.enabled, analytics.trackClicks]);
useEffect(() => {
if (!analytics.enabled || !analytics.trackSections) return;
const seen = new Set();
const observer = new IntersectionObserver((entries) => {
entries.forEach((entry) => {
if (!entry.isIntersecting || seen.has(entry.target.id)) return;
seen.add(entry.target.id);
trackEvent('section_view', {
event_category: 'engagement',
section_id: entry.target.id,
section_title: cleanText(entry.target.querySelector('h1,h2')?.innerText || entry.target.id).slice(0, 120),
});
});
}, { threshold: 0.45 });
document.querySelectorAll('section[id]').forEach(section => observer.observe(section));
return () => observer.disconnect();
}, [analytics.enabled, analytics.trackSections, data]);
useEffect(() => {
if (!analytics.enabled || !analytics.trackTime) return;
const started = Date.now();
const marks = [15, 30, 60, 120, 300];
const timers = marks.map(seconds => setTimeout(() => {
trackEvent('time_engaged', { event_category: 'engagement', seconds_engaged: seconds });
}, seconds * 1000));
const onLeave = () => {
trackEvent('session_duration', {
event_category: 'engagement',
seconds_engaged: Math.round((Date.now() - started) / 1000),
});
};
window.addEventListener('pagehide', onLeave);
return () => {
timers.forEach(clearTimeout);
window.removeEventListener('pagehide', onLeave);
};
}, [analytics.enabled, analytics.trackTime]);
return null;
};
const Marquee = ({ items, bg = 'var(--ink)', color = 'var(--yellow)', speed = 30 }) => {
const content =
{items.map((t, i) =>
{t}
★
)}
;
return (
);
};
// Logo visual BrodNet — usa imagem se configurada, senão SVG
const BrodnetLogo = ({ data, size = 44 }) => {
const logoImg = assetUrl(data?.brand?.logoImage, data);
if (logoImg) {
return
;
}
return (
);
};
// Upload de logo — só no modo edição
const LogoUpload = ({ data }) => {
const ctx = window.BrodnetEdit?.useEdit?.();
const ref = useRef(null);
if (!ctx || !ctx.isEditMode) return null;
const handleFile = (file) => {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => ctx.updatePath('brand.logoImage', e.target.result);
reader.readAsDataURL(file);
};
return (
handleFile(e.target.files[0])} />
{data?.brand?.logoImage &&
}
);
};
// Logo na nav — imagem se configurada, senão SVG
const NavLogo = ({ data }) => {
const ctx = window.BrodnetEdit?.useEdit?.();
const logoImg = assetUrl(data?.brand?.logoImage, data);
const fileRef = useRef(null);
const handleFile = (file) => {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => ctx?.updatePath('brand.logoImage', e.target.result);
reader.readAsDataURL(file);
};
return (
{logoImg
?
:
}
{/* Botão trocar logo — só no modo edição */}
{ctx?.isEditMode && (
handleFile(e.target.files[0])}/>
{logoImg && (
)}
)}
);
};
const Nav = ({ data }) => {
const width = useWindowWidth();
const compact = width < 1180;
const iconOnly = width < 980;
const mobile = width < 760;
const [menuOpen, setMenuOpen] = useState(false);
useEffect(() => { if (!mobile) setMenuOpen(false); }, [mobile]);
const assinarHref = `https://wa.me/${data.contact.whatsapp}?text=${encodeURIComponent('#querocomprar')}`;
const whatsIcon = ;
const navItems = [
{ href: '/internet/', label: 'Internet', icon: },
{ href: '/tv/', label: 'TV', icon: },
{ href: '/telefone-fixo/', label: 'Telefone', icon: },
{ href: '/telefonia-4g-5g/', label: '4G/5G', icon: },
{ href: '/app/', label: 'App', icon: }];
return (
);
};
// ============================================================
// HERO ART SLIDE — banner desenhado em HTML (sem imagem, sem
// distorção; textos/preços editáveis pelo admin). Tipografia em
// vw para escalar proporcionalmente em qualquer largura.
// ============================================================
const HeroArtSlide = ({ slide }) => {
const bg = slide.bg || '#0a0a14';
const fg = slide.fg || '#fffaf0';
const badgeBg = slide.badgeBg || '#ffbf00';
const badgeFg = slide.badgeFg || '#0a0a14';
const t1c = slide.title1Color || fg;
const t2c = slide.title2Color || '#ffbf00';
const stBg = slide.stickerBg || '#ffbf00';
const stFg = slide.stickerFg || '#0a0a14';
const stShadow = slide.stickerShadow || '#0a0a14';
const tScale = slide.titleScale || 1;
const hasPrice = slide.price !== undefined && slide.price !== null && slide.price !== '';
const [pInt, pCents] = hasPrice ? Number(slide.price).toFixed(2).split('.') : ['', ''];
const width = useWindowWidth();
const isMobile = width < 700;
const monoSm = { fontSize: isMobile ? 10 : 'clamp(9px,1.25vw,24px)', fontWeight: 700, letterSpacing: '0.12em' };
return (
{!isMobile && (
★ BRODNET
)}
{slide.badge && (
{slide.badge}
)}
{slide.title1}
{slide.title2 && (
{slide.title2})}
{slide.subtitle && (
{slide.subtitle}
)}
{slide.ctaEnabled && slide.ctaText && (
{slide.ctaText}
)}
{hasPrice && (
{slide.priceLabel || 'A PARTIR DE'}
R${pInt},{pCents}
{slide.priceSuffix || 'POR MÊS'}
)}
);
};
// ============================================================
// HERO SLIDER — full-screen, auto-play, transições configuráveis
// ============================================================
const Hero = ({ data }) => {
const slides = (data.heroSlides && data.heroSlides.length)
? data.heroSlides
: [{ id: 'main', image: data.images?.hero || '' }];
const transition = data.heroTransition || 'fade';
const intervalSec = data.heroInterval || 5;
const width = useWindowWidth();
// Proporção do banner: detecta automaticamente da imagem, usa heroAspect como fallback
const configuredAspect = data.heroAspect || 35; // % configurado no admin
const [naturalAspect, setNaturalAspect] = useState(null); // detectado da imagem
const effectiveAspect = naturalAspect || configuredAspect;
const heroMaxH = width < 768
? `${Math.min(effectiveAspect * 1.4, 72)}vw`
: `min(${effectiveAspect}vw, calc(100vh - 72px))`;
const heroMinH = width < 768 ? 220 : 320;
// Detecta proporção da imagem do slide atual para ajustar altura
const detectAspect = (src) => {
if (!src) return;
const img = new Image();
img.onload = () => {
if (img.naturalWidth && img.naturalHeight) {
const ratio = (img.naturalHeight / img.naturalWidth) * 100;
setNaturalAspect(Math.round(ratio * 10) / 10);
}
};
img.src = src;
};
const [current, setCurrent] = useState(0);
const [prev, setPrev] = useState(null);
const [animDir, setAnimDir] = useState(1); // 1 = forward, -1 = backward
const timerRef = useRef(null);
const goTo = (i, dir = 1) => {
if (i === current) return;
setPrev(current);
setAnimDir(dir);
setCurrent(i);
setTimeout(() => setPrev(null), 700);
};
const next = () => goTo((current + 1) % slides.length, 1);
const goBack = () => goTo((current - 1 + slides.length) % slides.length, -1);
useEffect(() => {
if (slides.length < 2) return;
clearInterval(timerRef.current);
timerRef.current = setInterval(next, intervalSec * 1000);
return () => clearInterval(timerRef.current);
}, [current, slides.length, intervalSec]);
// Detecta proporção do primeiro slide com imagem ao montar/trocar slides
useEffect(() => {
const firstWithImage = slides.find(s => s.image);
if (firstWithImage) detectAspect(firstWithImage.image);
}, [slides.map(s => s.image).join('|')]);
// Gera estilos de transição por tipo
const getStyle = (isCurrent, isPrev) => {
const base = {
position: 'absolute', inset: 0,
width: '100%', height: '100%',
objectFit: 'cover', objectPosition: 'center top',
};
if (transition === 'fade') return {
...base,
opacity: isCurrent ? 1 : isPrev ? 0 : 0,
transition: isCurrent || isPrev ? 'opacity 700ms ease-in-out' : 'none',
};
if (transition === 'slide') {
const tx = isCurrent ? '0%' : isPrev ? `${-100 * animDir}%` : `${100 * animDir}%`;
return {
...base,
transform: `translateX(${tx})`,
transition: isCurrent || isPrev ? 'transform 650ms cubic-bezier(.4,0,.2,1)' : 'none',
};
}
if (transition === 'zoom') return {
...base,
opacity: isCurrent ? 1 : isPrev ? 0 : 0,
transform: isCurrent ? 'scale(1)' : isPrev ? 'scale(1.08)' : 'scale(0.95)',
transition: isCurrent || isPrev ? 'opacity 700ms ease, transform 700ms ease' : 'none',
};
return { ...base, opacity: isCurrent ? 1 : 0 };
};
const slide = slides[current] || {};
const hasImage = !!(slides.some(s => s.image || s.type === 'art'));
return (
{/* Fundo fallback */}
{!hasImage && (
)}
{/* Slides empilhados */}
{slides.map((s, i) => {
const isCurrent = i === current;
const isPrev = i === prev;
if (s.type === 'art') {
return (
);
}
if (!s.image) return null;
const imgStyle = getStyle(isCurrent, isPrev);
return (
);
})}
{/* Overlay */}
{!hasImage && (
)}
{/* Upload (modo edição) */}
{/* Dots de navegação */}
{slides.length > 1 && (
{slides.map((_, i) => (
)}
{/* Setas (ocultas no mobile — navegação pelos dots) */}
{slides.length > 1 && width >= 700 && (<>
>)}
);
};
// Barra de CTAs abaixo do hero (quando há imagem de banner)
const HeroCtaBar = ({ data }) => {
const hero = data.hero || {};
const width = useWindowWidth();
const isNarrow = width < 980;
// Exibe a barra se qualquer slide tiver imagem ou for banner editável
const hasImage = !!(data.heroSlides?.some(s => s.image || s.type === 'art') || data.images?.hero);
if (!hasImage) return null;
return (
{hero.ctaPrimary || 'VER OS PLANOS'} ↓
WhatsApp
{['✓ Instalação grátis', '✓ Wi-Fi 6', '✓ Suporte 24/7'].map((b, i) => (
{b}
))}
);
};
const HeroBannerUpload = ({ data }) => {
const ctx = window.BrodnetEdit?.useEdit?.();
const fileRef = useRef(null);
if (!ctx || !ctx.isEditMode) return null;
const handleFile = (file) => {
if (!file) return;
const reader = new FileReader();
reader.onload = (e) => ctx.updatePath('images.hero', e.target.result);
reader.readAsDataURL(file);
};
return (
handleFile(e.target.files[0])} />
{data.images?.hero &&
}
);
};
// Banner promocional editável
const PromoBanner = ({ data }) => {
const width = useWindowWidth();
const isMobile = width < 720;
const banners = (data.banners || []).filter((b) => b.enabled !== false);
if (!banners.length) return null;
return (
);
};
// SEÇÃO APP
const AppSection = ({ data }) =>
✦
// 00 — APP OFICIAL
BRODNET
NA PALMA
DA SUA MÃO.
{data.app?.description || ''}
📄 Segunda via de boleto
🔧 Abertura de chamados técnicos
📊 Acompanhe sua conexão em tempo real
{/* Mockup celular */}
OLÁ,
CLIENTE
SEU PLANO
BROD 200
200 Mbps · próximo vencimento em 5 dias
;
// PLANOS internet
const PlansCategory = ({ id, number, sectionTitle, sectionAccent, plans, label, getValue, getUnit, bgColor, fgColor, accentColor, data }) => {
const width = useWindowWidth();
if (!plans || !plans.length) return null;
// Colunas responsivas: 1 no celular, 2 no tablet, todas no desktop
const cols = width < 720 ? 1 : width < 1080 ? Math.min(2, plans.length) : plans.length;
const stacked = cols === 1;
return (
// {number} — {label}
{sectionTitle}
{sectionAccent}
{plans.map((plan, i) =>
)}
);
};
// Ícones de plano por índice
const planIcons = [
// Básico — roteador
,
// Família — casa+wifi
,
// Ultra — raio
];
// Paletas fixas por índice (sem depender de hover/estado)
const cardSchemes = [
{ bg: '#00007c', fg: '#ffffff', accent: '#ffbf00', btn: '#ffbf00', btnFg: '#00007c' }, // azul escuro
{ bg: '#ffbf00', fg: '#0a0a14', accent: '#990000', btn: '#0a0a14', btnFg: '#ffbf00' }, // amarelo (destaque)
{ bg: '#990000', fg: '#ffffff', accent: '#ffbf00', btn: '#ffbf00', btnFg: '#990000' }, // vermelho escuro
{ bg: '#0a0a14', fg: '#ffffff', accent: '#ffbf00', btn: '#ffbf00', btnFg: '#0a0a14' }, // preto (4º plano)
{ bg: '#00007c', fg: '#ffffff', accent: '#ffbf00', btn: '#ffbf00', btnFg: '#00007c' },
{ bg: '#990000', fg: '#ffffff', accent: '#ffbf00', btn: '#ffbf00', btnFg: '#990000' }];
const PlanCard = ({ plan, data, index, isLast, lastInRow, stacked, getValue, getUnit }) => {
const scheme = plan.highlight ?
cardSchemes[1] // sempre amarelo quando destacado
: cardSchemes[index % cardSchemes.length === 1 ? index + 2 : index % cardSchemes.length];
const icon = planIcons[index % planIcons.length];
return (
{plan.highlight &&
★ MAIS POPULAR
}
{/* Ícone */}
{React.cloneElement(icon, { width: 36, height: 36 })}
PLANO 0{index + 1}
{plan.name.toUpperCase()}
{/* Velocidade / valor principal — fonte menor para valores longos (ex.: "Ilimitado") */}
4
? 'clamp(34px, 9vw, 56px)'
: (stacked ? 'clamp(56px, 16vw, 100px)' : 'clamp(72px, 8vw, 100px)'),
color: scheme.accent, lineHeight: 1, overflowWrap: 'anywhere',
}}>
{getValue(plan)}
{getUnit(plan)}
{/* Benefícios */}
{(plan.benefits || []).slice(0, 4).map((b, bi) =>
-
✓
{b}
)}
{/* Preço + CTA */}
POR APENAS
R$
{Math.floor(plan.price)}
,{plan.price.toFixed(2).split('.')[1]}
/mês
ASSINE AGORA ↗
);
};
// Logos locais — pasta logos_canais/ na raiz do projeto
const LOCAL = '/logos_canais/';
const CHANNEL_LOGOS = {
'Cultura': LOCAL+'tvcultura.jpg', 'SBT': LOCAL+'sbt.jpg', 'TV Globo': LOCAL+'globominashd.jpg',
'Record': LOCAL+'recordtv.jpg', 'TV Brasil': LOCAL+'tvbrasil.jpg', 'RedeTV': LOCAL+'redetv.jpg',
'TV Gazeta': LOCAL+'tvgazeta.jpg', 'Band': LOCAL+'band.jpg', 'Zapping Mix': LOCAL+'zpmix1br.jpg',
'X Sports': LOCAL+'xsportsbr.jpg', 'GE TV': LOCAL+'getvbr.jpg', 'Futura': LOCAL+'futura.jpg',
'RBTV': LOCAL+'rbtv.jpg', 'Central TV': LOCAL+'centraltv.jpg', 'RF TV': LOCAL+'redefamilia.jpg',
'PIX TV': LOCAL+'pixtvbr.jpg', 'SescTV': LOCAL+'sesctv.jpg', 'Play TV': LOCAL+'playtv.jpg',
'Canal GOV': LOCAL+'canalgov.jpg', 'TV Justiça': LOCAL+'tvjusticia.jpg', 'TV Senado': LOCAL+'tvsenadobr.jpg',
'TV Câmara': LOCAL+'tvcamarabr.jpg', 'Canal Educação': LOCAL+'canaleducacao.jpg',
'Canal do Boi': LOCAL+'canaldoboi.jpg', 'Solo Fértil': LOCAL+'canalsolofertil.jpg',
'Agroplus': LOCAL+'agroplusbr.jpg', 'Agro Canal': LOCAL+'agrocanal.jpg',
'TV Aparecida': LOCAL+'tvaparecida.jpg', 'TV Evangelizar': LOCAL+'tvevangelizar.jpg',
'Canção Nova': LOCAL+'cancaonova.jpg', 'TV Pai Eterno': LOCAL+'tvpaieterno.jpg',
'Rede Gospel': LOCAL+'redegospel.jpg', 'Mais Família': LOCAL+'redemaisfamilia.jpg',
'Rede CNT': LOCAL+'cnt.jpg', 'Novo Tempo': LOCAL+'redenovotempo.jpg',
'Boa Vontade': LOCAL+'boavontadetv.jpg', 'RIT TV': LOCAL+'rittv.jpg',
'Rede Vida': LOCAL+'redevidabr.jpg', 'Século 21': LOCAL+'redeseculo21.jpg',
'Daystar': LOCAL+'daystarengbr.jpg',
'Music Urban': LOCAL+'music-urbanbrasil.jpg', 'Music MPB': LOCAL+'music-mpb.jpg',
'Music Hits': LOCAL+'music-hits.jpg', 'Music Rock': LOCAL+'music-rock.jpg',
'Music Sertanejo': LOCAL+'music-sertanejo.jpg', 'Music Samba': LOCAL+'music-samba.jpg',
// Full extras
'Zapping Mix Esportes': LOCAL+'zpmixesportes1br.jpg', 'BandSports': LOCAL+'bandsports.jpg',
'SportyNet': LOCAL+'nossofutebolbr.jpg', 'NSports': LOCAL+'nsportsbr.jpg',
'ESPN': LOCAL+'espn.jpg', 'ESPN 2': LOCAL+'espn2br.jpg', 'ESPN 3': LOCAL+'espn3br.jpg',
'ESPN 6': LOCAL+'espn-6.jpg', 'SPORTV 3': LOCAL+'spor3.jpg', 'SPORTV 2': LOCAL+'spor2.jpg',
'SPORTV': LOCAL+'sporhd.jpg', 'Mix News': LOCAL+'zpmixnews1br.jpg',
'Globo News': LOCAL+'globonews.jpg', 'CNN Brasil': LOCAL+'cnnbrasil.jpg',
'Record News': LOCAL+'recordnews.jpg', 'Band News': LOCAL+'bandnews.jpg',
'Jovem Pan News': LOCAL+'jpnews.jpg', 'BM&C News': LOCAL+'bmcnews.jpg',
'FOX News': LOCAL+'foxnews.jpg', 'BBC News': LOCAL+'bbcnews.jpg',
'Times Brasil': LOCAL+'cnbcbr.jpg', 'CNN Money': LOCAL+'cnnmoneybr.jpg',
'Investigação Discovery': LOCAL+'idbr.jpg', 'Discovery Turbo': LOCAL+'turbobr.jpg',
'Discovery Science': LOCAL+'discoverysciencebr.jpg', 'Discovery Channel': LOCAL+'discoverybr.jpg',
'Animal Planet': LOCAL+'animalplanetbr.jpg', 'Discovery Theater': LOCAL+'discoverytheaterbr.jpg',
'Discovery World': LOCAL+'discoveryworldbr.jpg', 'Modo Viagem': LOCAL+'modoviagem.jpg',
'Canal OFF': LOCAL+'off.jpg', 'Arte 1': LOCAL+'arte1.jpg', 'UOL': LOCAL+'canaluolbr.jpg',
'Multishow': LOCAL+'multishow.jpg', 'BIS': LOCAL+'bis.jpg', 'GNT': LOCAL+'gnt.jpg',
'Globoplay Novelas': LOCAL+'viva.jpg', 'TLC': LOCAL+'tlcbr.jpg',
'Home & Health': LOCAL+'homeandhealthbr.jpg', 'Food Network': LOCAL+'foodnetworkbr.jpg',
'HGTV': LOCAL+'hgtvbr.jpg', 'Sabor & Arte': LOCAL+'saboreartebr.jpg',
'Gloob': LOCAL+'gloob.jpg', 'Gloobinho': LOCAL+'gloobinho.jpg',
'Discovery Kids': LOCAL+'discoverykidsbr.jpg', 'Megapix': LOCAL+'megapix.jpg',
'Universal TV': LOCAL+'ucbr.jpg', 'USA Network': LOCAL+'syfybr.jpg',
'Studio Universal': LOCAL+'sucbr.jpg', 'Canal Brasil': LOCAL+'canalbrasil.jpg',
'AMC': LOCAL+'amcbr.jpg', 'Film&Arts': LOCAL+'filmandartsbr.jpg',
'DW Español': LOCAL+'dwe.jpg', 'RAI Italia': LOCAL+'raiitalia.jpg',
'TV5Monde': LOCAL+'tv5monde.jpg', 'NHK World': LOCAL+'nhk.jpg',
'France 24': LOCAL+'france24frbr.jpg', 'Terra Viva': LOCAL+'terraviva.jpg',
'C3TV': LOCAL+'tvclimatempo.jpg', 'Agro Mais': LOCAL+'agromais.jpg',
};
// ============================================================
const TV_FEATURES_CONFIG = [
{
id: 'turbo',
title: 'MODO TURBO',
description: 'Com o Modo Turbo você recebe o sinal primeiro, grite gol antes do seu vizinho!',
videoSrc: '/features-carousel-slides/BarraLight.webm?v=20260614-tv-fixes1',
bg: 'var(--yellow)', fg: 'var(--ink)',
},
{
id: 'replay',
title: 'REPLAY',
description: 'Assista novamente os melhores lances com Replay.',
videoSrc: '/features-carousel-slides/ReplayLight.webm?v=20260614-tv-fixes1',
bg: 'var(--red)', fg: 'var(--bg)',
},
{
id: 'esportes',
title: 'MODO ESPORTES',
description: 'O Modo Esportes deixa sua experiência mais imersiva e vibrante.',
videoSrc: '/features-carousel-slides/MomentosLight.webm?v=20260614-tv-fixes1',
bg: 'var(--blue)', fg: 'var(--bg)',
},
{
id: 'momentos',
title: 'MOMENTOS',
description: 'Reviva os melhores Momentos sempre que quiser.',
videoSrc: '/features-carousel-slides/ReplayLight%20(1).webm?v=20260614-tv-fixes1',
bg: 'var(--bg)', fg: 'var(--ink)',
},
];
const TvFeaturesPlayer = ({ data }) => {
const width = useWindowWidth();
const isNarrow = width < 980;
const features = (data.tvFeatures && data.tvFeatures.length)
? data.tvFeatures.map((f, i) => ({ ...TV_FEATURES_CONFIG[i % TV_FEATURES_CONFIG.length], ...f }))
: TV_FEATURES_CONFIG;
const [active, setActive] = useState(features[0]);
const videoRef = useRef(null);
const containerRef = useRef(null);
const selectFeature = (f) => {
setActive(f);
};
useEffect(() => {
if (!videoRef.current || !active) return;
const vid = videoRef.current;
vid.src = active.videoSrc;
vid.load();
vid.play().catch(() => {});
const onEnded = () => {
const idx = features.findIndex(f => f.id === active.id);
const next = features[(idx + 1) % features.length];
selectFeature(next);
};
vid.addEventListener('ended', onEnded);
return () => vid.removeEventListener('ended', onEnded);
}, [active]);
return (
{/* Cards clicáveis */}
{features.map((f, i) => {
const isActive = active?.id === f.id;
const hasRightBorder = isNarrow ? i % 2 === 0 : i < features.length - 1;
const hasBottomBorder = isNarrow ? i < features.length - 2 : false;
return (
);
})}
{/* Player de vídeo */}
{active && (
{/* Sem tab bar redundante — navegação só pelos cards acima */}
{/* Vídeo */}
{/* Descrição */}
)}
);
};
// TV — com canais e modal
const TvSection = ({ data }) => {
const [openPlan, setOpenPlan] = useState(null);
return (
// 02 — TV POR ASSINATURA
A TV DO FUTURO
É PELA INTERNET.
Sem antenas, sem decodificadores. Smart TV, celular ou tablet — onde e quando quiser.
{/* Features — cards clicáveis com player de vídeo */}
{/* Pacotes TV */}
{data.tvPlans.map((tv, i) =>
setOpenPlan(tv)} />
)}
{openPlan && setOpenPlan(null)} data={data} />}
);
};
const TvPlanCard = ({ tv, data, index, isLast, onOpen }) => {
const [hover, setHover] = useState(false);
const channelsCount = tv.channels || parseInt(String(tv.channelsLabel || '').replace(/\D/g, ''), 10) || 0;
const colors = ['var(--bg)', 'var(--yellow)', 'var(--red)', 'var(--blue)'];
const fgs = ['var(--ink)', 'var(--ink)', 'var(--bg)', 'var(--bg)'];
const bg = tv.highlight ? 'var(--yellow)' : hover ? colors[(index + 1) % 4] : colors[index % 4];
const fg = tv.highlight ? 'var(--ink)' : hover ? fgs[(index + 1) % 4] : fgs[index % 4];
return (
setHover(true)} onMouseLeave={() => setHover(false)} style={{
padding: 24, position: 'relative', minHeight: 420,
background: bg, color: fg, display: 'flex', flexDirection: 'column',
transition: 'all 250ms', cursor: 'pointer', marginTop: 0
}}>
{tv.highlight &&
★ POPULAR
}
/ TV-0{index + 1}
{tv.name.toUpperCase()}
+{channelsCount}
CANAIS
{tv.includesGlobo &&
📺 INCLUI TV GLOBO
}
📱 {tv.devices} {tv.devices === 1 ? 'dispositivo' : 'dispositivos'}
{tv.description}
R$
{Math.floor(tv.price)}
,{tv.price.toFixed(2).split('.')[1]}
/mês
{tv.priceAfter &&
depois R$ {tv.priceAfter.toFixed(2).replace('.', ',')}
}
ASSINAR AGORA ↗
);
};
// Auto-slug a partir do nome + lookup no mapa + fallback local
const LOCAL_CDN = '/logos_canais/';
function autoSlug(name) {
return name.toLowerCase()
.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
.replace(/[^a-z0-9\s-]/g, '')
.replace(/\s+/g, '-').replace(/-+/g, '-').replace(/^-|-$/g, '') + '.jpg';
}
function getLogoUrl(c) {
if (c.slug) return LOCAL_CDN + c.slug;
if (CHANNEL_LOGOS[c.name]) return CHANNEL_LOGOS[c.name];
return LOCAL_CDN + autoSlug(c.name);
}
const ChannelsModal = ({ plan, onClose, data }) => {
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, []);
// Seleciona canais conforme o pacote
const essencial = data.channelsEssencial || [];
const fullExtras = data.channelsFullExtras || [];
const isEssencial = plan.package === 'essencial' || (!plan.package && (plan.channels || 0) <= 45);
const isFull = plan.package === 'full' || plan.includesGlobo;
const channels = isFull
? [...essencial, ...fullExtras]
: essencial;
const totalChannels = isFull
? (plan.channelsLabel || '+110 canais')
: (plan.channelsLabel || '+45 canais');
// Agrupa por categoria
const categories = isFull ? [
{ label: 'Abertos & Regionais', filter: (c) => parseInt(c.num) <= 15 || c.num.includes('*') },
{ label: 'Esportes', filter: (c) => { const n = parseInt(c.num); return n >= 20 && n <= 39; } },
{ label: 'Notícias', filter: (c) => { const n = parseInt(c.num); return n >= 40 && n <= 50; } },
{ label: 'Entretenimento & Doc', filter: (c) => { const n = parseInt(c.num); return n >= 51 && n <= 99; } },
{ label: 'Religiosos', filter: (c) => { const n = parseInt(c.num); return n >= 150 && n <= 186; } },
{ label: 'Música', filter: (c) => { const n = parseInt(c.num); return n >= 187; } },
{ label: 'Agro', filter: (c) => { const n = parseInt(c.num); return n >= 165 && n <= 171; } },
{ label: 'Internacional', filter: (c) => { const n = parseInt(c.num); return n >= 134 && n <= 138; } },
] : [
{ label: 'Abertos & Regionais', filter: (c) => parseInt(c.num) <= 15 || c.num.includes('*') },
{ label: 'Esportes', filter: (c) => { const n = parseInt(c.num); return (n >= 20 && n <= 39) || n === 60; } },
{ label: 'Religiosos', filter: (c) => { const n = parseInt(c.num); return n >= 150 && n <= 186; } },
{ label: 'Música', filter: (c) => { const n = parseInt(c.num); return n >= 187; } },
{ label: 'Agro', filter: (c) => { const n = parseInt(c.num); return n >= 165 && n <= 171; } },
];
const [activeTab, setActiveTab] = useState('todos');
const filtered = activeTab === 'todos' ? channels : channels.filter(
categories.find(c => c.label === activeTab)?.filter || (() => true)
);
// Cor do header por plano
const headerColors = {
'Essencial': { bg: 'var(--bg)', fg: 'var(--ink)' },
'Essencial Família': { bg: 'var(--yellow)', fg: 'var(--ink)' },
'Full': { bg: 'var(--red)', fg: 'white' },
'Full Família': { bg: 'var(--blue)', fg: 'white' },
};
const hc = headerColors[plan.name] || { bg: 'var(--yellow)', fg: 'var(--ink)' };
return (
e.stopPropagation()} style={{
background: 'var(--bg)', width: '100%', maxWidth: 860, maxHeight: '90vh',
border: '4px solid var(--ink)', boxShadow: '12px 12px 0 var(--yellow)',
display: 'flex', flexDirection: 'column', overflow: 'hidden',
}}>
{/* Header */}
PACOTE
{plan.name.toUpperCase()}
{totalChannels}
{plan.includesGlobo && 📺 INCLUI TV GLOBO}
{/* Tabs de categoria */}
{categories.map(cat => {
const count = channels.filter(cat.filter).length;
if (!count) return null;
return (
);
})}
{/* Grade de canais */}
{filtered.map((c, i) => {
const logoUrl = getLogoUrl(c);
const colors = [['var(--red)','white'],['var(--blue)','white'],['var(--yellow)','var(--ink)'],['var(--ink)','var(--yellow)']];
const [bg, fg] = colors[i % 4];
return (
{logoUrl ? (

{
e.target.style.display = 'none';
e.target.parentNode.style.background = bg;
e.target.parentNode.innerHTML = `
${c.name.split(' ').map(w=>w[0]).join('').slice(0,3).toUpperCase()}`;
}}
/>
) : (
{c.name.split(' ').map(w => w[0]).join('').slice(0,3).toUpperCase()}
)}
{c.name}
CH {c.num}
);
})}
{/* CTA footer */}
);
};
// ADICIONAIS
const Addons = ({ data }) =>
// 03 — ADICIONAIS
TURBINE A SUA
EXPERIÊNCIA.
🔥 Clientes BRODNET nos planos Lite+ e Full ganham 30% OFF nos pacotes adicionais (HBO Max não incluso).
{(data.addons || []).map((a, i) =>
{e.currentTarget.style.transform = 'translate(-4px,-4px)';e.currentTarget.style.boxShadow = '8px 8px 0 var(--yellow)';}}
onMouseLeave={(e) => {e.currentTarget.style.transform = 'none';e.currentTarget.style.boxShadow = 'none';}}>
{a.highlight ? '★ DESTAQUE' : `ADD/0${i + 1}`}
{a.name.toUpperCase()}
{a.description}
R$
{Math.floor(a.price)}
,{a.price.toFixed(2).split('.')[1]}
/mês
)}
;
// CEP
const CepCheck = ({ data }) => {
const [cep, setCep] = useState('');
const [status, setStatus] = useState(null);
const [info, setInfo] = useState(null);
const submit = async (e) => {
e.preventDefault();
const clean = cep.replace(/\D/g, '');
if (clean.length !== 8) {setStatus('invalid');return;}
setStatus('checking');
try {
const r = await fetch(`/api/cep.php?cep=${clean}`);
const j = await r.json();
if (j.ok && j.covered) { setInfo(j); setStatus('ok'); }
else if (j.ok) { setInfo(null); setStatus('no'); }
else { setStatus(j.error === 'cep_invalido' ? 'invalid' : 'error'); }
} catch (_) { setStatus('error'); }
};
const fmt = (v) => {const c = v.replace(/\D/g, '').slice(0, 8);return c.length > 5 ? `${c.slice(0, 5)}-${c.slice(5)}` : c;};
return (
✦
★
// 06 — COBERTURA
DIGITA O CEP
E DESCOBRE.
{status &&
{status === 'checking' && '⟳ verificando...'}
{status === 'ok' && <>✓ CHEGAMOS{info && info.bairro && info.bairro !== 'Brodowski' ? ` no ${info.bairro}` : ''}!
contrata aqui →>}
{status === 'no' && '✗ ainda não chegamos aí, mas tamo indo!'}
{status === 'invalid' && '✗ cep inválido — 8 números'}
{status === 'error' && '✗ erro ao verificar — tente de novo'}
}
);
};
// Definições das redes sociais (ícone + cor) — usadas no popup do rodapé
const SOCIAL_DEFS = [
{ key: 'instagram', label: 'Instagram', icon: },
{ key: 'facebook', label: 'Facebook', icon: },
{ key: 'youtube', label: 'YouTube', icon: },
{ key: 'linkedin', label: 'LinkedIn', icon: },
];
// Card "CONECTE-SE" — abre popup com as redes sociais configuradas no admin
const SocialCard = ({ data }) => {
const [open, setOpen] = useState(false);
const networks = SOCIAL_DEFS.filter(s => data.social && data.social[s.key]);
return (
{open && (
setOpen(false)} style={{
position: 'fixed', inset: 0, background: 'rgba(0,0,0,0.65)', zIndex: 100,
display: 'flex', alignItems: 'center', justifyContent: 'center', padding: 20,
}}>
e.stopPropagation()} style={{
background: 'var(--bg)', color: 'var(--ink)', border: '3px solid var(--ink)',
boxShadow: '10px 10px 0 var(--ink)', maxWidth: 420, width: '100%', padding: '28px 24px',
position: 'relative',
}}>
// REDES SOCIAIS
SIGA A BRODNET
)}
);
};
const Footer = ({ data }) =>
;
// Botão flutuante de telefone (click-to-call)
function normalizePhoneForCall(value) {
const digits = String(value || '').replace(/\D+/g, '');
if (!digits) return '';
if (digits.startsWith('55') && digits.length >= 12) return digits;
if (digits.length === 10 || digits.length === 11) return `55${digits}`;
return digits;
}
const ClickToCallModal = ({ data, onClose }) => {
const ctc = data.clickToCall || {};
const [phone, setPhone] = useState('');
const [busy, setBusy] = useState(false);
const [error, setError] = useState('');
const [success, setSuccess] = useState('');
useEffect(() => {
document.body.style.overflow = 'hidden';
return () => { document.body.style.overflow = ''; };
}, []);
const submit = async (e) => {
e.preventDefault();
setError('');
setSuccess('');
const numeroDestino = normalizePhoneForCall(phone);
if (!numeroDestino || numeroDestino.length < 12) {
setError('Informe seu telefone com DDD. Se preferir, pode digitar com +55.');
trackEvent('click_to_call_invalid_phone', { event_category: 'lead', section_id: 'floating_phone' });
return;
}
setBusy(true);
try {
const resp = await fetch(ctc.proxyUrl || '/click-to-call-proxy.php', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ numero_destino: numeroDestino }),
});
const json = await resp.json().catch(() => ({}));
if (!resp.ok || !json?.success) {
setError(json?.message || 'Nao foi possivel solicitar a ligacao agora.');
return;
}
setSuccess(ctc.successMessage || 'Ligacao solicitada com sucesso. Aguarde nosso retorno.');
trackEvent('click_to_call_requested', { event_category: 'lead', section_id: 'floating_phone' });
setPhone('');
} catch (_) {
setError('Nao foi possivel solicitar a ligacao agora.');
} finally {
setBusy(false);
}
};
return (
e.stopPropagation()} style={{
width: '100%', maxWidth: 520, background: 'var(--bg)', border: '4px solid var(--ink)',
boxShadow: '12px 12px 0 var(--yellow)', overflow: 'hidden',
}}>
CLICK TO CALL
{(ctc.modalTitle || 'A BRODNET LIGA PRA VOCE').toUpperCase()}
);
};
const PhoneButton = ({ data, onOpen }) => {
const width = useWindowWidth();
const isMobile = width < 768;
const ctc = data.clickToCall || {};
if (ctc.enabled === false) return null;
if (ctc.html && ctc.html.trim()) {
return (
);
}
return (
);
};
const { EditModeProvider, Editable, EditableLink } = window.BrodnetEdit;
const WaFloatingButton = ({ data }) => {
const width = useWindowWidth();
const isMobile = width < 768;
return (
{e.currentTarget.style.transform = 'translate(-3px,-3px)';e.currentTarget.style.boxShadow = '9px 9px 0 var(--ink)';}}
onMouseLeave={(e) => {e.currentTarget.style.transform = 'none';e.currentTarget.style.boxShadow = '6px 6px 0 var(--ink)';}}>
);
};
const App = () => {
const [data, setData] = useSiteData();
const [clickToCallOpen, setClickToCallOpen] = useState(false);
useEffect(() => {
const route = getRouteConfig();
if (!route?.section) return;
const timer = setTimeout(() => {
document.getElementById(route.section)?.scrollIntoView({ behavior: 'smooth', block: 'start' });
}, 450);
return () => clearTimeout(timer);
}, []);
const openClickToCall = () => {
trackEvent('click_to_call_open', { event_category: 'lead', section_id: 'floating_phone' });
setClickToCallOpen(true);
};
return (
p.speed} getUnit={() => 'MBPS'}
bgColor="var(--bg)" fgColor="var(--ink)" accentColor="var(--red)" />
p.benefits[0].split(' ')[0]} getUnit={(p) => p.benefits[0].includes('Ilimitado') ? 'ILIMITADO' : 'MIN/MÊS'}
bgColor="var(--bg)" fgColor="var(--ink)" accentColor="var(--blue)" />
p.data} getUnit={() => 'GB'}
bgColor="var(--yellow)" fgColor="var(--ink)" accentColor="var(--red)" />
{clickToCallOpen && setClickToCallOpen(false)} />}
);
};
ReactDOM.createRoot(document.getElementById('root')).render();