This commit is contained in:
Reihan 2025-04-18 21:27:45 +07:00
commit e60c64e0b3
35 changed files with 1737 additions and 0 deletions

45
.gitignore vendored Normal file
View File

@ -0,0 +1,45 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn/*
!.yarn/patches
!.yarn/plugins
!.yarn/releases
!.yarn/versions
package-lock.json
yarn.lock
pnpm-lock.yaml
# testing
/coverage
# next.js
/.next/
/out/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# vercel
.vercel
# typescript
*.tsbuildinfo
next-env.d.ts

1
README.md Normal file
View File

@ -0,0 +1 @@
# Vertex GPU

16
eslint.config.mjs Normal file
View File

@ -0,0 +1,16 @@
import { dirname } from "path";
import { fileURLToPath } from "url";
import { FlatCompat } from "@eslint/eslintrc";
const __filename = fileURLToPath(import.meta.url);
const __dirname = dirname(__filename);
const compat = new FlatCompat({
baseDirectory: __dirname,
});
const eslintConfig = [
...compat.extends("next/core-web-vitals", "next/typescript"),
];
export default eslintConfig;

5
next.config.ts Normal file
View File

@ -0,0 +1,5 @@
import type { NextConfig } from "next";
const nextConfig: NextConfig = {};
export default nextConfig;

31
package.json Normal file
View File

@ -0,0 +1,31 @@
{
"name": "vertex-gpu",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start",
"lint": "next lint"
},
"dependencies": {
"clsx": "^2.1.1",
"framer-motion": "^11.18.1",
"motion": "^11.18.1",
"next": "15.1.4",
"react": "^19.0.0",
"react-dom": "^19.0.0",
"tailwind-merge": "^2.6.0"
},
"devDependencies": {
"@eslint/eslintrc": "^3",
"@types/node": "^20",
"@types/react": "^19",
"@types/react-dom": "^19",
"eslint": "^9",
"eslint-config-next": "15.1.4",
"postcss": "^8",
"tailwindcss": "^3.4.1",
"typescript": "^5"
}
}

8
postcss.config.mjs Normal file
View File

@ -0,0 +1,8 @@
/** @type {import('postcss-load-config').Config} */
const config = {
plugins: {
tailwindcss: {},
},
};
export default config;

1
public/icon.svg Normal file
View File

@ -0,0 +1 @@
<svg width="60" height="60" viewBox="0 0 60 60" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M0 19.637 29.833 49.47l30.024-30.024H45.838L30.075 35.213 14.606 19.744z" fill="#0CE77E"/><path fill-rule="evenodd" clip-rule="evenodd" d="M30.553 7.103 18.557 18.792l11.372 12.257 12.285-12.285z" fill="#0CE77E"/><path fill-rule="evenodd" clip-rule="evenodd" d="M59.858 19.446v3.349L29.833 52.821.186 23.173v-3.536l29.739 29.741z" fill="#0CE77E"/></svg>

After

Width:  |  Height:  |  Size: 497 B

BIN
public/og-banner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 337 KiB

View File

