// Shared components: Nav, Footer, icons, solution card — v2 // Favicon (function () { const link = document.querySelector("link[rel~='icon']") || document.createElement('link'); link.rel = 'shortcut icon'; link.type = 'image/png'; link.href = 'assets/logo_line_branco.png'; document.head.appendChild(link); })(); // Mobile-only CSS fixes (function () { const style = document.createElement('style'); style.textContent = ` /* Garante tamanho idêntico para as logos no desktop/tablet ignorando cache do CSS */ .brands .row .brand-logo-wrapper img, .brands .row .brand-logo-wrapper:nth-child(1) img, .brands .row .brand-logo-wrapper:nth-child(2) img, .brands .row .brand-logo-wrapper:nth-child(3) img, .brands .row .brand-logo-wrapper:nth-child(4) img { height: 85px !important; width: 220px !important; object-fit: contain !important; } @media (max-width: 768px) { /* Logos de marcas — empilha verticalmente */ .brands .row { flex-direction: column !important; align-items: center !important; gap: 32px !important; } /* Garante tamanho idêntico para as logos no mobile ignorando cache */ .brands .row .brand-logo-wrapper img, .brands .row .brand-logo-wrapper:nth-child(1) img, .brands .row .brand-logo-wrapper:nth-child(2) img, .brands .row .brand-logo-wrapper:nth-child(3) img, .brands .row .brand-logo-wrapper:nth-child(4) img { height: 75px !important; width: 180px !important; object-fit: contain !important; } /* Tabs desktop ocultas no mobile */ .tabs-desktop { display: none !important; } /* Dropdown mobile visível */ .tabs-mobile-wrap { display: block !important; } } /* Dropdown mobile oculto no desktop */ .tabs-mobile-wrap { display: none; } `; document.head.appendChild(style); })(); // Hook: detecta se está em mobile (≤768px) function useIsMobile() { const [isMobile, setIsMobile] = React.useState(() => window.innerWidth <= 768); React.useEffect(() => { const handler = () => setIsMobile(window.innerWidth <= 768); window.addEventListener('resize', handler); return () => window.removeEventListener('resize', handler); }, []); return isMobile; } // Register GSAP plugins once at load time if (window.gsap) { [window.ScrollTrigger, window.SplitText, window.Flip, window.Observer, window.CustomEase] .filter(Boolean) .forEach(p => window.gsap.registerPlugin(p)); } // Word-by-word mask reveal — wraps any children and animates each word on scroll entry. // For accessibility: aria-label carries the full text; word spans are aria-hidden. function WordReveal({ children, as: Tag = 'h2', ...props }) { const ref = React.useRef(null); function flatten(node) { if (node == null) return ''; if (typeof node === 'string') return node; if (Array.isArray(node)) return node.map(flatten).join(''); if (!React.isValidElement(node)) return ''; if (node.type === 'br') return ' '; return flatten(node.props.children); } let _kid = 0; function split(node) { if (node == null || node === false || node === true) return []; if (typeof node === 'number') return [{node}]; if (typeof node === 'string') { return node.split(/(\s+)/).reduce((acc, part) => { if (!part) return acc; if (/^\s+$/.test(part)) { acc.push({part}); } else { const k = _kid++; acc.push( ); } return acc; }, []); } if (Array.isArray(node)) return node.flatMap(split); if (!React.isValidElement(node)) return [node]; if (node.type === 'br') return []; return [React.cloneElement(node, { key: _kid++, 'aria-hidden': 'true', children: split(node.props.children), })]; } React.useEffect(() => { if (!window.gsap || !ref.current) return; const inners = Array.from(ref.current.querySelectorAll('.word-inner')); if (!inners.length) return; // Esconde imediatamente window.gsap.set(inners, { yPercent: 140 }); const obs = new IntersectionObserver(([entry]) => { if (!entry.isIntersecting) return; obs.disconnect(); window.gsap.to(inners, { yPercent: 0, duration: 1.6, ease: 'expo.out', stagger: 0.08 }); }, { threshold: 0.05, rootMargin: '0px 0px -8% 0px' }); obs.observe(ref.current); return () => obs.disconnect(); }, []); const label = flatten(children); const rendered = split([].concat(children)); return ( {rendered} ); } // Utilitário: observa um elemento e roda um callback GSAP quando entra na tela function onVisible(el, callback, opts = {}) { if (!el) return () => { }; const obs = new IntersectionObserver(([entry]) => { if (!entry.isIntersecting) return; obs.disconnect(); callback(el); }, { threshold: opts.threshold ?? 0.08, rootMargin: opts.rootMargin ?? '0px 0px -8% 0px' }); obs.observe(el); return () => obs.disconnect(); } function FadeUp({ children, as: Tag = 'div', delay = 0, ...props }) { const ref = React.useRef(null); React.useEffect(() => { const el = ref.current; if (!el || !window.gsap) return; window.gsap.set(el, { opacity: 0, y: 40 }); return onVisible(el, () => window.gsap.to(el, { opacity: 1, y: 0, duration: 1.6, delay, ease: 'power3.out' }) ); }, []); return {children}; } function Footer() { const [instaHover, setInstaHover] = React.useState(false); return ( ); } // Icon set — coherent outline family (1.6 stroke, round caps, 24x24 grid) function SvcIcon({ name }) { const c = { width: 28, height: 28, viewBox: "0 0 24 24", fill: "none", stroke: "#0e0e0e", strokeWidth: 1.6, strokeLinecap: "round", strokeLinejoin: "round" }; const svgs = { // Smartphone — Social Media social: , // Paintbrush — Design design: , // Browser — Sites sites: , // Bar chart with arrow — Tráfego trafego: , // Server stack — Sistemas sistemas: , // SMS Bubble — IA ia: , // Play in frame — Vídeo video: , }; return svgs[name] || svgs.sites; } function SolutionCard({ icon, title, desc, onClick }) { return (
{title}
{desc}
); } function HoverBorderGradient({ children, onClick, inverse = false, pill = false }) { const [hovered, setHovered] = React.useState(false); const btnRef = React.useRef(null); const sweepRef = React.useRef(null); const baseBg = inverse ? '#ffffff' : '#0e0e0e'; const sweepBg = inverse ? '#0e0e0e' : '#ffffff'; const baseText = inverse ? '#0e0e0e' : '#ffffff'; const hoverText = inverse ? '#ffffff' : '#0e0e0e'; function handleMouseEnter(e) { const btn = btnRef.current; const sweep = sweepRef.current; if (!btn || !sweep) return; const rect = btn.getBoundingClientRect(); const x = e.clientX - rect.left; const y = e.clientY - rect.top; // Diameter large enough to cover button from any entry corner const size = Math.sqrt(rect.width ** 2 + rect.height ** 2) * 2.2; // Snap circle to entry point at scale 0 (no transition), then expand sweep.style.transition = 'none'; sweep.style.width = size + 'px'; sweep.style.height = size + 'px'; sweep.style.left = (x - size / 2) + 'px'; sweep.style.top = (y - size / 2) + 'px'; sweep.style.transform = 'scale(0)'; sweep.getBoundingClientRect(); // force reflow sweep.style.transition = 'transform 1.6s cubic-bezier(0.16, 1, 0.3, 1)'; sweep.style.transform = 'scale(1)'; setHovered(true); } function handleMouseLeave() { const sweep = sweepRef.current; if (!sweep) return; sweep.style.transition = 'transform 0.35s cubic-bezier(0.4, 0, 1, 1)'; sweep.style.transform = 'scale(0)'; setHovered(false); } return ( ); } function WebGLShader() { const isMobile = useIsMobile(); const canvasRef = React.useRef(null); const sceneRef = React.useRef({ scene: null, camera: null, renderer: null, mesh: null, uniforms: null, animationId: null, }); const physicsRef = React.useRef({ targetX: 0.5, targetY: 0.5, currentX: 0.5, currentY: 0.5, }); React.useEffect(() => { if (!canvasRef.current || !window.THREE) return; const THREE = window.THREE; const canvas = canvasRef.current; const { current: refs } = sceneRef; const vertexShader = ` attribute vec3 position; void main() { gl_Position = vec4(position, 1.0); } `; const fragmentShader = ` precision highp float; uniform vec2 resolution; uniform float time; uniform vec2 mouse; // 0.0 a 1.0 uniform float mobile; // 1.0 em mobile, 0.0 em desktop float random(vec2 st) { return fract(sin(dot(st.xy, vec2(12.9898,78.233))) * 43758.5453123); } void main() { vec2 uv = gl_FragCoord.xy / resolution.xy; vec2 p = uv * 2.0 - 1.0; p.x *= resolution.x / resolution.y; // Mouse com proporção de tela vec2 m = mouse * 2.0 - 1.0; m.x *= resolution.x / resolution.y; float t = time * 0.4; // INTERAÇÃO COM MOUSE: O mouse mistura o fluido criando um "redemoinho" mais forte vec2 mouseDir = p - m; float mouseDist = length(mouseDir); float mouseEffect = exp(-mouseDist * 2.0); // Raio de ação maior p += vec2(-mouseDir.y, mouseDir.x) * mouseEffect * 1.5; // Efeito giratório muito mais forte p += mouseDir * mouseEffect * 0.8; // Repulsão mais visível // Distorção de domínio (Efeito de fluido/Lava Lamp) p.x += sin(p.y * 2.5 + t) * 0.3; p.y += cos(p.x * 2.0 + t * 0.8) * 0.3; // Raio X adaptado ao aspect ratio: em portrait mobile o range visível de X // é pequeno (ex: 0.46 para 390x844), então escalamos os raios proporcionalmente. float axr = min(resolution.x / resolution.y, 1.0); // 4 focos principais de luz (Orbs) orbitando lentamente vec2 pos1 = vec2(sin(t)*0.8*axr, cos(t*1.2)*0.5); vec2 pos2 = vec2(cos(t*1.3)*0.9*axr, sin(t*0.9)*0.6); vec2 pos3 = vec2(sin(t*0.7 - 2.0)*0.7*axr, cos(t*1.5)*0.4); vec2 pos4 = vec2(cos(t*1.1 + 1.0)*0.6*axr, sin(t*0.8 + 3.0)*0.5); // O mouse "puxa" e "empurra" as cores com mais força pos1 += (m - pos1) * 0.40 * mouseEffect; pos2 += (m - pos2) * -0.30 * mouseEffect; pos3 += (m - pos3) * 0.30 * mouseEffect; pos4 += (m - pos4) * -0.40 * mouseEffect; // Caída exponencial para as luzes se misturarem até o infinito float b1 = exp(-length(p - pos1) * 1.5); float b2 = exp(-length(p - pos2) * 1.3); float b3 = exp(-length(p - pos3) * 1.8); float b4 = exp(-length(p - pos4) * 1.4); // Cores puras da paleta Line vec3 c1 = vec3(0.000, 0.486, 0.988); // Azul claro #007CFC vec3 c2 = vec3(1.000, 0.569, 0.733); // Rosa #FF91BB vec3 c3 = vec3(0.957, 0.718, 0.039); // Amarelo #F4B70A vec3 c4 = vec3(0.933, 0.035, 0.016); // Vermelho #EE0904 vec3 bg = vec3(0.0549); // Fundo #0e0e0e // Mobile: cores mais escuras e sem noise; desktop: brilho original com grain float brightness = mix(0.85, 0.32, mobile); vec3 color = bg + (c1*b1 + c2*b2 + c3*b3 + c4*b4) * brightness; // Vinheta: Escurece suavemente as bordas da tela para ancorar no fundo float vignette = 1.0 - smoothstep(0.5, 2.5, length(uv * 2.0 - 1.0)); color = mix(bg, color, vignette); // Brilho extra no local exato do mouse para destacar o movimento float mouseGlow = mix(0.08, 0.01, mobile); color += vec3(mouseGlow) * exp(-mouseDist * 3.0); // Noise/grain animado — apenas no desktop float noise = (random(uv + time) - 0.5) * 0.12 * (1.0 - mobile); color += noise; gl_FragColor = vec4(color, 1.0); } `; const initScene = () => { refs.scene = new THREE.Scene(); refs.renderer = new THREE.WebGLRenderer({ canvas }); refs.renderer.setPixelRatio(isMobile ? 1 : Math.min(window.devicePixelRatio, 2)); refs.renderer.setClearColor(new THREE.Color(0x0e0e0e)); refs.camera = new THREE.OrthographicCamera(-1, 1, 1, -1, 0, -1); refs.uniforms = { resolution: { value: [window.innerWidth, window.innerHeight] }, time: { value: 0.0 }, mouse: { value: [0.5, 0.5] }, mobile: { value: isMobile ? 1.0 : 0.0 }, }; const position = [ -1.0, -1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, -1.0, 0.0, -1.0, 1.0, 0.0, 1.0, 1.0, 0.0, ]; const positions = new THREE.BufferAttribute(new Float32Array(position), 3); const geometry = new THREE.BufferGeometry(); geometry.setAttribute("position", positions); const material = new THREE.RawShaderMaterial({ vertexShader, fragmentShader, uniforms: refs.uniforms, side: THREE.DoubleSide, }); refs.mesh = new THREE.Mesh(geometry, material); refs.scene.add(refs.mesh); handleResize(); }; const animate = () => { const phys = physicsRef.current; const lerp = (a, b, t) => a + (b - a) * t; // Interpolação super sedosa para o mouse phys.currentX = lerp(phys.currentX, phys.targetX, 0.05); phys.currentY = lerp(phys.currentY, phys.targetY, 0.05); if (refs.uniforms) { refs.uniforms.time.value += 0.01; refs.uniforms.mouse.value = [phys.currentX, phys.currentY]; } if (refs.renderer && refs.scene && refs.camera) { refs.renderer.render(refs.scene, refs.camera); } refs.animationId = requestAnimationFrame(animate); }; const handleResize = () => { if (!refs.renderer || !refs.uniforms || !canvasRef.current) return; const parent = canvasRef.current.parentElement; const width = parent ? parent.clientWidth : window.innerWidth; // Aumentamos 300px na altura total para vazar para a próxima seção (só desktop) const height = (parent ? parent.clientHeight : window.innerHeight) + (isMobile ? 0 : 300); refs.renderer.setSize(width, height, false); refs.uniforms.resolution.value = [width, height]; }; // No mobile, adia o início do WebGL para o browser terminar o primeiro paint // e a animação da logo entrar sem concorrência de GPU let startTimer; const start = () => { initScene(); animate(); if (isMobile && canvasRef.current) { canvasRef.current.style.transition = 'opacity 1.4s ease'; canvasRef.current.style.opacity = '1'; } }; if (isMobile) { startTimer = setTimeout(start, 1000); } else { start(); } window.addEventListener("resize", handleResize); // Passa as interações do mouse dinamicamente apenas quando está em cima do Hero const handleMouseMove = (e) => { const parent = canvasRef.current?.parentElement; if (!parent) return; const rect = parent.getBoundingClientRect(); if (e.clientY >= rect.top && e.clientY <= rect.bottom) { physicsRef.current.targetX = (e.clientX - rect.left) / rect.width; // Invertemos o Y pois o WebGL lê de baixo para cima physicsRef.current.targetY = 1.0 - ((e.clientY - rect.top) / rect.height); } }; const handleMouseOut = () => { physicsRef.current.targetX = 0.5; physicsRef.current.targetY = 0.5; }; window.addEventListener("mousemove", handleMouseMove); window.addEventListener("mouseout", handleMouseOut); return () => { clearTimeout(startTimer); if (refs.animationId) cancelAnimationFrame(refs.animationId); window.removeEventListener("resize", handleResize); window.removeEventListener("mousemove", handleMouseMove); window.removeEventListener("mouseout", handleMouseOut); if (refs.mesh) { refs.scene?.remove(refs.mesh); refs.mesh.geometry.dispose(); if (refs.mesh.material.dispose) refs.mesh.material.dispose(); } refs.renderer?.dispose(); }; }, []); return ( ); } function TestimonialCarousel() { const trackRef = React.useRef(null); const isDragging = React.useRef(false); const startX = React.useRef(0); const scrollLeft = React.useRef(0); const baseItems = [ { name: 'Cliente 1', role: 'Escreva feedback aqui', q: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation.' }, { name: 'Cliente 2', role: 'Escreva feedback aqui', q: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.' }, { name: 'Cliente 3', role: 'Escreva feedback aqui', q: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.' }, ]; // Duplicamos a lista múltiplas vezes para criar um efeito de loop suave const items = [...baseItems, ...baseItems, ...baseItems, ...baseItems]; React.useEffect(() => { let reqId; const track = trackRef.current; const play = () => { if (!isDragging.current && track && track.children.length > baseItems.length) { track.scrollLeft += 0.8; // Velocidade suave do auto-scroll // Loop contínuo const singleSetWidth = track.children[baseItems.length].offsetLeft - track.children[0].offsetLeft; if (track.scrollLeft >= singleSetWidth) { track.scrollLeft -= singleSetWidth; } } reqId = requestAnimationFrame(play); }; reqId = requestAnimationFrame(play); return () => cancelAnimationFrame(reqId); }, [baseItems.length]); const onMouseDown = (e) => { isDragging.current = true; startX.current = e.pageX - trackRef.current.offsetLeft; scrollLeft.current = trackRef.current.scrollLeft; }; const onMouseLeave = () => { isDragging.current = false; }; const onMouseUp = () => { isDragging.current = false; }; const onMouseMove = (e) => { if (!isDragging.current) return; e.preventDefault(); const x = e.pageX - trackRef.current.offsetLeft; const walk = (x - startX.current) * 1.5; // Aceleração do arrasto trackRef.current.scrollLeft = scrollLeft.current - walk; }; return (
{ isDragging.current = true; }} onTouchEnd={() => { isDragging.current = false; }} > {items.map((t, i) => (
{t.name}
{t.role}

{t.q}
))}
); } // Grade de Reels (3 por coluna) function ReelsGrid() { const items = [ { id: '1194100027', title: 'Reel 10', platform: 'vimeo' }, { id: '1189239634', title: 'Reel 11', platform: 'vimeo' }, { id: '1194147383', title: 'Reel 08', platform: 'vimeo' }, { id: 'J323thLmh5A', title: 'Reel 01' }, { id: 'W-5Szihyq7Q', title: 'Reel 02' }, { id: 'y6Tgp3P-xC8', title: 'Reel 03' }, { id: '8e8LagmNCcI', title: 'Reel 04' }, { id: 'wVjYrR3MIZc', title: 'Reel 05' }, { id: '1194147946', title: 'Reel 06', platform: 'vimeo' }, ]; return (
{items.map((item, i) => (
{item.platform === 'vimeo' ? ( ) : ( )}
))}
); } // Home page — based on /UI/Desktop---2/index.jsx (figma node 104:29) // Hook: esconde targets imediatamente, anima quando o triggerRef entra na tela function useReveal(triggerRef, getTargets, fromVars, toVars) { React.useEffect(() => { if (!window.gsap) return; const triggerEl = triggerRef?.current; if (!triggerEl) return; const rawTargets = typeof getTargets === 'function' ? getTargets() : getTargets; const targets = (Array.isArray(rawTargets) ? rawTargets : [rawTargets]).filter(Boolean); if (!targets.length) return; window.gsap.set(targets, fromVars); return onVisible(triggerEl, () => window.gsap.to(targets, toVars)); }, []); } function HomePage({ setRoute }) { const isMobile = useIsMobile(); const heroLogoRef = React.useRef(null); const solGridRef = React.useRef(null); const cardsRef = React.useRef(null); const videosTitleRef = React.useRef(null); const curvedBodyRef = React.useRef(null); const arcoRef = React.useRef(null); const videosGridRef = React.useRef(null); const solCtaRef = React.useRef(null); const curvedCtaRef = React.useRef(null); const videosCtaRef = React.useRef(null); const artCtaRef = React.useRef(null); const testiRef = React.useRef(null); const testiCtaRef = React.useRef(null); // Hero entrance — CSS animation no mobile (compositor thread, imune ao WebGL) // GSAP com subida completa apenas no desktop React.useEffect(() => { if (isMobile || !window.gsap) return; window.gsap.fromTo(heroLogoRef.current, { opacity: 0, y: 100, scale: 0.9 }, { opacity: 1, y: 0, scale: 1, duration: 2.6, ease: 'power4.out', delay: 0.3 } ); }, []); // Sol-cards stagger useReveal(solGridRef, () => solGridRef.current ? [...solGridRef.current.querySelectorAll('.sol-card')] : [], { opacity: 0, y: 60 }, { opacity: 1, y: 0, duration: 1.4, stagger: 0.13, ease: 'power3.out', clearProps: 'transform' }); // CTA soluções useReveal(solCtaRef, () => solCtaRef.current, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }); // Parágrafo curved useReveal(curvedBodyRef, () => curvedBodyRef.current, { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 1.6, ease: 'power3.out' }); // Arco (diagrama de processo) useReveal(arcoRef, () => arcoRef.current, { opacity: 0, y: 50, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 1.9, ease: 'expo.out' }); // CTA curved useReveal(curvedCtaRef, () => curvedCtaRef.current, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }); // "Todo grande vídeo" título — word-by-word mask reveal useReveal(videosTitleRef, () => videosTitleRef.current ? [...videosTitleRef.current.querySelectorAll('.word-inner')] : [], { yPercent: 140 }, { yPercent: 0, duration: 1.6, ease: 'expo.out', stagger: 0.08 }); // Videos grid items stagger useReveal(videosGridRef, () => videosGridRef.current ? [...videosGridRef.current.querySelectorAll('.ph')] : [], { opacity: 0, y: 40, scale: 0.98 }, { opacity: 1, y: 0, scale: 1, duration: 1.4, stagger: 0.14, ease: 'power3.out' }); // CTA vídeos useReveal(videosCtaRef, () => videosCtaRef.current, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }); // Art images stagger useReveal(cardsRef, () => cardsRef.current ? [...cardsRef.current.querySelectorAll('img')] : [], { opacity: 0, y: 50, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 1.6, stagger: 0.17, ease: 'expo.out', clearProps: 'transform' }); // CTA arte useReveal(artCtaRef, () => artCtaRef.current, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }); // Testimonials useReveal(testiRef, () => testiRef.current, { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 1.6, ease: 'power3.out' }); useReveal(testiCtaRef, () => testiCtaRef.current, { opacity: 0, y: 24 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }); return (
{/* HERO */}
{isMobile && }

