Merge version_1 into main
Merge version_1 into main
This commit was merged in pull request #1.
This commit is contained in:
47
package.json
47
package.json
@@ -1,41 +1,20 @@
|
|||||||
{
|
{
|
||||||
"name": "webild-components-2",
|
"name": "webild-components-2", "version": "0.1.0", "private": true,
|
||||||
"version": "0.1.0",
|
|
||||||
"private": true,
|
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"dev": "next dev --turbopack",
|
"dev": "react-scripts start", "build": "react-scripts build", "start": "react-scripts start", "test": "react-scripts test", "eject": "react-scripts eject"
|
||||||
"build": "next build --turbopack",
|
|
||||||
"start": "next start",
|
|
||||||
"lint": "eslint"
|
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@gsap/react": "^2.1.2",
|
"@gsap/react": "^2.1.2", "@react-three/drei": "^10.7.7", "@react-three/fiber": "^9.4.0", "clsx": "^2.1.1", "cobe": "^0.6.5", "embla-carousel-auto-scroll": "^8.6.0", "embla-carousel-react": "^8.6.0", "gsap": "^3.13.0", "lenis": "^1.3.15", "lucide-react": "^0.555.0", "motion-number": "^1.0.0", "react": "^18.2.0", "react-dom": "^18.2.0", "react-fast-marquee": "^1.6.5", "react-scripts": "5.0.1", "tailwind-merge": "^3.4.0", "three": "^0.181.2", "web-vitals": "^2.1.4"
|
||||||
"@react-three/drei": "^10.7.7",
|
|
||||||
"@react-three/fiber": "^9.4.0",
|
|
||||||
"clsx": "^2.1.1",
|
|
||||||
"cobe": "^0.6.5",
|
|
||||||
"embla-carousel-auto-scroll": "^8.6.0",
|
|
||||||
"embla-carousel-react": "^8.6.0",
|
|
||||||
"gsap": "^3.13.0",
|
|
||||||
"lenis": "^1.3.15",
|
|
||||||
"lucide-react": "^0.555.0",
|
|
||||||
"motion-number": "^1.0.0",
|
|
||||||
"next": "16.0.7",
|
|
||||||
"react": "19.2.1",
|
|
||||||
"react-dom": "19.2.1",
|
|
||||||
"react-fast-marquee": "^1.6.5",
|
|
||||||
"tailwind-merge": "^3.4.0",
|
|
||||||
"three": "^0.181.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@eslint/eslintrc": "^3",
|
"@tailwindcss/postcss": "^4", "@types/node": "^20", "@types/react": "^18.2.0", "@types/react-dom": "^18.2.0", "tailwindcss": "^4", "typescript": "^5"
|
||||||
"@tailwindcss/postcss": "^4",
|
},
|
||||||
"@types/node": "^20",
|
"browserslist": {
|
||||||
"@types/react": "^19",
|
"production": [
|
||||||
"@types/react-dom": "^19",
|
">0.2%", "not dead", "not op_mini all"
|
||||||
"eslint": "^9",
|
],
|
||||||
"eslint-config-next": "16.0.7",
|
"development": [
|
||||||
"tailwindcss": "^4",
|
"last 1 chrome version", "last 1 firefox version", "last 1 safari version"
|
||||||
"typescript": "^5"
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
1235
public/index.html
Normal file
1235
public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
16
src/App.tsx
Normal file
16
src/App.tsx
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import NavbarStyleApple from './components/navbar/NavbarStyleApple/NavbarStyleApple';
|
||||||
|
import HeroBillboard from './components/sections/hero/HeroBillboard';
|
||||||
|
import FeatureCardOne from './components/sections/feature/FeatureCardOne';
|
||||||
|
|
||||||
|
function App() {
|
||||||
|
return (
|
||||||
|
<div className="App">
|
||||||
|
<NavbarStyleApple />
|
||||||
|
<HeroBillboard />
|
||||||
|
<FeatureCardOne />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export default App;
|
||||||
@@ -1,87 +1,69 @@
|
|||||||
"use client";
|
import React from 'react';
|
||||||
|
import { Menu, X } from 'lucide-react';
|
||||||
|
|
||||||
import { useState, useCallback } from "react";
|
const NavbarStyleApple: React.FC = () => {
|
||||||
import MobileMenu from "../mobileMenu/MobileMenu";
|
const [isMenuOpen, setIsMenuOpen] = React.useState(false);
|
||||||
import ButtonTextUnderline from "@/components/button/ButtonTextUnderline";
|
|
||||||
import Logo from "../Logo";
|
|
||||||
import { Plus } from "lucide-react";
|
|
||||||
import { NavbarProps } from "@/types/navigation";
|
|
||||||
import { useScrollState } from "./useScrollState";
|
|
||||||
import { cls } from "@/lib/utils";
|
|
||||||
|
|
||||||
const SCROLL_THRESHOLD = 50;
|
|
||||||
|
|
||||||
const NavbarStyleApple = ({
|
|
||||||
navItems,
|
|
||||||
// logoSrc,
|
|
||||||
// logoAlt = "",
|
|
||||||
brandName = "Webild",
|
|
||||||
}: NavbarProps) => {
|
|
||||||
const isScrolled = useScrollState(SCROLL_THRESHOLD);
|
|
||||||
const [menuOpen, setMenuOpen] = useState(false);
|
|
||||||
|
|
||||||
const handleMenuToggle = useCallback(() => {
|
|
||||||
setMenuOpen((prev) => !prev);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const handleMobileNavClick = useCallback(() => {
|
|
||||||
setMenuOpen(false);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<nav
|
<nav className="fixed top-0 left-0 right-0 z-50 bg-white/80 backdrop-blur-md border-b border-gray-200">
|
||||||
className={cls(
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
"fixed z-[1000] top-0 left-0 w-full transition-all duration-500 ease-in-out",
|
<div className="flex justify-between items-center h-16">
|
||||||
isScrolled
|
<div className="flex-shrink-0">
|
||||||
? "bg-background/80 backdrop-blur-sm h-15"
|
<img
|
||||||
: "bg-background/0 backdrop-blur-0 h-20"
|
className="h-8 w-auto"
|
||||||
)}
|
src="/images/logo-small-gradient.0ed287ce-1768922368007.svg"
|
||||||
>
|
alt="Logo"
|
||||||
<div className="flex items-center justify-between h-full w-content-width mx-auto">
|
|
||||||
<div className="flex items-center transition-all duration-500 ease-in-out">
|
|
||||||
<Logo brandName={brandName} />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div
|
|
||||||
className="hidden md:flex items-center gap-6 transition-all duration-500 ease-in-out"
|
|
||||||
role="navigation"
|
|
||||||
>
|
|
||||||
{navItems.map((item, index) => (
|
|
||||||
<ButtonTextUnderline
|
|
||||||
key={index}
|
|
||||||
text={item.name}
|
|
||||||
href={item.id}
|
|
||||||
className="!text-base"
|
|
||||||
/>
|
/>
|
||||||
))}
|
</div>
|
||||||
</div>
|
|
||||||
|
<div className="hidden md:block">
|
||||||
|
<div className="ml-10 flex items-baseline space-x-8">
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
|
||||||
|
Home
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
|
||||||
|
Products
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
|
||||||
|
Services
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 px-3 py-2 text-sm font-medium">
|
||||||
|
Contact
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<button
|
<div className="md:hidden">
|
||||||
className="flex md:hidden shrink-0 h-8 aspect-square rounded-theme bg-foreground items-center justify-center cursor-pointer"
|
<button
|
||||||
onClick={handleMenuToggle}
|
onClick={() => setIsMenuOpen(!isMenuOpen)}
|
||||||
aria-label="Toggle menu"
|
className="inline-flex items-center justify-center p-2 rounded-md text-gray-400 hover:text-gray-500 hover:bg-gray-100"
|
||||||
aria-expanded={menuOpen}
|
>
|
||||||
aria-controls="mobile-menu"
|
{isMenuOpen ? <X className="h-6 w-6" /> : <Menu className="h-6 w-6" />}
|
||||||
>
|
</button>
|
||||||
<Plus
|
</div>
|
||||||
className={cls(
|
</div>
|
||||||
"w-1/2 h-1/2 text-background transition-transform duration-300",
|
|
||||||
menuOpen ? "rotate-45" : "rotate-0"
|
|
||||||
)}
|
|
||||||
strokeWidth={1.5}
|
|
||||||
aria-hidden="true"
|
|
||||||
/>
|
|
||||||
</button>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<MobileMenu
|
{isMenuOpen && (
|
||||||
menuOpen={menuOpen}
|
<div className="md:hidden">
|
||||||
onMenuToggle={handleMenuToggle}
|
<div className="px-2 pt-2 pb-3 space-y-1 sm:px-3 bg-white border-t border-gray-200">
|
||||||
navItems={navItems}
|
<a href="#" className="text-gray-900 hover:text-gray-600 block px-3 py-2 text-base font-medium">
|
||||||
onNavClick={handleMobileNavClick}
|
Home
|
||||||
/>
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 block px-3 py-2 text-base font-medium">
|
||||||
|
Products
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 block px-3 py-2 text-base font-medium">
|
||||||
|
Services
|
||||||
|
</a>
|
||||||
|
<a href="#" className="text-gray-900 hover:text-gray-600 block px-3 py-2 text-base font-medium">
|
||||||
|
Contact
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
</nav>
|
</nav>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default NavbarStyleApple;
|
export default NavbarStyleApple;
|
||||||
@@ -1,171 +1,62 @@
|
|||||||
"use client";
|
import React from 'react';
|
||||||
|
import { Zap, Shield, Rocket, Users } from 'lucide-react';
|
||||||
|
|
||||||
import CardStack from "@/components/cardStack/CardStack";
|
const features = [
|
||||||
import MediaContent from "@/components/shared/MediaContent";
|
{
|
||||||
import Button from "@/components/button/Button";
|
icon: <Zap className="h-8 w-8" />,
|
||||||
import { cls, shouldUseInvertedText } from "@/lib/utils";
|
title: "Lightning Fast", description: "Experience blazing fast performance with our optimized architecture and cutting-edge technology."
|
||||||
import { getButtonProps } from "@/lib/buttonUtils";
|
},
|
||||||
import { useTheme } from "@/providers/themeProvider/ThemeProvider";
|
{
|
||||||
import type { LucideIcon } from "lucide-react";
|
icon: <Shield className="h-8 w-8" />,
|
||||||
import type { ButtonConfig, GridVariant, CardAnimationType, TitleSegment } from "@/components/cardStack/types";
|
title: "Secure & Reliable", description: "Your data is protected with enterprise-grade security measures and 99.9% uptime guarantee."
|
||||||
|
},
|
||||||
import type { TextboxLayout, InvertedBackground } from "@/providers/themeProvider/config/constants";
|
{
|
||||||
|
icon: <Rocket className="h-8 w-8" />,
|
||||||
type FeatureCard = {
|
title: "Scale with Ease", description: "Grow your business without limits. Our platform scales automatically to meet your needs."
|
||||||
title: string;
|
},
|
||||||
description: string;
|
{
|
||||||
button?: ButtonConfig;
|
icon: <Users className="h-8 w-8" />,
|
||||||
} & (
|
title: "Team Collaboration", description: "Work together seamlessly with real-time collaboration tools and shared workspaces."
|
||||||
| {
|
}
|
||||||
imageSrc: string;
|
];
|
||||||
imageAlt?: string;
|
|
||||||
videoSrc?: never;
|
|
||||||
videoAriaLabel?: never;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
videoSrc: string;
|
|
||||||
videoAriaLabel?: string;
|
|
||||||
imageSrc?: never;
|
|
||||||
imageAlt?: never;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
interface FeatureCardOneProps {
|
|
||||||
features: FeatureCard[];
|
|
||||||
carouselMode?: "auto" | "buttons";
|
|
||||||
gridVariant: GridVariant;
|
|
||||||
uniformGridCustomHeightClasses?: string;
|
|
||||||
animationType: CardAnimationType;
|
|
||||||
title: string;
|
|
||||||
titleSegments?: TitleSegment[];
|
|
||||||
description: string;
|
|
||||||
tag?: string;
|
|
||||||
tagIcon?: LucideIcon;
|
|
||||||
buttons?: ButtonConfig[];
|
|
||||||
textboxLayout: TextboxLayout;
|
|
||||||
useInvertedBackground: InvertedBackground;
|
|
||||||
ariaLabel?: string;
|
|
||||||
className?: string;
|
|
||||||
containerClassName?: string;
|
|
||||||
cardClassName?: string;
|
|
||||||
mediaClassName?: string;
|
|
||||||
textBoxTitleClassName?: string;
|
|
||||||
textBoxTitleImageWrapperClassName?: string;
|
|
||||||
textBoxTitleImageClassName?: string;
|
|
||||||
textBoxDescriptionClassName?: string;
|
|
||||||
cardTitleClassName?: string;
|
|
||||||
cardDescriptionClassName?: string;
|
|
||||||
cardButtonClassName?: string;
|
|
||||||
cardButtonTextClassName?: string;
|
|
||||||
gridClassName?: string;
|
|
||||||
carouselClassName?: string;
|
|
||||||
controlsClassName?: string;
|
|
||||||
textBoxClassName?: string;
|
|
||||||
textBoxTagClassName?: string;
|
|
||||||
textBoxButtonContainerClassName?: string;
|
|
||||||
textBoxButtonClassName?: string;
|
|
||||||
textBoxButtonTextClassName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const FeatureCardOne = ({
|
|
||||||
features,
|
|
||||||
carouselMode = "buttons",
|
|
||||||
gridVariant,
|
|
||||||
uniformGridCustomHeightClasses,
|
|
||||||
animationType,
|
|
||||||
title,
|
|
||||||
titleSegments,
|
|
||||||
description,
|
|
||||||
tag,
|
|
||||||
tagIcon,
|
|
||||||
buttons,
|
|
||||||
textboxLayout,
|
|
||||||
useInvertedBackground,
|
|
||||||
ariaLabel = "Feature section",
|
|
||||||
className = "",
|
|
||||||
containerClassName = "",
|
|
||||||
cardClassName = "",
|
|
||||||
mediaClassName = "",
|
|
||||||
textBoxTitleClassName = "",
|
|
||||||
textBoxTitleImageWrapperClassName = "",
|
|
||||||
textBoxTitleImageClassName = "",
|
|
||||||
textBoxDescriptionClassName = "",
|
|
||||||
cardTitleClassName = "",
|
|
||||||
cardDescriptionClassName = "",
|
|
||||||
cardButtonClassName = "",
|
|
||||||
cardButtonTextClassName = "",
|
|
||||||
gridClassName = "",
|
|
||||||
carouselClassName = "",
|
|
||||||
controlsClassName = "",
|
|
||||||
textBoxClassName = "",
|
|
||||||
textBoxTagClassName = "",
|
|
||||||
textBoxButtonContainerClassName = "",
|
|
||||||
textBoxButtonClassName = "",
|
|
||||||
textBoxButtonTextClassName = "",
|
|
||||||
}: FeatureCardOneProps) => {
|
|
||||||
const theme = useTheme();
|
|
||||||
const shouldUseLightText = shouldUseInvertedText(useInvertedBackground, theme.cardStyle);
|
|
||||||
|
|
||||||
|
const FeatureCardOne: React.FC = () => {
|
||||||
return (
|
return (
|
||||||
<CardStack
|
<section className="py-24 bg-white">
|
||||||
mode={carouselMode}
|
<div className="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8">
|
||||||
gridVariant={gridVariant}
|
<div className="text-center mb-16">
|
||||||
uniformGridCustomHeightClasses={uniformGridCustomHeightClasses}
|
<h2 className="text-3xl md:text-4xl font-bold text-gray-900 mb-4">
|
||||||
animationType={animationType}
|
Why Choose Our Platform?
|
||||||
|
</h2>
|
||||||
title={title}
|
<p className="text-lg text-gray-600 max-w-3xl mx-auto">
|
||||||
titleSegments={titleSegments}
|
Discover the features that make us the preferred choice for thousands of businesses worldwide.
|
||||||
description={description}
|
</p>
|
||||||
tag={tag}
|
|
||||||
tagIcon={tagIcon}
|
|
||||||
buttons={buttons}
|
|
||||||
textboxLayout={textboxLayout}
|
|
||||||
useInvertedBackground={useInvertedBackground}
|
|
||||||
className={className}
|
|
||||||
containerClassName={containerClassName}
|
|
||||||
gridClassName={gridClassName}
|
|
||||||
carouselClassName={carouselClassName}
|
|
||||||
controlsClassName={controlsClassName}
|
|
||||||
textBoxClassName={textBoxClassName}
|
|
||||||
titleClassName={textBoxTitleClassName}
|
|
||||||
titleImageWrapperClassName={textBoxTitleImageWrapperClassName}
|
|
||||||
titleImageClassName={textBoxTitleImageClassName}
|
|
||||||
descriptionClassName={textBoxDescriptionClassName}
|
|
||||||
tagClassName={textBoxTagClassName}
|
|
||||||
buttonContainerClassName={textBoxButtonContainerClassName}
|
|
||||||
buttonClassName={textBoxButtonClassName}
|
|
||||||
buttonTextClassName={textBoxButtonTextClassName}
|
|
||||||
ariaLabel={ariaLabel}
|
|
||||||
>
|
|
||||||
{features.map((feature, index) => (
|
|
||||||
<div
|
|
||||||
key={`${feature.title}-${index}`}
|
|
||||||
className={cls("card flex flex-col gap-4 p-4 rounded-theme-capped min-h-0 h-full", cardClassName)}
|
|
||||||
>
|
|
||||||
<MediaContent
|
|
||||||
imageSrc={feature.imageSrc}
|
|
||||||
videoSrc={feature.videoSrc}
|
|
||||||
imageAlt={feature.imageAlt || "Feature image"}
|
|
||||||
videoAriaLabel={feature.videoAriaLabel || "Feature video"}
|
|
||||||
imageClassName={cls("relative z-1 min-h-0 h-full", mediaClassName)}
|
|
||||||
/>
|
|
||||||
<div className="relative z-1 flex flex-col gap-1">
|
|
||||||
<h3 className={cls("text-2xl font-medium", shouldUseLightText && "text-background", cardTitleClassName)}>
|
|
||||||
{feature.title}
|
|
||||||
</h3>
|
|
||||||
<p className={cls("text-sm leading-[1.1]", shouldUseLightText ? "text-background" : "text-foreground", cardDescriptionClassName)}>
|
|
||||||
{feature.description}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
{feature.button && (
|
|
||||||
<Button {...getButtonProps(feature.button, 0, theme.defaultButtonVariant, cls("w-full", cardButtonClassName), cardButtonTextClassName)} />
|
|
||||||
)}
|
|
||||||
</div>
|
</div>
|
||||||
))}
|
|
||||||
</CardStack>
|
<div className="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-4 gap-8">
|
||||||
|
{features.map((feature, index) => (
|
||||||
|
<div key={index} className="group">
|
||||||
|
<div className="bg-white rounded-2xl p-8 shadow-lg hover:shadow-xl transition-shadow duration-300 border border-gray-100 hover:border-blue-200">
|
||||||
|
<div className="flex items-center justify-center w-16 h-16 bg-blue-100 rounded-xl mb-6 group-hover:bg-blue-200 transition-colors duration-300">
|
||||||
|
<div className="text-blue-600">
|
||||||
|
{feature.icon}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<h3 className="text-xl font-semibold text-gray-900 mb-4">
|
||||||
|
{feature.title}
|
||||||
|
</h3>
|
||||||
|
|
||||||
|
<p className="text-gray-600 leading-relaxed">
|
||||||
|
{feature.description}
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
FeatureCardOne.displayName = "FeatureCardOne";
|
export default FeatureCardOne;
|
||||||
|
|
||||||
export default FeatureCardOne;
|
|
||||||
@@ -1,93 +1,42 @@
|
|||||||
"use client";
|
import React from 'react';
|
||||||
|
import { ArrowRight, Play } from 'lucide-react';
|
||||||
|
|
||||||
import TextBox from "@/components/Textbox";
|
const HeroBillboard: React.FC = () => {
|
||||||
import MediaContent from "@/components/shared/MediaContent";
|
|
||||||
import { cls } from "@/lib/utils";
|
|
||||||
import type { LucideIcon } from "lucide-react";
|
|
||||||
import type { ButtonConfig } from "@/types/button";
|
|
||||||
|
|
||||||
interface HeroBillboardProps {
|
|
||||||
title: string;
|
|
||||||
description: string;
|
|
||||||
tag?: string;
|
|
||||||
tagIcon?: LucideIcon;
|
|
||||||
buttons?: ButtonConfig[];
|
|
||||||
imageSrc?: string;
|
|
||||||
videoSrc?: string;
|
|
||||||
imageAlt?: string;
|
|
||||||
videoAriaLabel?: string;
|
|
||||||
ariaLabel?: string;
|
|
||||||
className?: string;
|
|
||||||
containerClassName?: string;
|
|
||||||
textBoxClassName?: string;
|
|
||||||
titleClassName?: string;
|
|
||||||
descriptionClassName?: string;
|
|
||||||
tagClassName?: string;
|
|
||||||
buttonContainerClassName?: string;
|
|
||||||
buttonClassName?: string;
|
|
||||||
buttonTextClassName?: string;
|
|
||||||
mediaWrapperClassName?: string;
|
|
||||||
imageClassName?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const HeroBillboard = ({
|
|
||||||
title,
|
|
||||||
description,
|
|
||||||
tag,
|
|
||||||
tagIcon,
|
|
||||||
buttons,
|
|
||||||
imageSrc,
|
|
||||||
videoSrc,
|
|
||||||
imageAlt = "",
|
|
||||||
videoAriaLabel = "Hero video",
|
|
||||||
ariaLabel = "Hero section",
|
|
||||||
className = "",
|
|
||||||
containerClassName = "",
|
|
||||||
textBoxClassName = "",
|
|
||||||
titleClassName = "",
|
|
||||||
descriptionClassName = "",
|
|
||||||
tagClassName = "",
|
|
||||||
buttonContainerClassName = "",
|
|
||||||
buttonClassName = "",
|
|
||||||
buttonTextClassName = "",
|
|
||||||
mediaWrapperClassName = "",
|
|
||||||
imageClassName = "",
|
|
||||||
}: HeroBillboardProps) => {
|
|
||||||
return (
|
return (
|
||||||
<section
|
<section className="relative min-h-screen flex items-center justify-center bg-gradient-to-br from-blue-50 to-indigo-100 overflow-hidden">
|
||||||
aria-label={ariaLabel}
|
<div className="absolute inset-0 bg-gradient-to-r from-blue-600/10 to-purple-600/10"></div>
|
||||||
className={cls("w-full py-hero-page-padding", className)}
|
|
||||||
>
|
<div className="relative z-10 max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 text-center">
|
||||||
<div className={cls("w-content-width mx-auto flex flex-col gap-14 md:gap-15", containerClassName)}>
|
<div className="space-y-8">
|
||||||
<TextBox
|
<h1 className="text-4xl md:text-6xl lg:text-7xl font-bold text-gray-900 leading-tight">
|
||||||
title={title}
|
Build the Future
|
||||||
description={description}
|
<span className="block bg-gradient-to-r from-blue-600 to-purple-600 bg-clip-text text-transparent">
|
||||||
tag={tag}
|
Today
|
||||||
tagIcon={tagIcon}
|
</span>
|
||||||
buttons={buttons}
|
</h1>
|
||||||
className={cls("flex flex-col gap-3 md:gap-1", textBoxClassName)}
|
|
||||||
titleClassName={cls("text-6xl font-medium text-balance", titleClassName)}
|
<p className="max-w-3xl mx-auto text-lg md:text-xl text-gray-600 leading-relaxed">
|
||||||
descriptionClassName={cls("text-base md:text-lg leading-[1.2]", descriptionClassName)}
|
Transform your ideas into reality with our cutting-edge technology solutions.
|
||||||
tagClassName={cls("px-3 py-1 text-sm rounded-theme card text-foreground inline-flex items-center gap-2 mb-3", tagClassName)}
|
Experience the power of innovation at your fingertips.
|
||||||
buttonContainerClassName={cls("flex gap-4 mt-3", buttonContainerClassName)}
|
</p>
|
||||||
buttonClassName={buttonClassName}
|
|
||||||
buttonTextClassName={buttonTextClassName}
|
<div className="flex flex-col sm:flex-row gap-4 justify-center items-center">
|
||||||
center={true}
|
<button className="inline-flex items-center px-8 py-4 bg-blue-600 text-white font-semibold rounded-lg hover:bg-blue-700 transition-colors duration-200 group">
|
||||||
/>
|
Get Started
|
||||||
<div className={cls("w-full overflow-hidden rounded-theme-capped card p-4", mediaWrapperClassName)}>
|
<ArrowRight className="ml-2 h-5 w-5 group-hover:translate-x-1 transition-transform duration-200" />
|
||||||
<MediaContent
|
</button>
|
||||||
imageSrc={imageSrc}
|
|
||||||
videoSrc={videoSrc}
|
<button className="inline-flex items-center px-8 py-4 bg-white text-gray-900 font-semibold rounded-lg border-2 border-gray-300 hover:border-gray-400 transition-colors duration-200 group">
|
||||||
imageAlt={imageAlt}
|
<Play className="mr-2 h-5 w-5" />
|
||||||
videoAriaLabel={videoAriaLabel}
|
Watch Demo
|
||||||
imageClassName={cls("z-1", imageClassName)}
|
</button>
|
||||||
/>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div className="absolute bottom-0 left-0 right-0 h-32 bg-gradient-to-t from-white to-transparent"></div>
|
||||||
</section>
|
</section>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
HeroBillboard.displayName = "HeroBillboard";
|
|
||||||
|
|
||||||
export default HeroBillboard;
|
export default HeroBillboard;
|
||||||
75
src/components/ui/ThemeProvider.tsx
Normal file
75
src/components/ui/ThemeProvider.tsx
Normal file
@@ -0,0 +1,75 @@
|
|||||||
|
import React, { createContext, useContext, useEffect, useState } from 'react';
|
||||||
|
|
||||||
|
type Theme = 'dark' | 'light' | 'system';
|
||||||
|
|
||||||
|
type ThemeProviderProps = {
|
||||||
|
children: React.ReactNode;
|
||||||
|
defaultTheme?: Theme;
|
||||||
|
storageKey?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
type ThemeProviderState = {
|
||||||
|
theme: Theme;
|
||||||
|
setTheme: (theme: Theme) => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialState: ThemeProviderState = {
|
||||||
|
theme: 'system',
|
||||||
|
setTheme: () => null,
|
||||||
|
};
|
||||||
|
|
||||||
|
const ThemeProviderContext = createContext<ThemeProviderState>(initialState);
|
||||||
|
|
||||||
|
export function ThemeProvider({
|
||||||
|
children,
|
||||||
|
defaultTheme = 'system',
|
||||||
|
storageKey = 'vite-ui-theme',
|
||||||
|
...props
|
||||||
|
}: ThemeProviderProps) {
|
||||||
|
const [theme, setTheme] = useState<Theme>(
|
||||||
|
() => (localStorage.getItem(storageKey) as Theme) || defaultTheme
|
||||||
|
);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const root = window.document.documentElement;
|
||||||
|
|
||||||
|
root.classList.remove('light', 'dark');
|
||||||
|
|
||||||
|
if (theme === 'system') {
|
||||||
|
const systemTheme = window.matchMedia('(prefers-color-scheme: dark)')
|
||||||
|
.matches
|
||||||
|
? 'dark'
|
||||||
|
: 'light';
|
||||||
|
|
||||||
|
root.classList.add(systemTheme);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
root.classList.add(theme);
|
||||||
|
}, [theme]);
|
||||||
|
|
||||||
|
const value = {
|
||||||
|
theme,
|
||||||
|
setTheme: (theme: Theme) => {
|
||||||
|
localStorage.setItem(storageKey, theme);
|
||||||
|
setTheme(theme);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<ThemeProviderContext.Provider {...props} value={value}>
|
||||||
|
{children}
|
||||||
|
</ThemeProviderContext.Provider>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export const useTheme = () => {
|
||||||
|
const context = useContext(ThemeProviderContext);
|
||||||
|
|
||||||
|
if (context === undefined)
|
||||||
|
throw new Error('useTheme must be used within a ThemeProvider');
|
||||||
|
|
||||||
|
return context;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ThemeProvider;
|
||||||
27
src/index.css
Normal file
27
src/index.css
Normal file
@@ -0,0 +1,27 @@
|
|||||||
|
@tailwind base;
|
||||||
|
@tailwind components;
|
||||||
|
@tailwind utilities;
|
||||||
|
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
html {
|
||||||
|
scroll-behavior: smooth;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
||||||
|
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
||||||
|
sans-serif;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
|
||||||
|
monospace;
|
||||||
|
}
|
||||||
13
src/index.tsx
Normal file
13
src/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
import React from 'react';
|
||||||
|
import ReactDOM from 'react-dom/client';
|
||||||
|
import './index.css';
|
||||||
|
import App from './App';
|
||||||
|
|
||||||
|
const root = ReactDOM.createRoot(
|
||||||
|
document.getElementById('root') as HTMLElement
|
||||||
|
);
|
||||||
|
root.render(
|
||||||
|
<React.StrictMode>
|
||||||
|
<App />
|
||||||
|
</React.StrictMode>
|
||||||
|
);
|
||||||
10
tailwind.config.js
Normal file
10
tailwind.config.js
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
module.exports = {
|
||||||
|
content: [
|
||||||
|
"./src/**/*.{js,jsx,ts,tsx}", "./public/index.html"
|
||||||
|
],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
@@ -1,42 +1,21 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"target": "ES2017",
|
"target": "es5", "lib": [
|
||||||
"lib": [
|
"dom", "dom.iterable", "esnext"
|
||||||
"dom",
|
|
||||||
"dom.iterable",
|
|
||||||
"esnext"
|
|
||||||
],
|
],
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"strict": false,
|
|
||||||
"noImplicitAny": false,
|
|
||||||
"noEmit": true,
|
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"module": "esnext",
|
"allowSyntheticDefaultImports": true,
|
||||||
"moduleResolution": "bundler",
|
"strict": true,
|
||||||
"resolveJsonModule": true,
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"noFallthroughCasesInSwitch": true,
|
||||||
|
"module": "esnext", "moduleResolution": "node", "resolveJsonModule": true,
|
||||||
"isolatedModules": true,
|
"isolatedModules": true,
|
||||||
"jsx": "react-jsx",
|
"noEmit": true,
|
||||||
"incremental": true,
|
"jsx": "react-jsx"
|
||||||
"plugins": [
|
|
||||||
{
|
|
||||||
"name": "next"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"paths": {
|
|
||||||
"@/*": [
|
|
||||||
"./src/*"
|
|
||||||
]
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
"include": [
|
"include": [
|
||||||
"next-env.d.ts",
|
"src"
|
||||||
"**/*.ts",
|
|
||||||
"**/*.tsx",
|
|
||||||
".next/types/**/*.ts",
|
|
||||||
".next/dev/types/**/*.ts"
|
|
||||||
],
|
|
||||||
"exclude": [
|
|
||||||
"node_modules"
|
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user