Skip to content

Feat: Adding a launch animation for Catalysis launch. - #61

Merged
Akshat-Raj merged 2 commits into
Genesis-ISE-DSCE:mainfrom
Akshat-Raj:launch
Apr 17, 2026
Merged

Feat: Adding a launch animation for Catalysis launch.#61
Akshat-Raj merged 2 commits into
Genesis-ISE-DSCE:mainfrom
Akshat-Raj:launch

Conversation

@Akshat-Raj

Copy link
Copy Markdown
Collaborator

Adding a launch animation for Catalysis launch.

Copilot AI review requested due to automatic review settings April 16, 2026 17:31
@vercel

vercel Bot commented Apr 16, 2026

Copy link
Copy Markdown

@Akshat-Raj is attempting to deploy a commit to the Genesis' projects Team on Vercel.

A member of the Team first needs to authorize it.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a new /genesis route that plays a full-screen “Catalysis launch” animation using Framer Motion.

Changes:

  • Introduces GenesisAnimation client component implementing the multi-phase launch sequence (stars, lightning, Pokéball split, logo/CTA reveal).
  • Adds /genesis page and a dedicated layout intended to remove global chrome (Navbar/Footer/AudioPlayer).
  • Adds framer-motion dependency to support the animation.

Reviewed changes

Copilot reviewed 4 out of 5 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
src/components/ui/GenesisAnimation.tsx Implements the full-screen launch animation and click-to-start gate.
src/app/genesis/page.tsx Adds the /genesis page that renders the animation component.
src/app/genesis/layout.tsx Adds a route-segment layout intended to provide a clean full-screen canvas.
package.json Adds framer-motion dependency.
package-lock.json Locks framer-motion and related dependencies.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +18 to +24
return (
<html lang="en" className="h-full">
<body className="h-full m-0 p-0 overflow-hidden bg-black">
{children}
</body>
</html>
);

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In a route segment layout (src/app/genesis/layout.tsx), returning and is not supported by Next.js app router—only the root layout should define those tags. This can cause build/runtime errors. Wrap children in a div/section and move any body-level styling to that wrapper (or use a template), leaving / in src/app/layout.tsx only.

Suggested change
return (
<html lang="en" className="h-full">
<body className="h-full m-0 p-0 overflow-hidden bg-black">
{children}
</body>
</html>
);
return <div className="h-full m-0 p-0 overflow-hidden bg-black">{children}</div>;

Copilot uses AI. Check for mistakes.
Comment thread src/app/genesis/page.tsx
import GenesisAnimation from "@/components/ui/GenesisAnimation";

/**
* /genesis — Full-screen 5-phase Catalysis fight-to-logo animation.

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc says this is a “5-phase” animation, but GenesisAnimation runs more phases (up through Phase 9 + idle loop). Please update the comment so it accurately describes the current sequence to avoid confusing future edits.

Suggested change
* /genesis Full-screen 5-phase Catalysis fight-to-logo animation.
* /genesis Full-screen Catalysis fight-to-logo animation sequence
* that progresses through Phase 9 before entering an idle loop.

Copilot uses AI. Check for mistakes.
Comment on lines +6 to +16
// Precomputed at module level — avoids Math.random() inside render (react-hooks/purity)
const STARS = Array.from({ length: 70 }, (_, i) => ({
id: i,
top: `${Math.random() * 100}%`,
left: `${Math.random() * 100}%`,
size: Math.random() * 2.5 + 0.5,
delay: Math.random() * 4,
dur: Math.random() * 3 + 2,
}));
import Image from "next/image";

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Imports are split by executable code: STARS is declared before import Image from "next/image". This is inconsistent with the rest of the codebase’s import-at-top style (e.g., src/components/ui/SplashScreen.tsx:1-5) and can trip lint rules like import/first. Move the Image import up with the other imports before declaring module-level constants.

Copilot uses AI. Check for mistakes.
Comment on lines +176 to +205
const triggerShake = () => {
setShaking(true);
setTimeout(() => setShaking(false), 260);
};

// Animation only runs after the user clicks the Pokéball
useEffect(() => {
if (!started) return;

const run = async () => {
// ── PHASE 1: Pokémon + Pikachu pop in ────────────────────────────────
await Promise.all([
animate(".pikachu", { opacity: 1, scale: [0, 1.05, 1] }, { duration: 0.4 }),
...POKEMONS.map((_, i) =>
animate(`.pokemon-${i}`, { opacity: 1, scale: [0, 1.12, 1] }, { duration: 0.4, delay: 0.06 + i * 0.1 })
),
]);

// ── PHASE 2: Pikachu charges up ───────────────────────────────────────
setCharged(true);
await new Promise<void>((r) => setTimeout(r, 700));

// ── PHASE 3: Five sequential lightning strikes ─────────────────────────
for (let i = 0; i < POKEMONS.length; i++) {
setLightningPhase(i);
setHitSet((prev) => new Set([...prev, i]));
triggerShake();
setGlobalFlash(true);
setTimeout(() => setGlobalFlash(false), 160);
await new Promise<void>((r) => setTimeout(r, 370));

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The animation effect schedules multiple setTimeouts (e.g., in triggerShake and the lightning loop) and also starts an infinite animation on .catalysis-logo without any cleanup. If the user navigates away mid-sequence, this can trigger state updates after unmount and leave animations running. Add a cleanup function in the useEffect that tracks/clears all timeouts and stops any repeat: Infinity animation (store the returned controls and call stop()), guarded by an aborted flag.

Copilot uses AI. Check for mistakes.
Comment on lines +351 to +436
{/* ─────────────────────────────────────────────────────────────────────
ANIMATION CONTENT — always in DOM, starts invisible, driven by
the useEffect animation sequence that begins on `started = true`.
───────────────────────────────────────────────────────────────────── */}


