From 5167fbdea724f2ed8ca07478b1ac140237dd48e9 Mon Sep 17 00:00:00 2001 From: Aryan Verma Date: Tue, 16 Sep 2025 11:30:06 +0530 Subject: [PATCH] feat(ui): add interactive elements (parallax hero, FAQ, scroll-top) and entrance/hover animations; sticky header polish --- components/landingpage/FAQ.tsx | 26 ++++++ components/landingpage/Header.tsx | 4 +- components/landingpage/LandingPage.tsx | 104 +++++++---------------- components/landingpage/ParallaxHover.tsx | 42 +++++++++ components/landingpage/ScrollTop.tsx | 28 ++++++ 5 files changed, 130 insertions(+), 74 deletions(-) create mode 100644 components/landingpage/FAQ.tsx create mode 100644 components/landingpage/ParallaxHover.tsx create mode 100644 components/landingpage/ScrollTop.tsx diff --git a/components/landingpage/FAQ.tsx b/components/landingpage/FAQ.tsx new file mode 100644 index 0000000..970c24d --- /dev/null +++ b/components/landingpage/FAQ.tsx @@ -0,0 +1,26 @@ +export default function FAQ() { + const faqs = [ + { q: 'Is Void free and open source?', a: 'Yes. Void is an open source project with a permissive license.' }, + { q: 'Can I use any LLM provider?', a: 'Yes. You can connect to private and frontier providers, or any OpenAI-compatible endpoint.' }, + { q: 'Does Void collect my code or prompts?', a: 'No. You can self-host or connect directly. We don\'t proxy by default.' }, + ] + + return ( +
+

+ FAQ +

+
+ {faqs.map((f, i) => ( +
+ + {f.q} + + +
{f.a}
+
+ ))} +
+
+ ) +} diff --git a/components/landingpage/Header.tsx b/components/landingpage/Header.tsx index 3682bf3..a46a167 100644 --- a/components/landingpage/Header.tsx +++ b/components/landingpage/Header.tsx @@ -9,11 +9,11 @@ import { baseUrl } from "@/app/sitemap" export const Header = () => { - return
+ return
{/* header */} -
+

diff --git a/components/landingpage/LandingPage.tsx b/components/landingpage/LandingPage.tsx index a7e2dcf..365420e 100644 --- a/components/landingpage/LandingPage.tsx +++ b/components/landingpage/LandingPage.tsx @@ -13,10 +13,13 @@ import { StarOnGithubButton, DownloadButton } from '@/app/Buttons'; import Image from 'next/image'; import { discordLink, emailLink } from '../links'; import { Hammer, Apple } from 'lucide-react' +import ParallaxHover from './ParallaxHover' +import FAQ from './FAQ' +import ScrollTop from './ScrollTop' const BigContent = ({ title, desc, src, children, imgClassName = '' }: { title: string, desc: React.ReactNode, src: string, children?: React.ReactNode, imgClassName?: string }) => { - return
+ return

{title}

@@ -27,7 +30,7 @@ const BigContent = ({ title, desc, src, children, imgClassName = '' }: { title: src={src} alt={title} // shadow-[0px_0px_0px_4px_rgba(0,0,0)] and ring can overlap - className={`bg-[#1e1e1e] aspect-[16_10] max-w-[300px] lg:max-w-[400px] w-full h-full rounded-xl object-contain ${imgClassName}`} + className={`bg-[#1e1e1e] aspect-[16_10] max-w-[300px] lg:max-w-[400px] w-full h-full rounded-xl object-contain transition-transform duration-300 ease-out hover:scale-[1.02] ${imgClassName}`} /> {children}
@@ -61,7 +64,7 @@ const ProviderLogo = ({ bg-white flex justify-center items-center shadow-xl rounded-lg overflow-hidden border border-gray-300/40 relative transition-transform - duration-200 hover:-translate-y-1`} + duration-200 hover:-translate-y-1 hover:shadow-2xl`} > {alt}
@@ -94,11 +97,11 @@ const GridElement = ({ name, src = undefined, alt = undefined, children, imageCl } return <> -
+
{name ?
{name}
: null} -
+
{childContents}
@@ -118,9 +121,11 @@ const Fold = () => {
-

+

- {`A + + {`A + {`The open source`} @@ -134,7 +139,7 @@ const Fold = () => {

{/* Description */} -
+
{/*
{`Use AI autocomplete, inline edits, codebase chat, agentic features, and more, in a privacy-first AI IDE.`} @@ -153,14 +158,14 @@ const Fold = () => { {/* {`Void is an open source Cursor alternative. We offer autocomplete, inline edits, codebase chat, AI agents, and integrations with tools like Greptile and Ollama, and options for keeping your data private.`} */}
-
+
{/* Backed by YC */} -
+
Backed by @@ -209,7 +214,7 @@ const CoreFeatures = () => { return
-

+

{/* {`The AI Feature Classics.`} */} {/* {`Native AI Integrations.`} */} {`The AI Features You Love.`} @@ -231,7 +236,7 @@ const CoreFeatures = () => { const ALotMoreFeatures = () => { return
-
+

{/* {`All you could ask for.`} */} {/* {`And Much More.`} */} @@ -243,7 +248,7 @@ const ALotMoreFeatures = () => {

{/* Box 1 */} -
+
Private LLMs
@@ -268,7 +273,7 @@ const ALotMoreFeatures = () => {
{/* Box 2 */} -
+
Frontier LLMs
@@ -289,7 +294,7 @@ const ALotMoreFeatures = () => {
-
+

{/* {`All you could ask for.`} */} {/* {`And Much More.`} */} @@ -310,60 +315,11 @@ const ALotMoreFeatures = () => { {/* */} {/* */} {/* */} - {/* - -
-
- {`And More...`} -
-
-
-
- {`If you're building in AI, `} - - get in touch - - {` to discuss integrating with Void.`} -
-
-
-
*/} -

-
- -
-

- {"Agent Mode and MCP."} -

-
- {`Use any model in Agent mode - even open source models that don't natively support tool calling.`} -
+ {/* */} - -
- {/* Agent Mode */} -
-
- Agent Mode -
- {/* */} -
- {`Agent mode can search, create, edit, and delete files & folders. It also has terminal access and MCP tool access.`} -
-
- - {/* Gather Mode */} -
-
- Gather Mode -
- {/* */} - -
- {`Gather mode is a restricted version of Agent mode that can only read and search, but not modify or edit.`} -
-
+ {/*
*/} + {/* ... */} + {/*
*/}
@@ -372,7 +328,7 @@ const ALotMoreFeatures = () => { const PoweredByVscode = () => { return

@@ -395,7 +351,7 @@ const PoweredByVscode = () => {

} const InterestedInContributing = () => { - return
+ return

@@ -410,7 +366,7 @@ const InterestedInContributing = () => {
@@ -419,8 +375,9 @@ const InterestedInContributing = () => { + const GetStartedWithVoid = () => { - return
+ return
@@ -460,6 +417,7 @@ export default function LandingPage() { +
@@ -476,5 +434,7 @@ export default function LandingPage() {
+ + ) } diff --git a/components/landingpage/ParallaxHover.tsx b/components/landingpage/ParallaxHover.tsx new file mode 100644 index 0000000..ab7f338 --- /dev/null +++ b/components/landingpage/ParallaxHover.tsx @@ -0,0 +1,42 @@ +import React, { useRef } from "react"; + +type ParallaxHoverProps = { + children: React.ReactNode; + maxRotate?: number; + translate?: number; + className?: string; +}; + +export default function ParallaxHover({ + children, + maxRotate = 6, + translate = 8, + className = "", +}: ParallaxHoverProps) { + const ref = useRef(null); + + const onMove: React.MouseEventHandler = (e) => { + const el = ref.current; + if (!el) return; + const rect = el.getBoundingClientRect(); + const px = (e.clientX - rect.left) / rect.width - 0.5; + const py = (e.clientY - rect.top) / rect.height - 0.5; + const rx = -py * maxRotate; + const ry = px * maxRotate; + const tx = px * translate; + const ty = py * translate; + el.style.transform = `perspective(800px) rotateX(${rx}deg) rotateY(${ry}deg) translate3d(${tx}px, ${ty}px, 0)`; + }; + + const onLeave: React.MouseEventHandler = () => { + const el = ref.current; + if (!el) return; + el.style.transform = ""; + }; + + return ( +
+ {children} +
+ ); +} diff --git a/components/landingpage/ScrollTop.tsx b/components/landingpage/ScrollTop.tsx new file mode 100644 index 0000000..4683d2c --- /dev/null +++ b/components/landingpage/ScrollTop.tsx @@ -0,0 +1,28 @@ +'use client' + +import { useEffect, useState } from 'react' + +export default function ScrollTop() { + const [show, setShow] = useState(false) + + useEffect(() => { + const onScroll = () => { + setShow(window.scrollY > 300) + } + onScroll() + window.addEventListener('scroll', onScroll) + return () => window.removeEventListener('scroll', onScroll) + }, []) + + if (!show) return null + + return ( + + ) +}