From d8a4979fe4a84b38a77e58fdcfc61c3386eb26a4 Mon Sep 17 00:00:00 2001 From: amianthus <49116958+SirTenzin@users.noreply.github.com> Date: Thu, 16 Apr 2026 17:52:14 +0100 Subject: [PATCH] feat: migrate landing page to TypeScript and fix auth cookies - Migrate all apps/website components from JS/JSX to TypeScript/TSX - Add blog pages (layout, listing, slug) in TypeScript - Add tsconfig.json and tailwind.config.mjs to website - Fix cookie domain handling in vite/App.tsx (set .useautumn.com in prod) - Update auth.ts, afterSessionCreated, afterSessionDeleted Made-with: Cursor --- apps/website/app/blog/[slug]/page.tsx | 114 ++ apps/website/app/blog/layout.js | 30 - apps/website/app/blog/layout.tsx | 33 + apps/website/app/blog/page.js | 79 - apps/website/app/blog/page.tsx | 80 + apps/website/app/constant.js | 1303 ---------------- apps/website/app/constant.tsx | 1340 +++++++++++++++++ apps/website/app/layout.js | 81 - apps/website/app/layout.tsx | 90 ++ apps/website/app/not-found.js | 111 -- apps/website/app/not-found.tsx | 114 ++ apps/website/app/page.tsx | 44 + apps/website/app/privacy/page.js | 191 --- apps/website/app/privacy/page.tsx | 201 +++ .../components/animated-footer-image.jsx | 25 - .../components/animated-footer-image.tsx | 29 + apps/website/components/autumn-config.jsx | 247 --- apps/website/components/autumn-config.tsx | 261 ++++ apps/website/components/blogComponents.jsx | 89 -- apps/website/components/blogComponents.tsx | 110 ++ .../components/dashboard-icon-pixel.jsx | 65 - .../components/dashboard-icon-pixel.tsx | 70 + apps/website/components/elastic-footer.jsx | 143 -- apps/website/components/elastic-footer.tsx | 144 ++ apps/website/components/faq.jsx | 147 -- apps/website/components/faq.tsx | 154 ++ .../components/feature-icon-animation.jsx | 68 - .../components/feature-icon-animation.tsx | 75 + apps/website/components/features.jsx | 73 - apps/website/components/features.tsx | 72 + apps/website/components/footer.jsx | 181 --- apps/website/components/footer.tsx | 180 +++ apps/website/components/hero.tsx | 307 ++++ apps/website/components/home-sections.tsx | 46 + apps/website/components/navbar.jsx | 421 ------ apps/website/components/navbar.tsx | 453 ++++++ apps/website/components/preloader.jsx | 238 --- apps/website/components/preloader.tsx | 244 +++ apps/website/components/pricing-models.jsx | 252 ---- apps/website/components/pricing-models.tsx | 263 ++++ apps/website/components/pricing.jsx | 177 --- apps/website/components/pricing.tsx | 178 +++ apps/website/components/problem-animation.jsx | 80 - apps/website/components/problem-animation.tsx | 80 + apps/website/components/problem.tsx | 100 ++ apps/website/components/production-scale.jsx | 229 --- apps/website/components/production-scale.tsx | 223 +++ apps/website/components/section-divider.jsx | 19 - apps/website/components/section-divider.tsx | 19 + .../website/components/solution-animation.jsx | 54 - .../website/components/solution-animation.tsx | 54 + apps/website/components/solution.jsx | 68 - apps/website/components/solution.tsx | 68 + apps/website/components/testimonials.jsx | 222 --- apps/website/components/testimonials.tsx | 222 +++ apps/website/lib/blogUtils.js | 62 - apps/website/lib/blogUtils.ts | 75 + apps/website/lib/types.ts | 34 + apps/website/lib/utils.ts | 2 + apps/website/tailwind.config.mjs | 7 + apps/website/tsconfig.json | 50 + server/src/utils/auth.ts | 8 +- .../utils/authUtils/afterSessionCreated.ts | 3 +- .../utils/authUtils/afterSessionDeleted.ts | 2 +- vite/src/App.tsx | 15 +- 65 files changed, 5547 insertions(+), 4672 deletions(-) create mode 100644 apps/website/app/blog/[slug]/page.tsx delete mode 100644 apps/website/app/blog/layout.js create mode 100644 apps/website/app/blog/layout.tsx delete mode 100644 apps/website/app/blog/page.js create mode 100644 apps/website/app/blog/page.tsx delete mode 100755 apps/website/app/constant.js create mode 100755 apps/website/app/constant.tsx delete mode 100644 apps/website/app/layout.js create mode 100644 apps/website/app/layout.tsx delete mode 100644 apps/website/app/not-found.js create mode 100644 apps/website/app/not-found.tsx create mode 100644 apps/website/app/page.tsx delete mode 100644 apps/website/app/privacy/page.js create mode 100644 apps/website/app/privacy/page.tsx delete mode 100644 apps/website/components/animated-footer-image.jsx create mode 100644 apps/website/components/animated-footer-image.tsx delete mode 100644 apps/website/components/autumn-config.jsx create mode 100644 apps/website/components/autumn-config.tsx delete mode 100644 apps/website/components/blogComponents.jsx create mode 100644 apps/website/components/blogComponents.tsx delete mode 100644 apps/website/components/dashboard-icon-pixel.jsx create mode 100644 apps/website/components/dashboard-icon-pixel.tsx delete mode 100644 apps/website/components/elastic-footer.jsx create mode 100644 apps/website/components/elastic-footer.tsx delete mode 100644 apps/website/components/faq.jsx create mode 100644 apps/website/components/faq.tsx delete mode 100644 apps/website/components/feature-icon-animation.jsx create mode 100644 apps/website/components/feature-icon-animation.tsx delete mode 100755 apps/website/components/features.jsx create mode 100755 apps/website/components/features.tsx delete mode 100644 apps/website/components/footer.jsx create mode 100644 apps/website/components/footer.tsx create mode 100755 apps/website/components/hero.tsx create mode 100755 apps/website/components/home-sections.tsx delete mode 100644 apps/website/components/navbar.jsx create mode 100644 apps/website/components/navbar.tsx delete mode 100644 apps/website/components/preloader.jsx create mode 100644 apps/website/components/preloader.tsx delete mode 100755 apps/website/components/pricing-models.jsx create mode 100755 apps/website/components/pricing-models.tsx delete mode 100755 apps/website/components/pricing.jsx create mode 100755 apps/website/components/pricing.tsx delete mode 100755 apps/website/components/problem-animation.jsx create mode 100755 apps/website/components/problem-animation.tsx create mode 100755 apps/website/components/problem.tsx delete mode 100755 apps/website/components/production-scale.jsx create mode 100755 apps/website/components/production-scale.tsx delete mode 100644 apps/website/components/section-divider.jsx create mode 100644 apps/website/components/section-divider.tsx delete mode 100644 apps/website/components/solution-animation.jsx create mode 100644 apps/website/components/solution-animation.tsx delete mode 100644 apps/website/components/solution.jsx create mode 100644 apps/website/components/solution.tsx delete mode 100755 apps/website/components/testimonials.jsx create mode 100755 apps/website/components/testimonials.tsx delete mode 100644 apps/website/lib/blogUtils.js create mode 100644 apps/website/lib/blogUtils.ts create mode 100644 apps/website/lib/types.ts create mode 100644 apps/website/lib/utils.ts create mode 100644 apps/website/tailwind.config.mjs create mode 100644 apps/website/tsconfig.json diff --git a/apps/website/app/blog/[slug]/page.tsx b/apps/website/app/blog/[slug]/page.tsx new file mode 100644 index 000000000..364634caf --- /dev/null +++ b/apps/website/app/blog/[slug]/page.tsx @@ -0,0 +1,114 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { notFound } from "next/navigation"; +import { MDXRemote } from "next-mdx-remote/rsc"; +import { mdxComponents } from "@/components/blogComponents"; +import { getAllPosts, getPostBySlug } from "@/lib/blogUtils"; +import type { BlogParams } from "@/lib/types"; + +export function generateStaticParams() { + return getAllPosts().map((post) => ({ slug: post.slug })); +} + +export async function generateMetadata({ + params, +}: { + params: BlogParams; +}): Promise { + const { slug } = await params; + const post = getPostBySlug({ slug }); + if (!post) return { title: "Post Not Found" }; + + return { + title: post.title, + description: post.description, + openGraph: { + title: post.title, + description: post.description, + type: "article", + ...(post.date ? { publishedTime: post.date } : {}), + authors: [post.author], + ...(post.image && { + images: [{ url: post.image }], + }), + }, + }; +} + +function formatDate(dateString: string | null) { + if (!dateString) return ""; + return new Date(dateString).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +export default async function BlogPostPage({ params }: { params: BlogParams }) { + const { slug } = await params; + const post = getPostBySlug({ slug }); + if (!post) notFound(); + + return ( +
+
+ + + + + Back to blog + + +
+
+ {formatDate(post.date)} + + {post.author} +
+

+ {post.title} +

+ {post.description && ( +

+ {post.description} +

+ )} +
+ + {post.image && ( +
+ {post.title} +
+ )} + +
+ +
+ +
+
+
+ ); +} diff --git a/apps/website/app/blog/layout.js b/apps/website/app/blog/layout.js deleted file mode 100644 index e6048c01a..000000000 --- a/apps/website/app/blog/layout.js +++ /dev/null @@ -1,30 +0,0 @@ -import Navbar from "@/components/navbar"; -import Footer from "@/components/footer"; - -export default function BlogLayout({ children }) { - return ( -
-
-
-
- -
- -
-
-
-
-
-
- - {children} - -
-
-
-
- ); -} diff --git a/apps/website/app/blog/layout.tsx b/apps/website/app/blog/layout.tsx new file mode 100644 index 000000000..21ac4e9c7 --- /dev/null +++ b/apps/website/app/blog/layout.tsx @@ -0,0 +1,33 @@ +import Footer from "@/components/footer"; +import Navbar from "@/components/navbar"; +import type { LayoutProps, PageStyle } from "@/lib/types"; + +export default function BlogLayout({ children }: LayoutProps) { + return ( +
+
+
+
+ +
+ +
+
+
+
+
+
+ + {children} + +
+
+
+
+ ); +} diff --git a/apps/website/app/blog/page.js b/apps/website/app/blog/page.js deleted file mode 100644 index 98382eb81..000000000 --- a/apps/website/app/blog/page.js +++ /dev/null @@ -1,79 +0,0 @@ -import { getAllPosts } from "@/lib/blogUtils"; -import Image from "next/image"; -import Link from "next/link"; - -export const metadata = { - title: "Blog", - description: - "Thoughts on billing infrastructure, usage-based pricing, and building for AI startups.", -}; - -function formatDate(dateString) { - if (!dateString) return ""; - return new Date(dateString).toLocaleDateString("en-US", { - year: "numeric", - month: "long", - day: "numeric", - }); -} - -export default function BlogListingPage() { - const posts = getAllPosts(); - - return ( -
-
-

- From the - Blog -

-

- Thoughts on billing infrastructure, usage-based pricing, and building - for AI startups. -

- - {posts.length === 0 && ( -

- No posts yet. Check back soon. -

- )} - -
- {posts.map((post) => ( - -
-
- {formatDate(post.date)} - - {post.author} -
-

- {post.title} -

- {post.description && ( -

- {post.description} -

- )} -
- {post.image && ( -
- {post.title} -
- )} - - ))} -
-
-
- ); -} diff --git a/apps/website/app/blog/page.tsx b/apps/website/app/blog/page.tsx new file mode 100644 index 000000000..003f52d2d --- /dev/null +++ b/apps/website/app/blog/page.tsx @@ -0,0 +1,80 @@ +import type { Metadata } from "next"; +import Image from "next/image"; +import Link from "next/link"; +import { getAllPosts } from "@/lib/blogUtils"; + +export const metadata: Metadata = { + title: "Blog", + description: + "Thoughts on billing infrastructure, usage-based pricing, and building for AI startups.", +}; + +function formatDate(dateString: string | null) { + if (!dateString) return ""; + return new Date(dateString).toLocaleDateString("en-US", { + year: "numeric", + month: "long", + day: "numeric", + }); +} + +export default function BlogListingPage() { + const posts = getAllPosts(); + + return ( +
+
+

+ From the + Blog +

+

+ Thoughts on billing infrastructure, usage-based pricing, and building + for AI startups. +

+ + {posts.length === 0 && ( +

+ No posts yet. Check back soon. +

+ )} + +
+ {posts.map((post) => ( + +
+
+ {formatDate(post.date)} + + {post.author} +
+

+ {post.title} +

+ {post.description && ( +

+ {post.description} +

+ )} +
+ {post.image && ( +
+ {post.title} +
+ )} + + ))} +
+
+
+ ); +} diff --git a/apps/website/app/constant.js b/apps/website/app/constant.js deleted file mode 100755 index 65bb9df56..000000000 --- a/apps/website/app/constant.js +++ /dev/null @@ -1,1303 +0,0 @@ -import { motion } from "motion/react"; -import { forwardRef } from "react"; - -export const CyberGlitchIcon = forwardRef(({ paths, className }, ref) => ( - - {paths.map((d, i) => ( - - ))} - -)); -CyberGlitchIcon.displayName = "CyberGlitchIcon"; - -export const MenuGridIcon = ({ isOpen }) => { - const gridSquares = [ - { is: "M0 0H3V3H0V0Z", ts: "M0 0H3V3H0V0Z" }, - { is: "M6 0H9V3H6V0Z", ts: "M3 3H6V6H3V3Z" }, - { is: "M12 0H15V3H12V0Z", ts: "M12 0H15V3H12V0Z" }, - { is: "M0 6H3V9H0V6Z", ts: "M3 9H6V12H3V9Z" }, - { is: "M6 6H9V9H6V6Z", ts: "M6 6H9V9H6V6Z" }, - { is: "M12 6H15V9H12V6Z", ts: "M9 3H12V6H9V3Z" }, - { is: "M0 12H3V15H0V12Z", ts: "M0 12H3V15H0V12Z" }, - { is: "M6 12H9V15H6V12Z", ts: "M9 9H12V12H9V9Z" }, - { is: "M12 12H15V15H12V12Z", ts: "M12 12H15V15H12V12Z" }, - ]; - - return ( - - {gridSquares.map((sq, i) => ( - - ))} - - ); -}; - -export const CTALines = () => { - const lines = [ - { bottom: "0%", opacity: 0.25 }, - { bottom: "15%", opacity: 0.18 }, - { bottom: "30%", opacity: 0.12 }, - { bottom: "45%", opacity: 0.08 }, - { bottom: "60%", opacity: 0.05 }, - { bottom: "75%", opacity: 0.02 }, - ]; - - return ( - - {lines.map((line, i) => ( -
- ))} - - ); -}; - -export const IconCTAStart = () => ( - - - - - - - - - - - - - - - - - - -); - -export const IconCTADocs = () => ( - - - - - - - - - - - - - - - - - - - - - - - - - - -); - -export const IconDiscord = forwardRef((props, ref) => ( - -)); -IconDiscord.displayName = "IconDiscord"; - -// 2. Blog -export const IconBlog = forwardRef((props, ref) => ( - -)); -IconBlog.displayName = "IconBlog"; - -// 3. Docs -export const IconDocs = forwardRef((props, ref) => ( - -)); -IconDocs.displayName = "IconDocs"; - -// 4. Pricing -export const IconPricing = forwardRef((props, ref) => ( - -)); -IconPricing.displayName = "IconPricing"; - -export const IconDashboard = forwardRef(({ className }, ref) => ( - - - - - - - - - - - -)); -IconDashboard.displayName = "IconDashboard"; - -export const IconWebhooks = (props) => ( - - - - - - - - - - - - - -); - -// Usage Analytics: analytics.svg -export const IconAnalytics = (props) => ( - - - - - - - - - - - -); - -// Team Billing: billing.svg -export const IconTeam = (props) => ( - - - - - - - - - - - -); - -// Auto Top-ups: top-ups.svg -export const IconTopUp = (props) => ( - - - - - - - - - - - - - - - -); - -// Custom Plans: custom-plans.svg -export const IconPlans = (props) => ( - - - - - - - - - - - -); - -// Pricing Versioning: pricing.svg -export const IconVersioning = (props) => ( - - - - - - - - - - - - - - -); - -// React Components: react-components.svg -export const IconReact = (props) => ( - - - - - - - - - - -); - -// Referral Programs: referral.svg -export const IconReferral = (props) => ( - - - - - - - - - - - - - - - - - -); - -export const IconArrowLeft = ({ className, disabled, ...props }) => ( - - - - - - - - - - - - - - - - - - - -); - -export const IconArrowRight = ({ className, disabled, ...props }) => ( - - - - - - - - - - - - - - - - - -); - -export const IconQuotes = ({ className, ...props }) => ( - - - - - - - - - - -); - -export const IconTick = ({ className, ...props }) => ( - - - - -); - -export const IconArrowRightSmall = ({ className, ...props }) => ( - - - - - - - - - - - - - - - - - -); - -export const AnimatedPlusMinus = ({ isOpen, className, ...props }) => { - // Fragments that form the vertical line of the "Plus" - const vPixels = [ - { y: 3, originY: "center", xBurst: -8, delay: 0 }, - { y: 5, originY: "center", xBurst: -4, delay: 0.05 }, - { y: 9, originY: "center", xBurst: 4, delay: 0.05 }, - { y: 11, originY: "center", xBurst: 8, delay: 0 }, - ]; - - return ( - - {/* Corner decorative pixels - stay static but fade slightly */} - {[ - [0, 0], - [14, 0], - [0, 14], - [14, 14], - ].map(([x, y], i) => ( - - ))} - - {/* Horizontal Bar - The "Minus" part (Static) */} - - - - - - - {/* Vertical Bursting Pixels */} - {vPixels.map((pixel, i) => ( - - ))} - - ); -}; - -export const IconPlus = ({ className, ...props }) => ( - - - - - - - - - - - - - - - - -); - -export const IconMinus = ({ className, ...props }) => ( - - - - - - - - - - -); - -export const faqData = [ - { - id: 1, - question: "Do I still need Stripe?", - answer: - "Yes. Autumn works with Stripe—it handles the billing logic that Stripe doesn't. You keep your Stripe account, your customer relationships, and your payment data. Autumn sits between your app and Stripe, managing webhooks, usage limits, and state.\n\nYou're never locked in. Your subscriptions live in Stripe.", - }, - { - id: 2, - question: "What if Autumn goes down? Will my app go down?", - answer: - "We run on redundant infrastructure and high availability is our priority. However, not being able to reach Autumn does not mean that your app will go down. Our SDKs default to fail open and fail fast, meaning that in a worst case scenario, some users may get temporary additional access.\n\nWe can work with you to reconcile usage tracking and balances afterward if needed.", - }, - { - id: 3, - question: "How is Autumn different from Orb or Metronome?", - answer: - "Orb and Metronome focus on usage metering—tracking how much customers consume, suitable for end of month invoicing. You still have to build access control and state management separately, meaning you'll wire together your own logic, Stripe billing and a metering provider.\n\nAutumn is a complete system of record. We handle usage metering + entitlements + feature gating + billing state in one API. `check()` tells you if a user can access a feature in <50ms.", - }, - { - id: 4, - question: "What if I need to move off Autumn? Am I locked in?", - answer: - "Autumn is open source. You can self-host anytime, or export all your data. Your Stripe subscriptions, customers and payment details remain yours. Moving off Autumn is simply a case of building what you would have built in-house without Autumn (but this has never happened, touch wood!). ", - }, - { - id: 5, - question: "How long would it take to go live?", - answer: - "If you're setting up payments for the first time, most teams go live in under an hour. Migrating from an existing billing system typically takes 1–2 weeks, depending on complexity.\n\n For Series A+ companies, we provide a forward deployed service to work with your team, dual-write to your internal system and Autumn, then smoothly migrate over. Minimal work needed on your part.", - }, - { - id: 6, - question: "Can you handle our event volume?", - answer: - "Yes. Autumn supports 10,000+ events per second per end customer. We've processed millions of billing events daily for AI companies at scale. If you have specific requirements, reach out—we'll walk through your architecture.", - }, - { - id: 7, - question: "What if I can't use `check()`?", - answer: - "Latency-sensitive customers may not be able to use `check()` in real-time. In these cases, you can cache the Autumn customer data on your end, or use our single `customer.products.updated` webhook to replicate the Autumn state into your own system.", - }, -]; - -export const featuresData = [ - { - title: "Usage Ledgers", - description: - "Recurring, one-time and rollover credit balances. Stack balances across plans and topups. Deduct from soonest expiry first.", - Icon: IconWebhooks, - }, - { - title: "Payment Logic", - description: - "Checkouts, upgrades, downgrades, add-ons, proration, 3DS, edge cases, webhooks: all handled in a single API call.", - Icon: IconWebhooks, - }, - { - title: "Custom Plans", - description: - "Create one-off deals for enterprise customers. Unique pricing, features, and limits without touching code.", - Icon: IconPlans, - }, - { - title: "Usage Analytics", - description: - "Fast timeseries charts and event logs out of the box. Powered by ClickHouse.", - Icon: IconAnalytics, - }, - { - title: "Team Billing", - description: - "Grant plans and features to entities under an organization. Create pools of credits, or assign to users directly.", - Icon: IconTeam, - }, - { - title: "Auto Top-ups", - description: - "Let users refill credits when balance runs low. Configure thresholds and amounts. Fully automated.", - Icon: IconTopUp, - }, - { - title: "Pricing Versioning", - description: - "Change your pricing model without breaking existing customers. Grandfather old plans or migrate users gradually. No database or Stripe migrations.", - Icon: IconVersioning, - }, - { - title: "Alerts and Spend Limits", - description: - "Give customers governance over their usage. Configure alerts, limits and overage per customer.", - Icon: IconReact, - }, - { - title: "Coupons and Referrals", - description: - "Built-in referral system with rewards, tracking, and attribution. Launch referral programs in minutes.", - Icon: IconReferral, - }, -]; - -export const PixelatedPattern = ({ className, ...props }) => ( - // eslint-disable-next-line @next/next/no-img-element - -); - -export const PRELOADER_LOGO_PATH = - "M0 138.789C12.1644 110.86 24.3298 82.9305 36.4942 55.001L159.78 0V149.976C112.437 178.653 65.0945 207.323 17.7521 236L113.418 65.9915L107.494 73.674C81.1937 107.783 54.8935 141.893 28.601 176.002C19.0674 163.601 9.53363 151.191 0 138.789Z"; - -export const PRELOADER_LOGO_VIEWBOX = { width: 160, height: 236 }; - -export const PreloaderLogo = ({ className, ...props }) => ( - -); - -export const ProblemBgSvg = (props) => ( -
- {/* eslint-disable-next-line @next/next/no-img-element */} - Problem Background -
-); diff --git a/apps/website/app/constant.tsx b/apps/website/app/constant.tsx new file mode 100755 index 000000000..3a56efbe1 --- /dev/null +++ b/apps/website/app/constant.tsx @@ -0,0 +1,1340 @@ +import { motion } from "motion/react"; +import type { ComponentPropsWithoutRef } from "react"; +import { forwardRef } from "react"; +import type { ImgProps, SvgIconProps } from "@/lib/types"; +import { cn } from "@/lib/utils"; + +type CyberGlitchIconProps = { + className?: string; + paths: string[]; +}; + +type ArrowIconProps = SvgIconProps & { + disabled?: boolean; +}; + +export const CyberGlitchIcon = forwardRef( + ({ paths, className }, ref) => ( + + {paths.map((d, i) => ( + + ))} + + ), +); +CyberGlitchIcon.displayName = "CyberGlitchIcon"; + +export const MenuGridIcon = ({ isOpen }: { isOpen: boolean }) => { + const gridSquares = [ + { is: "M0 0H3V3H0V0Z", ts: "M0 0H3V3H0V0Z" }, + { is: "M6 0H9V3H6V0Z", ts: "M3 3H6V6H3V3Z" }, + { is: "M12 0H15V3H12V0Z", ts: "M12 0H15V3H12V0Z" }, + { is: "M0 6H3V9H0V6Z", ts: "M3 9H6V12H3V9Z" }, + { is: "M6 6H9V9H6V6Z", ts: "M6 6H9V9H6V6Z" }, + { is: "M12 6H15V9H12V6Z", ts: "M9 3H12V6H9V3Z" }, + { is: "M0 12H3V15H0V12Z", ts: "M0 12H3V15H0V12Z" }, + { is: "M6 12H9V15H6V12Z", ts: "M9 9H12V12H9V9Z" }, + { is: "M12 12H15V15H12V12Z", ts: "M12 12H15V15H12V12Z" }, + ]; + + return ( + + {gridSquares.map((sq, i) => ( + + ))} + + ); +}; + +export const CTALines = () => { + const lines = [ + { bottom: "0%", opacity: 0.25 }, + { bottom: "15%", opacity: 0.18 }, + { bottom: "30%", opacity: 0.12 }, + { bottom: "45%", opacity: 0.08 }, + { bottom: "60%", opacity: 0.05 }, + { bottom: "75%", opacity: 0.02 }, + ]; + + return ( + + {lines.map((line, i) => ( +
+ ))} + + ); +}; + +export const IconCTAStart = (props: SvgIconProps) => ( + + + + + + + + + + + + + + + + + + +); + +export const IconCTADocs = (props: SvgIconProps) => ( + + + + + + + + + + + + + + + + + + + + + + + + + + +); + +export const IconDiscord = forwardRef( + (props, ref) => ( + + ), +); +IconDiscord.displayName = "IconDiscord"; + +// 2. Blog +export const IconBlog = forwardRef( + (props, ref) => ( + + ), +); +IconBlog.displayName = "IconBlog"; + +// 3. Docs +export const IconDocs = forwardRef( + (props, ref) => ( + + ), +); +IconDocs.displayName = "IconDocs"; + +// 4. Pricing +export const IconPricing = forwardRef( + (props, ref) => ( + + ), +); +IconPricing.displayName = "IconPricing"; + +export const IconDashboard = forwardRef( + ({ className }, ref) => ( + + + + + + + + + + + + ), +); +IconDashboard.displayName = "IconDashboard"; + +export const IconWebhooks = (props: SvgIconProps) => ( + + + + + + + + + + + + + +); + +// Usage Analytics: analytics.svg +export const IconAnalytics = (props: SvgIconProps) => ( + + + + + + + + + + + +); + +// Team Billing: billing.svg +export const IconTeam = (props: SvgIconProps) => ( + + + + + + + + + + + +); + +// Auto Top-ups: top-ups.svg +export const IconTopUp = (props: SvgIconProps) => ( + + + + + + + + + + + + + + + +); + +// Custom Plans: custom-plans.svg +export const IconPlans = (props: SvgIconProps) => ( + + + + + + + + + + + +); + +// Pricing Versioning: pricing.svg +export const IconVersioning = (props: SvgIconProps) => ( + + + + + + + + + + + + + + +); + +// React Components: react-components.svg +export const IconReact = (props: SvgIconProps) => ( + + + + + + + + + + +); + +// Referral Programs: referral.svg +export const IconReferral = (props: SvgIconProps) => ( + + + + + + + + + + + + + + + + + +); + +export const IconArrowLeft = ({ + className, + disabled, + ...props +}: ArrowIconProps) => ( + + + + + + + + + + + + + + + + + + + +); + +export const IconArrowRight = ({ + className, + disabled, + ...props +}: ArrowIconProps) => ( + + + + + + + + + + + + + + + + + +); + +export const IconQuotes = ({ className, ...props }: SvgIconProps) => ( + + + + + + + + + + +); + +export const IconTick = ({ className, ...props }: SvgIconProps) => ( + + + + +); + +export const IconArrowRightSmall = ({ className }: SvgIconProps) => ( + + + + + + + + + + + + + + + + + +); + +export const AnimatedPlusMinus = ({ + isOpen, + className, +}: SvgIconProps & { + isOpen: boolean; +}) => { + // Fragments that form the vertical line of the "Plus" + const vPixels = [ + { y: 3, originY: "center", xBurst: -8, delay: 0 }, + { y: 5, originY: "center", xBurst: -4, delay: 0.05 }, + { y: 9, originY: "center", xBurst: 4, delay: 0.05 }, + { y: 11, originY: "center", xBurst: 8, delay: 0 }, + ]; + + return ( + + {/* Corner decorative pixels - stay static but fade slightly */} + {[ + [0, 0], + [14, 0], + [0, 14], + [14, 14], + ].map(([x, y], i) => ( + + ))} + + {/* Horizontal Bar - The "Minus" part (Static) */} + + + + + + + {/* Vertical Bursting Pixels */} + {vPixels.map((pixel, i) => ( + + ))} + + ); +}; + +export const IconPlus = ({ className, ...props }: SvgIconProps) => ( + + + + + + + + + + + + + + + + +); + +export const IconMinus = ({ className, ...props }: SvgIconProps) => ( + + + + + + + + + + +); + +export const faqData = [ + { + id: 1, + question: "Do I still need Stripe?", + answer: + "Yes. Autumn works with Stripe—it handles the billing logic that Stripe doesn't. You keep your Stripe account, your customer relationships, and your payment data. Autumn sits between your app and Stripe, managing webhooks, usage limits, and state.\n\nYou're never locked in. Your subscriptions live in Stripe.", + }, + { + id: 2, + question: "What if Autumn goes down? Will my app go down?", + answer: + "We run on redundant infrastructure and high availability is our priority. However, not being able to reach Autumn does not mean that your app will go down. Our SDKs default to fail open and fail fast, meaning that in a worst case scenario, some users may get temporary additional access.\n\nWe can work with you to reconcile usage tracking and balances afterward if needed.", + }, + { + id: 3, + question: "How is Autumn different from Orb or Metronome?", + answer: + "Orb and Metronome focus on usage metering—tracking how much customers consume, suitable for end of month invoicing. You still have to build access control and state management separately, meaning you'll wire together your own logic, Stripe billing and a metering provider.\n\nAutumn is a complete system of record. We handle usage metering + entitlements + feature gating + billing state in one API. `check()` tells you if a user can access a feature in <50ms.", + }, + { + id: 4, + question: "What if I need to move off Autumn? Am I locked in?", + answer: + "Autumn is open source. You can self-host anytime, or export all your data. Your Stripe subscriptions, customers and payment details remain yours. Moving off Autumn is simply a case of building what you would have built in-house without Autumn (but this has never happened, touch wood!). ", + }, + { + id: 5, + question: "How long would it take to go live?", + answer: + "If you're setting up payments for the first time, most teams go live in under an hour. Migrating from an existing billing system typically takes 1–2 weeks, depending on complexity.\n\n For Series A+ companies, we provide a forward deployed service to work with your team, dual-write to your internal system and Autumn, then smoothly migrate over. Minimal work needed on your part.", + }, + { + id: 6, + question: "Can you handle our event volume?", + answer: + "Yes. Autumn supports 10,000+ events per second per end customer. We've processed millions of billing events daily for AI companies at scale. If you have specific requirements, reach out—we'll walk through our architecture.", + }, + { + id: 7, + question: "What if I can't use `check()`?", + answer: + "Latency-sensitive customers may not be able to use `check()` in real-time. In these cases, you can cache the Autumn customer data on your end, or use our single `customer.products.updated` webhook to replicate the Autumn state into your own system.", + }, +]; + +export const featuresData = [ + { + title: "Usage Ledgers", + description: + "Recurring, one-time and rollover credit balances. Stack balances across plans and topups. Deduct from soonest expiry first.", + Icon: IconWebhooks, + }, + { + title: "Payment Logic", + description: + "Checkouts, upgrades, downgrades, add-ons, proration, 3DS, edge cases, webhooks: all handled in a single API call.", + Icon: IconWebhooks, + }, + { + title: "Custom Plans", + description: + "Create one-off deals for enterprise customers. Unique pricing, features, and limits without touching code.", + Icon: IconPlans, + }, + { + title: "Usage Analytics", + description: + "Fast timeseries charts and event logs out of the box. Powered by ClickHouse.", + Icon: IconAnalytics, + }, + { + title: "Team Billing", + description: + "Grant plans and features to entities under an organization. Create pools of credits, or assign to users directly.", + Icon: IconTeam, + }, + { + title: "Auto Top-ups", + description: + "Let users refill credits when balance runs low. Configure thresholds and amounts. Fully automated.", + Icon: IconTopUp, + }, + { + title: "Pricing Versioning", + description: + "Change your pricing model without breaking existing customers. Grandfather old plans or migrate users gradually. No database or Stripe migrations.", + Icon: IconVersioning, + }, + { + title: "Alerts and Spend Limits", + description: + "Give customers governance over their usage. Configure alerts, limits and overage per customer.", + Icon: IconReact, + }, + { + title: "Coupons and Referrals", + description: + "Built-in referral system with rewards, tracking, and attribution. Launch referral programs in minutes.", + Icon: IconReferral, + }, +]; + +export const PixelatedPattern = ({ className, ...props }: ImgProps) => ( + // eslint-disable-next-line @next/next/no-img-element + +); + +export const PRELOADER_LOGO_PATH = + "M0 138.789C12.1644 110.86 24.3298 82.9305 36.4942 55.001L159.78 0V149.976C112.437 178.653 65.0945 207.323 17.7521 236L113.418 65.9915L107.494 73.674C81.1937 107.783 54.8935 141.893 28.601 176.002C19.0674 163.601 9.53363 151.191 0 138.789Z"; + +export const PRELOADER_LOGO_VIEWBOX = { width: 160, height: 236 }; + +export const PreloaderLogo = ({ className, ...props }: SvgIconProps) => ( + +); + +export const ProblemBgSvg = (props: ComponentPropsWithoutRef<"div">) => ( +
+ {/* eslint-disable-next-line @next/next/no-img-element */} + Problem Background +
+); diff --git a/apps/website/app/layout.js b/apps/website/app/layout.js deleted file mode 100644 index 33cecf4a4..000000000 --- a/apps/website/app/layout.js +++ /dev/null @@ -1,81 +0,0 @@ -import { Geist, Geist_Mono } from "next/font/google"; -import "./globals.css"; - -const geistSans = Geist({ - variable: "--font-geist-sans", - subsets: ["latin"], -}); - -const geistMono = Geist_Mono({ - variable: "--font-geist-mono", - subsets: ["latin"], -}); - -export const metadata = { - title: { - default: "Autumn — Billing Infrastructure for AI Startups", - template: "%s | Autumn", - }, - description: - "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic. Autumn keeps webhooks, payments, and usage perfectly in-sync.", - keywords: [ - "AI billing", - "usage-based billing", - "subscription management", - "AI startups", - "billing infrastructure", - "payment integration", - "usage limits", - "credit system", - ], - authors: [{ name: "Autumn" }], - creator: "Autumn", - metadataBase: new URL("https://autumndev.vercel.app"), - openGraph: { - type: "website", - locale: "en_US", - url: "https://autumndev.vercel.app", - siteName: "Autumn", - title: "Autumn — Billing Infrastructure for AI Startups", - description: - "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic.", - images: [ - { - url: "/images/og-image.png", - width: 1200, - height: 630, - alt: "Autumn — Billing Infrastructure for AI Startups", - }, - ], - }, - twitter: { - card: "summary_large_image", - title: "Autumn — Billing Infrastructure for AI Startups", - description: - "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic.", - images: ["/images/og-image.png"], - }, - robots: { - index: true, - follow: true, - googleBot: { - index: true, - follow: true, - "max-video-preview": -1, - "max-image-preview": "large", - "max-snippet": -1, - }, - }, -}; - -export default function RootLayout({ children }) { - return ( - - {children} - - ); -} diff --git a/apps/website/app/layout.tsx b/apps/website/app/layout.tsx new file mode 100644 index 000000000..3d38b2097 --- /dev/null +++ b/apps/website/app/layout.tsx @@ -0,0 +1,90 @@ +import type { Metadata } from "next"; +import { Geist, Geist_Mono } from "next/font/google"; +import type { LayoutProps } from "@/lib/types"; +import { cn } from "@/lib/utils"; +import "./globals.css"; + +const geistSans = Geist({ + variable: "--font-geist-sans", + subsets: ["latin"], +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +const url = "https://useautumn.com"; + +export const metadata: Metadata = { + title: { + default: "Autumn — Billing Infrastructure for AI Startups", + template: "%s | Autumn", + }, + description: + "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic. Autumn keeps webhooks, payments, and usage perfectly in-sync.", + keywords: [ + "AI billing", + "usage-based billing", + "subscription management", + "AI startups", + "billing infrastructure", + "payment integration", + "usage limits", + "credit system", + ], + authors: [{ name: "Autumn" }], + creator: "Autumn", + metadataBase: new URL(url), + openGraph: { + type: "website", + locale: "en_US", + url, + siteName: "Autumn", + title: "Autumn — Billing Infrastructure for AI Startups", + description: + "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic.", + images: [ + { + url: "/images/og-image.png", + width: 1200, + height: 630, + alt: "Autumn — Billing Infrastructure for AI Startups", + }, + ], + }, + twitter: { + card: "summary_large_image", + title: "Autumn — Billing Infrastructure for AI Startups", + description: + "The drop-in billing layer for AI startups. Stop rebuilding usage limits, credit systems, and subscription logic.", + images: ["/images/og-image.png"], + }, + robots: { + index: true, + follow: true, + googleBot: { + index: true, + follow: true, + "max-video-preview": -1, + "max-image-preview": "large", + "max-snippet": -1, + }, + }, +}; + +export default function RootLayout({ children }: LayoutProps) { + return ( + + {children} + + ); +} diff --git a/apps/website/app/not-found.js b/apps/website/app/not-found.js deleted file mode 100644 index db985d678..000000000 --- a/apps/website/app/not-found.js +++ /dev/null @@ -1,111 +0,0 @@ -"use client"; -import Navbar from "@/components/navbar"; -import Image from "next/image"; -import Link from "next/link"; -import { IconArrowRightSmall } from "@/app/constant"; -import { motion } from "motion/react"; - -export default function NotFound() { - return ( -
-
-
-
- - - -
-
- - 404 Background City Base - - - - 404 Background City Glitch - -
- -
- Autumn Logo - -

- Page not found -

- -

- The page you are looking for doesn't exist or has been moved. -

- - - - -
-
-
-
- ); -} diff --git a/apps/website/app/not-found.tsx b/apps/website/app/not-found.tsx new file mode 100644 index 000000000..0f2f7ee20 --- /dev/null +++ b/apps/website/app/not-found.tsx @@ -0,0 +1,114 @@ +"use client"; +import { motion } from "motion/react"; +import Image from "next/image"; +import Link from "next/link"; +import { IconArrowRightSmall } from "@/app/constant"; +import Navbar from "@/components/navbar"; +import type { PageStyle } from "@/lib/types"; + +export default function NotFound() { + return ( +
+
+
+
+ + + +
+
+ + 404 Background City Base + + + + 404 Background City Glitch + +
+ +
+ Autumn Logo + +

+ Page not found +

+ +

+ The page you are looking for doesn't exist or has been moved. +

+ + + + +
+
+
+
+ ); +} diff --git a/apps/website/app/page.tsx b/apps/website/app/page.tsx new file mode 100644 index 000000000..f64ad1fde --- /dev/null +++ b/apps/website/app/page.tsx @@ -0,0 +1,44 @@ +import ElasticRecoil from "@/components/elastic-footer"; +import HomeSections from "@/components/home-sections"; +import Navbar from "@/components/navbar"; +import Preloader from "@/components/preloader"; +import type { PageStyle } from "@/lib/types"; + +export default function Home() { + return ( +
+ + +
+
+
+ +
+ +
+
+
+
+
+
+
+ + +
+
+
+
+
+
+
+
+
+ +
+ ); +} diff --git a/apps/website/app/privacy/page.js b/apps/website/app/privacy/page.js deleted file mode 100644 index 164ce0f21..000000000 --- a/apps/website/app/privacy/page.js +++ /dev/null @@ -1,191 +0,0 @@ -import AnimatedFooterImage from "@/components/animated-footer-image"; -import Footer from "@/components/footer"; -import Navbar from "@/components/navbar"; - -const privacyTerms = [ - { - title: "1. Acceptance of Terms", - content: - 'By accessing or using the Autumn platform, APIs, SDKs, or any related services (collectively, the "Services") provided by Rebase, Inc., a Delaware corporation ("Autumn," "we," "us," or "our"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, do not use our Services.', - isUppercase: false, - }, - { - title: "2. Description of Services", - content: - "Autumn provides billing infrastructure, including usage-based billing, credits management, pricing configuration, and entitlements management for software applications. Our Services are designed for developers and businesses building products that require flexible billing and pricing capabilities.", - isUppercase: false, - }, - { - title: "3. Account Registration", - content: - "To use certain features of our Services, you must create an account. You agree to provide accurate, current, and complete information during registration and to update such information as necessary. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account.", - isUppercase: false, - }, - { - title: "4. Acceptable Use", - content: - "You agree not to use the Services for any unlawful purpose or in violation of any applicable laws; (b) interfere with or disrupt the integrity or performance of the Services; (c) attempt to gain unauthorized access to the Services or related systems; (d) use the Services to transmit malicious code or harmful content; or (e) resell or redistribute the Services without our prior written consent.", - isUppercase: false, - }, - { - title: "5. Payment Terms", - content: - "Fees for the Services are set forth on our pricing page or in a separate agreement. You agree to pay all applicable fees in accordance with the payment terms. All fees are non-refundable except as expressly stated otherwise. We reserve the right to modify our pricing with 30 days' notice.", - isUppercase: false, - }, - { - title: "6. Data and Privacy", - content: - 'Your use of the Services is subject to our Privacy Policy. You retain ownership of any data you submit to the Services ("Customer Data"). You grant us a limited license to process Customer Data solely to provide the Services. We implement reasonable security measures to protect Customer Data, but you acknowledge that no system is completely secure.', - isUppercase: false, - }, - { - title: "7. Intellectual Property", - content: - "The Services, including all software, APIs, documentation, and related materials, are owned by Autumn and protected by intellectual property laws. We grant you a limited, non-exclusive, non-transferable license to use the Services in accordance with these Terms. You may not copy, modify, or create derivative works of the Services except as expressly permitted.", - isUppercase: false, - }, - { - title: "8. Confidentiality", - content: - "Each party agrees to maintain the confidentiality of any non-public information disclosed by the other party and to use such information only for the purposes of these Terms. This obligation does not apply to information that is publicly available, independently developed, or rightfully received from a third party.", - isUppercase: false, - }, - { - title: "9. Warranties and Disclaimers", - content: - 'THE SERVICES ARE PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE DO NOT WARRANT THAT THE SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE.', - isUppercase: true, - }, - { - title: "10. Limitation of Liability", - content: - "TO THE MAXIMUM EXTENT PERMITTED BY LAW, AUTUMN SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS OR REVENUES. OUR TOTAL LIABILITY FOR ANY CLAIMS ARISING FROM THESE TERMS SHALL NOT EXCEED THE AMOUNTS PAID BY YOU TO AUTUMN IN THE TWELVE MONTHS PRECEDING THE CLAIM.", - isUppercase: true, - }, - { - title: "11. Indemnification", - content: - "You agree to indemnify, defend, and hold harmless Autumn and its officers, directors, employees, and agents from any claims, liabilities, damages, losses, or expenses arising from your use of the Services, your violation of these Terms, or your infringement of any third-party rights.", - isUppercase: false, - }, - { - title: "12. Term and Termination", - content: - "These Terms remain in effect until terminated. Either party may terminate for convenience with 30 days' written notice. We may suspend or terminate your access immediately if you breach these Terms. Upon termination, your right to use the Services ceases, and you must pay any outstanding fees.", - isUppercase: false, - }, - { - title: "13. Modifications", - content: - "We may modify these Terms at any time by posting the revised Terms on our website. Material changes will be communicated with at least 30 days' notice. Your continued use of the Services after such changes constitutes acceptance of the modified Terms.", - isUppercase: false, - }, - { - title: "14. Governing Law and Disputes", - content: - "These Terms are governed by the laws of the State of Delaware, without regard to conflict of law principles. Any disputes arising from these Terms shall be resolved through binding arbitration in accordance with the rules of the American Arbitration Association, except that either party may seek injunctive relief in any court of competent jurisdiction.", - isUppercase: false, - }, - { - title: "15. General Provisions", - content: - "These Terms constitute the entire agreement between you and Autumn regarding the Services. If any provision is found unenforceable, the remaining provisions will continue in effect. Our failure to enforce any right or provision does not constitute a waiver. You may not assign these Terms without our prior written consent.", - isUppercase: false, - }, - { - title: "16. Contact Information", - content: ( - <> - For questions about these Terms, please contact us at - security@useautumn.com or at: -
- Rebase, Inc. (d/b/a Autumn) -
- Email: security@useautumn.com -
- Website: https://useautumn.com - - ), - isUppercase: false, - }, -]; - -export default function PrivacyPolicy() { - return ( -
-
-
-
-
- -
-
- -
-
-
-
-
-
-
- -
-
-
-
-

- Terms of Service -

-
-
-
- -
-
-
-

- Autumn (Rebase, Inc.) -
- Effective Date: February 1, 2025 -

- - {privacyTerms.map((term, index) => ( -
-

- {term.title} -

-

- {term.content} -

-
- ))} -
-
-
-
- -
-
- -
-
-
-
-
-
-
-
- -
- -
- ); -} diff --git a/apps/website/app/privacy/page.tsx b/apps/website/app/privacy/page.tsx new file mode 100644 index 000000000..0b6797725 --- /dev/null +++ b/apps/website/app/privacy/page.tsx @@ -0,0 +1,201 @@ +import type { ReactNode } from "react"; +import AnimatedFooterImage from "@/components/animated-footer-image"; +import Footer from "@/components/footer"; +import Navbar from "@/components/navbar"; +import type { PageStyle } from "@/lib/types"; +import { cn } from "@/lib/utils"; + +const privacyTerms: Array<{ + title: string; + content: ReactNode; + isUppercase: boolean; +}> = [ + { + title: "1. Acceptance of Terms", + content: + 'By accessing or using the Autumn platform, APIs, SDKs, or any related services (collectively, the "Services") provided by Rebase, Inc., a Delaware corporation ("Autumn," "we," "us," or "our"), you agree to be bound by these Terms of Service ("Terms"). If you do not agree to these Terms, do not use our Services.', + isUppercase: false, + }, + { + title: "2. Description of Services", + content: + "Autumn provides billing infrastructure, including usage-based billing, credits management, pricing configuration, and entitlements management for software applications. Our Services are designed for developers and businesses building products that require flexible billing and pricing capabilities.", + isUppercase: false, + }, + { + title: "3. Account Registration", + content: + "To use certain features of our Services, you must create an account. You agree to provide accurate, current, and complete information during registration and to update such information as necessary. You are responsible for maintaining the confidentiality of your account credentials and for all activities that occur under your account.", + isUppercase: false, + }, + { + title: "4. Acceptable Use", + content: + "You agree not to use the Services for any unlawful purpose or in violation of any applicable laws; (b) interfere with or disrupt the integrity or performance of the Services; (c) attempt to gain unauthorized access to the Services or related systems; (d) use the Services to transmit malicious code or harmful content; or (e) resell or redistribute the Services without our prior written consent.", + isUppercase: false, + }, + { + title: "5. Payment Terms", + content: + "Fees for the Services are set forth on our pricing page or in a separate agreement. You agree to pay all applicable fees in accordance with the payment terms. All fees are non-refundable except as expressly stated otherwise. We reserve the right to modify our pricing with 30 days' notice.", + isUppercase: false, + }, + { + title: "6. Data and Privacy", + content: + 'Your use of the Services is subject to our Privacy Policy. You retain ownership of any data you submit to the Services ("Customer Data"). You grant us a limited license to process Customer Data solely to provide the Services. We implement reasonable security measures to protect Customer Data, but you acknowledge that no system is completely secure.', + isUppercase: false, + }, + { + title: "7. Intellectual Property", + content: + "The Services, including all software, APIs, documentation, and related materials, are owned by Autumn and protected by intellectual property laws. We grant you a limited, non-exclusive, non-transferable license to use the Services in accordance with these Terms. You may not copy, modify, or create derivative works of the Services except as expressly permitted.", + isUppercase: false, + }, + { + title: "8. Confidentiality", + content: + "Each party agrees to maintain the confidentiality of any non-public information disclosed by the other party and to use such information only for the purposes of these Terms. This obligation does not apply to information that is publicly available, independently developed, or rightfully received from a third party.", + isUppercase: false, + }, + { + title: "9. Warranties and Disclaimers", + content: + 'THE SERVICES ARE PROVIDED "AS IS" AND "AS AVAILABLE" WITHOUT WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. WE DO NOT WARRANT THAT THE SERVICES WILL BE UNINTERRUPTED OR ERROR-FREE.', + isUppercase: true, + }, + { + title: "10. Limitation of Liability", + content: + "TO THE MAXIMUM EXTENT PERMITTED BY LAW, AUTUMN SHALL NOT BE LIABLE FOR ANY INDIRECT, INCIDENTAL, SPECIAL, CONSEQUENTIAL, OR PUNITIVE DAMAGES, OR ANY LOSS OF PROFITS OR REVENUES. OUR TOTAL LIABILITY FOR ANY CLAIMS ARISING FROM THESE TERMS SHALL NOT EXCEED THE AMOUNTS PAID BY YOU TO AUTUMN IN THE TWELVE MONTHS PRECEDING THE CLAIM.", + isUppercase: true, + }, + { + title: "11. Indemnification", + content: + "You agree to indemnify, defend, and hold harmless Autumn and its officers, directors, employees, and agents from any claims, liabilities, damages, losses, or expenses arising from your use of the Services, your violation of these Terms, or your infringement of any third-party rights.", + isUppercase: false, + }, + { + title: "12. Term and Termination", + content: + "These Terms remain in effect until terminated. Either party may terminate for convenience with 30 days' written notice. We may suspend or terminate your access immediately if you breach these Terms. Upon termination, your right to use the Services ceases, and you must pay any outstanding fees.", + isUppercase: false, + }, + { + title: "13. Modifications", + content: + "We may modify these Terms at any time by posting the revised Terms on our website. Material changes will be communicated with at least 30 days' notice. Your continued use of the Services after such changes constitutes acceptance of the modified Terms.", + isUppercase: false, + }, + { + title: "14. Governing Law and Disputes", + content: + "These Terms are governed by the laws of the State of Delaware, without regard to conflict of law principles. Any disputes arising from these Terms shall be resolved through binding arbitration in accordance with the rules of the American Arbitration Association, except that either party may seek injunctive relief in any court of competent jurisdiction.", + isUppercase: false, + }, + { + title: "15. General Provisions", + content: + "These Terms constitute the entire agreement between you and Autumn regarding the Services. If any provision is found unenforceable, the remaining provisions will continue in effect. Our failure to enforce any right or provision does not constitute a waiver. You may not assign these Terms without our prior written consent.", + isUppercase: false, + }, + { + title: "16. Contact Information", + content: ( + <> + For questions about these Terms, please contact us at + security@useautumn.com or at: +
+ Rebase, Inc. (d/b/a Autumn) +
+ Email: security@useautumn.com +
+ Website: https://useautumn.com + + ), + isUppercase: false, + }, +]; + +export default function PrivacyPolicy() { + return ( +
+
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+ +
+
+
+
+

+ Terms of Service +

+
+
+
+ +
+
+
+

+ Autumn (Rebase, Inc.) +
+ Effective Date: February 1, 2025 +

+ + {privacyTerms.map((term, index) => ( +
+

+ {term.title} +

+

+ {term.content} +

+
+ ))} +
+
+
+
+ +
+
+ +
+
+
+
+
+
+
+
+ +
+ +
+ ); +} diff --git a/apps/website/components/animated-footer-image.jsx b/apps/website/components/animated-footer-image.jsx deleted file mode 100644 index ce7484254..000000000 --- a/apps/website/components/animated-footer-image.jsx +++ /dev/null @@ -1,25 +0,0 @@ -"use client"; -import Image from "next/image"; -import { forwardRef } from "react"; - -const AnimatedFooterImage = forwardRef(function AnimatedFooterImage(_, ref) { - return ( -
-
- footer background -
-
- ); -}); - -export default AnimatedFooterImage; diff --git a/apps/website/components/animated-footer-image.tsx b/apps/website/components/animated-footer-image.tsx new file mode 100644 index 000000000..c45b2414d --- /dev/null +++ b/apps/website/components/animated-footer-image.tsx @@ -0,0 +1,29 @@ +"use client"; +import Image from "next/image"; +import { forwardRef } from "react"; + +const AnimatedFooterImage = forwardRef( + function AnimatedFooterImage(_props, ref) { + return ( +
+
+ footer background +
+
+ ); + }, +); + +AnimatedFooterImage.displayName = "AnimatedFooterImage"; + +export default AnimatedFooterImage; diff --git a/apps/website/components/autumn-config.jsx b/apps/website/components/autumn-config.jsx deleted file mode 100644 index 9c5776af1..000000000 --- a/apps/website/components/autumn-config.jsx +++ /dev/null @@ -1,247 +0,0 @@ -"use client"; - -import Image from "next/image"; -import { useEffect, useLayoutEffect, useRef, useState } from "react"; -import { Light as SyntaxHighlighter } from "react-syntax-highlighter"; -import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"; - -// useLayoutEffect runs synchronously before browser paint on the client, -// eliminating the container-query font-size race that causes CLS on reload. -const useIsomorphicLayoutEffect = - typeof window !== "undefined" ? useLayoutEffect : useEffect; - -SyntaxHighlighter.registerLanguage("javascript", js); - -const autumnTheme = { - hljs: { - display: "block", - background: "transparent", - color: "#BFBFBF", - padding: "0", - margin: "0", - }, - "hljs-comment": { color: "#6B6B6B" }, - "hljs-keyword": { color: "#9564ff" }, - "hljs-built_in": { color: "#BFBFBF" }, - "hljs-string": { color: "#2B8C3F" }, - "hljs-number": { color: "#9564ff" }, - "hljs-literal": { color: "#2B8C3F" }, - "hljs-attr": { color: "#FF1F12" }, - "hljs-property": { color: "#FF1F12" }, - "hljs-variable": { color: "#0161B5" }, - "hljs-title": { color: "#BFBFBF" }, - "hljs-params": { color: "#0161B5" }, - "hljs-punctuation": { color: "#BFBFBF" }, -}; - -const TOTAL_LINES = 21; -const LINE_HEIGHT = 24; - -const codeContent = `// Your entire billing integration -const { allowed, remaining } = await -autumn.check({ - featureId: "ai_tokens" -}); - -if (allowed) { - await autumn.track({ - featureId: "ai_tokens", - value: 1024 - }); -}`; - -const TYPING_SPEED = 10; - -export default function AutumnConfig({ - lines = TOTAL_LINES, - initialDelay = 0, - awaitEvent = null, -}) { - const fullCode = (() => { - const lineCount = codeContent.split("\n").length; - return codeContent + "\n".repeat(Math.max(0, lines - lineCount)); - })(); - const containerRef = useRef(null); - const [fontSize, setFontSize] = useState(16); - const [displayed, setDisplayed] = useState(""); - const [cursorVisible, setCursorVisible] = useState(true); - const [awaitDone, setAwaitDone] = useState(!awaitEvent); - const [started, setStarted] = useState(initialDelay === 0 && !awaitEvent); - const done = displayed.length >= fullCode.length; - - // Wait for the signal event before starting the delay countdown - useEffect(() => { - if (!awaitEvent) return; - const handler = () => setAwaitDone(true); - window.addEventListener(awaitEvent, handler, { once: true }); - return () => window.removeEventListener(awaitEvent, handler); - }, [awaitEvent]); - - // Start typing after awaitDone, respecting initialDelay - useEffect(() => { - if (!awaitDone) return; - const t = setTimeout(() => setStarted(true), initialDelay); - return () => clearTimeout(t); - }, [awaitDone, initialDelay]); - - useIsomorphicLayoutEffect(() => { - const el = containerRef.current; - if (!el) return; - const measure = () => - setFontSize(Math.min(Math.max(el.offsetWidth * 0.0385, 10), 20)); - measure(); - const ro = new ResizeObserver(measure); - ro.observe(el); - return () => ro.disconnect(); - }, []); - - const displayedPadded = - displayed + "\n".repeat(Math.max(0, lines - displayed.split("\n").length)); - useEffect(() => { - if (!started || done) return; - const timer = setTimeout(() => { - setDisplayed(fullCode.slice(0, displayed.length + 1)); - }, TYPING_SPEED); - return () => clearTimeout(timer); - }, [displayed, done, started]); - - // Blinking cursor after done - useEffect(() => { - if (!done) return; - const interval = setInterval(() => setCursorVisible((v) => !v), 530); - return () => clearInterval(interval); - }, [done]); - - return ( -
- {/* Title bar */} - {/*
-
- Box -
- - autumn.config.ts - -
- Box -
- - cross - -
*/} -
- {/* Left Vent: flex-1 makes it stretch, min-w-0 allows it to shrink below its content if needed */} -
-
-
-
-
- - {/* Filename: shrink-0 ensures the text never gets squashed */} - - billing.ts - - - {/* Right Vent: Matches the left one */} -
-
-
-
-
- - {/* Close Button Container */} -
- Box -
-
- - {/* Code area — fluidly driven by container query relative font sizes so height never distorts */} -
- {/* Dynamic-height inner box: 20 lines × 1.25em */} -
- - {displayedPadded} - -
- -
- -
- -
- -
-
-
- allowed: - true - remaining: - 8976 -
- 92ms -
-
-
-
- ); -} diff --git a/apps/website/components/autumn-config.tsx b/apps/website/components/autumn-config.tsx new file mode 100644 index 000000000..1c812830e --- /dev/null +++ b/apps/website/components/autumn-config.tsx @@ -0,0 +1,261 @@ +"use client"; + +import Image from "next/image"; +import { useEffect, useLayoutEffect, useRef, useState } from "react"; +import { Light as RawSyntaxHighlighter } from "react-syntax-highlighter"; +import js from "react-syntax-highlighter/dist/esm/languages/hljs/javascript"; + +const SyntaxHighlighter = RawSyntaxHighlighter as unknown as ((props: { + children: string; + customStyle?: Record; + language: string; + lineNumberStyle?: Record; + showLineNumbers?: boolean; + style?: Record>; +}) => JSX.Element) & { + registerLanguage: (name: string, language: unknown) => void; +}; + +// useLayoutEffect runs synchronously before browser paint on the client, +// eliminating the container-query font-size race that causes CLS on reload. +const useIsomorphicLayoutEffect = + typeof window !== "undefined" ? useLayoutEffect : useEffect; + +SyntaxHighlighter.registerLanguage("javascript", js); + +const autumnTheme = { + hljs: { + display: "block", + background: "transparent", + color: "#BFBFBF", + padding: "0", + margin: "0", + }, + "hljs-comment": { color: "#6B6B6B" }, + "hljs-keyword": { color: "#9564ff" }, + "hljs-built_in": { color: "#BFBFBF" }, + "hljs-string": { color: "#2B8C3F" }, + "hljs-number": { color: "#9564ff" }, + "hljs-literal": { color: "#2B8C3F" }, + "hljs-attr": { color: "#FF1F12" }, + "hljs-property": { color: "#FF1F12" }, + "hljs-variable": { color: "#0161B5" }, + "hljs-title": { color: "#BFBFBF" }, + "hljs-params": { color: "#0161B5" }, + "hljs-punctuation": { color: "#BFBFBF" }, +}; + +const TOTAL_LINES = 16; +const LINE_HEIGHT = 24; + +const codeContent = `// Your entire billing integration +const { allowed } = await check({ + featureId: "ai_tokens" +}); + +if (allowed) { + await track({ + featureId: "ai_tokens", + value: 1024 + }); +}`; + +const TYPING_SPEED = 10; + +export default function AutumnConfig({ + lines = TOTAL_LINES, + initialDelay = 0, + awaitEvent = null, +}: { + awaitEvent?: string | null; + initialDelay?: number; + lines?: number; +}) { + const fullCode = (() => { + const lineCount = codeContent.split("\n").length; + return codeContent + "\n".repeat(Math.max(0, lines - lineCount)); + })(); + const containerRef = useRef(null); + const [fontSize, setFontSize] = useState(16); + const [displayed, setDisplayed] = useState(""); + const [cursorVisible, setCursorVisible] = useState(true); + const [awaitDone, setAwaitDone] = useState(!awaitEvent); + const [started, setStarted] = useState(initialDelay === 0 && !awaitEvent); + const done = displayed.length >= fullCode.length; + + // Wait for the signal event before starting the delay countdown + useEffect(() => { + if (!awaitEvent) return; + const handler = () => setAwaitDone(true); + window.addEventListener(awaitEvent, handler, { once: true }); + return () => window.removeEventListener(awaitEvent, handler); + }, [awaitEvent]); + + // Start typing after awaitDone, respecting initialDelay + useEffect(() => { + if (!awaitDone) return; + const t = setTimeout(() => setStarted(true), initialDelay); + return () => clearTimeout(t); + }, [awaitDone, initialDelay]); + + useIsomorphicLayoutEffect(() => { + const el = containerRef.current; + if (!el) return; + const measure = () => + setFontSize(Math.min(Math.max(el.offsetWidth * 0.0385, 10), 20)); + measure(); + const ro = new ResizeObserver(measure); + ro.observe(el); + return () => ro.disconnect(); + }, []); + + const displayedPadded = + displayed + "\n".repeat(Math.max(0, lines - displayed.split("\n").length)); + useEffect(() => { + if (!started || done) return; + const timer = setTimeout(() => { + setDisplayed(fullCode.slice(0, displayed.length + 1)); + }, TYPING_SPEED); + return () => clearTimeout(timer); + }, [displayed, done, started, fullCode]); + + // Blinking cursor after done + useEffect(() => { + if (!done) return; + const interval = setInterval(() => setCursorVisible((v) => !v), 530); + return () => clearInterval(interval); + }, [done]); + + return ( +
+ {/* Title bar */} + {/*
+
+ Box +
+ + autumn.config.ts + +
+ Box +
+ + cross + +
*/} +
+ {/* Left Vent: flex-1 makes it stretch, min-w-0 allows it to shrink below its content if needed */} +
+
+
+
+
+ + {/* Filename: shrink-0 ensures the text never gets squashed */} + + billing.ts + + + {/* Right Vent: Matches the left one */} +
+
+
+
+
+ + {/* Close Button Container */} +
+ Box +
+
+ + {/* Code area — fluidly driven by container query relative font sizes so height never distorts */} +
+ {/* Dynamic-height inner box: 20 lines × 1.25em */} +
+ + {displayedPadded} + +
+ +
+ +
+ +
+ +
+
+
+ allowed: + true + remaining: + 8976 +
+ 92ms +
+
+
+
+ ); +} diff --git a/apps/website/components/blogComponents.jsx b/apps/website/components/blogComponents.jsx deleted file mode 100644 index 19ceab8f5..000000000 --- a/apps/website/components/blogComponents.jsx +++ /dev/null @@ -1,89 +0,0 @@ -import Link from "next/link"; - -function BlogHeading({ as: Tag, children, ...props }) { - return ( - - {children} - - ); -} - -export const mdxComponents = { - h1: (props) => , - h2: (props) => , - h3: (props) => , - h4: (props) => , - a: ({ href, children, ...props }) => { - const isExternal = href?.startsWith("http"); - if (isExternal) { - return ( - - {children} - - ); - } - return ( - - {children} - - ); - }, - pre: ({ children, ...props }) => ( -
-      {children}
-    
- ), - code: ({ children, ...props }) => { - const isInline = typeof children === "string"; - if (isInline && !props.className) { - return ( - - {children} - - ); - } - return {children}; - }, - blockquote: ({ children, ...props }) => ( -
- {children} -
- ), - hr: (props) =>
, - table: ({ children, ...props }) => ( -
- - {children} -
-
- ), - th: ({ children, ...props }) => ( - - {children} - - ), - td: ({ children, ...props }) => ( - - {children} - - ), -}; diff --git a/apps/website/components/blogComponents.tsx b/apps/website/components/blogComponents.tsx new file mode 100644 index 000000000..fc5f1ed6c --- /dev/null +++ b/apps/website/components/blogComponents.tsx @@ -0,0 +1,110 @@ +import Link from "next/link"; +import type { ComponentPropsWithoutRef, ElementType, ReactNode } from "react"; + +function BlogHeading({ + as: Tag, + children, + ...props +}: { + as: ElementType; + children?: ReactNode; +} & ComponentPropsWithoutRef<"h1">) { + return ( + + {children} + + ); +} + +export const mdxComponents = { + h1: (props: ComponentPropsWithoutRef<"h1">) => ( + + ), + h2: (props: ComponentPropsWithoutRef<"h2">) => ( + + ), + h3: (props: ComponentPropsWithoutRef<"h3">) => ( + + ), + h4: (props: ComponentPropsWithoutRef<"h4">) => ( + + ), + a: ({ href, children, ...props }: ComponentPropsWithoutRef<"a">) => { + const isExternal = href?.startsWith("http"); + if (isExternal) { + return ( + + {children} + + ); + } + return ( + + {children} + + ); + }, + pre: ({ children, ...props }: ComponentPropsWithoutRef<"pre">) => ( +
+			{children}
+		
+ ), + code: ({ children, ...props }: ComponentPropsWithoutRef<"code">) => { + const isInline = typeof children === "string"; + if (isInline && !props.className) { + return ( + + {children} + + ); + } + return {children}; + }, + blockquote: ({ + children, + ...props + }: ComponentPropsWithoutRef<"blockquote">) => ( +
+ {children} +
+ ), + hr: (props: ComponentPropsWithoutRef<"hr">) => ( +
+ ), + table: ({ children, ...props }: ComponentPropsWithoutRef<"table">) => ( +
+ + {children} +
+
+ ), + th: ({ children, ...props }: ComponentPropsWithoutRef<"th">) => ( + + {children} + + ), + td: ({ children, ...props }: ComponentPropsWithoutRef<"td">) => ( + + {children} + + ), +}; diff --git a/apps/website/components/dashboard-icon-pixel.jsx b/apps/website/components/dashboard-icon-pixel.jsx deleted file mode 100644 index 3758710dc..000000000 --- a/apps/website/components/dashboard-icon-pixel.jsx +++ /dev/null @@ -1,65 +0,0 @@ -"use client"; -import { forwardRef, useImperativeHandle, useRef, useEffect } from "react"; -import gsap from "gsap"; - -export const DashboardIconPixel = forwardRef(function DashboardIconPixel( - { Icon }, - ref, -) { - const iconRef = useRef(null); - const tlRef = useRef(null); - - useImperativeHandle(ref, () => ({ - restart: () => tlRef.current?.play(), - reverse: () => tlRef.current?.reverse(), - })); - - useEffect(() => { - const pixelEls = iconRef.current?.querySelectorAll(".icon-pixel-path"); - if (!pixelEls?.length) return; - - // Sort pixels by visual position: bottom-left → top-right diagonal - const pixels = Array.from(pixelEls).sort((a, b) => { - const aBox = a.getBBox(); - const bBox = b.getBBox(); - return ( - aBox.x + - aBox.width / 2 - - (aBox.y + aBox.height / 2) - - (bBox.x + bBox.width / 2 - (bBox.y + bBox.height / 2)) - ); - }); - - gsap.set(pixels, { - opacity: 0.15, - scale: 0.8, - transformOrigin: "left bottom", - fill: "currentColor", - }); - - tlRef.current = gsap.timeline({ paused: true }); - - tlRef.current - .to(pixels, { - opacity: 1, - scale: 1.15, - fill: "#FFFFFF", - duration: 0.2, - stagger: 0.01, - ease: "power2.out", - }) - .to(pixels, { - scale: 1, - duration: 0.01, - ease: "back.out(3)", - }); - - return () => tlRef.current?.kill(); - }, []); - - return ( - - - - ); -}); diff --git a/apps/website/components/dashboard-icon-pixel.tsx b/apps/website/components/dashboard-icon-pixel.tsx new file mode 100644 index 000000000..561b1e362 --- /dev/null +++ b/apps/website/components/dashboard-icon-pixel.tsx @@ -0,0 +1,70 @@ +"use client"; +import gsap from "gsap"; +import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; +import type { PixelHoverHandle, PixelIconComponent } from "@/lib/types"; + +export const DashboardIconPixel = forwardRef< + PixelHoverHandle, + { Icon: PixelIconComponent } +>(function DashboardIconPixel({ Icon }, ref) { + const iconRef = useRef(null); + const tlRef = useRef(null); + + useImperativeHandle(ref, () => ({ + restart: () => tlRef.current?.play(), + reverse: () => tlRef.current?.reverse(), + })); + + useEffect(() => { + const pixelEls = + iconRef.current?.querySelectorAll(".icon-pixel-path"); + if (!pixelEls?.length) return; + + // Sort pixels by visual position: bottom-left → top-right diagonal + const pixels = Array.from(pixelEls).sort((a, b) => { + const aBox = a.getBBox(); + const bBox = b.getBBox(); + return ( + aBox.x + + aBox.width / 2 - + (aBox.y + aBox.height / 2) - + (bBox.x + bBox.width / 2 - (bBox.y + bBox.height / 2)) + ); + }); + + gsap.set(pixels, { + opacity: 0.15, + scale: 0.8, + transformOrigin: "left bottom", + fill: "currentColor", + }); + + tlRef.current = gsap.timeline({ paused: true }); + + tlRef.current + .to(pixels, { + opacity: 1, + scale: 1.15, + fill: "#FFFFFF", + duration: 0.2, + stagger: 0.01, + ease: "power2.out", + }) + .to(pixels, { + scale: 1, + duration: 0.01, + ease: "back.out(3)", + }); + + return () => { + tlRef.current?.kill(); + }; + }, []); + + return ( + + + + ); +}); +DashboardIconPixel.displayName = "DashboardIconPixel"; diff --git a/apps/website/components/elastic-footer.jsx b/apps/website/components/elastic-footer.jsx deleted file mode 100644 index 20f2d120b..000000000 --- a/apps/website/components/elastic-footer.jsx +++ /dev/null @@ -1,143 +0,0 @@ -"use client"; -import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"; -import { useEffect, useState } from "react"; -import AnimatedFooterImage from "./animated-footer-image"; - -export default function ElasticRecoil({ children }) { - const liftAmount = useMotionValue(0); - - const [showFooter, setShowFooter] = useState(false); - const [isMobile, setIsMobile] = useState(false); - - useEffect(() => { - const check = () => setIsMobile(window.innerWidth < 768); - check(); - window.addEventListener("resize", check); - return () => window.removeEventListener("resize", check); - }, []); - - const desktopSpring = { stiffness: 200, damping: 15, mass: 0.5 }; - const mobileSpring = { stiffness: 800, damping: 60, mass: 0.2 }; - const animatedLift = useSpring( - liftAmount, - isMobile ? mobileSpring : desktopSpring, - ); - const cappedMax = isMobile ? -420 : -580; - const y = useTransform(animatedLift, [0, 400], [0, cappedMax]); - - useEffect(() => { - return animatedLift.on("change", (v) => { - setShowFooter(v > 1); - }); - }, [animatedLift]); - - useEffect(() => { - let timeout; - let recoilFired = false; - let isTouching = false; // Track if finger is on screen - - const triggerRebound = () => { - liftAmount.set(0); - if (!recoilFired) { - recoilFired = true; - window.dispatchEvent(new CustomEvent("elastic-recoil")); - } - }; - - const handleWheel = (e) => { - const isAtBottom = - window.innerHeight + window.pageYOffset >= - document.documentElement.scrollHeight - 5; - - if (isAtBottom && e.deltaY > 0) { - const normalizedDelta = - e.deltaMode === 1 - ? e.deltaY * 20 - : e.deltaMode === 2 - ? e.deltaY * 300 - : e.deltaY; - liftAmount.set(Math.min(liftAmount.get() + normalizedDelta * 0.5, 400)); - recoilFired = false; - - clearTimeout(timeout); - timeout = setTimeout(() => { - if (!isTouching) { - triggerRebound(); - } - }, 1500); - } else if (e.deltaY < 0) { - if (!isTouching) { - liftAmount.set(0); - recoilFired = false; - } - } - }; - - let lastTouchY = 0; - let atBottomOnStart = false; - const handleTouchStart = (e) => { - isTouching = true; - lastTouchY = e.touches[0].clientY; - atBottomOnStart = - window.innerHeight + window.pageYOffset >= - document.documentElement.scrollHeight - 5; - recoilFired = false; - clearTimeout(timeout); - }; - - const handleTouchMove = (e) => { - const currentY = e.touches[0].clientY; - const delta = lastTouchY - currentY; - lastTouchY = currentY; - - if (!atBottomOnStart) { - atBottomOnStart = - window.innerHeight + window.pageYOffset >= - document.documentElement.scrollHeight - 5; - return; - } - - if (delta > 0) { - liftAmount.set(Math.min(liftAmount.get() + delta * 4, 400)); - recoilFired = false; - } else if (liftAmount.get() > 0) { - liftAmount.set(Math.max(0, liftAmount.get() + delta * 4)); - } - }; - - const handleTouchEnd = () => { - isTouching = false; - atBottomOnStart = false; - if (liftAmount.get() > 0) { - clearTimeout(timeout); - timeout = setTimeout(() => { - triggerRebound(); - }, 1500); - } - }; - - window.addEventListener("wheel", handleWheel, { passive: true }); - window.addEventListener("touchstart", handleTouchStart, { passive: true }); - window.addEventListener("touchmove", handleTouchMove, { passive: true }); - window.addEventListener("touchend", handleTouchEnd, { passive: true }); - window.addEventListener("touchcancel", handleTouchEnd, { passive: true }); - - return () => { - clearTimeout(timeout); - window.removeEventListener("wheel", handleWheel); - window.removeEventListener("touchstart", handleTouchStart); - window.removeEventListener("touchmove", handleTouchMove); - window.removeEventListener("touchend", handleTouchEnd); - window.removeEventListener("touchcancel", handleTouchEnd); - }; - }, [liftAmount]); - - return ( -
- {showFooter && } - - {children} - -
- ); -} diff --git a/apps/website/components/elastic-footer.tsx b/apps/website/components/elastic-footer.tsx new file mode 100644 index 000000000..a745acb57 --- /dev/null +++ b/apps/website/components/elastic-footer.tsx @@ -0,0 +1,144 @@ +"use client"; +import { motion, useMotionValue, useSpring, useTransform } from "framer-motion"; +import { useEffect, useState } from "react"; +import type { LayoutProps } from "@/lib/types"; +import AnimatedFooterImage from "./animated-footer-image"; + +export default function ElasticRecoil({ children }: LayoutProps) { + const liftAmount = useMotionValue(0); + + const [showFooter, setShowFooter] = useState(false); + const [isMobile, setIsMobile] = useState(false); + + useEffect(() => { + const check = () => setIsMobile(window.innerWidth < 768); + check(); + window.addEventListener("resize", check); + return () => window.removeEventListener("resize", check); + }, []); + + const desktopSpring = { stiffness: 200, damping: 15, mass: 0.5 }; + const mobileSpring = { stiffness: 800, damping: 60, mass: 0.2 }; + const animatedLift = useSpring( + liftAmount, + isMobile ? mobileSpring : desktopSpring, + ); + const cappedMax = isMobile ? -420 : -580; + const y = useTransform(animatedLift, [0, 400], [0, cappedMax]); + + useEffect(() => { + return animatedLift.on("change", (v) => { + setShowFooter(v > 1); + }); + }, [animatedLift]); + + useEffect(() => { + let timeout: ReturnType | null = null; + let recoilFired = false; + let isTouching = false; + + const triggerRebound = () => { + liftAmount.set(0); + if (!recoilFired) { + recoilFired = true; + window.dispatchEvent(new CustomEvent("elastic-recoil")); + } + }; + + const handleWheel = (e: WheelEvent) => { + const isAtBottom = + window.innerHeight + window.pageYOffset >= + document.documentElement.scrollHeight - 5; + + if (isAtBottom && e.deltaY > 0) { + const normalizedDelta = + e.deltaMode === 1 + ? e.deltaY * 20 + : e.deltaMode === 2 + ? e.deltaY * 300 + : e.deltaY; + liftAmount.set(Math.min(liftAmount.get() + normalizedDelta * 0.5, 400)); + recoilFired = false; + + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + if (!isTouching) { + triggerRebound(); + } + }, 1500); + } else if (e.deltaY < 0) { + if (!isTouching) { + liftAmount.set(0); + recoilFired = false; + } + } + }; + + let lastTouchY = 0; + let atBottomOnStart = false; + const handleTouchStart = (e: TouchEvent) => { + isTouching = true; + lastTouchY = e.touches[0].clientY; + atBottomOnStart = + window.innerHeight + window.pageYOffset >= + document.documentElement.scrollHeight - 5; + recoilFired = false; + if (timeout) clearTimeout(timeout); + }; + + const handleTouchMove = (e: TouchEvent) => { + const currentY = e.touches[0].clientY; + const delta = lastTouchY - currentY; + lastTouchY = currentY; + + if (!atBottomOnStart) { + atBottomOnStart = + window.innerHeight + window.pageYOffset >= + document.documentElement.scrollHeight - 5; + return; + } + + if (delta > 0) { + liftAmount.set(Math.min(liftAmount.get() + delta * 4, 400)); + recoilFired = false; + } else if (liftAmount.get() > 0) { + liftAmount.set(Math.max(0, liftAmount.get() + delta * 4)); + } + }; + + const handleTouchEnd = () => { + isTouching = false; + atBottomOnStart = false; + if (liftAmount.get() > 0) { + if (timeout) clearTimeout(timeout); + timeout = setTimeout(() => { + triggerRebound(); + }, 1500); + } + }; + + window.addEventListener("wheel", handleWheel, { passive: true }); + window.addEventListener("touchstart", handleTouchStart, { passive: true }); + window.addEventListener("touchmove", handleTouchMove, { passive: true }); + window.addEventListener("touchend", handleTouchEnd, { passive: true }); + window.addEventListener("touchcancel", handleTouchEnd, { passive: true }); + + return () => { + if (timeout) clearTimeout(timeout); + window.removeEventListener("wheel", handleWheel); + window.removeEventListener("touchstart", handleTouchStart); + window.removeEventListener("touchmove", handleTouchMove); + window.removeEventListener("touchend", handleTouchEnd); + window.removeEventListener("touchcancel", handleTouchEnd); + }; + }, [liftAmount]); + + return ( +
+ {showFooter && } + + {children} + +
+ ); +} diff --git a/apps/website/components/faq.jsx b/apps/website/components/faq.jsx deleted file mode 100644 index 2b95ca5d6..000000000 --- a/apps/website/components/faq.jsx +++ /dev/null @@ -1,147 +0,0 @@ -"use client"; -import { faqData, AnimatedPlusMinus } from "@/app/constant"; -import Image from "next/image"; -import { useState } from "react"; -import { motion, AnimatePresence } from "framer-motion"; - -export default function FAQ() { - const [openId, setOpenId] = useState(3); - - const springConfig = { - type: "spring", - stiffness: 280, - damping: 32, - mass: 1, - restDelta: 0.01, - }; - - const toggleAccordion = (id) => { - setOpenId(openId === id ? null : id); - }; - - return ( -
-
-
-

- - Frequently Asked - -
- Questions -

-
- -
- -
-
-
- -
- {faqData.map((faq) => { - const isOpen = openId === faq.id; - - return ( -
toggleAccordion(faq.id)} - className={`group relative border-b last:border-b-0 border-[#292929] cursor-pointer w-full flex flex-col justify-center transition-colors duration-300 ${isOpen ? "" : "hover:bg-[#080808]"}`} - > -
-
- - faq background -
-
- -
-
-

- {faq.question} -

-
- -
-
- -
-
- - {isOpen && ( - - {faq.answer.split("\n\n").map((paragraph, idx) => ( -

{paragraph}

- ))} -
- )} -
-
-
-
-
- ); - })} - - -
-
-
- ); -} diff --git a/apps/website/components/faq.tsx b/apps/website/components/faq.tsx new file mode 100644 index 000000000..c3a9d6c1a --- /dev/null +++ b/apps/website/components/faq.tsx @@ -0,0 +1,154 @@ +"use client"; +import { AnimatePresence, motion } from "framer-motion"; +import { useState } from "react"; +import { AnimatedPlusMinus, faqData } from "@/app/constant"; +import { cn } from "@/lib/utils"; + +export default function FAQ() { + const [openId, setOpenId] = useState(3); + + const springConfig = { + type: "spring" as const, + stiffness: 280, + damping: 32, + mass: 1, + restDelta: 0.01, + }; + + const toggleAccordion = (id: number) => { + setOpenId(openId === id ? null : id); + }; + + return ( +
+
+
+

+ + Frequently Asked + +
+ Questions +

+
+ +
+ +
+
+
+ +
+ {faqData.map((faq) => { + const isOpen = openId === faq.id; + + return ( +
toggleAccordion(faq.id)} + className={cn( + "group relative flex w-full cursor-pointer flex-col justify-center border-b border-[#292929] transition-colors duration-300 last:border-b-0", + !isOpen && "hover:bg-[#080808]", + )} + > +
+
+ + faq background +
+
+ +
+
+

+ {faq.question} +

+
+ +
+
+ +
+
+ + {isOpen && ( + + {faq.answer.split("\n\n").map((paragraph, idx) => ( +

{paragraph}

+ ))} +
+ )} +
+
+
+
+
+ ); + })} + + +
+
+
+ ); +} diff --git a/apps/website/components/feature-icon-animation.jsx b/apps/website/components/feature-icon-animation.jsx deleted file mode 100644 index bfcb978cb..000000000 --- a/apps/website/components/feature-icon-animation.jsx +++ /dev/null @@ -1,68 +0,0 @@ -"use client"; -import { forwardRef, useImperativeHandle, useRef, useEffect } from "react"; -import gsap from "gsap"; - -export const FeatureIconAnimation = forwardRef(({ Icon }, ref) => { - const iconRef = useRef(null); - const tlRef = useRef(null); - - useImperativeHandle(ref, () => ({ - play: () => tlRef.current?.play(), - reverse: () => tlRef.current?.reverse(), - })); - - useEffect(() => { - const pixelEls = iconRef.current?.querySelectorAll("path"); - if (!pixelEls?.length) return; - - // Sort pixels by distance from center — center pixels animate first, outer ones last - const centers = Array.from(pixelEls).map((el) => { - const b = el.getBBox(); - return { el, cx: b.x + b.width / 2, cy: b.y + b.height / 2 }; - }); - const midX = centers.reduce((s, c) => s + c.cx, 0) / centers.length; - const midY = centers.reduce((s, c) => s + c.cy, 0) / centers.length; - const pixels = centers - .sort((a, b) => { - const da = (a.cx - midX) ** 2 + (a.cy - midY) ** 2; - const db = (b.cx - midX) ** 2 + (b.cy - midY) ** 2; - return da - db; - }) - .map((c) => c.el); - - gsap.set(pixels, { - opacity: 0.15, - scale: 0.8, - transformOrigin: "center center", - fill: "currentColor", - }); - - tlRef.current = gsap.timeline({ paused: true }); - - tlRef.current - .to(pixels, { - opacity: 1, - scale: 1.15, - fill: "#9564ff", - duration: 0.2, - stagger: 0.04, - ease: "power2.out", - }) - .to(pixels, { - scale: 1, - duration: 0.15, - ease: "back.out(3)", - }); - - return () => tlRef.current?.kill(); - }, []); - - return ( -
-
- -
-
- ); -}); -FeatureIconAnimation.displayName = "FeatureIconAnimation"; diff --git a/apps/website/components/feature-icon-animation.tsx b/apps/website/components/feature-icon-animation.tsx new file mode 100644 index 000000000..74eddf187 --- /dev/null +++ b/apps/website/components/feature-icon-animation.tsx @@ -0,0 +1,75 @@ +"use client"; +import gsap from "gsap"; +import { forwardRef, useEffect, useImperativeHandle, useRef } from "react"; +import type { PixelAnimationHandle, PixelIconComponent } from "@/lib/types"; + +export const FeatureIconAnimation = forwardRef< + PixelAnimationHandle, + { Icon: PixelIconComponent } +>(({ Icon }, ref) => { + const iconRef = useRef(null); + const tlRef = useRef(null); + + useImperativeHandle(ref, () => ({ + play: () => tlRef.current?.play(), + reverse: () => tlRef.current?.reverse(), + })); + + useEffect(() => { + const pixelEls = + iconRef.current?.querySelectorAll("path"); + if (!pixelEls?.length) return; + + // Sort pixels by distance from center — center pixels animate first, outer ones last + const centers = Array.from(pixelEls).map((el) => { + const b = el.getBBox(); + return { el, cx: b.x + b.width / 2, cy: b.y + b.height / 2 }; + }); + const midX = centers.reduce((s, c) => s + c.cx, 0) / centers.length; + const midY = centers.reduce((s, c) => s + c.cy, 0) / centers.length; + const pixels = centers + .sort((a, b) => { + const da = (a.cx - midX) ** 2 + (a.cy - midY) ** 2; + const db = (b.cx - midX) ** 2 + (b.cy - midY) ** 2; + return da - db; + }) + .map((c) => c.el); + + gsap.set(pixels, { + opacity: 0.15, + scale: 0.8, + transformOrigin: "center center", + fill: "currentColor", + }); + + tlRef.current = gsap.timeline({ paused: true }); + + tlRef.current + .to(pixels, { + opacity: 1, + scale: 1.15, + fill: "#9564ff", + duration: 0.2, + stagger: 0.04, + ease: "power2.out", + }) + .to(pixels, { + scale: 1, + duration: 0.15, + ease: "back.out(3)", + }); + + return () => { + tlRef.current?.kill(); + }; + }, []); + + return ( +
+
+ +
+
+ ); +}); +FeatureIconAnimation.displayName = "FeatureIconAnimation"; diff --git a/apps/website/components/features.jsx b/apps/website/components/features.jsx deleted file mode 100755 index 4da1f75e0..000000000 --- a/apps/website/components/features.jsx +++ /dev/null @@ -1,73 +0,0 @@ -"use client"; -import { featuresData } from "@/app/constant"; -import { useRef } from "react"; -import { FeatureIconAnimation } from "./feature-icon-animation"; - -function FeatureCard({ feature }) { - const isDesktop = - typeof window !== "undefined" && - window.matchMedia("(hover: hover)").matches; - const iconRef = useRef(null); - return ( -
isDesktop && iconRef.current?.play()} - onMouseLeave={() => isDesktop && iconRef.current?.reverse()} - className="group relative flex px-4 md:px-4 flex-col justify-between p-6 bg-[#0F0F0F] min-h-[200px] md:min-h-[280px] border-r border-b border-[#292929] overflow-hidden cursor-pointer" - > -
-
- {/* Hover Gradient Overlay */} -
- -
- -
- -
-
-

- {feature.title} -

-

- {feature.description} -

-
-
-
- ); -} - -export default function Features() { - return ( -
-
-

-
Everything you need
-
for AI and usage billing.
-

-
- Your entire billing infrastructure, {" "} - - fully managed. - -
-
- -
-
- {featuresData.map((feature, i) => ( - - ))} - {/*
*/} -
-
- ); -} diff --git a/apps/website/components/features.tsx b/apps/website/components/features.tsx new file mode 100755 index 000000000..12bc08463 --- /dev/null +++ b/apps/website/components/features.tsx @@ -0,0 +1,72 @@ +"use client"; +import { useRef } from "react"; +import { featuresData } from "@/app/constant"; +import type { PixelAnimationHandle } from "@/lib/types"; +import { FeatureIconAnimation } from "./feature-icon-animation"; + +function FeatureCard({ feature }: { feature: (typeof featuresData)[number] }) { + const isDesktop = + typeof window !== "undefined" && + window.matchMedia("(hover: hover)").matches; + const iconRef = useRef(null); + return ( +
isDesktop && iconRef.current?.play()} + onMouseLeave={() => isDesktop && iconRef.current?.reverse()} + className="group relative flex px-4 md:px-4 flex-col justify-between p-6 bg-[#0F0F0F] min-h-[200px] md:min-h-[280px] border-r border-b border-[#292929] overflow-hidden cursor-pointer" + > +
+
+ {/* Hover Gradient Overlay */} +
+ +
+ +
+ +
+
+

+ {feature.title} +

+

+ {feature.description} +

+
+
+
+ ); +} + +export default function Features() { + return ( +
+
+

+
Everything you need
+
for AI and usage billing.
+

+
+ Your entire billing infrastructure,{" "} + fully managed. +
+
+ +
+
+ {featuresData.map((feature, i) => ( + + ))} + {/*
*/} +
+
+ ); +} diff --git a/apps/website/components/footer.jsx b/apps/website/components/footer.jsx deleted file mode 100644 index ef07d6bd8..000000000 --- a/apps/website/components/footer.jsx +++ /dev/null @@ -1,181 +0,0 @@ -import Image from "next/image"; -import Link from "next/link"; - -const footerColumns = [ - { - title: "PRODUCT", - links: [ - { label: "FEATURES", href: "#" }, - { label: "INTEGRATIONS", href: "#" }, - { label: "PRICING", href: "#" }, - { label: "CHANGELOG", href: "#" }, - { label: "ROADMAP", href: "#" }, - ], - }, - { - title: "COMPANY", - links: [ - { label: "OUR TEAM", href: "#" }, - { label: "OUR VALUES", href: "/privacy" }, - { label: "BLOG", href: "/blog" }, - ], - }, - { - title: "RESOURCES", - links: [ - { label: "DOWNLOADS", href: "https://useautumn.com/" }, - { label: "DOCUMENTATION", href: "https://docs.useautumn.com/welcome" }, - { label: "CONTACT", href: "https://cal.com/ayrod/a?user=ayrod" }, - ], - }, -]; - -export default function Footer() { - return ( - <> -
-
-
-
- -
- {/* Left Side: Logo and Description */} -
- - Autumn - -

- Autumn is built on top of Stripe Billing (for now), so their - fees (0.7%, and 2.9% + 30c) still apply. -

-
- - {/* Right Side: Columns */} -
- {footerColumns.map((col, index) => ( -
-

-
- {col.title} -

-
    - {col.links.map((link, i) => ( -
  • - - - - - - {link.label} - - - - -
  • - ))} -
-
- ))} -
-
-
- -
-
- {/* Social Left Box */} -
-
- SOCIAL -
-
- - - - LINKEDIN - - - - -
- - - - TWITTER - - - - -
-
- -
- Copyright © 2026 Autumn All rights reserved -
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- - ); -} diff --git a/apps/website/components/footer.tsx b/apps/website/components/footer.tsx new file mode 100644 index 000000000..17942d4da --- /dev/null +++ b/apps/website/components/footer.tsx @@ -0,0 +1,180 @@ +import Image from "next/image"; +import Link from "next/link"; + +const footerColumns: any[] = [ + // { + // title: "PRODUCT", + // links: [ + // { label: "FEATURES", href: "#" }, + // { label: "INTEGRATIONS", href: "#" }, + // { label: "PRICING", href: "#" }, + // { label: "CHANGELOG", href: "#" }, + // { label: "ROADMAP", href: "#" }, + // ], + // }, + // { + // title: "COMPANY", + // links: [ + // { label: "OUR TEAM", href: "#" }, + // { label: "OUR VALUES", href: "/privacy" }, + // { label: "BLOG", href: "/blog" }, + // ], + // }, + // { + // title: "RESOURCES", + // links: [ + // { label: "DOWNLOADS", href: "https://useautumn.com/" }, + // { label: "DOCUMENTATION", href: "https://docs.useautumn.com/welcome" }, + // { label: "CONTACT", href: "https://cal.com/ayrod/a?user=ayrod" }, + // ], + // }, +]; + +export default function Footer() { + return ( + <> +
+
+
+
+ +
+ {/* Left Side: Logo and Description */} +
+ + Autumn + +

+ Autumn is drop-in billing for AI companies. +

+
+ + {/* Right Side: Columns */} +
+ {footerColumns.map((col, index) => ( +
+

+
+ {col.title} +

+
    + {col.links.map((link, i) => ( +
  • + + + + + + {link.label} + + + + +
  • + ))} +
+
+ ))} +
+
+
+ +
+
+ {/* Social Left Box */} +
+
+ SOCIAL +
+
+ + + + LINKEDIN + + + + +
+ + + + TWITTER + + + + +
+
+ +
+ Copyright © 2026 Autumn All rights reserved +
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ + ); +} diff --git a/apps/website/components/hero.tsx b/apps/website/components/hero.tsx new file mode 100755 index 000000000..e1a7f4455 --- /dev/null +++ b/apps/website/components/hero.tsx @@ -0,0 +1,307 @@ +"use client"; + +import { useGSAP } from "@gsap/react"; +import gsap from "gsap"; +import { motion } from "motion/react"; +import Image from "next/image"; +import Link from "next/link"; +import { useEffect, useRef, useState } from "react"; +import { CTALines, IconCTADocs, IconCTAStart } from "@/app/constant"; +import AutumnConfig from "./autumn-config"; + +// import dynamic from "next/dynamic"; + +// const AutumnConfig = dynamic(() => import("./autumn-config"), { ssr: false }); + +const BADGE_TEXT = "// 100% open source"; + +const getLoggedInHintCookie = () => { + if (typeof window === "undefined") return null; + return ( + document.cookie + .split("; ") + .find((row) => row.startsWith("logged_in_hint=")) + ?.split("=")[1] === "1" + ); +}; + +export default function Hero() { + const containerRef = useRef(null); + const heroTlRef = useRef(null); + const [displayedText, setDisplayedText] = useState(""); + const [isLoggedIn, setIsLoggedIn] = useState(false); + + // Read the hint cookie after mount to avoid SSR/CSR hydration mismatch. + useEffect(() => { + setIsLoggedIn(getLoggedInHintCookie() === true); + }, []); + + // Badge typewriter + useEffect(() => { + const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*0123456789"; + let intervalId: ReturnType | null = null; + let timeoutId: ReturnType | null = null; + + const startTypewriter = () => { + timeoutId = setTimeout(() => { + let iteration = 0; + intervalId = setInterval(() => { + setDisplayedText( + BADGE_TEXT.split("") + .map((char, index) => { + if (char === " ") return " "; + if (index < Math.floor(iteration)) return char; + if (index === Math.floor(iteration)) + return chars[Math.floor(Math.random() * chars.length)]; + return ""; + }) + .join(""), + ); + + iteration += 0.4; + + if (iteration >= BADGE_TEXT.length) { + if (intervalId) clearInterval(intervalId); + setDisplayedText(BADGE_TEXT); + } + }, 30); + }, 150); + }; + + window.addEventListener("preloader:complete", startTypewriter, { + once: true, + }); + return () => { + window.removeEventListener("preloader:complete", startTypewriter); + if (timeoutId) clearTimeout(timeoutId); + if (intervalId) clearInterval(intervalId); + }; + }, []); + + useEffect(() => { + const handler = () => heroTlRef.current?.play(); + window.addEventListener("preloader:complete", handler, { once: true }); + return () => window.removeEventListener("preloader:complete", handler); + }, []); + + useGSAP( + () => { + gsap.set(".hero-root", { opacity: 0 }); + + gsap.set(".hero-bg", { + opacity: 0, + filter: "blur(6px) brightness(1)", + scale: 0.97, + transformOrigin: "center top", + }); + + gsap.set(".hero-reveal", { + opacity: 0, + y: 25, + filter: "blur(12px)", + scale: 0.96, + transformOrigin: "center bottom", + }); + + gsap.set(".hero-cta", { opacity: 0, scale: 0.95 }); + + const tl = gsap.timeline({ + paused: true, + defaults: { overwrite: "auto" }, + }); + heroTlRef.current = tl; + + tl.to(".hero-root", { opacity: 1, duration: 0.3, ease: "none" }) + + .to(".hero-bg", { + opacity: 1, + filter: "blur(0px) brightness(1)", + scale: 1, + duration: 0.4, + ease: "power2.out", + }) + + .to(".hero-bg", { + filter: "blur(0px) brightness(1.6)", + duration: 0.125, + ease: "power2.in", + }) + + .to(".hero-bg", { + filter: "blur(0px) brightness(1)", + duration: 0.125, + ease: "power2.out", + }) + + .to( + ".hero-reveal", + { + opacity: 1, + y: 0, + filter: "blur(0px)", + scale: 1, + duration: 1.1, + stagger: 0.1, + ease: "power3.out", + }, + "-=0.2", + ) + + .to( + ".hero-cta", + { + opacity: 1, + scale: 1, + duration: 0.3, + stagger: 0.06, + ease: "back.out(1.5)", + }, + "-=0.1", + ); + }, + { scope: containerRef }, + ); + + return ( +
+
+
+
+