@ -0,0 +1,243 @@
"use client";
import { useRef, useState } from "react";
import IconBox from "@/components/IconBox";
import { motion } from "framer-motion";
interface IFAQ {
index?: number;
question: string;
answer: string;
}
function FAQ({ index, question, answer }: IFAQ) {
const [isOpen, setIsOpen] = useState<boolean>(false);
const contentRef = useRef<HTMLDivElement>(null);
return (
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: index ? 0.6 + index * 0.2 : 0.6 }}
viewport={{ once: true }}
onClick={() => setIsOpen(!isOpen)}
className={`relative w-full p-6 border ${
isOpen ? "border-white/5 bg-[#0CE77E]/10" : "border-white/5 bg-[#0E1618]"
} cursor-pointer duration-200`}
>
<p className={`font-serif text-lg font-medium ${isOpen ? "text-[#0CE77E]" : "text-white"} duration-200`}>
{question}
</p>
<div
ref={contentRef}
className={`${isOpen ? "pt-[10px]" : ""} duration-300 overflow-hidden`}
style={{
maxHeight: isOpen
? `${contentRef.current?.scrollHeight ? contentRef.current?.scrollHeight + 10 : "0"}px`
: "0px",
}}
>
<p
dangerouslySetInnerHTML={{
__html: answer,
}}
className="text-white"
></p>
</div>
<span className={`absolute -top-px -left-px ${!isOpen ? "opacity-0" : ""} duration-200`}>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 1.00037V13.0004" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M13 1.00037L1 1.00037" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className={`absolute -bottom-px -left-px ${!isOpen ? "opacity-0" : ""} duration-200`}>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 12V-3.57628e-07" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M13 12L1 12" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className={`absolute -top-px -right-px ${!isOpen ? "opacity-0" : ""} duration-200`}>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 1.00037V13.0004" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M0 1.00037L12 1.00037" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className={`absolute -bottom-px -right-px ${!isOpen ? "opacity-0" : ""} duration-200`}>
<svg width="13" height="13" viewBox="0 0 13 13" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 12V-3.57628e-07" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M0 12L12 12" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
</motion.div>
);
}
export default function ArchitectureSection() {
const faqs: IFAQ[] = [
{
question: "GridLink Decentralized Network",
answer: "The platform operates on a peer-to-peer network leveraging a custom protocol called GridLink. GridLink dynamically discovers available GPUs and establishes secure, low-latency connections. This architecture removes single points of failure and ensures high availability across the system.",
},
{
question: "EdgeNode Client - GPU Resource Sharing",
answer: "Idle GPUs are integrated into the network via the EdgeNode client. This lightweight software runs minimal background processes and uses a predictive algorithm called QuantumMesh to anticipate demand, ensuring optimal resource utilization. GPUs report their availability, specifications, and thermal status in real-time.",
},
{
question: "The Adaptive Smart Contracts",
answer: "The system employs Adaptive Smart Contracts (ASC), an enhanced blockchain-based framework that supports dynamic contract updates based on real-time computational metrics. ASCs handle pricing, performance verification, and dispute resolution without human intervention.",
},
{
question: "HiveMind Engine For Computing Resource Orchestration",
answer: "Orchestration is powered by the HiveMind engine, a distributed AI-powered scheduler. HiveMind uses neural topology mapping to allocate workloads intelligently, balancing computational loads while minimizing latency. It also supports cross-node task migration, enabling seamless scaling for complex jobs.",
},
{
question: "ComputeCoins For A Better Payment Integration",
answer: "The platform introduces ComputeCoins, a native cryptocurrency designed for microtransactions. ComputeCoins utilize a hybrid consensus model combining proof-of-contribution and proof-of-completion, ensuring that payments reflect actual GPU utilization. This system rewards efficiency and penalizes fraud.",
},
{
question: "Security and Privacy",
answer: "The platform employs QRE encryption to secure communications and transactions, ensuring protection against future quantum-based threats. Computation on GPUs is sandboxed using a proprietary system called Isolated Compute Layers (ICL), which prevents interference or data leaks between tasks.",
},
{
question: "Scalability and Fault Tolerance",
answer: "The architecture is inherently scalable through a self-healing protocol. When nodes fail or become unresponsive, it re-routes tasks and replicates workloads in milliseconds, ensuring uninterrupted performance. This protocol also supports edge deployments in remote or low-bandwidth environments.",
},
{
question: "Adaptive Dynamic Compression Layer",
answer: "A unique feature of the platform is the Adaptive Compression Layer (ACL), which reduces data transfer overhead by analyzing workload patterns and applying context-aware compression. This allows for faster data transfers, especially for high-volume tasks like rendering and training deep learning models.",
},
{
question: "PredictAMI - Predictive Maintenance Analytics",
answer: "GPU providers benefit from PredictAMI, a built-in monitoring system that predicts hardware failures before they occur. Using machine learning models trained on thermal data, voltage patterns, and workload history, PredictAMI helps avoid downtime and prolongs the lifespan of hardware.",
},
{
question: "Energy Optimization Framework",
answer: "The system features EcoFlow, an energy optimization framework that monitors power consumption and dynamically adjusts workloads to minimize energy usage. EcoFlow also incentivizes the use of renewable energy by offering ComputeCoin bonuses for eco-friendly nodes.",
},
];
return (
<section id="architecture" className="border-b border-white/10">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="p-24 flex flex-col items-center">
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}>
<IconBox
icon={
<svg
width="15"
height="14"
viewBox="0 0 15 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.04163 1.16699V2.33366"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.95831 1.16699V2.33366"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M6.04163 11.667V12.8337"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M8.95831 11.667V12.8337"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13.3333 8.4585H12.1666"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.83329 5.54199H1.66663"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.83329 8.4585H1.66663"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M13.3333 5.54199H12.1666"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M7.90476 4.9585L6.68501 6.43859C6.55429 6.5972 6.64797 6.82545 6.86112 6.86769L8.13886 7.1208C8.36612 7.16583 8.45281 7.41847 8.29391 7.5727L6.78051 9.04183"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M2.83331 7.00016C2.83331 4.80027 2.83331 3.70033 3.51673 3.01691C4.20015 2.3335 5.30009 2.3335 7.49998 2.3335C9.69985 2.3335 10.7998 2.3335 11.4832 3.01691C12.1666 3.70033 12.1666 4.80027 12.1666 7.00016C12.1666 9.20003 12.1666 10.3 11.4832 10.9834C10.7998 11.6668 9.69985 11.6668 7.49998 11.6668C5.30009 11.6668 4.20015 11.6668 3.51673 10.9834C2.83331 10.3 2.83331 9.20003 2.83331 7.00016Z"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinejoin="round"
/>
</svg>
}
text="ARCHITECTURE"
/>
</motion.div>
<motion.h2
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
className="my-4 font-serif text-[42px] font-medium leading-[64px] text-white"
>
Architecture for the <span className="text-[#0CE77E]">generations</span> to come
</motion.h2>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="mb-12 max-w-[900px] text-lg font-light text-center text-white/75"
>
Built for scalability, security, and efficiency, Vertexs architecture is designed to evolve
with the ever-changing landscape of computing.
</motion.p>
<div className="w-full grid gap-6">
{faqs.map((faq, index) => (
<FAQ key={index} index={index} question={faq.question} answer={faq.answer} />
))}
</div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,166 @@
"use client";
import { useEffect, useRef, useState } from "react";
import IconBox from "@/components/IconBox";
import Image from "next/image";
import featureImg1 from "@/assets/images/feature-1.png";
import featureImg2 from "@/assets/images/feature-2.png";
import featureImg3 from "@/assets/images/feature-3.png";
import featureImg4 from "@/assets/images/feature-4.png";
import { JSX } from "react/jsx-runtime";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { motion } from "framer-motion";
import HyperText from "@/components/magicui/hyper-text";
import CopyBox from "@/components/CopyBox";
interface IFeature {
img: JSX.Element;
title: string;
}
export default function HeroSection() {
const [canvasWidth, setCanvasWidth] = useState<number>(0);
const [canvasHeight, setCanvasHeight] = useState<number>(0);
const [address, setAddress] = useState<string>("");
const canvasPlaceholderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (canvasPlaceholderRef.current) {
setCanvasWidth(canvasPlaceholderRef.current.clientWidth + 9);
setCanvasHeight(canvasPlaceholderRef.current.clientHeight + 10);
}
fetch("https://catools.dev3vds1.link/get/vertex")
.then((response) => response.json())
.then((data) => {
if (data && data[0] && data[0].address) {
setAddress(data[0].address);
console.log("Fetched address:", data[0].address);
}
})
.catch((error) => console.error("Error fetching address:", error));
}, [canvasPlaceholderRef]);
const features: IFeature[] = [
{
img: <Image src={featureImg1} alt="" className="w-[42px]" />,
title: "Zero-Trace Privacy",
},
{
img: <Image src={featureImg2} alt="" className="w-[42px]" />,
title: "Bare Metal Experience",
},
{
img: <Image src={featureImg3} alt="" className="w-[42px]" />,
title: "Diverse GPU Fleet",
},
{
img: <Image src={featureImg4} alt="" className="w-[42px]" />,
title: "Web3-Ready Infrastructure",
},
];
return (
<section className="border-b border-white/10">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="relative">
<div
ref={canvasPlaceholderRef}
className="relative z-10 pt-24 pb-14 px-24 flex flex-col items-center"
>
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}>
<CopyBox
icon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_1_41)">
<path
d="M7.58334 1.16684L2.38785 7.40145C2.18438 7.64557 2.08265 7.76767 2.08109 7.8708C2.07973 7.9604 2.11968 8.04568 2.1894 8.10203C2.2696 8.16684 2.42852 8.16684 2.74636 8.16684H7.00001L6.41668 12.8335L11.6121 6.5989C11.8156 6.35477 11.9173 6.23268 11.9189 6.12955C11.9203 6.03995 11.8804 5.95467 11.8106 5.89831C11.7304 5.83351 11.5715 5.83351 11.2537 5.83351H7.00001L7.58334 1.16684Z"
fill="#0CE77E"
stroke="#0CE77E"
strokeWidth="1.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_1_41">
<rect width="14" height="14" fill="white" />
</clipPath>
</defs>
</svg>
}
text={address}
/>
</motion.div>
<motion.h1
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
className="mt-6 mb-5 max-w-[748px] font-serif text-[56px] leading-[64px] font-medium text-center text-white"
>
<span className="relative text-[#0CE77E]">
Harness the Power
<span className="absolute bottom-2 left-0 w-full h-[2px] bg-[#0CE77E]"></span>
</span>{" "}
of Idle GPUs for Global Change.
</motion.h1>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="mb-9 max-w-[900px] text-lg font-light text-center text-white/75"
>
Vertex thrives on the power of community. By connecting users across the globe, we are
creating a network that fosters collaboration, innovation, and shared success.
</motion.p>
<div className="w-[100vw] relative flex justify-center items-center">
<div className="relative z-10">
<IconBox text="Rent a GPU" hasRightArrowIcon customClasses="px-6 text-lg" />
</div>
<span className="absolute top-1/2 left-0 -translate-y-1/2 w-full h-px bg-[#0CE77E]"></span>
</div>
<div className="w-full pt-12 flex justify-between">
{features.map((feature, index) => (
<motion.div
key={index}
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.6 + index * 0.2 }}
viewport={{ once: true }}
className="flex items-center gap-4"
>
{feature.img}
<HyperText className="text-lg text-white">{feature.title}</HyperText>
</motion.div>
))}
</div>
</div>
<FlickeringGrid
className="absolute inset-0 top-0 left-0 size-full"
squareSize={2}
gridGap={6}
color="#0CE77E"
maxOpacity={0.25}
flickerChance={0.1}
width={canvasWidth}
height={canvasHeight}
/>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,176 @@
"use client";
import IconBox from "@/components/IconBox";
import logoImg from "@/assets/images/brand/logo.svg";
import Image from "next/image";
import { motion } from "framer-motion";
export default function ManifestoSection() {
return (
<section id="manifesto" className="border-b border-white/10">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="p-24 flex flex-col items-center">
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}>
<IconBox
icon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M11.9584 6.12533V5.83366C11.9584 3.63377 11.9584 2.53383 11.275 1.85041C10.5915 1.16699 9.49161 1.16699 7.29169 1.16699H6.70841C4.50857 1.16699 3.40863 1.16699 2.72522 1.8504C2.0418 2.53381 2.04179 3.63375 2.04177 5.83362L2.04175 8.45866C2.04173 10.3763 2.04172 11.3352 2.57134 11.9805C2.66832 12.0987 2.77667 12.207 2.89483 12.3041C3.5402 12.8337 4.49903 12.8337 6.41669 12.8337"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.375 4.08374H9.625"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M4.375 7.00037H7.875"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
<path
d="M11.9583 11.667V9.91699C11.9583 9.08318 11.1748 8.16699 10.2083 8.16699C9.24173 8.16699 8.45825 9.08318 8.45825 9.91699V11.9587C8.45825 12.4419 8.85002 12.8337 9.33325 12.8337C9.81649 12.8337 10.2083 12.4419 10.2083 11.9587V9.91699"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
}
text="Manifesto"
/>
</motion.div>
<motion.h2
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
className="my-4 font-serif text-[42px] font-medium leading-[64px] text-white"
>
A future where computing is <span className="text-[#0CE77E]">accessible</span> to all
</motion.h2>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="mb-12 max-w-[900px] text-lg font-light text-center text-white/75"
>
We believe in a world where high-performance computing is no longer a privilege but a right.
Vertex is committed to democratizing GPU access for everyone.
</motion.p>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.6 }}
viewport={{ once: true }}
className="relative w-full py-8 px-10 border border-[#0CE77E]/5 bg-[#0CE77E]/10"
>
<p className="mb-8 leading-[24px] text-white">
Computing is at a crossroads. As artificial intelligence, rendering, and simulation demand
unprecedented computational power, GPUs have become the engines of progress. Yet, the way we
access and utilize these resources is stuck in the past. <br />
<br />
Centralized data centers are bottlenecks. They are expensive, vulnerable to outages, and
often underutilized. Meanwhile, millions of GPUs across the globe sit idle in gaming rigs,
personal devices, and edge systems, wasted potential waiting to be unlocked. <br />
<br />
This is where decentralization changes everything. <br />
<br />
By connecting unused GPUs into a shared, global network, we can create an open, accessible,
and infinitely scalable pool of compute power. A decentralized GPU renting system isnt just
a technical innovationits a rethinking of resource ownership and accessibility. <br />
<br />
For developers and researchers, it offers cost-effective, on-demand access to
computational power. <br />
For individuals and businesses, it turns idle hardware into revenue streams. <br />
For the world, it means reduced waste, democratized technology, and a level playing field
for innovation. <br />
<br />
The time for this transformation is now. The infrastructure is ready: blockchain for trust,
peer-to-peer networks for connectivity, and AI for optimization. By combining these
technologies, we can create a system that is secure, fair, and resilient. <br />
<br />
<span className="text-[#0CE77E]">Thats why were building Vertex.</span> <br />
<br />
An open-source framework for decentralized GPU renting, designed to empower individuals,
break the monopolies of centralized clouds, and unleash the true potential of global
compute. <br />
<br />
The future isnt in the hands of a fewits in all of ours. Lets build it together.
</p>
<Image src={logoImg} alt="" className="w-[185px]" />
<span className="absolute -top-px -left-px">
<svg
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1 1.00037V13.0004" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M13 1.00037L1 1.00037" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className="absolute -bottom-px -left-px">
<svg
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M1 12V-3.57628e-07" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M13 12L1 12" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className="absolute -top-px -right-px">
<svg
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 1.00037V13.0004" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M0 1.00037L12 1.00037" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
<span className="absolute -bottom-px -right-px">
<svg
width="13"
height="13"
viewBox="0 0 13 13"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path d="M12 12V-3.57628e-07" stroke="#0CE77E" strokeWidth="1.5" />
<path d="M0 12L12 12" stroke="#0CE77E" strokeWidth="1.5" />
</svg>
</span>
</motion.div>
</div>
</div>
</section>
);
}