Line

{/* NOSSAS SOLUÇÕES */}
Nossas soluções
setRoute('social')} /> setRoute('design')} /> setRoute('trafego')} /> setRoute('sistemas')} />
setRoute('sites')} /> setRoute('ia')} /> setRoute('videos')} />
setRoute('social')}>Conheça todos os nossos serviços
{/* Pare de postar por postar — white curved */}
Pare de postar por postar.
Comece a construir presença.

Um processo pensado em cinco etapas para transformar sua presença digital em algo que permanece.

Nosso Processo
setRoute('social')}>Construa agora a sua presença
{/* Todo grande vídeo */}

{['Todo', 'grande', 'vídeo'].map((w, i) => ( {i > 0 && ' '} ))} {['tem', 'algo', 'em', 'comum:'].map((w, i) => ( {i > 0 && ' '} ))}

setRoute('videos')}>Veja nossos vídeos
{/* Todo pedaço de arte */}
Todo pedaço de arte
nos conta uma história.
Páscoa Imobiliária OneB Natal
setRoute('design')}>Conheça o nosso trabalho
{/* Testimonials */}
A transformação que a Line gera no seu negócio
window.open('https://wa.me/5551989236353?text=Gostaria%20de%20mais%20informa%C3%A7%C3%B5es%20sobre%20os%20servi%C3%A7os%20da%20Line', '_blank')}>Quero solicitar um orçamento!
); } // Service pages — map: todos, social, videos, design, sites, sistemas, ia, trafego, contato function ServiceTabs({ route, setRoute }) { const [dropOpen, setDropOpen] = React.useState(false); const items = [ { id: 'social', label: 'Social Media' }, { id: 'videos', label: 'Criação de Vídeos' }, { id: 'design', label: 'Design' }, { id: 'sites', label: 'Criação de Site' }, { id: 'trafego', label: 'Tráfego Pago' }, { id: 'ia', label: 'Atendimento de IA' }, { id: 'sistemas', label: 'Criação de Sistemas' }, ]; const current = items.find(it => it.id === route) || items[0]; return (
Nossos serviços
{/* Desktop: carrossel de tabs */}
{items.map(it => ( ))}
{/* Mobile: dropdown */}
{dropOpen && (
{items.filter(it => it.id !== route).map(it => ( ))}
)}
); } function PageFrame({ children, setRoute, route }) { const frameRef = React.useRef(null); React.useEffect(() => { if (!window.gsap) return; const el = frameRef.current; if (!el) return; const cleanups = []; // Helper: esconde e observa; quando entra na tela, anima com stagger const reveal = (targets, from, to, triggerEl) => { const arr = (Array.isArray(targets) ? targets : [targets]).filter(Boolean); if (!arr.length) return; window.gsap.set(arr, from); cleanups.push(onVisible(triggerEl || arr[0], () => window.gsap.to(arr, to))); }; // .big title — anima no mount (sem esperar scroll) const big = el.querySelector('.big'); if (big) { window.gsap.set(big, { opacity: 0, y: 50 }); const t = window.gsap.to(big, { opacity: 1, y: 0, duration: 1.5, ease: 'power3.out', delay: 0.2 }); cleanups.push(() => t.kill()); } // two-col: stagger nas colunas const twoCol = el.querySelector('.two-col'); if (twoCol) reveal([...twoCol.children], { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 1.4, stagger: 0.22, ease: 'power3.out' }, twoCol); // media-strip items const mediaStrip = el.querySelector('.media-strip'); if (mediaStrip) reveal([...mediaStrip.children], { opacity: 0, y: 60, scale: 0.98 }, { opacity: 1, y: 0, scale: 1, duration: 1.6, stagger: 0.17, ease: 'expo.out' }, mediaStrip); // design-gallery e design-hero const gallery = el.querySelector('.design-gallery'); if (gallery) reveal([...gallery.children], { opacity: 0, y: 40, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 1.4, stagger: 0.14, ease: 'power3.out', clearProps: 'transform' }, gallery); const designHero = el.querySelector('.design-hero'); if (designHero) reveal([...designHero.children], { opacity: 0, y: 40, scale: 0.97 }, { opacity: 1, y: 0, scale: 1, duration: 1.4, stagger: 0.14, ease: 'power3.out', clearProps: 'transform' }, designHero); // brands const brandsRow = el.querySelector('.brands .row'); if (brandsRow) reveal([...brandsRow.children], { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 1.4, stagger: 0.17, ease: 'power3.out' }, el.querySelector('.brands')); // partners, traf-highlight, closing, closing-cta [ [el.querySelector('.partners'), { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 1.6, ease: 'power3.out' }], [el.querySelector('.traf-highlight'), { opacity: 0, y: 40 }, { opacity: 1, y: 0, duration: 1.6, ease: 'power3.out' }], [el.querySelector('.closing'), { opacity: 0, y: 30 }, { opacity: 1, y: 0, duration: 1.6, ease: 'power3.out' }], [el.querySelector('.closing-cta'), { opacity: 0, y: 20 }, { opacity: 1, y: 0, duration: 1.3, ease: 'power3.out' }], ].forEach(([target, from, to]) => { if (target) reveal([target], from, to); }); return () => cleanups.forEach(fn => fn()); }, [route]); return (
{children}
); } // ====== SOCIAL ====== function SocialPage({ setRoute }) { return ( Presença que permanece, conteúdo que conecta
feed · identidade social
A sua história merece ser contada todos os dias

{`As redes sociais são onde a história da sua empresa acontece. Mas poucos sabem transformá-los em algo que fica. Na Line, cada publicação é pensada para ir além do feed, para criar um laço entre a sua marca e as pessoas que um dia vão escolhê-la.\nDesenvolvemos estratégia de conteúdo, identidade visual para redes sociais, calendário editorial, produção de copies e gestão completa dos perfis. Tudo para que a sua presença digital não seja apenas frequente, mas seja memorável.`}

case · social
Marcas que já eternizamos
partner
eurohome
shopping
space
window.open('https://wa.me/5551989236353?text=Quero%20construir%20a%20minha%20presen%C3%A7a%20no%20digital%20com%20a%20Line', '_blank')}>Quero construir minha presença
); } // ====== DESIGN ====== function DesignPage({ setRoute }) { const isMobile = useIsMobile(); const imgStyle = isMobile ? { width: '100%', height: 'auto', display: 'block', borderRadius: '24px' } : { width: '100%', height: '676px', objectFit: 'cover', borderRadius: '39px' }; return ( Cada peça, uma história para contar.
Páscoa Hamburguer
Design não é só um post bonito.

