diff --git a/lp-code/package-lock.json b/lp-code/package-lock.json
index c4981bb..d0f3a50 100644
--- a/lp-code/package-lock.json
+++ b/lp-code/package-lock.json
@@ -60,7 +60,6 @@
"integrity": "sha512-H3mcG6ZDLTlYfaSNi0iOKkigqMFvkTKlGUYlD8GW7nNOYRrevuA46iTypPyv+06V3fEmvvazfntkBU34L0azAw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@babel/code-frame": "^7.28.6",
"@babel/generator": "^7.28.6",
@@ -1645,7 +1644,6 @@
"integrity": "sha512-ne4A0IpG3+2ETuREInjPNhUGis1SFjv1d5asp8MzEAGtOZeTeHVDOYqOgqfhvseqg/iXty2hjBf1zAOb7RNiNw==",
"devOptional": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"undici-types": "~7.16.0"
}
@@ -1656,7 +1654,6 @@
"integrity": "sha512-WPigyYuGhgZ/cTPRXB2EwUw+XvsRA3GqHlsP4qteqrnnjDrApbS7MxcGr/hke5iUoeB7E/gQtrs9I37zAJ0Vjw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"csstype": "^3.2.2"
}
@@ -1716,7 +1713,6 @@
"integrity": "sha512-BtE0k6cjwjLZoZixN0t5AKP0kSzlGu7FctRXYuPAm//aaiZhmfq1JwdYpYr1brzEspYyFeF+8XF5j2VK6oalrA==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@typescript-eslint/scope-manager": "8.54.0",
"@typescript-eslint/types": "8.54.0",
@@ -1968,7 +1964,6 @@
"integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==",
"dev": true,
"license": "MIT",
- "peer": true,
"bin": {
"acorn": "bin/acorn"
},
@@ -2074,7 +2069,6 @@
}
],
"license": "MIT",
- "peer": true,
"dependencies": {
"baseline-browser-mapping": "^2.9.0",
"caniuse-lite": "^1.0.30001759",
@@ -2317,7 +2311,6 @@
"integrity": "sha512-LEyamqS7W5HB3ujJyvi0HQK/dtVINZvd5mAAp9eT5S/ujByGjiZLCzPcHVzuXbpJDJF/cxwHlfceVUDZ2lnSTw==",
"dev": true,
"license": "MIT",
- "peer": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.8.0",
"@eslint-community/regexpp": "^4.12.1",
@@ -3279,7 +3272,6 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
"integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=12"
},
@@ -3340,7 +3332,6 @@
"resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz",
"integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==",
"license": "MIT",
- "peer": true,
"engines": {
"node": ">=0.10.0"
}
@@ -3562,7 +3553,6 @@
"integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
"dev": true,
"license": "Apache-2.0",
- "peer": true,
"bin": {
"tsc": "bin/tsc",
"tsserver": "bin/tsserver"
@@ -3648,7 +3638,6 @@
"resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
"integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
"license": "MIT",
- "peer": true,
"dependencies": {
"esbuild": "^0.27.0",
"fdir": "^6.5.0",
@@ -3770,7 +3759,6 @@
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
"dev": true,
"license": "MIT",
- "peer": true,
"funding": {
"url": "https://github.com/sponsors/colinhacks"
}
diff --git a/lp-code/src/App.tsx b/lp-code/src/App.tsx
index 9520410..644ca54 100644
--- a/lp-code/src/App.tsx
+++ b/lp-code/src/App.tsx
@@ -1,6 +1,8 @@
-import { useRef, useState } from 'react'
+import { useRef } from 'react'
import { useGSAP } from '@gsap/react'
import { gsap } from "gsap";
+import MotionDemo from './components/MotionDemo';
+import LaptopSection from './components/LaptopSection';
gsap.registerPlugin(useGSAP);
@@ -49,11 +51,13 @@ const onClickGood = contextSafe(() => {
})
})
- return (
-
+
+ //'true' pra visualizar teste e 'false' pra esconder
+ const showTests = true;
+
+return (
+
+
Landing Page da Code
- )
+
+ {/* Seção de testes */}
+ {showTests && (
+
+
+
+
+ )}
+
+);
}
export default App
\ No newline at end of file
diff --git a/lp-code/src/components/LaptopSection.tsx b/lp-code/src/components/LaptopSection.tsx
new file mode 100644
index 0000000..b4dc48b
--- /dev/null
+++ b/lp-code/src/components/LaptopSection.tsx
@@ -0,0 +1,129 @@
+import React from "react";
+import { useGSAP } from "@gsap/react";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+
+// Registro obrigatório dos plugins
+gsap.registerPlugin(ScrollTrigger, useGSAP);
+
+/**
+ * LaptopSection
+ * Componente de teste para animação avançada com ScrollTrigger.
+ *
+ * Objetivo:
+ * - Testar performance com Pin + Scrub
+ * - Simular abertura de um laptop em 3D
+ * - Validar fluidez de animações sincronizadas com scroll
+ *
+ * Recursos:
+ * - Pin (fixa a seção durante scroll)
+ * - Scrub (animação segue o scroll)
+ * - Transformações 3D (rotateX + perspective)
+ * - Responsividade básica (mobile e desktop)
+ *
+ * Observações:
+ * - Usa useGSAP para garantir cleanup automático
+ * - Inclui verificações de null para evitar erros em strict mode
+ */
+const LaptopSection: React.FC = () => {
+ const containerRef = React.useRef
(null);
+ const laptopRef = React.useRef(null);
+ const screenRef = React.useRef(null);
+
+ useGSAP(
+ () => {
+
+ // Verificações de segurança - null checks
+ // Se algum ref essencial não estiver pronto, não builda a timeline
+ if (!containerRef.current || !laptopRef.current || !screenRef.current) {
+ return;
+ }
+
+ const mm = gsap.matchMedia();
+
+ mm.add({
+ isDesktop: "(min-width: 1024px)",
+ isMobile: "(max-width: 1023px)"
+ }, (context) => {
+ // Verifica a condição atual
+ const isDesktop = context.conditions?.isDesktop;
+
+ const tl = gsap.timeline({
+ scrollTrigger: {
+ trigger: containerRef.current,
+ start: "top top",
+ end: "+=1000",
+ scrub: 1,
+ pin: true,
+ // Adicionado para evitar flashes visuais e jump no pin
+ anticipatePin: 1,
+ },
+ });
+
+ // Ajuste de escala pra Mobile
+ if (!isDesktop) {
+ gsap.set(laptopRef.current, { scale: 0.6 });
+ } else {
+ gsap.set(laptopRef.current, { scale: 1 });
+ }
+
+ tl.to(screenRef.current, {
+ rotateX: 0,
+ ease: "power2.inOut",
+ })
+ .to(".laptop-text", {
+ opacity: 1,
+ y: isDesktop ? -20 : -10,
+ stagger: 0.2,
+ duration: 0.5
+ }, "-=0.5");
+ });
+
+ // O useGSAP já lida com o cleanup internamente,
+ // mas o mm.revert() é bom para limpar as queries de mídia.
+ return () => mm.revert();
+ },
+ { scope: containerRef }
+ );
+
+ return (
+
+
+
+ Performance de Elite
+
+
+ Sente o poder do hardware sob o teu comando.
+
+
+
+
+
+
+
+ {">"} SYSTEM READY
+
+
+
+
+
+
+
+ );
+};
+
+export default LaptopSection;
diff --git a/lp-code/src/components/MotionDemo.tsx b/lp-code/src/components/MotionDemo.tsx
new file mode 100644
index 0000000..c64d829
--- /dev/null
+++ b/lp-code/src/components/MotionDemo.tsx
@@ -0,0 +1,169 @@
+import React from "react";
+import { useRef } from "react";
+import {
+ useScrollTrigger,
+ useStaggerScroll,
+ useScrollTimeline,
+} from "../hooks/scrollHooks";
+import { useGSAP } from "@gsap/react";
+
+/**
+ * MotionDemo
+ * Componente de demonstração da infraestrutura de animações (Motion Base).
+ *
+ * Objetivos:
+ * - Validar hooks reutilizáveis de animação com ScrollTrigger
+ * - Demonstrar animações simples, stagger e timeline
+ *
+ * Recursos:
+ * - useScrollTrigger -> animação simples (fade + translate)
+ * - useStaggerScroll -> animação em cascata (stagger)
+ * - useScrollTimeline -> sequência orquestrada
+ *
+ * Responsividade:
+ * - Layout adaptado para mobile, tablet e desktop
+ * - Grid e tipografia ajustáveis via Tailwind
+ *
+ * Observações:
+ * - Todas as animações utilizam cleanup automático via useGSAP
+ * - Escopo controlado para evitar conflitos de seleção
+ */
+const MotionDemo: React.FC = () => {
+
+ // Animação Simples (Fade + Slide)
+ const singleRef = useScrollTrigger({ opacity: 0, x: -100, duration: 1 });
+
+ // Cartões para Animação em Cascata (Stagger)
+ const cardsRef = useRef(null);
+
+ // Stagger (cards em cascata)
+ useStaggerScroll(
+ cardsRef,
+ ".card-item",
+ // Estado inicial: invisível e deslocado para baixo
+ {
+ opacity: 0,
+ y: 80,
+ rotation: 5,
+ },
+ // Estado final: visível e na posição original
+ {
+ opacity: 1,
+ y: 0,
+ rotation: 0,
+ duration: 0.6,
+ stagger: 0.15,
+ }
+);
+
+ // Timeline orquestrada
+ const { containerRef, tl } = useScrollTimeline();
+
+ useGSAP(
+ () => {
+ if (!tl.current) return;
+
+ tl.current
+ .from(".item-1", { opacity: 0, y: -30, duration: 0.6 })
+ .from(".item-2", { scaleX: 0,transformOrigin: "left center", duration: 0.8 }, "-=0.2")
+ .from(".item-3", { opacity: 0, scale: 0.5, duration: 0.5 });
+ },
+ { scope: containerRef }
+ );
+
+
+
+
+
+ return (
+
+
+
+ Faz scroll para baixo para ver a magia! ↓
+
+
+
+ {/* DEMO 1: SIMPLES */}
+
+
+ Animação Simples
+
+
+ Eu apareci deslizando da esquerda usando o hook básico.
+
+
+
+ {/* DEMO 2: STAGGER */}
+
+
+ Efeito Stagger (Cascata)
+
+
+
+
+ {Array.from({ length: 9 }, (_, j) => {
+ const base2Value = j.toString(2);
+ return (
+
+ Card {base2Value}
+
+ ) })}
+
+
+
+ {/* DEMO 3: TIMELINE */}
+
+
+ A Timeline Master
+
+
+
+
+
+ Esta sequência foi orquestrada pelo Hook de Timeline!
+
+
+
+
+
+ );
+};
+
+export default MotionDemo;
diff --git a/lp-code/src/hooks/scrollHooks.ts b/lp-code/src/hooks/scrollHooks.ts
new file mode 100644
index 0000000..b3e2576
--- /dev/null
+++ b/lp-code/src/hooks/scrollHooks.ts
@@ -0,0 +1,168 @@
+import { useRef } from "react";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+import { useGSAP } from "@gsap/react";
+
+gsap.registerPlugin(ScrollTrigger, useGSAP);
+
+
+/**
+ * Hook para animação simples de um único elemento baseada em ScrollTrigger
+ * @param animationVars Configurações de animação do GSAP (ex: opacity, x, y)
+ * @param scrollVars Configurações opcionais para sobrescrever o comportamento do ScrollTrigger. (ex: start, end)
+ * @returns Ref a ser aplicada no elemento HTML
+ */
+export const useScrollTrigger = (
+ animationVars: gsap.TweenVars,
+ scrollVars?: Partial,
+) => {
+ const elementRef = useRef(null);
+
+ useGSAP(
+ () => {
+ if (!elementRef.current) return;
+
+ gsap.from(elementRef.current, {
+ ...animationVars,
+ scrollTrigger: {
+ trigger: elementRef.current,
+ start: "top 85%",
+ toggleActions: "play none none reverse",
+ ...scrollVars,
+ },
+ }); // gsap
+ },
+ {
+ scope: elementRef,
+ dependencies: [animationVars, scrollVars]
+ }, // useGSAP function
+ ); // useGSAP
+
+ return elementRef;
+}; // hook
+
+
+
+/**
+ * Hook para animação em cascata (stagger) de múltiplos elementos filhos
+ * @param containerRef Ref do container pai que engloba os itens a serem animados
+ * @param selector Seletor CSS para os itens filhos (ex: ".card")
+ * @param fromVars Configurações de animação inicial (ex: opacity: 0, y: 50)
+ * @param toVars Configurações de animação final (ex: opacity: 1, y: 0)
+ * @param scrollVars Configurações opcionais para o ScrollTrigger
+ * @returns Ref a ser aplicada no container pai
+ *
+ * @description Este hook é ideal para criar animações em cascata (stagger) onde múltiplos elementos filhos são animados sequencialmente à medida que entram na viewport. Ele utiliza o poder do GSAP para controlar a animação e o ScrollTrigger para ativá-la com base no scroll. O hook é flexível, permitindo personalizar tanto as animações quanto o comportamento do ScrollTrigger conforme necessário.
+ *
+ * @note Mudança de from para fromTo para melhor refletir a estrutura do GSAP e evitar confusão com o hook useScrollTrigger. O fromVars define o estado inicial dos elementos, enquanto o toVars define o estado final, incluindo o valor de stagger para controlar o atraso entre as animações dos itens.
+ * @note A adição
+ */
+export const useStaggerScroll = (
+ containerRef: React.RefObject,
+ selector: string,
+ fromVars: gsap.TweenVars,
+ toVars: gsap.TweenVars,
+ scrollVars?: Partial,
+) => {
+ useGSAP(
+ () => {
+ gsap.fromTo(
+ selector,
+ fromVars,
+ {
+ ...toVars,
+ stagger: toVars.stagger ?? 0.15,
+ overwrite: "auto", // overwrite garante que a animação seja reiniciada corretamente ao entrar/voltar à viewport com o scroll
+ scrollTrigger: {
+ trigger: containerRef.current,
+ start: "top 80%",
+ toggleActions: "play none none reverse",
+ ...scrollVars,
+ },
+ }); // gsap
+ }, // useGSAP function
+ {
+ scope: containerRef,
+ dependencies : [selector, fromVars, toVars, scrollVars]
+ }
+ ); // useGSAP
+}; // hook
+
+
+/**
+ * Hook para criação de uma timeline GSAP controlada por scroll.
+ * Permite orquestrar sequências complexas de animação.
+ * @param scrollVars - (Opcional) Configurações do ScrollTrigger
+ * @returns containerRef e timelineRef (tl)
+ */
+export const useScrollTimeline = (
+ scrollVars?: Partial,
+) => {
+ const containerRef = useRef(null);
+ const tl = useRef(null);
+
+ useGSAP(
+ () => {
+ if (!containerRef.current) return;
+
+ tl.current = gsap.timeline({
+ scrollTrigger: {
+ trigger: containerRef.current,
+ start: "top 80%",
+ toggleActions: "play none none reverse",
+ ...scrollVars,
+ }, // scrollTrigger
+ }); // gsap.timeline
+ }, // useGSAP function
+ {
+ scope: containerRef,
+ dependencies: [scrollVars]
+ },
+ ); // useGSAP
+
+ return { containerRef, tl };
+}; // hook
+
+
+
+/**
+ * Hook para fixar (pin) uma seção na tela e animar conforme o progresso do scroll.
+ * @param scrubValue Sensibilidade do acompanhamento do scroll (padrão 1).
+ * @param scrollVars Configurações adicionais de ScrollTrigger.
+ * @returns Objeto com as refs e a timeline gerada.
+ */
+export const useScrollPin = <
+ Section extends HTMLElement = HTMLDivElement,
+ Trigger extends HTMLElement = HTMLDivElement
+ >(
+ scrubValue: number | boolean = 1,
+ scrollVars?: Partial,
+) => {
+ const sectionRef = useRef(null);
+ const triggerRef = useRef(null);
+ const tl = useRef(null);
+
+ useGSAP(
+ () => {
+ if (!sectionRef.current || !triggerRef.current) return;
+
+ tl.current = gsap.timeline({
+ scrollTrigger: {
+ trigger: triggerRef.current,
+ start: "top top",
+ end: scrollVars?.end ?? "+=2000",
+ pin: sectionRef.current,
+ scrub: scrubValue,
+ markers: false,
+ ...scrollVars,
+ }, // scrollTrigger
+ }); // gsap.timeline
+ }, // useGSAP function
+ {
+ scope: sectionRef,
+ dependencies: [scrubValue, scrollVars]
+ },
+ ); // useGSAP
+
+ return { sectionRef, triggerRef, tl };
+}; // hook