{/* Global flash overlay */}
<motion.div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 50,
background: "radial-gradient(ellipse at center, rgba(255,255,160,0.95) 0%, rgba(255,220,0,0.6) 35%, transparent 65%)",
}}
animate={{ opacity: globalFlash ? 0.75 : 0 }}
transition={{ duration: globalFlash ? 0.05 : 0.25 }}
/>

{/* Lightning SVG */}
<div className="absolute inset-0 pointer-events-none" style={{ zIndex: 30 }}>
<svg width="100%" height="100%" viewBox={`0 0 ${dims.w} ${dims.h}`}>
<defs>
<filter id="bolt-glow" x="-60%" y="-60%" width="220%" height="220%">
<feGaussianBlur stdDeviation="5" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
<filter id="core-glow" x="-40%" y="-40%" width="180%" height="180%">
<feGaussianBlur stdDeviation="2" result="blur" />
<feMerge><feMergeNode in="blur" /><feMergeNode in="SourceGraphic" /></feMerge>
</filter>
</defs>
{POKEMONS.map((_, i) => (
<g key={i}>
<motion.path d={lightningPaths[i]} stroke="#FFD700" strokeWidth="10" strokeLinecap="round" fill="none" filter="url(#bolt-glow)"
initial={{ pathLength: 0, opacity: 0 }}
animate={i <= lightningPhase ? { pathLength: [0, 1], opacity: [0, 0.55, 0.4] } : { pathLength: 0, opacity: 0 }}
transition={i <= lightningPhase ? { duration: 0.22, ease: "easeOut" } : { duration: 0.15 }}
/>
<motion.path d={lightningPaths[i]} stroke="#FFFFF0" strokeWidth="2.5" strokeLinecap="round" fill="none" filter="url(#core-glow)"
initial={{ pathLength: 0, opacity: 0 }}
animate={i <= lightningPhase ? { pathLength: [0, 1], opacity: [0, 1, 0.9] } : { pathLength: 0, opacity: 0 }}
transition={i <= lightningPhase ? { duration: 0.22, ease: "easeOut" } : { duration: 0.15 }}
/>
</g>
))}
</svg>
</div>