{`O visual de uma marca é o seu primeiro idioma. Antes de qualquer palavra, é a estética que diz quem você é e se vale a pena ficar.\nCriamos cards estáticos para feed com identidade consistente, carrosséis pensados para engajar, salvar e compartilhar. E para composições que desafiam o ordinário, usamos Manipulação com IA para criar imagens, cenários e elementos únicos que só existem porque foram imaginados primeiro.`}

A sua marca tem o visual que merece?

Para marcas que estão nascendo ou se reinventando, desenvolvemos a Identidade Visual completa.

{[ 'idv_angulo.png', 'idv_zuno.png', 'idv_padaria.png', 'idv_flare.png', ].map((file, i, arr) => (
))}
window.open('https://wa.me/5551989236353?text=Quero%20que%20minha%20empresa%20tenha%20um%20design%20que%20permanece', '_blank')}>Quero um design que permanece
); } // ====== SITES ====== function InteractiveSiteMockup({ image, previewImage, title, previewZoom = '100%', previewOffsetY = '0px', containerStyle = {} }) { const [isFullscreen, setIsFullscreen] = React.useState(false); const [isHovered, setIsHovered] = React.useState(false); const cardBg = previewImage || image; // Travar o scroll da página de fundo enquanto estiver em tela cheia React.useEffect(() => { if (isFullscreen) { document.body.style.overflow = 'hidden'; } else { document.body.style.overflow = ''; } return () => { document.body.style.overflow = ''; }; }, [isFullscreen]); return ( <> {/* Hero / Miniatura Estática */}
setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} onClick={() => setIsFullscreen(true)} style={{ position: 'relative', width: '100%', height: '688px', borderRadius: '28px', overflow: 'hidden', background: '#0e0e0e', cursor: 'pointer', boxShadow: isHovered ? '0 20px 40px rgba(0,0,0,0.2)' : '0 10px 30px rgba(0,0,0,0.1)', transition: 'all 0.4s ease', ...containerStyle }} > {/* Imagem de Fundo (com animação de zoom) */}
{/* Overlay Escuro ao passar o mouse */}
{/* Ícone de Tela Cheia */}
{/* Tela Cheia Interativa */} {isFullscreen && (
{title}
)} ); } function SitesPage({ setRoute }) { const isMobile = useIsMobile(); const siteCardStyle = isMobile ? { height: '220px' } : {}; return ( O seu site à altura do que você entrega.
Um site que conta quem você é antes de você falar

{`Criamos sites institucionais que posicionam e geram credibilidade, landing pages construídas para converter visitantes em clientes, e-commerces pensados para transformar navegação em venda, e portfólios que traduzem visualmente o valor do seu trabalho.\nCada projeto nasce de um briefing profundo e termina em uma entrega que une estética, performance e identidade, criando uma linha que conecta a sua marca a quem a acessa.`}

window.open('https://wa.me/5551989236353?text=Quero%20criar%20um%20site%20que%20me%20representa', '_blank')}>Quero um site que me representa
); } // ====== CHATBOT COMPONENT ====== // Substitua pelo valor da sua chave de API — console.anthropic.com const ANTHROPIC_API_KEY = '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'; const CHAT_SYSTEM_PROMPT = `Você é a Lia, parte da equipe da Line, uma agência de marketing digital em Porto Alegre. Sobre a Line: - Serviços: Social Media, Design, Tráfego Pago (Meta Ads / Google Ads), Criação de Sites, Criação de Sistemas, Vídeos e Atendimento por IA - Contato: WhatsApp 51 9 8923.6353 | comercialine@gmail.com | Instagram @line.produtora - Em sistemas e IA, a Line trabalha em parceria com a Navena (tecnologia) Como você escreve: - Escreve como uma pessoa real digitando numa conversa, sem formalidade - Português brasileiro natural, às vezes com gírias leves quando cabe - Respostas curtas e diretas, sem enrolação - NUNCA usa travessão (esse símbolo: —). Jamais. Use vírgula, ponto ou reescreva a frase - Sem frases de efeito, sem filosofar, sem encerrar com reflexões bonitas - Nunca diz "Como uma IA..." ou "Sou programada para..." ou "Como assistente virtual..." - Não começa respostas com "Claro!", "Certamente!", "Ótima pergunta!", "Com certeza!" ou similares - Você pode responder QUALQUER pergunta, não só sobre marketing - Quando falar da Line, faz de forma natural, sem soar como propaganda - Para orçamentos e valores, diz pra entrar em contato pelo WhatsApp`; function IAChatBot() { const [messages, setMessages] = React.useState([ { role: 'assistant', text: 'Olá, como a Line pode te ajudar hoje?' } ]); const [input, setInput] = React.useState(''); const [isStreaming, setIsStreaming] = React.useState(false); const chatWindowRef = React.useRef(null); const scrollToBottom = () => { if (chatWindowRef.current) { chatWindowRef.current.scrollTo({ top: chatWindowRef.current.scrollHeight, behavior: "smooth" }); } }; React.useEffect(() => { scrollToBottom(); }, [messages]); const handleSend = async () => { if (!input.trim() || isStreaming) return; const userText = input.trim(); const history = [...messages, { role: 'user', text: userText }]; setMessages(history); setInput(''); setIsStreaming(true); // Adiciona mensagem vazia do assistente que vai receber o stream setMessages(prev => [...prev, { role: 'assistant', text: '' }]); try { const response = await fetch('https://api.anthropic.com/v1/messages', { method: 'POST', headers: { 'Content-Type': 'application/json', 'x-api-key': ANTHROPIC_API_KEY, 'anthropic-version': '2023-06-01', 'anthropic-dangerous-direct-browser-access': 'true', }, body: JSON.stringify({ model: 'claude-haiku-4-5-20251001', max_tokens: 500, system: CHAT_SYSTEM_PROMPT, stream: true, messages: history.map(m => ({ role: m.role, content: m.text })) }) }); if (!response.ok) throw new Error(`${response.status}`); const reader = response.body.getReader(); const decoder = new TextDecoder(); let accumulated = ''; while (true) { const { done, value } = await reader.read(); if (done) break; const lines = decoder.decode(value).split('\n'); for (const line of lines) { if (!line.startsWith('data: ')) continue; const data = line.slice(6).trim(); if (!data || data === '[DONE]') continue; try { const parsed = JSON.parse(data); if (parsed.type === 'content_block_delta' && parsed.delta?.text) { accumulated += parsed.delta.text; setMessages(prev => { const updated = [...prev]; updated[updated.length - 1] = { role: 'assistant', text: accumulated }; return updated; }); scrollToBottom(); } } catch (_) { /* ignora linhas malformadas */ } } } } catch (err) { const msg = ANTHROPIC_API_KEY === 'SUA_CHAVE_AQUI' ? 'Configure a chave de API para ativar o chat real.' : 'Tive um probleminha aqui. Tenta de novo em instantes!'; setMessages(prev => { const updated = [...prev]; updated[updated.length - 1] = { role: 'assistant', text: msg }; return updated; }); } setIsStreaming(false); }; return (
{messages.map((m, i) => { const isLast = i === messages.length - 1; const showCursor = isLast && isStreaming && m.role === 'assistant'; return (
{m.text} {showCursor && ( )}
); })} {isStreaming && messages[messages.length - 1]?.text === '' && (
digitando...
)}
setInput(e.target.value)} placeholder="Pergunte qualquer coisa" onKeyDown={e => e.key === 'Enter' && handleSend()} disabled={isStreaming} />
); } // ====== SISTEMAS ====== function SistemasPage({ setRoute }) { return ( Tecnologia construída do zero para o jeito único que o seu negócio funciona
Um sistema que nasce do seu negócio, não o contrário