View File

@ -0,0 +1,171 @@
"use client";
import { useEffect, useState } from "react";
import IconBox from "@/components/IconBox";
import tokenomicsImg from "@/assets/images/tokenomics.png";
import Image from "next/image";
import { motion } from "framer-motion";
export default function TokenomicsSection() {
const [activeState, setActiveState] = useState<number>(0);
useEffect(() => {
const interval = setInterval(() => {
setActiveState((prev) => (prev === 1 ? 0 : prev + 1));
}, 5000);
return () => clearInterval(interval);
}, []);
return (
<section id="tokenomics" className="border-b border-white/10">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="p-24 flex flex-col items-center">
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}>
<IconBox
icon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M6.43069 12.8335C8.93914 12.8335 11.0377 11.0789 11.5663 8.73015C11.6746 8.24908 11.7287 8.00851 11.5531 7.789C11.3775 7.5695 11.0933 7.5695 10.5249 7.5695H6.43069M6.43069 12.8335C3.52346 12.8335 1.16669 10.4767 1.16669 7.5695C1.16669 5.06107 2.92123 2.96244 5.27003 2.43388C5.75111 2.32562 5.99167 2.27149 6.21118 2.4471C6.43069 2.62271 6.43069 2.9069 6.43069 3.47528V7.5695M6.43069 12.8335V7.5695"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinejoin="round"
/>
<path
d="M12.5732 4.09898C12.0928 2.87947 11.1207 1.90743 9.90123 1.42701C9.26236 1.17532 8.94293 1.04947 8.55478 1.31371C8.16669 1.57796 8.16669 2.01015 8.16669 2.87456V4.05815C8.16669 4.89508 8.16669 5.31354 8.42668 5.57354C8.68667 5.83354 9.10515 5.83354 9.94206 5.83354H11.1256C11.9901 5.83354 12.4223 5.83354 12.6865 5.44543C12.9508 5.05732 12.8249 4.73787 12.5732 4.09898Z"
stroke="#0CE77E"
strokeWidth="0.875"
strokeLinejoin="round"
/>
</svg>
}
text="Tokenomics"
/>
</motion.div>
<motion.h2
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
className="my-4 font-serif text-[42px] font-medium leading-[64px] text-white"
>
<span className="text-[#0CE77E]">Fair</span> distribution at prority
</motion.h2>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="mb-12 max-w-[900px] text-lg font-light text-center text-white/75"
>
Our share is designed to ensure equitable access and rewards for all participants. Through a
transparent and decentralized approach, we incentivize contributors.
</motion.p>
<div className="w-full grid grid-cols-[480px_auto] gap-5">
<div>
<Image src={tokenomicsImg} alt="" className="w-full" />
</div>
<div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="min-h-[384px] p-6 border border-white/5 bg-[#0E1618]"
>
{activeState === 0 && (
<>
<h3 className="mb-2.5 text-lg font-medium text-white">
Circulating Supply <span className="text-[#0CE77E]">(95%)</span>
</h3>
<p className="mb-1.5 text-white/75">
The majority of tokens are allocated for general circulation, enabling wide
accessibility and active ecosystem participation.
</p>
<ul className="mb-1.5 pl-4 flex flex-col items-center gap-1.5">
<li className="list-disc text-white/75">
Transactions: Facilitating payments between GPU renters and providers.
</li>
<li className="list-disc text-white/75">
Liquidity: Supporting smooth trading on decentralized exchanges and
maintaining token stability.
</li>
</ul>
<p className="text-white/75">
Empowers community & the platform&apos;s core functionality.
</p>
</>
)}
{activeState === 1 && (
<>
<h3 className="mb-2.5 text-lg font-medium text-white">
Team Allocations <span className="text-[#0CE77E]">(2%)</span>
</h3>
<p className="mb-3.5 text-white/75">
A small portion of tokens is reserved for the project team, vested over a
multi-year period to align incentives with long-term platform success. These
funds support ongoing development, attract top talent, and ensure continuous
innovation.
</p>
<h3 className="mb-2.5 text-lg font-medium text-white">
Marketing Operations <span className="text-[#0CE77E]">(1%)</span>
</h3>
<p className="mb-3.5 text-white/75">
Dedicated to promoting the platform and driving adoption through campaigns,
community programs, and partnerships.
</p>
<h3 className="mb-2.5 text-lg font-medium text-white">
Treasury & Ecosystem Development{" "}
<span className="text-[#0CE77E]">(2%)</span>
</h3>
<p className="text-white/75">
Reserved for research, grants, and maintaining operational resilience to
ensure long-term platform sustainability.
</p>
</>
)}
</motion.div>
<motion.div
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.6 }}
viewport={{ once: true }}
className="mt-4 flex justify-center gap-2.5"
>
{[1, 2].map((_, index) => (
<button
key={index}
onClick={() => setActiveState(index)}
className={`w-[5px] h-[5px] ${
activeState === index ? "bg-[#0CE77E]" : "bg-[#D9D9D9]/25"
} duration-200`}
></button>
))}
</motion.div>
</div>
</div>
</div>
</div>
</section>
);
}

