// 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 (
{content}{content}
); }; // 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 {data?.brand?.logoText; } return ( BROD NET ); }; // 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 ? {data?.brand?.name : } {/* 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 (
{mobile ? ( /* Botão hambúrguer (mobile) */ ) : ( /* Nav desktop com ícones grandes */ )}
{/* Menu dropdown (mobile) */} {mobile && menuOpen && (
{navItems.map((item) => ( setMenuOpen(false)} style={{ display: 'flex', alignItems: 'center', gap: 14, padding: '14px 6px', textDecoration: 'none', color: 'white', borderBottom: '1px solid rgba(255,255,255,0.1)', }}> {item.icon} {item.label.toUpperCase()} ))} setMenuOpen(false)} style={{ marginTop: 14, background: 'var(--red)', color: 'white', textDecoration: 'none', padding: '16px', fontWeight: 800, fontSize: 15, border: '3px solid var(--ink)', boxShadow: '4px 4px 0 var(--ink)', display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10, }}> {whatsIcon}ASSINAR AGORA
)}
); }; // ============================================================ // 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 ( {s.label ); })} {/* 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 (
{banners.map((b) => {e.currentTarget.style.transform = 'translate(-3px,-3px)';e.currentTarget.style.boxShadow = '11px 11px 0 var(--ink)';}} onMouseLeave={(e) => {e.currentTarget.style.transform = 'none';e.currentTarget.style.boxShadow = '8px 8px 0 var(--ink)';}}> {!isMobile && (
)}
✦ OFERTA ESPECIAL ✦
{b.title}
{b.subtitle}
{b.price != null &&
{b.priceLabel || 'a partir de'}
R$ {Math.floor(b.price)} ,{b.price.toFixed(2).split('.')[1]} /mês
} {b.cta || 'EU QUERO'}
)}
); }; // 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
📄
2ª VIA
🔧
SUPORTE
📡
STATUS
💬
CHAT
; // 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 ? ( {c.name} { 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.

{setCep(fmt(e.target.value));setStatus(null);setInfo(null);}} placeholder="00000-000" inputMode="numeric" className="mono" style={{ flex: 1, border: 'none', padding: '18px 22px', fontSize: 20, fontWeight: 700, background: 'transparent', outline: 'none', color: 'var(--ink)', minWidth: 0 }} />
{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
{networks.length === 0 &&
Nenhuma rede social configurada.
} {networks.map(n => ( {n.icon} {n.label.toUpperCase()} ))}
)}
); }; 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()}
{ctc.modalDescription || 'Informe seu telefone com DDD. Nossa central inicia a ligacao e conecta voce ao atendimento.'}
Digite com DDD. Se informar apenas 10 ou 11 digitos, o site adiciona o +55 automaticamente.
{error &&
{error}
} {success &&
{success}
}
); }; 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 (