{`A maioria das empresas tenta adaptar o seu negócio a um sistema pronto. Na Line em parceria com a Navena, fazemos o inverso e construímos do zero uma solução que se encaixa exatamente na forma como você opera, cresce e pensa.\nDesenvolvemos sistemas de gestão completos como ERPs e CRMs, plataformas web personalizadas para operações específicas, aplicativos mobile que colocam o seu negócio na palma da mão dos seus clientes, e automações e integrações que conectam ferramentas, eliminam processos manuais e fazem o seu time ganhar tempo para o que realmente importa. Cada sistema é único, porque cada negócio é único.`}

dashboard · ERP
Line e Navena
Este serviço nasce da união entre duas especialidades. A Line traz o olhar estratégico, a identidade de marca e o posicionamento. A Navena traz a tecnologia, a robustez técnica e a inteligência por trás de cada automação. Juntas, entregamos uma solução que é ao mesmo tempo sofisticada e humana.
window.open('https://wa.me/5551989236353?text=Quero%20um%20sistema%20100%25%20personalizado%20para%20a%20minha%20empresa', '_blank')}>Quero um sistema feito para mim
); } // ====== IA ====== function IAPage({ setRoute }) { const isMobile = useIsMobile(); return ( A sua marca presente em cada conversa, 24 horas por dia.
IA 100% personalizada.