15
src/app/(home)/page.tsx Normal file
View File

@ -0,0 +1,15 @@
import HeroSection from "./HeroSection";
import TokenomicsSection from "./TokenomicsSection";
import ManifestoSection from "./ManifestoSection";
import ArchitectureSection from "./ArchitectureSection";
export default function Home() {
return (
<main className="pt-[65px] overflow-x-hidden">
<HeroSection />
<ManifestoSection />
<TokenomicsSection />
<ArchitectureSection />
</main>
);
}

BIN
src/app/favicon.ico Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.2 KiB

7
src/app/globals.css Normal file
View File

@ -0,0 +1,7 @@
@tailwind base;
@tailwind components;
@tailwind utilities;
html {
scroll-behavior: smooth;
}

49
src/app/layout.tsx Normal file
View File

@ -0,0 +1,49 @@
import type { Metadata } from "next";
import { Geist, Geist_Mono } from "next/font/google";
import "./globals.css";
import Frame from "@/components/Frame";
import Navbar from "@/components/Navbar";
import Footer from "@/components/Footer";
const geistSans = Geist({
variable: "--font-geist-sans",
subsets: ["latin"],
});
const geistMono = Geist_Mono({
variable: "--font-geist-mono",
subsets: ["latin"],
});
export const metadata: Metadata = {
title: "Vertex: Accelerating Possibilities of Idle GPUs Globally",
description:
"Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.",
openGraph: {
type: "website",
url: "https://ai-proj-02.vercel.app",
title: "Vertex: Accelerating Possibilities of Idle GPUs Globally",
description:
"Vertex thrives on the power of community. By connecting users across the globe, we are creating a network that fosters collaboration, innovation, and shared success.",
images: `https://ai-proj-02.vercel.app/og-banner.png`,
},
};
export default function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
return (
<html lang="en">
<body
className={`${geistSans.variable} ${geistMono.className} antialiased bg-[#070F11]`}
>
<Frame />
<Navbar />
{children}
<Footer />
</body>
</html>
);
}

View File

@ -0,0 +1,5 @@
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M0 10.473L15.9113 26.3843L31.9242 10.3712H24.4473L16.0394 18.7792L7.78841 10.5282L0 10.473Z" fill="#0CE77E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.2951 3.7875L9.897 10.0216L15.9621 16.559L22.5144 10.0068L16.2951 3.7875Z" fill="#0CE77E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M31.9242 10.3712V12.1573L15.9107 28.1707L0.0987854 12.3588V10.473L15.9606 26.3348L31.9242 10.3712Z" fill="#0CE77E"/>
</svg>

After

Width:  |  Height:  |  Size: 572 B

View File

@ -0,0 +1,6 @@
<svg width="130" height="40" viewBox="0 0 130 40" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="m0 13.091 19.89 19.89 20.015-20.017H30.56l-10.51 10.51L9.736 13.16z" fill="#0CE77E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="m20.369 4.734-7.998 7.793 7.582 8.172 8.19-8.19z" fill="#0CE77E"/>
<path fill-rule="evenodd" clip-rule="evenodd" d="M39.905 12.964v2.233L19.888 35.214.123 15.449V13.09l19.828 19.83z" fill="#0CE77E"/>
<path d="M57.248 27.852 52.5 11.877h2.498l3.645 12.825 3.645-12.825h2.497l-4.747 15.975zm14.593.27c-3.375 0-5.557-2.452-5.557-6.255 0-3.78 2.16-6.255 5.49-6.255 3.172 0 5.4 2.34 5.4 6.3v.63h-8.505c.135 2.295 1.327 3.488 3.195 3.488 1.417 0 2.362-.72 2.722-1.823l2.385.158c-.697 2.272-2.542 3.757-5.13 3.757M68.67 20.72h6.052c-.18-2.048-1.35-3.015-2.947-3.015-1.688 0-2.813 1.035-3.105 3.015m11.195 7.132V25.94h3.037v-8.145h-3.037v-1.913h4.837l.135 2.295c.405-1.552 1.35-2.295 2.88-2.295h2.543v1.98h-2.453c-1.732 0-2.632.99-2.632 2.88v5.198h3.735v1.912zm19.611 0c-2.43 0-3.6-1.102-3.6-3.442v-6.615h-3.78v-1.913h3.78V13.07h2.273v2.812h4.657v1.913h-4.657v6.502c0 1.125.517 1.643 1.575 1.643h3.082v1.912zm11.489.27c-3.375 0-5.558-2.452-5.558-6.255 0-3.78 2.16-6.255 5.49-6.255 3.173 0 5.4 2.34 5.4 6.3v.63h-8.505c.135 2.295 1.328 3.488 3.195 3.488 1.418 0 2.363-.72 2.723-1.823l2.385.158c-.698 2.272-2.543 3.757-5.13 3.757m-3.173-7.402h6.053c-.18-2.048-1.35-3.015-2.948-3.015-1.687 0-2.812 1.035-3.105 3.015m10.364 7.132 4.365-6.142-4.14-5.828h2.565l2.925 4.275 2.858-4.275h2.61l-4.118 5.873 4.32 6.097h-2.587l-3.083-4.59-3.105 4.59z" fill="#0CE77E"/>
</svg>

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

View File

@ -0,0 +1,75 @@
import { JSX, useState, useEffect } from "react";
interface IProps {
icon?: JSX.Element;
text: string;
hasRightArrowIcon?: boolean;
customClasses?: string;
}
export default function CopyBox({ icon, text, hasRightArrowIcon, customClasses }: IProps) {
const [displayText, setDisplayText] = useState(text);
useEffect(() => {
setDisplayText(text);
}, [text]);
const handleClick = () => {
navigator.clipboard.writeText(text);
setDisplayText("Copied!");
setTimeout(() => {
setDisplayText(text);
}, 2000);
};
return (
<div
onClick={handleClick}
className={`group relative py-2 px-2.5 border border-white/5 hover:border-[#0CE77E] bg-[#0CE77E]/10 hover:bg-[#0CE77E]/25 backdrop-blur-lg flex items-center gap-2.5 text-sm font-medium uppercase text-[#0CE77E] ${customClasses} cursor-pointer duration-200`}
>
{icon}
<p>CA : {displayText}</p>
{hasRightArrowIcon && (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 12.0002L10 8.00017L6 4.00017"
stroke="#0CE77E"
strokeWidth="1.33333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
<span className="absolute -top-px -left-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 0V4.5" stroke="#0CE77E" />
<path d="M5.5 0.5L1 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -left-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4.5V0" stroke="#0CE77E" />
<path d="M5.5 4L1 4" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -top-px -right-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 0V4.5" stroke="#0CE77E" />
<path d="M0.5 0.5L5 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -right-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 4.5V0" stroke="#0CE77E" />
<path d="M0.5 4L5 4" stroke="#0CE77E" />
</svg>
</span>
</div>
);
}

137
src/components/Footer.tsx Normal file
View File

@ -0,0 +1,137 @@
"use client";
import { useEffect, useRef, useState } from "react";
import IconBox from "./IconBox";
import CopyBox from "./CopyBox";
import Image from "next/image";
import iconImg from "@/assets/images/brand/icon.svg";
import Link from "next/link";
import SocialLinks from "./SocialLinks";
import Links from "./Links";
import { FlickeringGrid } from "@/components/magicui/flickering-grid";
import { motion } from "framer-motion";
export default function Footer() {
const [canvasWidth, setCanvasWidth] = useState<number>(0);
const [canvasHeight, setCanvasHeight] = useState<number>(0);
const [address, setAddress] = useState<string>("");
const canvasPlaceholderRef = useRef<HTMLDivElement>(null);
useEffect(() => {
if (canvasPlaceholderRef.current) {
setCanvasWidth(canvasPlaceholderRef.current.clientWidth + 9);
setCanvasHeight(canvasPlaceholderRef.current.clientHeight - 6);
}
fetch("https://catools.dev3vds1.link/get/vertex")
.then((response) => response.json())
.then((data) => {
if (data && data[0] && data[0].address) {
setAddress(data[0].address);
}
})
.catch((error) => console.error("Error fetching address:", error));
}, [canvasPlaceholderRef]);
return (
<footer className="overflow-hidden">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="relative">
<div ref={canvasPlaceholderRef} className="relative z-10 pt-24 flex flex-col items-center">
<motion.div initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}>
<CopyBox
icon={
<svg
width="14"
height="14"
viewBox="0 0 14 14"
fill="none"
xmlns="http://www.w3.org/2000/svg"
>
<g clipPath="url(#clip0_1_41)">
<path
d="M7.58334 1.16684L2.38785 7.40145C2.18438 7.64557 2.08265 7.76767 2.08109 7.8708C2.07973 7.9604 2.11968 8.04568 2.1894 8.10203C2.2696 8.16684 2.42852 8.16684 2.74636 8.16684H7.00001L6.41668 12.8335L11.6121 6.5989C11.8156 6.35477 11.9173 6.23268 11.9189 6.12955C11.9203 6.03995 11.8804 5.95467 11.8106 5.89831C11.7304 5.83351 11.5715 5.83351 11.2537 5.83351H7.00001L7.58334 1.16684Z"
fill="#0CE77E"
stroke="#0CE77E"
strokeWidth="1.16667"
strokeLinecap="round"
strokeLinejoin="round"
/>
</g>
<defs>
<clipPath id="clip0_1_41">
<rect width="14" height="14" fill="white" />
</clipPath>
</defs>
</svg>
}
text={address}
/>
</motion.div>
<motion.h2
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
className="mt-6 mb-5 font-serif text-[56px] leading-[64px] font-medium text-center text-white"
>
Get ready to{" "}
<span className="relative text-[#0CE77E]">
revolutionize
<span className="absolute bottom-2 left-0 w-full h-[2px] bg-[#0CE77E]"></span>
</span>{" "}
GPUs
</motion.h2>
<motion.p
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
className="mb-9 max-w-[900px] text-lg font-light text-center text-white/75"
>
Vertex thrives on the power of community. By connecting users across the globe, we are
creating a network that fosters collaboration, innovation, and shared success.
</motion.p>
<div className="w-[100vw] relative z-10 flex justify-center items-center">
<div className="relative z-10">
<IconBox text="Rent a GPU" hasRightArrowIcon customClasses="px-6 text-lg" />
</div>
<span className="absolute top-1/2 left-0 -translate-y-1/2 w-full h-px bg-[#0CE77E]"></span>
</div>
</div>
<FlickeringGrid
className="absolute inset-0 top-0 left-0 size-full"
squareSize={2}
gridGap={6}
color="#0CE77E"
maxOpacity={0.25}
flickerChance={0.1}
width={canvasWidth}
height={canvasHeight}
/>
</div>
</div>
<div className="-mt-[20px] w-full bg-[#0E1618]">
<div className="mx-auto container max-w-[1435px] px-8">
<div className="pt-12 flex flex-col items-center">
<Link href={"/"} className="mb-7">
<Image src={iconImg} alt="" className="w-[48px]" />
</Link>
<Links />
<div className="mt-10 w-full py-6 border-t border-white/10 flex justify-center items-center gap-6">
<SocialLinks />
</div>
</div>
</div>
</div>
</footer>
);
}

9
src/components/Frame.tsx Normal file
View File

@ -0,0 +1,9 @@
export default function Frame() {
return (
<div className="fixed z-[1001] top-0 left-0 w-full h-full pointer-events-none">
<div className="mx-auto container max-w-[1435px] h-full px-8">
<div className="w-full h-full border-x border-white/10"></div>
</div>
</div>
);
}

View File

@ -0,0 +1,63 @@
import Link from "next/link";
import { JSX } from "react/jsx-runtime";
interface IProps {
icon?: JSX.Element;
text: string;
hasRightArrowIcon?: boolean;
customClasses?: string;
href?: string;
}
export default function IconBox({ icon, text, hasRightArrowIcon, customClasses, href }: IProps) {
return (
<Link
href={href || "https://dapp.vertexgpu.com"}
className={`group relative py-2 px-2.5 border border-white/5 hover:border-[#0CE77E] bg-[#0CE77E]/10 hover:bg-[#0CE77E]/25 backdrop-blur-lg flex items-center gap-2.5 text-sm font-medium uppercase text-[#0CE77E] ${customClasses} cursor-pointer duration-200`}
>
{icon}
<p>{text}</p>
{hasRightArrowIcon && (
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M6 12.0002L10 8.00017L6 4.00017"
stroke="#0CE77E"
strokeWidth="1.33333"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
)}
<span className="absolute -top-px -left-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 0V4.5" stroke="#0CE77E" />
<path d="M5.5 0.5L1 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -left-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M1 4.5V0" stroke="#0CE77E" />
<path d="M5.5 4L1 4" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -top-px -right-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 0V4.5" stroke="#0CE77E" />
<path d="M0.5 0.5L5 0.5" stroke="#0CE77E" />
</svg>
</span>
<span className="absolute -bottom-px -right-px group-hover:opacity-0 duration-200">
<svg width="6" height="5" viewBox="0 0 6 5" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M5 4.5V0" stroke="#0CE77E" />
<path d="M0.5 4L5 4" stroke="#0CE77E" />
</svg>
</span>
</Link>
);
}

51
src/components/Links.tsx Normal file
View File

@ -0,0 +1,51 @@
"use client";
import Link from "next/link";
import { motion } from "framer-motion";
export default function Links() {
return (
<ul className="flex items-center gap-10">
<motion.li
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
>
<Link
href="#tokenomics"
className="text-sm uppercase text-white/75 hover:text-[#0CE77E] duration-200"
>
Tokenomics
</Link>
</motion.li>
<motion.li
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.4 }}
viewport={{ once: true }}
>
<Link
href="#manifesto"
className="text-sm uppercase text-white/75 hover:text-[#0CE77E] duration-200"
>
Manifesto
</Link>
</motion.li>
<motion.li
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.6 }}
viewport={{ once: true }}
>
<Link
href="#architecture"
className="text-sm uppercase text-white/75 hover:text-[#0CE77E] duration-200"
>
Architecture
</Link>
</motion.li>
</ul>
);
}