{/* Fighters */}
<div className="fighters absolute inset-0 pointer-events-none" style={{ zIndex: 15 }}>
{/* Pikachu — center */}
<div style={{ position: "absolute", left: "50%", top: "50%", transform: "translate(-50%, -50%)" }}>
<div className="pikachu" style={{ opacity: 0, textAlign: "center", position: "relative" }}>
{charged && (
<>
<motion.div style={{ position: "absolute", bottom: 26, left: 6, width: 14, height: 10, borderRadius: "50%", background: "radial-gradient(circle, #FFE000, #FF9500)", filter: "blur(3px)" }}
animate={{ opacity: [0.5, 1, 0.5], scale: [0.9, 1.35, 0.9] }}
transition={{ duration: 0.38, repeat: Infinity }} />
<motion.div style={{ position: "absolute", bottom: 26, right: 6, width: 14, height: 10, borderRadius: "50%", background: "radial-gradient(circle, #FFE000, #FF9500)", filter: "blur(3px)" }}
animate={{ opacity: [0.5, 1, 0.5], scale: [0.9, 1.35, 0.9] }}
transition={{ duration: 0.38, repeat: Infinity, delay: 0.1 }} />
<motion.div style={{ position: "absolute", inset: -14, borderRadius: "50%", border: "2px solid rgba(255,220,0,0.6)", boxShadow: "0 0 18px rgba(255,220,0,0.5), inset 0 0 18px rgba(255,220,0,0.1)" }}
animate={{ opacity: [0.3, 0.9, 0.3], scale: [1, 1.09, 1] }}
transition={{ duration: 0.35, repeat: Infinity }} />
</>
)}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src="https://play.pokemonshowdown.com/sprites/ani/pikachu.gif" alt="Pikachu" width={100} height={100}
style={{ imageRendering: "pixelated", display: "block", filter: charged ? "drop-shadow(0 0 22px rgba(255,220,0,1)) drop-shadow(0 0 44px rgba(255,160,0,0.8))" : "drop-shadow(0 0 6px rgba(255,220,0,0.4))", transition: "filter 0.4s ease" }} />
</div>
</div>

{/* Surrounding Pokémon */}
{POKEMONS.map((p, i) => (
<div key={i} style={{ position: "absolute", left: "50%", top: "50%", transform: `translate(calc(-50% + ${p.dx}px), calc(-50% + ${p.dy}px))` }}>
<div className={`pokemon-${i}`} style={{ opacity: 0, position: "relative" }}>
{hitSet.has(i) && (
<motion.div style={{ position: "absolute", inset: -4, background: "white", borderRadius: 8, zIndex: 5, pointerEvents: "none" }}
initial={{ opacity: 0.95 }} animate={{ opacity: 0 }} transition={{ duration: 0.35 }} />
)}
{/* eslint-disable-next-line @next/next/no-img-element */}
<img src={p.src} alt={p.name} width={p.size} height={p.size}
style={{ imageRendering: "pixelated", display: "block", filter: `drop-shadow(0 0 14px ${p.glow})` }} />
</div>
</div>
))}
</div>

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “click gate” start screen is rendered on top, but the fighters/lightning/logo DOM is still mounted underneath, so the remote Pokémon GIFs will start downloading immediately on page load (before the user clicks). If the intent is to defer network + CPU until interaction, conditionally render the animation content only when started is true (or at least lazy-load the sprite s / gate them behind started).

Copilot uses AI. Check for mistakes.
Comment on lines +468 to +472
<motion.a
href="/"
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The CTA uses <motion.a href="/">, which will trigger a full document navigation in the Next.js app router. Use next/link (e.g., wrap with Link and render motion as a child) or useRouter().push("/") to keep client-side navigation and preserve app state.

Copilot uses AI. Check for mistakes.
Comment on lines +357 to +366
{/* Global flash overlay */}
<motion.div
className="absolute inset-0 pointer-events-none"
style={{
zIndex: 50,
background: "radial-gradient(ellipse at center, rgba(255,255,160,0.95) 0%, rgba(255,220,0,0.6) 35%, transparent 65%)",
}}
animate={{ opacity: globalFlash ? 0.75 : 0 }}
transition={{ duration: globalFlash ? 0.05 : 0.25 }}
/>

Copilot AI Apr 16, 2026

Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This component includes rapid flashing (globalFlash) and shaking, plus multiple looping animations, but doesn’t respect prefers-reduced-motion. For accessibility, consider using Framer Motion’s useReducedMotion() (or a CSS media query) to disable/soften shake + flash + infinite loops and/or provide a visible “Skip animation” option when reduced motion is requested.

Copilot uses AI. Check for mistakes.
@Akshat-Raj
Akshat-Raj merged commit f8d5334 into Genesis-ISE-DSCE:main Apr 17, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants