Initial commit

This commit is contained in:
2025-10-11 11:55:25 +08:00
parent 467dad8449
commit 8107dee8d3
2879 changed files with 610575 additions and 0 deletions

View File

@@ -0,0 +1,64 @@
import { useRef, useState } from "react";
import { Splide, SplideTrack, SplideSlide } from "@splidejs/react-splide";
import Comment from "./Comment";
type CarouselProps = {
items: any;
};
const Carousel = ({ items }: CarouselProps) => {
const [activeIndex, setActiveIndex] = useState<number>(0);
const ref = useRef<any>(null);
const handleClick = (index: number) => {
setActiveIndex(index);
ref.current?.go(index);
};
return (
<Splide
className="splide-visible relative z-2"
options={{
pagination: false,
arrows: false,
gap: "1.5rem",
}}
onMoved={(e, newIndex) => setActiveIndex(newIndex)}
hasTrack={false}
ref={ref}
>
<SplideTrack>
{items.map((item: any) => (
<SplideSlide key={item.id}>
<div className="flex h-full">
<Comment comment={item} />
</div>
</SplideSlide>
))}
</SplideTrack>
<div className="flex justify-center mt-8 -mx-2 md:mt-15 lg:hidden">
{items.map((item: any, index: number) => (
<button
className="relative w-6 h-6 mx-2"
onClick={() => handleClick(index)}
key={item.id}
>
<span
className={`absolute inset-0 bg-conic-gradient rounded-full transition-opacity ${
index === activeIndex
? "opacity-100"
: "opacity-0"
}`}
></span>
<span className="absolute inset-0.25 bg-n-8 rounded-full">
<span className="absolute inset-2 bg-n-1 rounded-full"></span>
</span>
</button>
))}
</div>
</Splide>
);
};
export default Carousel;

View File

@@ -0,0 +1,28 @@
import Image from "@/components/Image";
type CommentProps = {
comment: any;
};
const Comment = ({ comment }: CommentProps) => (
<div className="flex flex-col bg-n-8 border border-n-1/5 rounded-2xl">
<div className="quote flex-1 px-5 py-10 md:px-10">{comment.text}</div>
<div className="flex items-center px-5 py-6 bg-n-7 rounded-b-[0.9375rem] md:px-10">
<div className="mr-5">
<h6 className="h6">{comment.name}</h6>
<div className="caption text-n-1/25">{comment.role}</div>
</div>
<div className="ml-auto">
<Image
className="w-full rounded-full"
src={comment.avatarUrl}
width={60}
height={60}
alt={comment.name}
/>
</div>
</div>
</div>
);
export default Comment;

View File

@@ -0,0 +1,25 @@
import Masonry, { ResponsiveMasonry } from "react-responsive-masonry";
import Comment from "./Comment";
type GridProps = {
items: any;
};
const Grid = ({ items }: GridProps) => {
return (
<ResponsiveMasonry
className="relative z-2"
columnsCountBreakPoints={{ 768: 2, 1280: 3 }}
>
<Masonry gutter="1.5rem">
{items.map((item: any) => (
<div key={item.id}>
<Comment comment={item} />
</div>
))}
</Masonry>
</ResponsiveMasonry>
);
};
export default Grid;

View File

@@ -0,0 +1,50 @@
import dynamic from "next/dynamic";
import { useMediaQuery } from "react-responsive";
import Section from "@/components/Section";
import Heading from "@/components/Heading";
import Image from "@/components/Image";
const Grid = dynamic(() => import("./Grid"), { ssr: false });
const Carousel = dynamic(() => import("./Carousel"), { ssr: false });
import { community } from "@/mocks/community";
type CommunityProps = {};
const Community = ({}: CommunityProps) => {
const isTablet = useMediaQuery({
query: "(min-width: 768px)",
});
return (
<Section>
<div className="container">
<Heading
className="md:text-center"
tagClassName="md:justify-center"
tag="ready to get started"
title="What the community is saying"
/>
<div className="relative">
{isTablet ? (
<Grid items={community} />
) : (
<Carousel items={community} />
)}
<div className="absolute top-[18.25rem] -left-[30.375rem] w-[56.625rem] opacity-60 mix-blend-color-dodge pointer-events-none">
<div className="absolute top-1/2 left-1/2 w-[58.85rem] h-[58.85rem] -translate-x-3/4 -translate-y-1/2">
<Image
className="w-full"
src="/images/gradient.png"
width={942}
height={942}
alt="Gradient"
/>
</div>
</div>
</div>
</div>
</Section>
);
};
export default Community;

View File

@@ -0,0 +1,101 @@
import Tippy from "@tippyjs/react";
import Heading from "@/components/Heading";
import Image from "@/components/Image";
import Section from "@/components/Section";
import { comparison } from "@/mocks/comparison";
type ComparisonProps = {};
const Comparison = ({}: ComparisonProps) => {
const check = (value: any, enterprise?: boolean) =>
typeof value === "boolean" ? (
value === true ? (
<Image
src={
enterprise
? "/images/check-yellow.svg"
: "/images/check.svg"
}
width={24}
height={24}
alt="Check"
/>
) : null
) : (
value
);
return (
<Section>
<div className="container">
<Heading
className="md:text-center"
title="Compare plans & features"
/>
<div className="-mx-5 px-5 overflow-auto">
<table className="table-fixed w-full min-w-[32rem]">
<tbody>
<tr className="h6">
<td className="w-[35%] py-4 pr-10">Features</td>
<td className="p-4 text-center text-color-2">
Basic
</td>
<td className="p-4 text-center text-color-1">
Premium
</td>
<td className="p-4 text-center text-color-3">
Enterprise
</td>
</tr>
{comparison.map((item) => (
<tr className="body-2" key={item.id}>
<td className="w-[35%] h-[4.75rem] py-2.5 pr-2.5 border-t border-n-1/5">
<div className="flex items-center">
{item.title}
<Tippy
className="p-2.5 bg-n-1 text-n-8 rounded-xl"
content="Provide dedicated servers for enterprises to ensure maximum security, performance, and uptime."
placement="right"
animation="shift-toward"
>
<div className="flex-shrink-0 ml-3 opacity-30 transition-opacity hover:opacity-100">
<Image
src="/images/icons/help-circle.svg"
width={24}
height={24}
alt="Help"
/>
</div>
</Tippy>
</div>
</td>
<td className="h-[4.75rem] p-2.5 border-t border-n-1/5 text-center">
{check(
item.pricing[0],
item.enterprise
)}
</td>
<td className="h-[4.75rem] p-2.5 border-t border-n-1/5 text-center">
{check(
item.pricing[1],
item.enterprise
)}
</td>
<td className="h-[4.75rem] p-2.5 border-t border-n-1/5 text-center">
{check(
item.pricing[2],
item.enterprise
)}
</td>
</tr>
))}
</tbody>
</table>
</div>
</div>
</Section>
);
};
export default Comparison;

View File

@@ -0,0 +1,78 @@
import { useState } from "react";
import Section from "@/components/Section";
import Heading from "@/components/Heading";
import { faq } from "@/mocks/faq";
type FaqProps = {};
const Faq = ({}: FaqProps) => {
const [activeId, setActiveId] = useState<string | null>(faq[0].id);
return (
<Section>
<div className="container lg:flex">
<Heading
className="lg:min-w-[22.75rem] lg:mr-12 lg:pt-8 xl:min-w-[32.75rem]"
textAlignClassName="md:text-center lg:text-left"
title="Frequently asked questions"
text={
<>
Havent found what youre looking for?{" "}
<a
className="text-n-1 hover:text-color-2"
href="mailto:info@ui8.net"
>
Contact us
</a>
</>
}
/>
<div className="-mt-8 lg:mt-0">
{faq.map((item) => (
<div
className="py-8 border-b border-n-1/5"
key={item.id}
>
<div
className="flex items-start justify-between cursor-pointer"
onClick={() =>
setActiveId(
activeId === item.id ? null : item.id
)
}
>
<div className="text-[1.25rem] leading-8">
{item.title}
</div>
<div className="relative w-6 h-6 mt-1 ml-10">
<div className="absolute top-[0.6875rem] left-1 w-4 h-0.5 bg-n-1 rounded-sm"></div>
<div
className={`absolute top-[0.6875rem] left-1 w-4 h-0.5 bg-n-1 rounded-sm transition-transform ${
item.id === activeId
? ""
: "rotate-90"
}`}
></div>
</div>
</div>
<div
className={`grid grid-rows-[0fr] transition-all ${
item.id === activeId
? "grid-rows-[1fr]"
: ""
}`}
>
<div className="body-2 text-n-3 overflow-hidden">
<div className="pt-6">{item.text}</div>
</div>
</div>
</div>
))}
</div>
</div>
</Section>
);
};
export default Faq;

View File

@@ -0,0 +1,50 @@
import Section from "@/components/Section";
import Heading from "@/components/Heading";
import PricingList from "@/components/PricingList";
import { useState } from "react";
import Logos from "@/components/Logos";
type PricingProps = {};
const Pricing = ({}: PricingProps) => {
const [monthly, setMonthly] = useState<boolean>(false);
return (
<Section className="overflow-hidden">
<div className="container relative z-2 md:pt-10 lg:pt-16 xl:pt-20">
<Heading
textAlignClassName="text-center"
titleLarge="Pay once, use forever"
textLarge="Get started with Brainwave - AI chat app today and experience the power of AI in your conversations!"
/>
<div className="w-[19rem] mx-auto mb-10 p-0.25 bg-gradient-to-b from-[#D77DEE]/90 to-n-1/15 rounded-xl">
<div className="flex p-[0.1875rem] bg-n-8 rounded-[0.6875rem]">
<button
className={`button flex-1 h-10 rounded-lg transition-colors ${
monthly ? "bg-n-6" : ""
}`}
onClick={() => setMonthly(true)}
>
monthly
</button>
<button
className={`button flex-1 h-10 rounded-lg transition-colors ${
monthly ? "" : "bg-n-6"
}`}
onClick={() => setMonthly(false)}
>
annually
<span className="ml-2.5 p-1 bg-color-1 rounded">
-10%
</span>
</button>
</div>
</div>
<PricingList monthly={monthly} />
<Logos className="hidden mt-20 lg:block" />
</div>
</Section>
);
};
export default Pricing;

View File

@@ -0,0 +1,22 @@
"use client";
import Layout from "@/components/Layout";
import Pricing from "./Pricing";
import Comparison from "./Comparison";
import Community from "./Community";
import Join from "@/components/Join";
import Faq from "./Faq";
const PricingPage = () => {
return (
<Layout>
<Pricing />
<Comparison />
<Community />
<Faq />
<Join />
</Layout>
);
};
export default PricingPage;