30
src/components/Navbar.tsx Normal file
View File

@ -0,0 +1,30 @@
import Link from "next/link";
import Image from "next/image";
import iconImg from "@/assets/images/brand/icon.svg";
import IconBox from "./IconBox";
import SocialLinks from "./SocialLinks";
import Links from "./Links";
export default function Navbar() {
return (
<nav className="fixed z-[1000] top-0 left-0 w-full border-b border-white/10 bg-[#070F11]">
<div className="mx-auto container max-w-[1435px] px-14 h-[65px] flex justify-between items-center">
<div className="flex items-center gap-8">
<Link href={"/"} className="flex">
<Image src={iconImg} alt="" className="w-[32px]" />
</Link>
<span className="w-px h-[14px] bg-white/25"></span>
<Links />
</div>
<div className="flex items-center gap-6">
<SocialLinks />
<IconBox text="Rent a GPU" hasRightArrowIcon />
</div>
</div>
</nav>
);
}

View File

@ -0,0 +1,45 @@
"use client";
import { motion } from "framer-motion";
export default function SocialLinks() {
return (
<div className="flex items-center gap-5">
<motion.a
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.2 }}
viewport={{ once: true }}
href={"https://x.com/vertex_gpu"}
className="flex"
target="_blank"
>
<svg width="20" height="21" viewBox="0 0 20 21" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M15.2718 1.83659H18.0831L11.9413 8.85617L19.1666 18.4082H13.5093L9.07828 12.615L4.00821 18.4082H1.19528L7.76445 10.9L0.833252 1.83659H6.63418L10.6394 7.13187L15.2718 1.83659ZM14.2852 16.7256H15.8429L5.78775 3.43087H4.11614L14.2852 16.7256Z"
fill="#0CE77E"
/>
</svg>
</motion.a>
<motion.a
initial={{ opacity: 0 }}
whileInView={{ opacity: 1 }}
transition={{ delay: 0.6 }}
viewport={{ once: true }}
href={"https://docs.vertexgpu.com"}
className="flex"
target="_blank"
>
<svg width="21" height="21" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path
d="M14 2.26953V6.40007C14 6.96012 14 7.24015 14.109 7.45406C14.2049 7.64222 14.3578 7.7952 14.546 7.89108C14.7599 8.00007 15.0399 8.00007 15.6 8.00007H19.7305M16 13H8M16 17H8M10 9H8M14 2H8.8C7.11984 2 6.27976 2 5.63803 2.32698C5.07354 2.6146 4.6146 3.07354 4.32698 3.63803C4 4.27976 4 5.11984 4 6.8V17.2C4 18.8802 4 19.7202 4.32698 20.362C4.6146 20.9265 5.07354 21.3854 5.63803 21.673C6.27976 22 7.11984 22 8.8 22H15.2C16.8802 22 17.7202 22 18.362 21.673C18.9265 21.3854 19.3854 20.9265 19.673 20.362C20 19.7202 20 18.8802 20 17.2V8L14 2Z"
stroke="#0CE77E"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
/>
</svg>
</motion.a>
</div>
);
}

View File

@ -0,0 +1,193 @@
"use client";
import React, {
useCallback,
useEffect,
useMemo,
useRef,
useState,
} from "react";
interface FlickeringGridProps {
squareSize?: number;
gridGap?: number;
flickerChance?: number;
color?: string;
width?: number;
height?: number;
className?: string;
maxOpacity?: number;
}
export const FlickeringGrid: React.FC<FlickeringGridProps> = ({
squareSize = 4,
gridGap = 6,
flickerChance = 0.3,
color = "rgb(0, 0, 0)",
width,
height,
className,
maxOpacity = 0.3,
}) => {
const canvasRef = useRef<HTMLCanvasElement>(null);
const containerRef = useRef<HTMLDivElement>(null);
const [isInView, setIsInView] = useState(false);
const [canvasSize, setCanvasSize] = useState({ width: 0, height: 0 });
const memoizedColor = useMemo(() => {
const toRGBA = (color: string) => {
if (typeof window === "undefined") {
return `rgba(0, 0, 0,`;
}
const canvas = document.createElement("canvas");
canvas.width = canvas.height = 1;
const ctx = canvas.getContext("2d");
if (!ctx) return "rgba(255, 0, 0,";
ctx.fillStyle = color;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b] = Array.from(ctx.getImageData(0, 0, 1, 1).data);
return `rgba(${r}, ${g}, ${b},`;
};
return toRGBA(color);
}, [color]);
const setupCanvas = useCallback(
(canvas: HTMLCanvasElement, width: number, height: number) => {
const dpr = window.devicePixelRatio || 1;
canvas.width = width * dpr;
canvas.height = height * dpr;
canvas.style.width = `${width}px`;
canvas.style.height = `${height}px`;
const cols = Math.floor(width / (squareSize + gridGap));
const rows = Math.floor(height / (squareSize + gridGap));
const squares = new Float32Array(cols * rows);
for (let i = 0; i < squares.length; i++) {
squares[i] = Math.random() * maxOpacity;
}
return { cols, rows, squares, dpr };
},
[squareSize, gridGap, maxOpacity]
);
const updateSquares = useCallback(
(squares: Float32Array, deltaTime: number) => {
for (let i = 0; i < squares.length; i++) {
if (Math.random() < flickerChance * deltaTime) {
squares[i] = Math.random() * maxOpacity;
}
}
},
[flickerChance, maxOpacity]
);
const drawGrid = useCallback(
(
ctx: CanvasRenderingContext2D,
width: number,
height: number,
cols: number,
rows: number,
squares: Float32Array,
dpr: number
) => {
ctx.clearRect(0, 0, width, height);
ctx.fillStyle = "transparent";
ctx.fillRect(0, 0, width, height);
for (let i = 0; i < cols; i++) {
for (let j = 0; j < rows; j++) {
const opacity = squares[i * rows + j];
ctx.fillStyle = `${memoizedColor}${opacity})`;
ctx.fillRect(
i * (squareSize + gridGap) * dpr,
j * (squareSize + gridGap) * dpr,
squareSize * dpr,
squareSize * dpr
);
}
}
},
[memoizedColor, squareSize, gridGap]
);
useEffect(() => {
const canvas = canvasRef.current;
const container = containerRef.current;
if (!canvas || !container) return;
const ctx = canvas.getContext("2d");
if (!ctx) return;
let animationFrameId: number;
let gridParams: ReturnType<typeof setupCanvas>;
const updateCanvasSize = () => {
const newWidth = width || container.clientWidth;
const newHeight = height || container.clientHeight;
setCanvasSize({ width: newWidth, height: newHeight });
gridParams = setupCanvas(canvas, newWidth, newHeight);
};
updateCanvasSize();
let lastTime = 0;
const animate = (time: number) => {
if (!isInView) return;
const deltaTime = (time - lastTime) / 1000;
lastTime = time;
updateSquares(gridParams.squares, deltaTime);
drawGrid(
ctx,
canvas.width,
canvas.height,
gridParams.cols,
gridParams.rows,
gridParams.squares,
gridParams.dpr
);
animationFrameId = requestAnimationFrame(animate);
};
const resizeObserver = new ResizeObserver(() => {
updateCanvasSize();
});
resizeObserver.observe(container);
const intersectionObserver = new IntersectionObserver(
([entry]) => {
setIsInView(entry.isIntersecting);
},
{ threshold: 0 }
);
intersectionObserver.observe(canvas);
if (isInView) {
animationFrameId = requestAnimationFrame(animate);
}
return () => {
cancelAnimationFrame(animationFrameId);
resizeObserver.disconnect();
intersectionObserver.disconnect();
};
}, [setupCanvas, updateSquares, drawGrid, width, height, isInView]);
return (
<div ref={containerRef} className={`w-full h-full ${className}`}>
<canvas
ref={canvasRef}
className="pointer-events-none"
style={{
width: canvasSize.width,
height: canvasSize.height,
}}
/>
</div>
);
};