{`Desenvolvemos uma inteligência artificial treinada com a identidade, o tom de voz e os valores da sua marca, para que cada conversa pareça humana, mesmo quando não é.\nA IA atua no WhatsApp, Instagram Direct, chat do site e e-mail. Ela atende e resolve dúvidas, qualifica leads antes de chegarem até você, agenda compromissos automaticamente e conduz vendas até o fechamento. Tudo isso sem perder a essência da sua marca em nenhuma linha.`}

Line e Navena
Este serviço nasce da união entre duas especialidades. A Line traz o olhar estratégico, a identidade de marca e o posicionamento. A Navena traz a tecnologia, a robustez técnica e a inteligência por trás de cada automação. Juntas, entregamos uma solução que é ao mesmo tempo sofisticada e humana.
window.open('https://wa.me/5551989236353?text=Quero%20uma%20IA%20100%25%20personalizada%20para%20a%20minha%20empresa', '_blank')}>Quero uma IA personalizada
); } // ====== TRÁFEGO PAGO ====== function TrafegoPage({ setRoute }) { return ( O seu negócio na frente da pessoa certa.
Outdoor Online

{`Começamos pela configuração e estrutura das campanhas, desenhando uma arquitetura sólida no Meta Ads e no Google Ads para que cada centavo trabalhe com precisão.\nA partir daí, cuidamos da gestão e otimização contínua, ajustando, testando e evoluindo em tempo real. Criamos os criativos completos, artes e copies alinhados com a identidade da sua marca e pensados para converter.\nE ao final de cada ciclo, entregamos relatórios detalhados com análise de resultados, para que você entenda exatamente onde a sua marca está crescendo.`}