+ + + {displayedText} + +

+
+

+ + The drop-in billing layer for + {" "} + AI startups +

+

+ Stop rebuilding usage limits, credit ledgers and payment logic.{" "} + + Autumn is your customer database + {" "} + that scales from your first user to your largest contract. +

+
+
+
+
+
+
+ +
+
+
+
+
+ {/* Primary CTA */} +
+ + + {/* Adjusted px-3 for mobile, md:px-4 for desktop */} +
+ + + {isLoggedIn ? "Dashboard" : "Start for free"} + + + + +
+
+ +
+ + {/* Secondary CTA */} +
+ + +
+ + + Book a call + + + + +
+
+ +
+ +
+
+
+
+ +
+
+
+
+
+
+
+
+
+ {/* MOBILE VIEW*/} +
+
+
+
+
+
+ ); +} diff --git a/apps/website/components/home-sections.tsx b/apps/website/components/home-sections.tsx new file mode 100755 index 000000000..ccd6b7b4e --- /dev/null +++ b/apps/website/components/home-sections.tsx @@ -0,0 +1,46 @@ +"use client"; + +import dynamic from "next/dynamic"; +import Hero from "./hero"; +import SectionDivider from "./section-divider"; + +const ProductionScale = dynamic(() => import("@/components/production-scale"), { + ssr: false, +}); +const Problem = dynamic(() => import("@/components/problem"), { ssr: false }); +const Solution = dynamic(() => import("@/components/solution"), { ssr: false }); +const Features = dynamic(() => import("@/components/features"), { ssr: false }); +const PricingModels = dynamic(() => import("@/components/pricing-models"), { + ssr: false, +}); +const Testimonials = dynamic(() => import("@/components/testimonials"), { + ssr: false, +}); +const Pricing = dynamic(() => import("@/components/pricing"), { ssr: false }); +const FAQ = dynamic(() => import("@/components/faq"), { ssr: false }); +const Footer = dynamic(() => import("@/components/footer"), { ssr: false }); + +export default function HomeSections() { + return ( + <> + + + + + + + + + + + + + + + + + +