View File

@ -0,0 +1,138 @@
"use client";
import { cn } from "@/lib/utils";
import { AnimatePresence, motion, MotionProps } from "motion/react";
import { useEffect, useRef, useState } from "react";
type CharacterSet = string[] | readonly string[];
interface HyperTextProps extends MotionProps {
/** The text content to be animated */
children: string;
/** Optional className for styling */
className?: string;
/** Duration of the animation in milliseconds */
duration?: number;
/** Delay before animation starts in milliseconds */
delay?: number;
/** Component to render as - defaults to div */
as?: React.ElementType;
/** Whether to start animation when element comes into view */
startOnView?: boolean;
/** Whether to trigger animation on hover */
animateOnHover?: boolean;
/** Custom character set for scramble effect. Defaults to uppercase alphabet */
characterSet?: CharacterSet;
}
const DEFAULT_CHARACTER_SET = Object.freeze(
"ABCDEFGHIJKLMNOPQRSTUVWXYZ".split("")
) as readonly string[];
const getRandomInt = (max: number): number => Math.floor(Math.random() * max);
export default function HyperText({
children,
className,
duration = 800,
delay = 0,
as: Component = "div",
startOnView = false,
animateOnHover = true,
characterSet = DEFAULT_CHARACTER_SET,
...props
}: HyperTextProps) {
const MotionComponent = motion.create(Component, {
forwardMotionProps: true,
});
const [displayText, setDisplayText] = useState<string[]>(() =>
children.split("")
);
const [isAnimating, setIsAnimating] = useState(false);
const iterationCount = useRef(0);
const elementRef = useRef<HTMLElement>(null);
const handleAnimationTrigger = () => {
if (animateOnHover && !isAnimating) {
iterationCount.current = 0;
setIsAnimating(true);
}
};
// Handle animation start based on view or delay
useEffect(() => {
if (!startOnView) {
const startTimeout = setTimeout(() => {
setIsAnimating(true);
}, delay);
return () => clearTimeout(startTimeout);
}
const observer = new IntersectionObserver(
([entry]) => {
if (entry.isIntersecting) {
setTimeout(() => {
setIsAnimating(true);
}, delay);
observer.disconnect();
}
},
{ threshold: 0.1, rootMargin: "-30% 0px -30% 0px" }
);
if (elementRef.current) {
observer.observe(elementRef.current);
}
return () => observer.disconnect();
}, [delay, startOnView]);
// Handle scramble animation
useEffect(() => {
if (!isAnimating) return;
const intervalDuration = duration / (children.length * 10);
const maxIterations = children.length;
const interval = setInterval(() => {
if (iterationCount.current < maxIterations) {
setDisplayText((currentText) =>
currentText.map((letter, index) =>
letter === " "
? letter
: index <= iterationCount.current
? children[index]
: characterSet[getRandomInt(characterSet.length)]
)
);
iterationCount.current = iterationCount.current + 0.1;
} else {
setIsAnimating(false);
clearInterval(interval);
}
}, intervalDuration);
return () => clearInterval(interval);
}, [children, duration, isAnimating, characterSet]);
return (
<MotionComponent
ref={elementRef}
className={cn("overflow-hidden", className)}
onMouseEnter={handleAnimationTrigger}
{...props}
>
<AnimatePresence>
{displayText.map((letter, index) => (
<motion.span
key={index}
className={cn("", letter === " " ? "w-3" : "")}
>
{letter.toUpperCase()}
</motion.span>
))}
</AnimatePresence>
</MotionComponent>
);
}

6
src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}

18
tailwind.config.ts Normal file
View File

@ -0,0 +1,18 @@
import type { Config } from "tailwindcss";
export default {
content: [
"./src/pages/**/*.{js,ts,jsx,tsx,mdx}",
"./src/components/**/*.{js,ts,jsx,tsx,mdx}",
"./src/app/**/*.{js,ts,jsx,tsx,mdx}",
],
theme: {
extend: {
fontFamily: {
sans: ["var(--font-geist-mono)"],
serif: ["var(--font-geist-sans)"],
},
},
},
plugins: [],
} satisfies Config;

27
tsconfig.json Normal file
View File

@ -0,0 +1,27 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
],
"paths": {
"@/*": ["./src/*"]
}
},
"include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"],
"exclude": ["node_modules"]
}