Pare de aparecer para todo mundo e comece a aparecer para os certos.
window.open('https://wa.me/5551989236353?text=Quero%20criar%20campanhas%20que%20convertem', '_blank')}>Quero campanhas que convertem
); } // ====== VÍDEOS ====== function VideosPage({ setRoute }) { return ( Vídeos que transformam momentos em memória
Instantes gravados para sempre

{`Um vídeo é o instante mais completo que existe, ele carrega imagem, movimento, som e emoção ao mesmo tempo. Na Line, cada produção nasce com um propósito: intenção.\nTudo começa na captação, com a gravação profissional de produtos, ambientes e detalhes que traduzem visualmente a essência da sua marca. Depois vem a edição: montagem, tratamento de cor, trilha sonora, motion e finalização que transformam cenas brutas num conteúdo que prende, emociona e converte.`}

window.open('https://wa.me/5551989236353?text=Quero%20que%20a%20minha%20empresa%20tenha%20v%C3%ADdeos%20com%20inten%C3%A7%C3%A3o', '_blank')}>Quero vídeos com intenção
); } // ====== CONTATO (form simples) ====== function ContatoPage({ setRoute }) { return ( Será um prazer colaborar com você
{['Seu nome', 'Seu e-mail', 'Seu telefone'].map(ph => ( ))}