From fd1b32640af0ffca1cfa88d941c678e4219cca05 Mon Sep 17 00:00:00 2001 From: Rami Bitar Date: Sun, 4 Oct 2026 13:16:12 -0400 Subject: [PATCH] Initial commit of vite-saas template Co-Authored-By: Claude Opus 5.5 --- .gitignore | 12 + .yarnrc.yml | 1 + README.md | 52 + components.json | 28 + package.json | 51 + public/icon.svg | 4 + src/components/ai-elements/snippet.tsx | 143 + src/components/layout/mobile-navigation.tsx | 128 + src/components/layout/site-footer.tsx | 63 + src/components/layout/site-header.tsx | 53 + src/components/layout/theme-provider.tsx | 9 + src/components/layout/theme-toggle.tsx | 41 + src/components/route-states.tsx | 34 + src/components/sections/cta-section.tsx | 25 + src/components/sections/features-section.tsx | 87 + src/components/sections/hero-section.tsx | 82 + src/components/sections/logos-section.tsx | 83 + src/components/sections/stats-section.tsx | 32 + src/components/sections/stats/stats-globe.tsx | 65 + src/components/shared/action-buttons.tsx | 41 + src/components/shared/brand.tsx | 20 + src/components/shared/container.tsx | 9 + src/components/shared/gradient-glow.tsx | 21 + src/components/shared/section-heading.tsx | 44 + src/components/ui/accordion.tsx | 79 + src/components/ui/alert-dialog.tsx | 197 + src/components/ui/alert.tsx | 76 + src/components/ui/aspect-ratio.tsx | 9 + src/components/ui/attachment.tsx | 206 + src/components/ui/avatar.tsx | 110 + src/components/ui/badge.tsx | 49 + src/components/ui/breadcrumb.tsx | 122 + src/components/ui/bubble.tsx | 125 + src/components/ui/button-group.tsx | 83 + src/components/ui/button.tsx | 67 + src/components/ui/calendar.tsx | 220 + src/components/ui/card.tsx | 103 + src/components/ui/carousel.tsx | 240 + src/components/ui/checkbox.tsx | 31 + src/components/ui/collapsible.tsx | 31 + src/components/ui/combobox.tsx | 297 + src/components/ui/command.tsx | 193 + src/components/ui/context-menu.tsx | 261 + src/components/ui/dialog.tsx | 175 + src/components/ui/direction.tsx | 20 + src/components/ui/drawer.tsx | 132 + src/components/ui/dropdown-menu.tsx | 267 + src/components/ui/empty.tsx | 104 + src/components/ui/field.tsx | 236 + src/components/ui/globe.tsx | 133 + src/components/ui/hover-card.tsx | 42 + src/components/ui/input-group.tsx | 154 + src/components/ui/input-otp.tsx | 85 + src/components/ui/input.tsx | 19 + src/components/ui/item.tsx | 196 + src/components/ui/kbd.tsx | 26 + src/components/ui/label.tsx | 22 + src/components/ui/marker.tsx | 69 + src/components/ui/marquee.tsx | 74 + src/components/ui/menubar.tsx | 278 + src/components/ui/message-scroller.tsx | 129 + src/components/ui/message.tsx | 92 + src/components/ui/native-select.tsx | 61 + src/components/ui/navigation-menu.tsx | 164 + src/components/ui/pagination.tsx | 129 + src/components/ui/popover.tsx | 87 + src/components/ui/progress.tsx | 29 + src/components/ui/radio-group.tsx | 42 + src/components/ui/resizable.tsx | 48 + src/components/ui/scroll-area.tsx | 53 + src/components/ui/select.tsx | 190 + src/components/ui/separator.tsx | 26 + src/components/ui/sheet.tsx | 147 + src/components/ui/sidebar.tsx | 700 ++ src/components/ui/skeleton.tsx | 13 + src/components/ui/slider.tsx | 57 + src/components/ui/sonner.tsx | 47 + src/components/ui/spinner.tsx | 10 + src/components/ui/switch.tsx | 31 + src/components/ui/table.tsx | 114 + src/components/ui/tabs.tsx | 88 + src/components/ui/textarea.tsx | 18 + src/components/ui/toggle-group.tsx | 87 + src/components/ui/toggle.tsx | 45 + src/components/ui/tooltip.tsx | 55 + src/hooks/use-mobile.ts | 19 + src/lib/site.ts | 53 + src/lib/utils.ts | 6 + src/router.tsx | 20 + src/routes/__root.tsx | 44 + src/routes/index.tsx | 28 + src/styles.css | 164 + src/vite-env.d.ts | 1 + tsconfig.json | 21 + vercel.json | 6 + vite.config.mjs | 25 + yarn.lock | 6510 +++++++++++++++++ 97 files changed, 15018 insertions(+) create mode 100644 .gitignore create mode 100644 .yarnrc.yml create mode 100644 README.md create mode 100644 components.json create mode 100644 package.json create mode 100644 public/icon.svg create mode 100644 src/components/ai-elements/snippet.tsx create mode 100644 src/components/layout/mobile-navigation.tsx create mode 100644 src/components/layout/site-footer.tsx create mode 100644 src/components/layout/site-header.tsx create mode 100644 src/components/layout/theme-provider.tsx create mode 100644 src/components/layout/theme-toggle.tsx create mode 100644 src/components/route-states.tsx create mode 100644 src/components/sections/cta-section.tsx create mode 100644 src/components/sections/features-section.tsx create mode 100644 src/components/sections/hero-section.tsx create mode 100644 src/components/sections/logos-section.tsx create mode 100644 src/components/sections/stats-section.tsx create mode 100644 src/components/sections/stats/stats-globe.tsx create mode 100644 src/components/shared/action-buttons.tsx create mode 100644 src/components/shared/brand.tsx create mode 100644 src/components/shared/container.tsx create mode 100644 src/components/shared/gradient-glow.tsx create mode 100644 src/components/shared/section-heading.tsx create mode 100644 src/components/ui/accordion.tsx create mode 100644 src/components/ui/alert-dialog.tsx create mode 100644 src/components/ui/alert.tsx create mode 100644 src/components/ui/aspect-ratio.tsx create mode 100644 src/components/ui/attachment.tsx create mode 100644 src/components/ui/avatar.tsx create mode 100644 src/components/ui/badge.tsx create mode 100644 src/components/ui/breadcrumb.tsx create mode 100644 src/components/ui/bubble.tsx create mode 100644 src/components/ui/button-group.tsx create mode 100644 src/components/ui/button.tsx create mode 100644 src/components/ui/calendar.tsx create mode 100644 src/components/ui/card.tsx create mode 100644 src/components/ui/carousel.tsx create mode 100644 src/components/ui/checkbox.tsx create mode 100644 src/components/ui/collapsible.tsx create mode 100644 src/components/ui/combobox.tsx create mode 100644 src/components/ui/command.tsx create mode 100644 src/components/ui/context-menu.tsx create mode 100644 src/components/ui/dialog.tsx create mode 100644 src/components/ui/direction.tsx create mode 100644 src/components/ui/drawer.tsx create mode 100644 src/components/ui/dropdown-menu.tsx create mode 100644 src/components/ui/empty.tsx create mode 100644 src/components/ui/field.tsx create mode 100644 src/components/ui/globe.tsx create mode 100644 src/components/ui/hover-card.tsx create mode 100644 src/components/ui/input-group.tsx create mode 100644 src/components/ui/input-otp.tsx create mode 100644 src/components/ui/input.tsx create mode 100644 src/components/ui/item.tsx create mode 100644 src/components/ui/kbd.tsx create mode 100644 src/components/ui/label.tsx create mode 100644 src/components/ui/marker.tsx create mode 100644 src/components/ui/marquee.tsx create mode 100644 src/components/ui/menubar.tsx create mode 100644 src/components/ui/message-scroller.tsx create mode 100644 src/components/ui/message.tsx create mode 100644 src/components/ui/native-select.tsx create mode 100644 src/components/ui/navigation-menu.tsx create mode 100644 src/components/ui/pagination.tsx create mode 100644 src/components/ui/popover.tsx create mode 100644 src/components/ui/progress.tsx create mode 100644 src/components/ui/radio-group.tsx create mode 100644 src/components/ui/resizable.tsx create mode 100644 src/components/ui/scroll-area.tsx create mode 100644 src/components/ui/select.tsx create mode 100644 src/components/ui/separator.tsx create mode 100644 src/components/ui/sheet.tsx create mode 100644 src/components/ui/sidebar.tsx create mode 100644 src/components/ui/skeleton.tsx create mode 100644 src/components/ui/slider.tsx create mode 100644 src/components/ui/sonner.tsx create mode 100644 src/components/ui/spinner.tsx create mode 100644 src/components/ui/switch.tsx create mode 100644 src/components/ui/table.tsx create mode 100644 src/components/ui/tabs.tsx create mode 100644 src/components/ui/textarea.tsx create mode 100644 src/components/ui/toggle-group.tsx create mode 100644 src/components/ui/toggle.tsx create mode 100644 src/components/ui/tooltip.tsx create mode 100644 src/hooks/use-mobile.ts create mode 100644 src/lib/site.ts create mode 100644 src/lib/utils.ts create mode 100644 src/router.tsx create mode 100644 src/routes/__root.tsx create mode 100644 src/routes/index.tsx create mode 100644 src/styles.css create mode 100644 src/vite-env.d.ts create mode 100644 tsconfig.json create mode 100644 vercel.json create mode 100644 vite.config.mjs create mode 100644 yarn.lock diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3afb834 --- /dev/null +++ b/.gitignore @@ -0,0 +1,12 @@ +.DS_Store +node_modules/* +dist +.output +.nitro +.vercel +.yarn/install-state.gz +.env*.local +src/routeTree.gen.ts +*.tsbuildinfo + +.tanstack diff --git a/.yarnrc.yml b/.yarnrc.yml new file mode 100644 index 0000000..3186f3f --- /dev/null +++ b/.yarnrc.yml @@ -0,0 +1 @@ +nodeLinker: node-modules diff --git a/README.md b/README.md new file mode 100644 index 0000000..0286856 --- /dev/null +++ b/README.md @@ -0,0 +1,52 @@ +# Vite SaaS + +A SaaS marketing site built with Vite, TanStack Start, Tailwind CSS v4 and shadcn/ui. +Ported from `nextjs-templates/nextjs-saas`. + +Runs in the nodesandbox runtime: `npm install` → `npm run dev` → port 3000. + +## Getting started + +```sh +corepack enable # uses the Yarn 4 pinned in package.json +yarn install +yarn dev # http://localhost:3000 +``` + +| Script | What it does | +| ---------------- | --------------------------------------------------------- | +| `yarn dev` | Vite dev server with SSR | +| `yarn build` | Production build; Nitro packages the server for the host | +| `yarn start` | Run the Node build from `.output/` | +| `yarn typecheck` | `tsc` | + +## Deploying to Vercel + +Push and deploy — no dashboard settings. `vercel.json` selects the TanStack +Start preset and installs with Yarn 4 via Corepack; Nitro writes +`.vercel/output/` during `vite build`. + +## nodesandbox compatibility + +The sandbox runs Vite's native tools as WebAssembly (`NAPI_RS_FORCE_WASI`), and +its installer resolves from `package.json` ranges rather than `yarn.lock`. The +toolchain versions match `vite-shopify-storefront`: + +- `vite` is pinned to **8.3.2** (rolldown 1.2.x WASI binding). +- WASI bindings (`@rolldown/binding-wasm32-wasi`, `@tailwindcss/oxide-wasm32-wasi`) + are **not** listed: the runtime installs each package's `*-wasm32-wasi` + optional dependency itself, and npm on other hosts rejects them. +- `@vitejs/plugin-react` (6.0.0) and Tailwind (4.3.1) match the harness samples. +- Nitro (the hosting adapter) only loads for `vite build`, never in `vite dev`. + +## Layout + +``` +src/ + router.tsx Router setup + routes/ File routes (__root.tsx is the document shell, index.tsx the page) + components/ Sections, layout and shadcn/ui components + lib/site.ts Site name, copy and links + styles.css Tailwind v4 theme +public/ Static assets served from / +``` diff --git a/components.json b/components.json new file mode 100644 index 0000000..c6a8ac1 --- /dev/null +++ b/components.json @@ -0,0 +1,28 @@ +{ + "$schema": "https://ui.shadcn.com/schema.json", + "style": "radix-nova", + "rsc": false, + "tsx": true, + "tailwind": { + "config": "", + "css": "src/styles.css", + "baseColor": "neutral", + "cssVariables": true, + "prefix": "" + }, + "iconLibrary": "lucide", + "rtl": false, + "aliases": { + "components": "@/components", + "utils": "@/lib/utils", + "ui": "@/components/ui", + "lib": "@/lib", + "hooks": "@/hooks" + }, + "menuColor": "default", + "menuAccent": "subtle", + "registries": { + "@magicui": "https://magicui.design/r/{name}", + "@ai-elements": "https://ai-sdk.dev/elements/api/registry/{name}.json" + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..9d945f9 --- /dev/null +++ b/package.json @@ -0,0 +1,51 @@ +{ + "name": "vite-saas", + "type": "module", + "private": true, + "version": "0.0.0", + "scripts": { + "dev": "vite dev --host 0.0.0.0 --port 3000", + "build": "vite build", + "start": "node .output/server/index.mjs", + "typecheck": "tsc --noEmit" + }, + "packageManager": "yarn@4.18.0+sha512.fcb8716fe7cd0eece141ffc18b92193a9df9204c1ba83189c288835223fc0bbe64af473bab0d5e9927a7daeb5caf2bb07eb2787cc9338ca040ea125f2a1f2f7e", + "dependencies": { + "@base-ui/react": "^1.6.0", + "@remixicon/react": "^4.9.0", + "@shadcn/react": "^0.1.0", + "@tanstack/react-router": "^1.170.41", + "@tanstack/react-start": "^1.168.60", + "class-variance-authority": "^0.7.1", + "clsx": "^2.1.1", + "cmdk": "^1.1.1", + "cobe": "^0.6.4", + "date-fns": "^4.4.0", + "embla-carousel-react": "^8.6.0", + "input-otp": "^1.4.2", + "lucide-react": "^1.22.0", + "motion": "^13.3.0", + "next-themes": "^0.4.6", + "nitro": "3.0.260903-beta", + "radix-ui": "^1.6.0", + "react": "^19.3.0", + "react-day-picker": "^10.0.1", + "react-dom": "^19.3.0", + "react-resizable-panels": "^4.12.0", + "shadcn": "^4.12.0", + "sonner": "^2.0.7", + "tailwind-merge": "^3.6.0", + "tw-animate-css": "^1.4.0", + "vaul": "^1.1.2" + }, + "devDependencies": { + "@tailwindcss/vite": "4.3.1", + "@types/node": "^26.6.4", + "@types/react": "^19.3.0", + "@types/react-dom": "^19.3.0", + "@vitejs/plugin-react": "6.0.0", + "tailwindcss": "4.3.1", + "typescript": "^5.7.3", + "vite": "8.3.2" + } +} diff --git a/public/icon.svg b/public/icon.svg new file mode 100644 index 0000000..427de68 --- /dev/null +++ b/public/icon.svg @@ -0,0 +1,4 @@ + + + + diff --git a/src/components/ai-elements/snippet.tsx b/src/components/ai-elements/snippet.tsx new file mode 100644 index 0000000..a7664f1 --- /dev/null +++ b/src/components/ai-elements/snippet.tsx @@ -0,0 +1,143 @@ +import { + InputGroup, + InputGroupAddon, + InputGroupButton, + InputGroupInput, + InputGroupText, +} from "@/components/ui/input-group"; +import { cn } from "@/lib/utils"; +import { CheckIcon, CopyIcon } from "lucide-react"; +import type { ComponentProps } from "react"; +import { + createContext, + useCallback, + useContext, + useEffect, + useMemo, + useRef, + useState, +} from "react"; + +interface SnippetContextType { + code: string; +} + +const SnippetContext = createContext({ + code: "", +}); + +export type SnippetProps = ComponentProps & { + code: string; +}; + +export const Snippet = ({ + code, + className, + children, + ...props +}: SnippetProps) => { + const contextValue = useMemo(() => ({ code }), [code]); + + return ( + + + {children} + + + ); +}; + +export type SnippetAddonProps = ComponentProps; + +export const SnippetAddon = (props: SnippetAddonProps) => ( + +); + +export type SnippetTextProps = ComponentProps; + +export const SnippetText = ({ className, ...props }: SnippetTextProps) => ( + +); + +export type SnippetInputProps = Omit< + ComponentProps, + "readOnly" | "value" +>; + +export const SnippetInput = ({ className, ...props }: SnippetInputProps) => { + const { code } = useContext(SnippetContext); + + return ( + + ); +}; + +export type SnippetCopyButtonProps = ComponentProps & { + onCopy?: () => void; + onError?: (error: Error) => void; + timeout?: number; +}; + +export const SnippetCopyButton = ({ + onCopy, + onError, + timeout = 2000, + children, + className, + ...props +}: SnippetCopyButtonProps) => { + const [isCopied, setIsCopied] = useState(false); + const timeoutRef = useRef(0); + const { code } = useContext(SnippetContext); + + const copyToClipboard = useCallback(async () => { + if (typeof window === "undefined" || !navigator?.clipboard?.writeText) { + onError?.(new Error("Clipboard API not available")); + return; + } + + try { + if (!isCopied) { + await navigator.clipboard.writeText(code); + setIsCopied(true); + onCopy?.(); + timeoutRef.current = window.setTimeout( + () => setIsCopied(false), + timeout + ); + } + } catch (error) { + onError?.(error as Error); + } + }, [code, onCopy, onError, timeout, isCopied]); + + useEffect( + () => () => { + window.clearTimeout(timeoutRef.current); + }, + [] + ); + + const Icon = isCopied ? CheckIcon : CopyIcon; + + return ( + + {children ?? } + + ); +}; diff --git a/src/components/layout/mobile-navigation.tsx b/src/components/layout/mobile-navigation.tsx new file mode 100644 index 0000000..d812d87 --- /dev/null +++ b/src/components/layout/mobile-navigation.tsx @@ -0,0 +1,128 @@ +import { useEffect, useRef, useState } from 'react'; +import { RiCloseLine, RiMenuLine } from '@remixicon/react'; + +import { Brand } from '@/components/shared/brand'; +import { + Sheet, + SheetClose, + SheetContent, + SheetDescription, + SheetTitle, + SheetTrigger, +} from '@/components/ui/sheet'; +import { siteConfig, type NavigationItem } from '@/lib/site'; + +export function MobileNavigation({ items }: { items: NavigationItem[] }) { + const [open, setOpen] = useState(false); + const navigating = useRef(false); + + useEffect(() => { + const desktop = window.matchMedia('(min-width: 768px)'); + const closeOnDesktop = () => { + if (desktop.matches) setOpen(false); + }; + + desktop.addEventListener('change', closeOnDesktop); + return () => desktop.removeEventListener('change', closeOnDesktop); + }, []); + + return ( + + + + + + { + // Let the anchor move focus to its section without the trigger + // interrupting the page's smooth scroll when the sheet closes. + if (navigating.current) event.preventDefault(); + navigating.current = false; + }} + > +
+ + + + +
+ + + + +
+
+ ); +} diff --git a/src/components/layout/site-footer.tsx b/src/components/layout/site-footer.tsx new file mode 100644 index 0000000..4f736ff --- /dev/null +++ b/src/components/layout/site-footer.tsx @@ -0,0 +1,63 @@ +import { RiGithubFill, RiTwitterXFill } from '@remixicon/react'; + +import { Brand } from '@/components/shared/brand'; +import { Container } from '@/components/shared/container'; +import { footerColumns, siteConfig } from '@/lib/site'; + +const socialLinks = [ + { label: 'GitHub', href: siteConfig.links.github, Icon: RiGithubFill }, + { label: 'X (Twitter)', href: siteConfig.links.twitter, Icon: RiTwitterXFill }, +]; + +export function SiteFooter() { + return ( +
+ +
+
+ +

+ {siteConfig.tagline} +

+
+ {footerColumns.map((column) => ( +
+

+ {column.heading} +

+ +
+ ))} +
+
+ + © {new Date().getFullYear()} {siteConfig.name}, Inc. All rights reserved. + +
+ {socialLinks.map(({ label, href, Icon }) => ( + + + ))} +
+
+
+
+ ); +} diff --git a/src/components/layout/site-header.tsx b/src/components/layout/site-header.tsx new file mode 100644 index 0000000..58d530b --- /dev/null +++ b/src/components/layout/site-header.tsx @@ -0,0 +1,53 @@ +import { MobileNavigation } from '@/components/layout/mobile-navigation'; +import { ThemeToggle } from '@/components/layout/theme-toggle'; +import { Brand } from '@/components/shared/brand'; +import { Container } from '@/components/shared/container'; +import { Button } from '@/components/ui/button'; +import { navigationItems, siteConfig } from '@/lib/site'; + +export function SiteHeader() { + return ( +
+ +
+ + + + +
+
+ + + + +
+
+
+ ); +} diff --git a/src/components/layout/theme-provider.tsx b/src/components/layout/theme-provider.tsx new file mode 100644 index 0000000..2e1dd00 --- /dev/null +++ b/src/components/layout/theme-provider.tsx @@ -0,0 +1,9 @@ +import * as React from 'react'; +import { ThemeProvider as NextThemesProvider } from 'next-themes'; + +export function ThemeProvider({ + children, + ...props +}: React.ComponentProps) { + return {children}; +} diff --git a/src/components/layout/theme-toggle.tsx b/src/components/layout/theme-toggle.tsx new file mode 100644 index 0000000..666b3c8 --- /dev/null +++ b/src/components/layout/theme-toggle.tsx @@ -0,0 +1,41 @@ +import { useSyncExternalStore } from 'react'; +import { RiMoonLine, RiSunLine } from '@remixicon/react'; +import { useTheme } from 'next-themes'; + +import { Button } from '@/components/ui/button'; + +const emptySubscribe = () => () => {}; + +export function ThemeToggle() { + const mounted = useSyncExternalStore( + emptySubscribe, + () => true, + () => false, + ); + const { resolvedTheme, setTheme } = useTheme(); + + const isDark = mounted && resolvedTheme === 'dark'; + + return ( + + ); +} diff --git a/src/components/route-states.tsx b/src/components/route-states.tsx new file mode 100644 index 0000000..f10e016 --- /dev/null +++ b/src/components/route-states.tsx @@ -0,0 +1,34 @@ +import { Link } from '@tanstack/react-router'; + +// Stand-ins for Next's built-in 404 and error pages. + +export function NotFound() { + return ( +
+
+
404
+
+
+ This page could not be found.{' '} + + Go home + +
+
+
+ ); +} + +export function ErrorMessage({ error }: { error: unknown }) { + return ( +
+
+
Something went wrong
+
+
+ {error instanceof Error ? error.message : String(error)} +
+
+
+ ); +} diff --git a/src/components/sections/cta-section.tsx b/src/components/sections/cta-section.tsx new file mode 100644 index 0000000..4c00bc1 --- /dev/null +++ b/src/components/sections/cta-section.tsx @@ -0,0 +1,25 @@ +import { ActionButtons } from '@/components/shared/action-buttons'; +import { Container } from '@/components/shared/container'; +import { GradientGlow } from '@/components/shared/gradient-glow'; +import { SectionHeading } from '@/components/shared/section-heading'; + +export function CtaSection() { + return ( +
+ + + + + +
+ ); +} diff --git a/src/components/sections/features-section.tsx b/src/components/sections/features-section.tsx new file mode 100644 index 0000000..37fa5f9 --- /dev/null +++ b/src/components/sections/features-section.tsx @@ -0,0 +1,87 @@ +import { Container } from '@/components/shared/container'; +import { SectionHeading } from '@/components/shared/section-heading'; + +const features = [ + { + step: '01', + title: 'Build', + description: + 'Native support for 35+ frameworks. Push your code and Vertex handles the build pipeline, caching, and dependencies automatically.', + icon: , + }, + { + step: '02', + title: 'Preview', + description: + 'Every push gets a live preview URL. Share it, comment on it, and ship with confidence — no environment drift, no surprises.', + icon: ( + <> + + + + ), + }, + { + step: '03', + title: 'Ship', + description: + 'Deploy to a global edge network in seconds. Instant rollbacks, zero-downtime releases, and traffic that lands close to every user.', + icon: ( + + ), + }, +]; + +export function FeaturesSection() { + return ( +
+ + + +
+ {features.map((feature) => ( +
+
+ + + {feature.step} + +
+

{feature.title}

+

+ {feature.description} +

+
+ ))} +
+
+
+ ); +} diff --git a/src/components/sections/hero-section.tsx b/src/components/sections/hero-section.tsx new file mode 100644 index 0000000..dc5d743 --- /dev/null +++ b/src/components/sections/hero-section.tsx @@ -0,0 +1,82 @@ +import { + Snippet, + SnippetAddon, + SnippetCopyButton, + SnippetInput, + SnippetText, +} from '@/components/ai-elements/snippet'; +import { ActionButtons } from '@/components/shared/action-buttons'; +import { Container } from '@/components/shared/container'; +import { GradientGlow } from '@/components/shared/gradient-glow'; + +const hero = { + announcement: { label: 'Vertex Edge is now generally available', href: '#' }, + title: 'Build. Preview. Ship.', + description: + 'Vertex is the frontend cloud. Give your team the tooling and global infrastructure to build, preview, and ship a faster web — from your first commit to production traffic.', + primary: { label: 'Start Deploying', href: '#' }, + secondary: { label: 'Get a Demo', href: '#' }, + command: 'vertex deploy --prod', +}; + +function GridBackdrop() { + return ( +