mirror of
https://github.com/jamiepine/voicebox.git
synced 2026-09-19 06:40:38 -07:00
Add landing page for voicebox.sh
- Created a new landing page using Next.js 16, featuring a modern design with Tailwind CSS. - Implemented essential components including Header, Footer, and DownloadSection for user navigation and download options. - Configured Tailwind CSS and PostCSS for styling, and set up TypeScript for type safety. - Added a README with setup instructions, project structure, and deployment guidelines. - Integrated GitHub links for easy access to the repository and releases. - Established a consistent UI with reusable components and responsive design.
This commit is contained in:
@@ -0,0 +1,55 @@
|
||||
import * as React from 'react';
|
||||
import { Slot } from '@radix-ui/react-slot';
|
||||
import { cva, type VariantProps } from 'class-variance-authority';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center whitespace-nowrap rounded-md text-sm font-medium ring-offset-background transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default: 'bg-primary text-primary-foreground hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground hover:bg-destructive/90',
|
||||
outline:
|
||||
'border border-input bg-background hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-10 px-4 py-2',
|
||||
sm: 'h-9 rounded-md px-3',
|
||||
lg: 'h-11 rounded-md text-lg px-8',
|
||||
icon: 'h-10 w-10',
|
||||
},
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: 'default',
|
||||
size: 'default',
|
||||
},
|
||||
}
|
||||
);
|
||||
|
||||
export interface ButtonProps
|
||||
extends React.ButtonHTMLAttributes<HTMLButtonElement>,
|
||||
VariantProps<typeof buttonVariants> {
|
||||
asChild?: boolean;
|
||||
}
|
||||
|
||||
const Button = React.forwardRef<HTMLButtonElement, ButtonProps>(
|
||||
({ className, variant, size, asChild = false, ...props }, ref) => {
|
||||
const Comp = asChild ? Slot : 'button';
|
||||
return (
|
||||
<Comp
|
||||
className={cn(buttonVariants({ variant, size, className }))}
|
||||
ref={ref}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
);
|
||||
Button.displayName = 'Button';
|
||||
|
||||
export { Button, buttonVariants };
|
||||
@@ -0,0 +1,78 @@
|
||||
import * as React from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Card = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'rounded-lg border bg-card text-card-foreground shadow-sm',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
Card.displayName = 'Card';
|
||||
|
||||
const CardHeader = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex flex-col space-y-1.5 p-6', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardHeader.displayName = 'CardHeader';
|
||||
|
||||
const CardTitle = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLHeadingElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<h3
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'text-2xl font-semibold leading-none tracking-tight',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardTitle.displayName = 'CardTitle';
|
||||
|
||||
const CardDescription = React.forwardRef<
|
||||
HTMLParagraphElement,
|
||||
React.HTMLAttributes<HTMLParagraphElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<p
|
||||
ref={ref}
|
||||
className={cn('text-sm text-muted-foreground', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardDescription.displayName = 'CardDescription';
|
||||
|
||||
const CardContent = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div ref={ref} className={cn('p-6 pt-0', className)} {...props} />
|
||||
));
|
||||
CardContent.displayName = 'CardContent';
|
||||
|
||||
const CardFooter = React.forwardRef<
|
||||
HTMLDivElement,
|
||||
React.HTMLAttributes<HTMLDivElement>
|
||||
>(({ className, ...props }, ref) => (
|
||||
<div
|
||||
ref={ref}
|
||||
className={cn('flex items-center p-6 pt-0', className)}
|
||||
{...props}
|
||||
/>
|
||||
));
|
||||
CardFooter.displayName = 'CardFooter';
|
||||
|
||||
export { Card, CardHeader, CardFooter, CardTitle, CardDescription, CardContent };
|
||||
@@ -0,0 +1,26 @@
|
||||
import { ReactNode } from 'react';
|
||||
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from './card';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
interface FeatureCardProps {
|
||||
title: string;
|
||||
description: string;
|
||||
icon?: ReactNode;
|
||||
className?: string;
|
||||
}
|
||||
|
||||
export function FeatureCard({ title, description, icon, className }: FeatureCardProps) {
|
||||
return (
|
||||
<Card className={cn('text-center', className)}>
|
||||
<CardHeader>
|
||||
{icon && <div className="flex justify-center mb-2">{icon}</div>}
|
||||
<CardTitle className="text-xl sm:text-2xl">{title}</CardTitle>
|
||||
</CardHeader>
|
||||
<CardContent>
|
||||
<CardDescription className="text-sm sm:text-base">
|
||||
{description}
|
||||
</CardDescription>
|
||||
</CardContent>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
'use client';
|
||||
|
||||
import { ReactNode } from 'react';
|
||||
import { cn } from '@/lib/utils';
|
||||
import Image from 'next/image';
|
||||
|
||||
interface HeroProps {
|
||||
title: ReactNode;
|
||||
description: string;
|
||||
actions?: ReactNode;
|
||||
className?: string;
|
||||
showLogo?: boolean;
|
||||
}
|
||||
|
||||
export function Hero({ title, description, actions, className, showLogo = true }: HeroProps) {
|
||||
return (
|
||||
<section className={cn('relative text-center pt-8 sm:pt-12 md:pt-14 -mb-6 sm:-mb-8 md:-mb-10 overflow-hidden -mx-4 sm:-mx-6 md:-mx-4 -mt-4 sm:mt-0 md:mt-0', className)}>
|
||||
<div className="relative z-10 px-4">
|
||||
{showLogo && (
|
||||
<div className="flex justify-center mb-4 sm:mb-6">
|
||||
<Image
|
||||
src="/voicebox-logo.png"
|
||||
alt="Voicebox Logo"
|
||||
width={1024}
|
||||
height={1024}
|
||||
className="w-32 sm:w-40 md:w-48 h-auto"
|
||||
priority
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
<h1 className="text-4xl sm:text-5xl md:text-6xl lg:text-7xl font-bold mb-4 sm:mb-6">
|
||||
{title}
|
||||
</h1>
|
||||
<p className="text-base sm:text-lg md:text-xl text-foreground/60 mb-6 sm:mb-8 max-w-2xl mx-auto px-2">
|
||||
{description}
|
||||
</p>
|
||||
{actions && (
|
||||
<div className="flex flex-col sm:flex-row gap-3 sm:gap-4 justify-center px-2">
|
||||
{actions}
|
||||
</div>
|
||||
)}
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
import { cn } from '@/lib/utils';
|
||||
import { ReactNode } from 'react';
|
||||
|
||||
interface SectionProps {
|
||||
children: ReactNode;
|
||||
className?: string;
|
||||
id?: string;
|
||||
}
|
||||
|
||||
export function Section({ children, className, id }: SectionProps) {
|
||||
return (
|
||||
<section id={id} className={cn('space-y-4 sm:space-y-6', className)}>
|
||||
{children}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
||||
export function SectionTitle({ children, className }: { children: ReactNode; className?: string }) {
|
||||
return (
|
||||
<h2 className={cn('text-2xl sm:text-3xl md:text-4xl font-bold text-center md:text-left', className)}>
|
||||
{children}
|
||||
</h2>
|
||||
);
|
||||
}
|
||||
@@ -0,0 +1,28 @@
|
||||
import * as React from 'react';
|
||||
import * as SeparatorPrimitive from '@radix-ui/react-separator';
|
||||
import { cn } from '@/lib/utils';
|
||||
|
||||
const Separator = React.forwardRef<
|
||||
React.ElementRef<typeof SeparatorPrimitive.Root>,
|
||||
React.ComponentPropsWithoutRef<typeof SeparatorPrimitive.Root>
|
||||
>(
|
||||
(
|
||||
{ className, orientation = 'horizontal', decorative = true, ...props },
|
||||
ref
|
||||
) => (
|
||||
<SeparatorPrimitive.Root
|
||||
ref={ref}
|
||||
decorative={decorative}
|
||||
orientation={orientation}
|
||||
className={cn(
|
||||
'shrink-0 bg-border',
|
||||
orientation === 'horizontal' ? 'h-[1px] w-full' : 'h-full w-[1px]',
|
||||
className
|
||||
)}
|
||||
{...props}
|
||||
/>
|
||||
)
|
||||
);
|
||||
Separator.displayName = SeparatorPrimitive.Root.displayName;
|
||||
|
||||
export { Separator };
|
||||
Reference in New Issue
Block a user