diff --git a/apps/website/app/blog/[slug]/page.js b/apps/website/app/blog/[slug]/page.js
deleted file mode 100644
index 4c5a8bb1b..000000000
--- a/apps/website/app/blog/[slug]/page.js
+++ /dev/null
@@ -1,108 +0,0 @@
-import { getAllPosts, getPostBySlug } from "@/lib/blogUtils";
-import { mdxComponents } from "@/components/blogComponents";
-import { MDXRemote } from "next-mdx-remote/rsc";
-import Image from "next/image";
-import Link from "next/link";
-import { notFound } from "next/navigation";
-
-export function generateStaticParams() {
- return getAllPosts().map((post) => ({ slug: post.slug }));
-}
-
-export async function generateMetadata({ params }) {
- 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",
- publishedTime: post.date,
- authors: [post.author],
- ...(post.image && {
- images: [{ url: post.image }],
- }),
- },
- };
-}
-
-function formatDate(dateString) {
- if (!dateString) return "";
- return new Date(dateString).toLocaleDateString("en-US", {
- year: "numeric",
- month: "long",
- day: "numeric",
- });
-}
-
-export default async function BlogPostPage({ params }) {
- const { slug } = await params;
- const post = getPostBySlug({ slug });
- if (!post) notFound();
-
- return (
-
-
-
-
-
-
- Back to blog
-
-
-
-
- {post.image && (
-
-
-
- )}
-
-
-
-
-
-
-
-
- );
-}
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
+
+
+
+
+ {post.image && (
+
+
+
+ )}
+
+
+
+
+
+
+
+
+ );
+}
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 && (
-
-
-
- )}
-
- ))}
-
-
-
- );
-}
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 && (
+
+
+
+ )}
+
+ ))}
+
+
+
+ );
+}
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 */}
-
-
-);
diff --git a/apps/website/app/constant.tsx b/apps/website/app/constant.tsx
new file mode 100755
index 000000000..ccff2acab
--- /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 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 }: 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 */}
+
+
+);
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 (
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- Page not found
-
-
-
- The page you are looking for doesn't exist or has been moved.
-
-
-
-
-
- Back to home
-
-
-
-
-
-
-
-
-
-
- );
-}
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 (
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Page not found
+
+
+
+ The page you are looking for doesn't exist or has been moved.
+
+
+
+
+
+ Back to home
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/website/app/page.js b/apps/website/app/page.js
deleted file mode 100644
index 1b6b3aab4..000000000
--- a/apps/website/app/page.js
+++ /dev/null
@@ -1,41 +0,0 @@
-import ElasticRecoil from "@/components/elastic-footer";
-import HomeSections from "@/components/home-sections";
-import Navbar from "@/components/navbar";
-import Preloader from "@/components/preloader";
-
-export default function Home() {
- return (
-
- );
-}
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 (
-
- );
-});
-
-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 (
+
+ );
+ },
+);
+
+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 */}
- {/*
-
-
-
-
- autumn.config.ts
-
-
-
-
-
-
-
-
*/}
-
- {/* 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 */}
-
-
-
-
-
- {/* 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..03d64af76
--- /dev/null
+++ b/apps/website/components/autumn-config.tsx
@@ -0,0 +1,262 @@
+"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 = 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,
+}: {
+ 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]);
+
+ // Blinking cursor after done
+ useEffect(() => {
+ if (!done) return;
+ const interval = setInterval(() => setCursorVisible((v) => !v), 530);
+ return () => clearInterval(interval);
+ }, [done]);
+
+ return (
+
+ {/* Title bar */}
+ {/*
+
+
+
+
+ autumn.config.ts
+
+
+
+
+
+
+
+
*/}
+
+ {/* 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 */}
+
+
+
+
+
+ {/* 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 }) => (
-
- ),
- 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">) => (
+
+ ),
+ 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]"}`}
- >
-
-
-
-
-
-
-
-
-
-
-
-
-
- {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]",
+ )}
+ >
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {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 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}
-
-
-
- {link.label}
-
-
-
-
-
- ))}
-
-
- ))}
-
-
-
-
-
-
- {/* Social Left Box */}
-
-
- SOCIAL
-
-
-
-
-
- LINKEDIN
-
-
- LINKEDIN
-
-
-
-
-
-
-
- TWITTER
-
-
- 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..a2a252ebe
--- /dev/null
+++ b/apps/website/components/footer.tsx
@@ -0,0 +1,181 @@
+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 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}
+
+
+
+ {link.label}
+
+
+
+
+
+ ))}
+
+
+ ))}
+
+
+
+
+
+
+ {/* Social Left Box */}
+
+
+ SOCIAL
+
+
+
+
+
+ LINKEDIN
+
+
+ LINKEDIN
+
+
+
+
+
+
+
+ TWITTER
+
+
+ TWITTER
+
+
+
+
+
+
+
+ Copyright © 2026 Autumn All rights reserved
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/website/components/hero.jsx b/apps/website/components/hero.tsx
similarity index 95%
rename from apps/website/components/hero.jsx
rename to apps/website/components/hero.tsx
index 0688eff98..e1a7f4455 100755
--- a/apps/website/components/hero.jsx
+++ b/apps/website/components/hero.tsx
@@ -1,307 +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;
- let timeoutId;
-
- 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) {
- clearInterval(intervalId);
- setDisplayedText(BADGE_TEXT);
- }
- }, 30);
- }, 150);
- };
-
- window.addEventListener("preloader:complete", startTypewriter, {
- once: true,
- });
- return () => {
- window.removeEventListener("preloader:complete", startTypewriter);
- clearTimeout(timeoutId);
- 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 (
-
-
-
-
-
-
- {BADGE_TEXT}
-
-
- {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*/}
-
-
-
- );
-}
+"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 (
+
+
+
+
+
+
+ {BADGE_TEXT}
+
+
+ {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.jsx b/apps/website/components/home-sections.tsx
similarity index 56%
rename from apps/website/components/home-sections.jsx
rename to apps/website/components/home-sections.tsx
index b6f234cf0..ccd6b7b4e 100755
--- a/apps/website/components/home-sections.jsx
+++ b/apps/website/components/home-sections.tsx
@@ -1,46 +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 (
- <>
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
- >
- );
-}
+"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 (
+ <>
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/website/components/navbar.jsx b/apps/website/components/navbar.jsx
deleted file mode 100644
index d1152c0ac..000000000
--- a/apps/website/components/navbar.jsx
+++ /dev/null
@@ -1,421 +0,0 @@
-"use client";
-
-import {
- CTALines,
- IconBlog,
- IconCTAStart,
- IconDashboard,
- IconDiscord,
- IconDocs,
- IconPricing,
- MenuGridIcon,
-} from "@/app/constant";
-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 {
- forwardRef,
- useEffect,
- useImperativeHandle,
- useRef,
- useState,
-} from "react";
-import { DashboardIconPixel } from "./dashboard-icon-pixel";
-
-const NAV_LINKS = [
- { label: "Docs", href: "https://docs.useautumn.com/welcome", Icon: IconDocs },
- { label: "Blog", href: "/blog", Icon: IconBlog },
- { label: "Pricing", href: "#pricing", Icon: IconPricing },
- {
- label: "Discord",
- href: "https://discord.com/invite/STqxY92zuS",
- Icon: IconDiscord,
- },
-];
-
-const NavIconPixel = forwardRef(function NavIconPixel({ 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;
-
- 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.01,
- stagger: 0.025,
- ease: "power2.out",
- })
- .to(pixels, {
- scale: 1,
- duration: 0.01,
- ease: "back.out(3)",
- });
-
- return () => tlRef.current?.kill();
- }, []);
-
- return (
-
-
-
-
-
- );
-});
-
-function NavLinkItem({ item }) {
- const iconRef = useRef(null);
- const isAnchor = item.href.startsWith("#");
-
- const handleClick = (e) => {
- if (!isAnchor) return;
- e.preventDefault();
- const target = document.querySelector(item.href);
- if (target) target.scrollIntoView({ behavior: "smooth" });
- };
-
- return (
-
- iconRef.current?.restart()}
- onMouseLeave={() => iconRef.current?.reverse()}
- >
-
-
- {item.label}
-
-
-
- );
-}
-
-export default function Navbar() {
- const containerRef = useRef(null);
- const dashboardIconRef = useRef(null);
- const RECOIL_DELAY = 1000;
- const [recoilHidden, setRecoilHidden] = useState(false);
- const recoilTimerRef = useRef(null);
- const [menuOpen, setMenuOpen] = useState(false);
- const [scrolled, setScrolled] = useState(false);
-
- useEffect(() => {
- const handleScroll = () => setScrolled(window.scrollY > 10);
- window.addEventListener("scroll", handleScroll, { passive: true });
- return () => window.removeEventListener("scroll", handleScroll);
- }, []);
-
- useEffect(() => {
- const onRecoil = () => {
- setRecoilHidden(true);
- clearTimeout(recoilTimerRef.current);
- recoilTimerRef.current = setTimeout(
- () => setRecoilHidden(false),
- RECOIL_DELAY,
- );
- };
- window.addEventListener("elastic-recoil", onRecoil);
- return () => {
- window.removeEventListener("elastic-recoil", onRecoil);
- clearTimeout(recoilTimerRef.current);
- };
- }, []);
-
- // Lock body scroll while mobile menu is open
- useEffect(() => {
- document.documentElement.style.overflow = menuOpen ? "hidden" : "";
- document.body.style.overflow = menuOpen ? "hidden" : "";
- return () => {
- document.documentElement.style.overflow = "";
- document.body.style.overflow = "";
- };
- }, [menuOpen]);
-
- useGSAP(
- () => {
- gsap.set(".nav-root", { opacity: 0 });
- gsap.set(".nav-logo", {
- opacity: 0,
- filter: "blur(6px) brightness(1)",
- scale: 0.92,
- transformOrigin: "left center",
- });
- gsap.set(".nav-link", { opacity: 0, y: -8 });
- gsap.set(".nav-dashboard", { opacity: 0, scale: 0.95 });
-
- const tl = gsap.timeline({ defaults: { overwrite: "auto" } });
-
- tl.to(".nav-root", { opacity: 1, duration: 0.3, ease: "none" })
- .to(".nav-logo", {
- opacity: 1,
- filter: "blur(0px) brightness(1)",
- scale: 1,
- duration: 0.7,
- ease: "power2.out",
- })
- .to(".nav-logo", {
- filter: "blur(0px) brightness(1.6)",
- duration: 0.225,
- ease: "power2.in",
- })
- .to(".nav-logo", {
- filter: "blur(0px) brightness(1)",
- duration: 0.125,
- ease: "power2.out",
- })
- .to(
- ".nav-link",
- {
- opacity: 1,
- y: 0,
- duration: 0.25,
- stagger: 0.06,
- ease: "power2.out",
- },
- "-=0.05",
- )
- .to(
- ".nav-dashboard",
- {
- opacity: 1,
- scale: 1,
- duration: 0.3,
- ease: "back.out(1.5)",
- },
- "-=0.1",
- );
- },
- { scope: containerRef },
- );
-
- const mobileTl = useRef(null);
-
- useGSAP(
- () => {
- gsap.set(".nav-mobile", {
- opacity: 0,
- clipPath: "inset(0% 0 100% 0)",
- pointerEvents: "none",
- });
-
- gsap.set(
- ".nav-mobile a, .nav-mobile img, .nav-mobile button, .nav-mobile .border-b",
- {
- opacity: 0,
- filter: "blur(8px)",
- scale: 0.95,
- },
- );
-
- mobileTl.current = gsap.timeline({
- paused: true,
- defaults: { overwrite: "auto" },
- });
-
- mobileTl.current
- .to(".nav-mobile", {
- opacity: 1,
- y: 0,
- pointerEvents: "auto",
- clipPath: "inset(0% 0 0% 0)",
- duration: 0.65,
- ease: "power3.inOut",
- })
- .to(
- ".nav-mobile a, .nav-mobile img, .nav-mobile button, .nav-mobile .border-b",
- {
- opacity: 1,
- filter: "blur(0px)",
- scale: 1,
- duration: 0.4,
- stagger: 0.02,
- ease: "power2.out",
- },
- "-=0.35",
- );
- },
- { scope: containerRef },
- );
-
- useGSAP(
- () => {
- if (mobileTl.current) {
- if (menuOpen) {
- mobileTl.current.timeScale(1).play();
- } else {
- mobileTl.current.timeScale(1.8).reverse();
- }
- }
- },
- { scope: containerRef, dependencies: [menuOpen] },
- );
-
- return (
-
-
- {scrolled && !recoilHidden && (
-
- )}
-
-
- {scrolled && !recoilHidden && (
- <>
-
-
-
- >
- )}
-
-
-
-
-
-
- {NAV_LINKS.map((item) => (
-
- ))}
-
-
-
-
- dashboardIconRef.current?.restart()}
- onMouseLeave={() => dashboardIconRef.current?.reverse()}
- >
-
-
-
-
- Dashboard
-
-
-
-
-
-
- setMenuOpen(!menuOpen)}
- whileTap={{ scale: 0.9 }}
- aria-label="Toggle menu"
- >
-
-
-
-
-
- {/* Nav items */}
-
- {NAV_LINKS.map((item) => {
- const isAnchor = item.href.startsWith("#");
- const isExternal = item.href.startsWith("http");
- return (
- {
- e.preventDefault();
- setMenuOpen(false);
- const target = document.querySelector(item.href);
- if (target) target.scrollIntoView({ behavior: "smooth" });
- } : undefined}
- className="flex items-center gap-4 px-4 py-3.5 border-b border-[#292929] active:bg-[#141414ea] text-[#ffffff99] hover:text-white active:text-white transition-colors text-sm tracking-[-1%]"
- >
-
- {item.label}
-
- );
- })}
-
-
-
-
- setMenuOpen(false)}
- className="flex items-center justify-between gap-4 px-2 py-2.5 bg-[#9564ff] active:bg-[#7D46F4] transition-colors duration-300 text-white text-sm tracking-widest"
- >
- Start for free
-
-
-
- {Array.from({ length: 3 }).map((_, i) => (
-
- ))}
-
-
- {!scrolled && (
-
- )}
-
- );
-}
diff --git a/apps/website/components/navbar.tsx b/apps/website/components/navbar.tsx
new file mode 100644
index 000000000..549009015
--- /dev/null
+++ b/apps/website/components/navbar.tsx
@@ -0,0 +1,452 @@
+"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 type { MouseEvent } from "react";
+import {
+ forwardRef,
+ useEffect,
+ useImperativeHandle,
+ useRef,
+ useState,
+} from "react";
+import {
+ CTALines,
+ IconBlog,
+ IconCTAStart,
+ IconDashboard,
+ IconDiscord,
+ IconDocs,
+ IconPricing,
+ MenuGridIcon,
+} from "@/app/constant";
+import type {
+ PageStyle,
+ PixelHoverHandle,
+ PixelIconComponent,
+} from "@/lib/types";
+import { cn } from "@/lib/utils";
+import { DashboardIconPixel } from "./dashboard-icon-pixel";
+
+const NAV_LINKS = [
+ { label: "Docs", href: "https://docs.useautumn.com/welcome", Icon: IconDocs },
+ { label: "Blog", href: "/blog", Icon: IconBlog },
+ { label: "Pricing", href: "#pricing", Icon: IconPricing },
+ {
+ label: "Discord",
+ href: "https://discord.com/invite/STqxY92zuS",
+ Icon: IconDiscord,
+ },
+];
+
+const NavIconPixel = forwardRef(
+ function NavIconPixel({ 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;
+
+ 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.01,
+ stagger: 0.025,
+ ease: "power2.out",
+ })
+ .to(pixels, {
+ scale: 1,
+ duration: 0.01,
+ ease: "back.out(3)",
+ });
+
+ return () => {
+ tlRef.current?.kill();
+ };
+ }, []);
+
+ return (
+
+
+
+
+
+ );
+ },
+);
+NavIconPixel.displayName = "NavIconPixel";
+
+function NavLinkItem({ item }: { item: (typeof NAV_LINKS)[number] }) {
+ const iconRef = useRef(null);
+ const isAnchor = item.href.startsWith("#");
+
+ const handleClick = (e: MouseEvent) => {
+ if (!isAnchor) return;
+ e.preventDefault();
+ const target = document.querySelector(item.href);
+ if (target) target.scrollIntoView({ behavior: "smooth" });
+ };
+
+ return (
+
+ iconRef.current?.restart()}
+ onMouseLeave={() => iconRef.current?.reverse()}
+ >
+
+
+ {item.label}
+
+
+
+ );
+}
+
+export default function Navbar({
+ animateIntro = true,
+}: {
+ animateIntro?: boolean;
+}) {
+ const containerRef = useRef(null);
+ const dashboardIconRef = useRef(null);
+ const RECOIL_DELAY = 1000;
+ const [recoilHidden, setRecoilHidden] = useState(false);
+ const recoilTimerRef = useRef | null>(null);
+ const [menuOpen, setMenuOpen] = useState(false);
+ const [scrolled, setScrolled] = useState(false);
+
+ useEffect(() => {
+ const handleScroll = () => setScrolled(window.scrollY > 10);
+ window.addEventListener("scroll", handleScroll, { passive: true });
+ return () => window.removeEventListener("scroll", handleScroll);
+ }, []);
+
+ useEffect(() => {
+ const onRecoil = () => {
+ setRecoilHidden(true);
+ if (recoilTimerRef.current) clearTimeout(recoilTimerRef.current);
+ recoilTimerRef.current = setTimeout(
+ () => setRecoilHidden(false),
+ RECOIL_DELAY,
+ );
+ };
+ window.addEventListener("elastic-recoil", onRecoil);
+ return () => {
+ window.removeEventListener("elastic-recoil", onRecoil);
+ if (recoilTimerRef.current) clearTimeout(recoilTimerRef.current);
+ };
+ }, []);
+
+ // Lock body scroll while mobile menu is open
+ useEffect(() => {
+ document.documentElement.style.overflow = menuOpen ? "hidden" : "";
+ document.body.style.overflow = menuOpen ? "hidden" : "";
+ return () => {
+ document.documentElement.style.overflow = "";
+ document.body.style.overflow = "";
+ };
+ }, [menuOpen]);
+
+ useGSAP(
+ () => {
+ if (!animateIntro) return;
+ gsap.set(".nav-root", { opacity: 0 });
+ gsap.set(".nav-logo", {
+ opacity: 0,
+ filter: "blur(6px) brightness(1)",
+ scale: 0.92,
+ transformOrigin: "left center",
+ });
+ gsap.set(".nav-link", { opacity: 0, y: -8 });
+ gsap.set(".nav-dashboard", { opacity: 0, scale: 0.95 });
+
+ const tl = gsap.timeline({ defaults: { overwrite: "auto" } });
+
+ tl.to(".nav-root", { opacity: 1, duration: 0.3, ease: "none" })
+ .to(".nav-logo", {
+ opacity: 1,
+ filter: "blur(0px) brightness(1)",
+ scale: 1,
+ duration: 0.7,
+ ease: "power2.out",
+ })
+ .to(".nav-logo", {
+ filter: "blur(0px) brightness(1.6)",
+ duration: 0.225,
+ ease: "power2.in",
+ })
+ .to(".nav-logo", {
+ filter: "blur(0px) brightness(1)",
+ duration: 0.125,
+ ease: "power2.out",
+ })
+ .to(
+ ".nav-link",
+ {
+ opacity: 1,
+ y: 0,
+ duration: 0.25,
+ stagger: 0.06,
+ ease: "power2.out",
+ },
+ "-=0.05",
+ )
+ .to(
+ ".nav-dashboard",
+ {
+ opacity: 1,
+ scale: 1,
+ duration: 0.3,
+ ease: "back.out(1.5)",
+ },
+ "-=0.1",
+ );
+ },
+ { scope: containerRef, dependencies: [animateIntro] },
+ );
+
+ const mobileTl = useRef(null);
+
+ useGSAP(
+ () => {
+ gsap.set(".nav-mobile", {
+ opacity: 0,
+ clipPath: "inset(0% 0 100% 0)",
+ pointerEvents: "none",
+ });
+
+ gsap.set(
+ ".nav-mobile a, .nav-mobile img, .nav-mobile button, .nav-mobile .border-b",
+ {
+ opacity: 0,
+ filter: "blur(8px)",
+ scale: 0.95,
+ },
+ );
+
+ mobileTl.current = gsap.timeline({
+ paused: true,
+ defaults: { overwrite: "auto" },
+ });
+
+ mobileTl.current
+ .to(".nav-mobile", {
+ opacity: 1,
+ y: 0,
+ pointerEvents: "auto",
+ clipPath: "inset(0% 0 0% 0)",
+ duration: 0.65,
+ ease: "power3.inOut",
+ })
+ .to(
+ ".nav-mobile a, .nav-mobile img, .nav-mobile button, .nav-mobile .border-b",
+ {
+ opacity: 1,
+ filter: "blur(0px)",
+ scale: 1,
+ duration: 0.4,
+ stagger: 0.02,
+ ease: "power2.out",
+ },
+ "-=0.35",
+ );
+ },
+ { scope: containerRef },
+ );
+
+ useGSAP(
+ () => {
+ if (mobileTl.current) {
+ if (menuOpen) {
+ mobileTl.current.timeScale(1).play();
+ } else {
+ mobileTl.current.timeScale(1.8).reverse();
+ }
+ }
+ },
+ { scope: containerRef, dependencies: [menuOpen] },
+ );
+
+ return (
+
+
+ {scrolled && !recoilHidden && (
+
+ )}
+
+
+ {scrolled && !recoilHidden && (
+ <>
+
+
+
+ >
+ )}
+
+
+
+
+
+
+ {NAV_LINKS.map((item) => (
+
+ ))}
+
+
+
+
+ dashboardIconRef.current?.restart()}
+ onMouseLeave={() => dashboardIconRef.current?.reverse()}
+ >
+
+
+
+
+ Dashboard
+
+
+
+
+
+
+ setMenuOpen(!menuOpen)}
+ whileTap={{ scale: 0.9 }}
+ aria-label="Toggle menu"
+ >
+
+
+
+
+
+ {/* Nav items */}
+
+ {NAV_LINKS.map((item) => {
+ const isAnchor = item.href.startsWith("#");
+ const isExternal = item.href.startsWith("http");
+ return (
+ {
+ e.preventDefault();
+ setMenuOpen(false);
+ const target = document.querySelector(item.href);
+ if (target)
+ target.scrollIntoView({ behavior: "smooth" });
+ }
+ : undefined
+ }
+ className="flex items-center gap-4 px-4 py-3.5 border-b border-[#292929] active:bg-[#141414ea] text-[#ffffff99] hover:text-white active:text-white transition-colors text-sm tracking-[-1%]"
+ >
+
+ {item.label}
+
+ );
+ })}
+
+
+
+
+ setMenuOpen(false)}
+ className="flex items-center justify-between gap-4 px-2 py-2.5 bg-[#9564ff] active:bg-[#7D46F4] transition-colors duration-300 text-white text-sm tracking-widest"
+ >
+ Start for free
+
+
+
+ {Array.from({ length: 3 }).map((_, i) => (
+
+ ))}
+
+
+ {!scrolled && (
+
+ )}
+
+ );
+}
diff --git a/apps/website/components/preloader.jsx b/apps/website/components/preloader.jsx
deleted file mode 100644
index 392f92769..000000000
--- a/apps/website/components/preloader.jsx
+++ /dev/null
@@ -1,238 +0,0 @@
-"use client";
-
-import { useEffect, useRef, useState } from "react";
-import gsap from "gsap";
-import {
- PRELOADER_LOGO_PATH,
- PRELOADER_LOGO_VIEWBOX,
- PreloaderLogo,
-} from "@/app/constant";
-
-export default function Preloader() {
- const wrapperRef = useRef(null);
- const logoWrapRef = useRef(null);
- const gridWrapRef = useRef(null);
- const blackCoverRef = useRef(null);
- const fallbackRef = useRef(null);
- const [done, setDone] = useState(false);
-
- if (typeof window !== "undefined") {
- window.history.scrollRestoration = "manual";
- window.scrollTo(0, 0);
- document.documentElement.scrollTop = 0;
- document.body.scrollTop = 0;
- }
-
- useEffect(() => {
- window.scrollTo(0, 0);
- document.documentElement.scrollTop = 0;
- document.body.scrollTop = 0;
-
- if (fallbackRef.current) {
- fallbackRef.current.style.opacity = "0";
- }
-
- const preventScroll = (e) => {
- e.preventDefault();
- e.stopPropagation();
- };
-
- window.addEventListener("wheel", preventScroll, {
- passive: false,
- capture: true,
- });
- window.addEventListener("touchmove", preventScroll, {
- passive: false,
- capture: true,
- });
-
- const wrapper = wrapperRef.current;
- const logoWrap = logoWrapRef.current;
- const gridWrap = gridWrapRef.current;
- const blackCover = blackCoverRef.current;
-
- gsap.set(gridWrap, {
- clipPath: "inset(0 100% 0 0)",
- willChange: "clip-path",
- force3D: true,
- });
- gsap.set(blackCover, { opacity: 0, willChange: "opacity" });
-
- const logoSvg = logoWrap.querySelector("svg");
- gsap.set(logoSvg, { opacity: 0 });
-
- const canvas = document.createElement("canvas");
- Object.assign(canvas.style, {
- position: "absolute",
- top: "50%",
- left: "50%",
- transform: "translate(-50%, -50%)",
- pointerEvents: "none",
- });
- wrapper.appendChild(canvas);
-
- const cleanup = () => {
- document.documentElement.style.overflow = "";
- document.body.style.overflow = "";
- document.body.style.paddingRight = "";
- window.removeEventListener("wheel", preventScroll, { capture: true });
- window.removeEventListener("touchmove", preventScroll, {
- capture: true,
- });
- if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
- };
-
- const runExitSequence = () => {
- const tl = gsap.timeline({ defaults: { overwrite: "auto" } });
-
- tl.to(canvas, { opacity: 0, duration: 0.35, ease: "power2.out" })
- .to(
- gridWrap,
- {
- clipPath: "inset(0 0% 0 0)",
- duration: 0.35,
- ease: "power2.out",
- force3D: true,
- },
- "<",
- )
- .to(blackCover, {
- opacity: 1,
- duration: 0.45,
- ease: "power2.inOut",
- })
- .to(wrapper, {
- opacity: 0,
- duration: 0.45,
- ease: "power2.inOut",
- onStart: () => {
- window.dispatchEvent(new CustomEvent("preloader:complete"));
- },
- onComplete: () => {
- cleanup();
- setDone(true);
- },
- });
- };
-
- gsap.to(gridWrap, {
- clipPath: "inset(0 12% 0 0)",
- duration: 0.8,
- ease: "power2.in",
- force3D: true,
- });
-
- setTimeout(() => {
- const W = PRELOADER_LOGO_VIEWBOX.width;
- const H = PRELOADER_LOGO_VIEWBOX.height;
-
- const offscreen = document.createElement("canvas");
- offscreen.width = W;
- offscreen.height = H;
- const octx = offscreen.getContext("2d");
- const path2d = new Path2D(PRELOADER_LOGO_PATH);
- octx.fillStyle = "white";
- octx.fill(path2d);
- const { data } = octx.getImageData(0, 0, W, H);
-
- const dpr = window.devicePixelRatio || 1;
- const displayW =
- logoSvg.offsetWidth || logoSvg.getBoundingClientRect().width || 54;
- const displayH = Math.round((displayW / W) * H);
- const isMobile = displayW < 70;
-
- const STEP = isMobile ? 6 : 4;
- const DOT_R = isMobile
- ? Math.max(0.6, (displayW / W) * STEP * 0.3)
- : Math.max(1, (displayW / W) * STEP * 0.45);
-
- const dots = [];
- for (let y = 0; y < H; y += STEP) {
- for (let x = 0; x < W; x += STEP) {
- const i = (y * W + x) * 4;
- if (data[i + 3] > 40) {
- dots.push({ nx: x / W, ny: y / H });
- }
- }
- }
-
- for (let i = dots.length - 1; i > 0; i--) {
- const j = Math.floor(Math.random() * (i + 1));
- [dots[i], dots[j]] = [dots[j], dots[i]];
- }
-
- canvas.width = displayW * dpr;
- canvas.height = displayH * dpr;
- canvas.style.width = `${displayW}px`;
- canvas.style.height = `${displayH}px`;
- const ctx = canvas.getContext("2d");
- ctx.scale(dpr, dpr);
-
- let painted = 0;
- const total = dots.length;
- const progress = { value: 0 };
-
- gsap.to(progress, {
- value: 1,
- duration: 1.05,
- ease: "power1.inOut",
- onUpdate() {
- const target = Math.floor(progress.value * total);
- while (painted < target) {
- const d = dots[painted];
- ctx.beginPath();
- ctx.arc(d.nx * displayW, d.ny * displayH, DOT_R, 0, Math.PI * 2);
- ctx.fillStyle = "#9564FF";
- ctx.fill();
- painted++;
- }
- },
- onComplete() {
- gsap.delayedCall(0.2, runExitSequence);
- },
- });
- }, 120);
-
- return () => {
- cleanup();
- };
- }, []);
-
- if (done) return null;
-
- return (
-
-
-
-
-
-
-
-
-
-
-
- );
-}
diff --git a/apps/website/components/preloader.tsx b/apps/website/components/preloader.tsx
new file mode 100644
index 000000000..a8a4335a3
--- /dev/null
+++ b/apps/website/components/preloader.tsx
@@ -0,0 +1,244 @@
+"use client";
+
+import gsap from "gsap";
+import { useEffect, useRef, useState } from "react";
+import {
+ PRELOADER_LOGO_PATH,
+ PRELOADER_LOGO_VIEWBOX,
+ PreloaderLogo,
+} from "@/app/constant";
+
+export default function Preloader() {
+ const wrapperRef = useRef(null);
+ const logoWrapRef = useRef(null);
+ const gridWrapRef = useRef(null);
+ const blackCoverRef = useRef(null);
+ const fallbackRef = useRef(null);
+ const [done, setDone] = useState(false);
+
+ if (typeof window !== "undefined") {
+ window.history.scrollRestoration = "manual";
+ window.scrollTo(0, 0);
+ document.documentElement.scrollTop = 0;
+ document.body.scrollTop = 0;
+ }
+
+ useEffect(() => {
+ window.scrollTo(0, 0);
+ document.documentElement.scrollTop = 0;
+ document.body.scrollTop = 0;
+
+ if (fallbackRef.current) {
+ fallbackRef.current.style.opacity = "0";
+ }
+
+ const preventScroll = (e: Event) => {
+ e.preventDefault();
+ e.stopPropagation();
+ };
+
+ window.addEventListener("wheel", preventScroll, {
+ passive: false,
+ capture: true,
+ });
+ window.addEventListener("touchmove", preventScroll, {
+ passive: false,
+ capture: true,
+ });
+
+ const wrapper = wrapperRef.current;
+ const logoWrap = logoWrapRef.current;
+ const gridWrap = gridWrapRef.current;
+ const blackCover = blackCoverRef.current;
+ if (!wrapper || !logoWrap || !gridWrap || !blackCover) return;
+
+ gsap.set(gridWrap, {
+ clipPath: "inset(0 100% 0 0)",
+ willChange: "clip-path",
+ force3D: true,
+ });
+ gsap.set(blackCover, { opacity: 0, willChange: "opacity" });
+
+ const logoSvg = logoWrap.querySelector("svg");
+ if (!logoSvg) return;
+ gsap.set(logoSvg, { opacity: 0 });
+
+ const canvas = document.createElement("canvas");
+ Object.assign(canvas.style, {
+ position: "absolute",
+ top: "50%",
+ left: "50%",
+ transform: "translate(-50%, -50%)",
+ pointerEvents: "none",
+ });
+ wrapper.appendChild(canvas);
+
+ const cleanup = () => {
+ document.documentElement.style.overflow = "";
+ document.body.style.overflow = "";
+ document.body.style.paddingRight = "";
+ window.removeEventListener("wheel", preventScroll, { capture: true });
+ window.removeEventListener("touchmove", preventScroll, {
+ capture: true,
+ });
+ if (canvas.parentNode) canvas.parentNode.removeChild(canvas);
+ };
+
+ const runExitSequence = () => {
+ const tl = gsap.timeline({ defaults: { overwrite: "auto" } });
+
+ tl.to(canvas, { opacity: 0, duration: 0.35, ease: "power2.out" })
+ .to(
+ gridWrap,
+ {
+ clipPath: "inset(0 0% 0 0)",
+ duration: 0.35,
+ ease: "power2.out",
+ force3D: true,
+ },
+ "<",
+ )
+ .to(blackCover, {
+ opacity: 1,
+ duration: 0.45,
+ ease: "power2.inOut",
+ })
+ .to(wrapper, {
+ opacity: 0,
+ duration: 0.45,
+ ease: "power2.inOut",
+ onStart: () => {
+ window.dispatchEvent(new CustomEvent("preloader:complete"));
+ },
+ onComplete: () => {
+ cleanup();
+ setDone(true);
+ },
+ });
+ };
+
+ gsap.to(gridWrap, {
+ clipPath: "inset(0 12% 0 0)",
+ duration: 0.8,
+ ease: "power2.in",
+ force3D: true,
+ });
+
+ setTimeout(() => {
+ const W = PRELOADER_LOGO_VIEWBOX.width;
+ const H = PRELOADER_LOGO_VIEWBOX.height;
+
+ const offscreen = document.createElement("canvas");
+ offscreen.width = W;
+ offscreen.height = H;
+ const octx = offscreen.getContext("2d");
+ if (!octx) return;
+ const path2d = new Path2D(PRELOADER_LOGO_PATH);
+ octx.fillStyle = "white";
+ octx.fill(path2d);
+ const { data } = octx.getImageData(0, 0, W, H);
+
+ const dpr = window.devicePixelRatio || 1;
+ const displayW =
+ logoWrap.getBoundingClientRect().width ||
+ logoSvg.getBoundingClientRect().width ||
+ 54;
+ const displayH = Math.round((displayW / W) * H);
+ const isMobile = displayW < 70;
+
+ const STEP = isMobile ? 6 : 4;
+ const DOT_R = isMobile
+ ? Math.max(0.6, (displayW / W) * STEP * 0.3)
+ : Math.max(1, (displayW / W) * STEP * 0.45);
+
+ const dots: Array<{ nx: number; ny: number }> = [];
+ for (let y = 0; y < H; y += STEP) {
+ for (let x = 0; x < W; x += STEP) {
+ const i = (y * W + x) * 4;
+ if (data[i + 3] > 40) {
+ dots.push({ nx: x / W, ny: y / H });
+ }
+ }
+ }
+
+ for (let i = dots.length - 1; i > 0; i--) {
+ const j = Math.floor(Math.random() * (i + 1));
+ [dots[i], dots[j]] = [dots[j], dots[i]];
+ }
+
+ canvas.width = displayW * dpr;
+ canvas.height = displayH * dpr;
+ canvas.style.width = `${displayW}px`;
+ canvas.style.height = `${displayH}px`;
+ const ctx = canvas.getContext("2d");
+ if (!ctx) return;
+ ctx.scale(dpr, dpr);
+
+ let painted = 0;
+ const total = dots.length;
+ const progress = { value: 0 };
+
+ gsap.to(progress, {
+ value: 1,
+ duration: 1.05,
+ ease: "power1.inOut",
+ onUpdate() {
+ const target = Math.floor(progress.value * total);
+ while (painted < target) {
+ const d = dots[painted];
+ ctx.beginPath();
+ ctx.arc(d.nx * displayW, d.ny * displayH, DOT_R, 0, Math.PI * 2);
+ ctx.fillStyle = "#9564FF";
+ ctx.fill();
+ painted++;
+ }
+ },
+ onComplete() {
+ gsap.delayedCall(0.2, runExitSequence);
+ },
+ });
+ }, 120);
+
+ return () => {
+ cleanup();
+ };
+ }, []);
+
+ if (done) return null;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+}
diff --git a/apps/website/components/pricing-models.jsx b/apps/website/components/pricing-models.jsx
deleted file mode 100755
index bc23290b0..000000000
--- a/apps/website/components/pricing-models.jsx
+++ /dev/null
@@ -1,252 +0,0 @@
-"use client";
-
-import { CTALines, IconCTAStart } from "@/app/constant";
-import { AnimatePresence, motion } from "motion/react";
-import Image from "next/image";
-import Link from "next/link";
-import { useState } from "react";
-
-const sidebarItems = [
- {
- id: "subscriptions",
- label: "Subscriptions",
- model: 0,
- desc: "Monthly or yearly plans with feature gating. Upgrades and downgrades handled automatically. Proration included.",
- },
- {
- id: "free-trials",
- label: "Free Trials",
- model: 2,
- desc: "Card-required or card-optional trials. Auto-convert to paid. Configurable trial lengths per plan.",
- },
- {
- id: "credits",
- label: "Credits & Top-ups",
- model: 1,
- desc: "Prepaid credits that features draw from. One-time purchases or auto-refill. Set minimum thresholds.",
- },
- {
- id: "usage",
- label: "Usage-Based",
- model: 4,
- desc: "Pay for what you use. Real-time metering, overage handling, usage resets. Combine with base\u00a0subscriptions.",
- },
- {
- id: "seat",
- label: "Seat-Based",
- model: 6,
- desc: "Per-user pricing with seat limits. Add or remove seats dynamically. Automatic proration.",
- },
- {
- id: "hybrid",
- label: "Hybrid Models",
- model: 5,
- desc: "Mix subscriptions, usage, credits, and seats in one plan. Example: $50/month + $0.02/token + 5 seats included.",
- },
- {
- id: "rollovers",
- label: "Rollovers & Expirations",
- model: 7,
- desc: "Roll credits to next period or expire after X days. Configure per plan or per feature.",
- },
- {
- id: "enterprise",
- label: "Custom Enterprise",
- model: 3,
- desc: "One-off pricing for large customers. Unique limits, custom billing cycles, manual overrides—all in the dashboard.",
- },
-];
-
-export default function PricingModels() {
- const [activeTab, setActiveTab] = useState(sidebarItems[0]);
-
- const images = {
- 0: "/images/pricing-models/Subscriptions.webp",
- 1: "/images/pricing-models/Subscriptions (1).webp",
- 2: "/images/pricing-models/Trial Configuration.webp",
- 3: "/images/pricing-models/Hybrid Plan Builder.webp",
- 4: "/images/pricing-models/Usage Metering.webp",
- 5: "/images/pricing-models/Hybrid Plan Builder (1).webp",
- 6: "/images/pricing-models/Subscriptions (2).webp",
- 7: "/images/pricing-models/Subscriptions (3).webp",
- };
-
- return (
-
-
-
-
-
- Any pricing model. {" "}
- Seriously.
-
-
-
- Configure in the dashboard or CLI.
- {" "}
- Rollout to all customers, or create custom plans for your largest customers.
-
-
-
-
-
-
-
-
-
- View templates
-
-
-
-
-
-
-
-
-
-
-
-
- Any pricing model. {" "}
- Seriously.
-
-
-
- Configure in the dashboard or CLI.
- {" "}
- Rollout to all customers, or create custom plans for your largest customers.
-
-
-
-
-
-
-
-
-
-
-
- {Object.entries(images).map(([key, src]) => {
- const isActive = activeTab.model === Number(key);
- return (
-
-
-
- );
- })}
-
-
-
-
-
-
- {sidebarItems.map((item) => {
- const isActive = activeTab.id === item.id;
- return (
- setActiveTab(item)}
- className={`flex flex-col cursor-pointer transition-colors border-b last:border-b-0 lg:border-none border-[#292929] ${
- isActive ? "lg:bg-transparent bg-[#0f0f0f]" : ""
- }`}
- >
- {/* Mobile Accordion Image */}
-
- {isActive && (
-
-
-
-
-
-
-
- )}
-
-
-
-
- {isActive && (
-
- )}
-
- {item.label}
-
-
- {/* Mobile Accordion Description */}
-
- {isActive && (
-
- {item.desc}
-
- )}
-
-
- );
- })}
-
-
-
-
-
-
- {activeTab.desc}
-
-
-
-
-
-
- );
-}
diff --git a/apps/website/components/pricing-models.tsx b/apps/website/components/pricing-models.tsx
new file mode 100755
index 000000000..354535faa
--- /dev/null
+++ b/apps/website/components/pricing-models.tsx
@@ -0,0 +1,263 @@
+"use client";
+
+import { AnimatePresence, motion } from "motion/react";
+import Image from "next/image";
+import Link from "next/link";
+import { useState } from "react";
+import { CTALines, IconCTAStart } from "@/app/constant";
+import { cn } from "@/lib/utils";
+
+const sidebarItems = [
+ {
+ id: "subscriptions",
+ label: "Subscriptions",
+ model: 0,
+ desc: "Monthly or yearly plans with feature gating. Upgrades and downgrades handled automatically. Proration included.",
+ },
+ {
+ id: "free-trials",
+ label: "Free Trials",
+ model: 2,
+ desc: "Card-required or card-optional trials. Auto-convert to paid. Configurable trial lengths per plan.",
+ },
+ {
+ id: "credits",
+ label: "Credits & Top-ups",
+ model: 1,
+ desc: "Prepaid credits that features draw from. One-time purchases or auto-refill. Set minimum thresholds.",
+ },
+ {
+ id: "usage",
+ label: "Usage-Based",
+ model: 4,
+ desc: "Pay for what you use. Real-time metering, overage handling, usage resets. Combine with base\u00a0subscriptions.",
+ },
+ {
+ id: "seat",
+ label: "Seat-Based",
+ model: 6,
+ desc: "Per-user pricing with seat limits. Add or remove seats dynamically. Automatic proration.",
+ },
+ {
+ id: "hybrid",
+ label: "Hybrid Models",
+ model: 5,
+ desc: "Mix subscriptions, usage, credits, and seats in one plan. Example: $50/month + $0.02/token + 5 seats included.",
+ },
+ {
+ id: "rollovers",
+ label: "Rollovers & Expirations",
+ model: 7,
+ desc: "Roll credits to next period or expire after X days. Configure per plan or per feature.",
+ },
+ {
+ id: "enterprise",
+ label: "Custom Enterprise",
+ model: 3,
+ desc: "One-off pricing for large customers. Unique limits, custom billing cycles, manual overrides—all in the dashboard.",
+ },
+] as const;
+
+const images: Record<(typeof sidebarItems)[number]["model"], string> = {
+ 0: "/images/pricing-models/Subscriptions.webp",
+ 1: "/images/pricing-models/Subscriptions (1).webp",
+ 2: "/images/pricing-models/Trial Configuration.webp",
+ 3: "/images/pricing-models/Hybrid Plan Builder.webp",
+ 4: "/images/pricing-models/Usage Metering.webp",
+ 5: "/images/pricing-models/Hybrid Plan Builder (1).webp",
+ 6: "/images/pricing-models/Subscriptions (2).webp",
+ 7: "/images/pricing-models/Subscriptions (3).webp",
+};
+
+export default function PricingModels() {
+ const [activeTab, setActiveTab] = useState<(typeof sidebarItems)[number]>(
+ sidebarItems[0],
+ );
+
+ return (
+
+
+
+
+
+ Any pricing model. {" "}
+ Seriously.
+
+
+
+ Configure in the dashboard or CLI.
+ {" "}
+
+ Rollout to all customers, or create custom plans for your
+ largest customers.
+
+
+
+
+
+
+
+
+
+
+ View templates
+
+
+
+
+
+
+
+
+
+
+
+
+ Any pricing model. {" "}
+ Seriously.
+
+
+
+ Configure in the dashboard or CLI.
+ {" "}
+
+ Rollout to all customers, or create custom plans for your largest
+ customers.
+
+
+
+
+
+
+
+
+
+
+
+ {Object.entries(images).map(([key, src]) => {
+ const isActive = activeTab.model === Number(key);
+ return (
+
+
+
+ );
+ })}
+
+
+
+
+
+
+ {sidebarItems.map((item) => {
+ const isActive = activeTab.id === item.id;
+ return (
+ setActiveTab(item)}
+ className={cn(
+ "flex cursor-pointer flex-col border-b border-[#292929] transition-colors last:border-b-0 lg:border-none",
+ isActive && "bg-[#0f0f0f] lg:bg-transparent",
+ )}
+ >
+ {/* Mobile Accordion Image */}
+
+ {isActive && (
+
+
+
+
+
+
+
+ )}
+
+
+
+
+ {isActive && (
+
+ )}
+
+ {item.label}
+
+
+ {/* Mobile Accordion Description */}
+
+ {isActive && (
+
+ {item.desc}
+
+ )}
+
+
+ );
+ })}
+
+
+
+
+
+
+ {activeTab.desc}
+
+
+
+
+
+
+ );
+}
diff --git a/apps/website/components/pricing.jsx b/apps/website/components/pricing.jsx
deleted file mode 100755
index cd95ab7ef..000000000
--- a/apps/website/components/pricing.jsx
+++ /dev/null
@@ -1,177 +0,0 @@
-"use client";
-import { IconArrowRightSmall, IconTick } from "@/app/constant";
-import { motion } from "motion/react";
-import Image from "next/image";
-import Link from "next/link";
-
-const plans = [
- {
- name: "FREE",
- price: "0",
- description: "Perfect while finding PMF. Everything you need to start.",
- features: [
- "Up to 8K monthly revenue",
- "All core features",
- "Community support",
- ],
- buttonText: "Get started",
- href: "https://app.useautumn.com/sign-in",
- isPro: false,
- },
- {
- name: "PRO",
- price: "375",
- description: "For teams scaling with real usage-based pricing.",
- features: [
- "Up to 50K monthly revenue",
- "Priority support",
- "Custom plans",
- "Usage analytics",
- ],
- buttonText: "Start with Pro",
- href: "https://app.useautumn.com/sign-in",
- isPro: true,
- },
- {
- name: "ENTERPRISE",
- price: "Custom",
- description: "For compliance, scale, or custom requirements.",
- features: [
- "Dedicated support",
- "Multi-region",
- "Compliance assistance",
- ],
- buttonText: "Book a call",
- href: "https://cal.com/ayrod/a?user=ayrod",
- isPro: false,
- },
-];
-
-export default function Pricing() {
- return (
- <>
-
- {/* Desktop Background */}
-
- {/* Mobile Background */}
-
-
-
- {/* Header */}
-
-
- Start free. Scale with confidence.
-
-
-
- {/* Pricing Columns */}
-
- {plans.map((plan, index) => (
-
- {plan.isPro && (
-
- )}
-
- {plan.isPro && (
-
- RECOMMENDED
-
- )}
-
-
-
-
- {plan.name}
-
-
- {plan.price !== "Custom" && (
-
- $
-
- )}
-
- {plan.price}
-
- {plan.price !== "Custom" && (
-
- /month
-
- )}
-
-
- {plan.description}
-
-
-
-
-
-
- {plan.features.map((feature, i) => (
-
-
-
- {feature}
-
-
- ))}
-
-
-
-
-
-
- {plan.buttonText}
-
-
-
-
-
-
-
-
-
- ))}
-
-
-
-
- Autumn is built on top of Stripe billing, so Stripe fees (0.7%
- and 2.9% + 30¢) still apply.
-
-
-
-
-
- >
- );
-}
diff --git a/apps/website/components/pricing.tsx b/apps/website/components/pricing.tsx
new file mode 100755
index 000000000..f00bd46c4
--- /dev/null
+++ b/apps/website/components/pricing.tsx
@@ -0,0 +1,178 @@
+"use client";
+import { motion } from "motion/react";
+import Image from "next/image";
+import Link from "next/link";
+import { IconArrowRightSmall, IconTick } from "@/app/constant";
+
+const plans = [
+ {
+ name: "FREE",
+ price: "0",
+ description: "Perfect while finding PMF. Everything you need to start.",
+ features: [
+ "Up to 8K monthly revenue",
+ "All core features",
+ "Community support",
+ ],
+ buttonText: "Get started",
+ href: "https://app.useautumn.com/sign-in",
+ isPro: false,
+ },
+ {
+ name: "PRO",
+ price: "375",
+ description: "For teams scaling with real usage-based pricing.",
+ features: [
+ "Up to 50K monthly revenue",
+ "Priority support",
+ "Custom plans",
+ "Usage analytics",
+ ],
+ buttonText: "Start with Pro",
+ href: "https://app.useautumn.com/sign-in",
+ isPro: true,
+ },
+ {
+ name: "ENTERPRISE",
+ price: "Custom",
+ description: "For compliance, scale, or custom requirements.",
+ features: ["Dedicated support", "Multi-region", "Compliance assistance"],
+ buttonText: "Book a call",
+ href: "https://cal.com/ayrod/a?user=ayrod",
+ isPro: false,
+ },
+];
+
+export default function Pricing() {
+ return (
+ <>
+
+ {/* Desktop Background */}
+
+ {/* Mobile Background */}
+
+
+
+ {/* Header */}
+
+
+ Start free. Scale with confidence.
+
+
+
+ {/* Pricing Columns */}
+
+ {plans.map((plan, index) => (
+
+ {plan.isPro && (
+
+ )}
+
+ {plan.isPro && (
+
+ RECOMMENDED
+
+ )}
+
+
+
+
+ {plan.name}
+
+
+ {plan.price !== "Custom" && (
+
+ $
+
+ )}
+
+ {plan.price}
+
+ {plan.price !== "Custom" && (
+
+ /month
+
+ )}
+
+
+ {plan.description}
+
+
+
+
+
+
+ {plan.features.map((feature, i) => (
+
+
+
+ {feature}
+
+
+ ))}
+
+
+
+
+
+
+ {plan.buttonText}
+
+
+
+
+
+
+
+
+
+ ))}
+
+
+
+
+ Autumn is built on top of Stripe billing, so Stripe fees (0.7%
+ and 2.9% + 30¢) still apply.
+
+
+
+
+
+ >
+ );
+}
diff --git a/apps/website/components/problem-animation.jsx b/apps/website/components/problem-animation.jsx
deleted file mode 100755
index 352bffae7..000000000
--- a/apps/website/components/problem-animation.jsx
+++ /dev/null
@@ -1,80 +0,0 @@
-"use client";
-
-const IMAGE_SOURCES = [
- "/images/issues/Slack.svg",
- "/images/issues/Frame%202147243001.svg",
- "/images/issues/Frame%202147242729.svg",
- "/images/issues/Slack-1.svg",
- "/images/issues/Frame%202147243134.svg",
- "/images/issues/Slack-2.svg",
- "/images/issues/Frame%202147243146.svg",
- "/images/issues/Stripe.svg",
-];
-
-const ITEM_COUNT = IMAGE_SOURCES.length;
-const PAUSE_SECONDS = 2;
-const SCROLL_SECONDS = 0.5;
-const TOTAL_SECONDS = ITEM_COUNT * (PAUSE_SECONDS + SCROLL_SECONDS);
-
-function buildKeyframes() {
- const stepPct = 100 / ITEM_COUNT;
- const pauseRatio = PAUSE_SECONDS / (PAUSE_SECONDS + SCROLL_SECONDS);
- const perItemTranslate = 50 / ITEM_COUNT;
-
- const frames = ["0% { transform: translateY(0); }"];
-
- for (let i = 0; i < ITEM_COUNT; i++) {
- const holdY = i * perItemTranslate;
- const nextY = (i + 1) * perItemTranslate;
- const pauseEndPct = (i * stepPct + stepPct * pauseRatio).toFixed(4);
- const stepEndPct = ((i + 1) * stepPct).toFixed(4);
-
- const holdTransform =
- holdY === 0 ? "translateY(0)" : `translateY(-${holdY.toFixed(4)}%)`;
-
- frames.push(`${pauseEndPct}% { transform: ${holdTransform}; }`);
- frames.push(
- `${stepEndPct}% { transform: translateY(-${nextY.toFixed(4)}%); }`,
- );
- }
-
- return frames.join("\n ");
-}
-
-const keyframesCSS = buildKeyframes();
-
-export default function ProblemAnimation() {
- return (
-
-
-
-
-
-
-
- {[...IMAGE_SOURCES, ...IMAGE_SOURCES].map((src, i) => (
-
-
-
- ))}
-
-
- );
-}
diff --git a/apps/website/components/problem-animation.tsx b/apps/website/components/problem-animation.tsx
new file mode 100755
index 000000000..4db5e956d
--- /dev/null
+++ b/apps/website/components/problem-animation.tsx
@@ -0,0 +1,80 @@
+"use client";
+
+const IMAGE_SOURCES = [
+ "/images/issues/Slack.svg",
+ "/images/issues/Frame%202147243001.svg",
+ "/images/issues/Frame%202147242729.svg",
+ "/images/issues/Slack-1.svg",
+ "/images/issues/Frame%202147243134.svg",
+ "/images/issues/Slack-2.svg",
+ "/images/issues/Frame%202147243146.svg",
+ "/images/issues/Stripe.svg",
+];
+
+const ITEM_COUNT = IMAGE_SOURCES.length;
+const PAUSE_SECONDS = 2;
+const SCROLL_SECONDS = 0.5;
+const TOTAL_SECONDS = ITEM_COUNT * (PAUSE_SECONDS + SCROLL_SECONDS);
+
+function buildKeyframes() {
+ const stepPct = 100 / ITEM_COUNT;
+ const pauseRatio = PAUSE_SECONDS / (PAUSE_SECONDS + SCROLL_SECONDS);
+ const perItemTranslate = 50 / ITEM_COUNT;
+
+ const frames = ["0% { transform: translateY(0); }"];
+
+ for (let i = 0; i < ITEM_COUNT; i++) {
+ const holdY = i * perItemTranslate;
+ const nextY = (i + 1) * perItemTranslate;
+ const pauseEndPct = (i * stepPct + stepPct * pauseRatio).toFixed(4);
+ const stepEndPct = ((i + 1) * stepPct).toFixed(4);
+
+ const holdTransform =
+ holdY === 0 ? "translateY(0)" : `translateY(-${holdY.toFixed(4)}%)`;
+
+ frames.push(`${pauseEndPct}% { transform: ${holdTransform}; }`);
+ frames.push(
+ `${stepEndPct}% { transform: translateY(-${nextY.toFixed(4)}%); }`,
+ );
+ }
+
+ return frames.join("\n ");
+}
+
+const keyframesCSS = buildKeyframes();
+
+export default function ProblemAnimation() {
+ return (
+
+
+
+
+
+
+
+ {[...IMAGE_SOURCES, ...IMAGE_SOURCES].map((src, i) => (
+
+
+
+ ))}
+
+
+ );
+}
diff --git a/apps/website/components/problem.jsx b/apps/website/components/problem.tsx
similarity index 94%
rename from apps/website/components/problem.jsx
rename to apps/website/components/problem.tsx
index d1717aeaf..47a267a32 100755
--- a/apps/website/components/problem.jsx
+++ b/apps/website/components/problem.tsx
@@ -44,8 +44,12 @@ export default function Problem() {
Maintaining payment logic, customer balances and feature access
- across pricing and product changes is months of work and unreliable.
- Autumn replaces all the billing code you're building yourself.
+ across pricing and product changes is months of work and
+ unreliable.
+
+ {" "}
+ Autumn replaces all the billing code you're building yourself.
+
diff --git a/apps/website/components/production-scale.jsx b/apps/website/components/production-scale.jsx
deleted file mode 100755
index 8215551bb..000000000
--- a/apps/website/components/production-scale.jsx
+++ /dev/null
@@ -1,229 +0,0 @@
-"use client";
-
-import { useGSAP } from "@gsap/react";
-import gsap from "gsap";
-import { ScrollTrigger } from "gsap/ScrollTrigger";
-import Image from "next/image";
-import { useEffect, useRef } from "react";
-
-gsap.registerPlugin(ScrollTrigger);
-
-const SCRAMBLE_CHARS =
- "!@#$%^&*()_+-=[]{}|;:,.<>?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
-
-function scrambleText(
- el,
- finalText,
- { charDuration = 60, cycleSpeed = 30 } = {},
-) {
- const len = finalText.length;
- let settled = 0;
- const timeouts = [];
-
- const cycleInterval = setInterval(() => {
- const display = finalText
- .split("")
- .map((ch, i) =>
- i < settled
- ? ch
- : SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)],
- )
- .join("");
- el.textContent = display;
- }, cycleSpeed);
-
- for (let i = 0; i < len; i++) {
- const t = setTimeout(
- () => {
- settled = i + 1;
- if (settled === len) {
- clearInterval(cycleInterval);
- el.textContent = finalText;
- }
- },
- (i + 1) * charDuration,
- );
- timeouts.push(t);
- }
-
- return function cleanup() {
- clearInterval(cycleInterval);
- timeouts.forEach(clearTimeout);
- el.textContent = finalText;
- };
-}
-
-const cards = [
- {
- bg: "#A175FF",
- icon: "/images/production/uptime2.svg",
- metric: "1 Billion +",
- label: "Monthly events",
- description:
- "Autumn handles billions of billing events monthly for some of your favorite apps.",
- clipart: true,
- },
- {
- bg: "#FFE8FA",
- icon: "/images/production/latency.svg",
- metric: "<50ms",
- label: "US latency",
- description:
- "Every billing check resolves in under 50ms. Your users never wait for a gate.",
- clipart: true,
- },
- {
- bg: "#D698FF",
- icon: "/images/production/uptiime.svg",
- metric: "10 minutes",
- label: "Support SLA",
- description:
- "Billing is critical. We're loved for our rapid response times.",
- clipart: true,
- },
- {
- bg: "#F55DD0",
- icon: "/images/production/churn.svg",
- metric: "Zero",
- label: "Churn rate",
- description: "Unless their company shuts down, our customers stay with us.",
- clipart: true,
- },
-];
-
-export default function ProductionScale() {
- const containerRef = useRef(null);
- const scrambleCleanups = useRef([]);
-
- useEffect(() => {
- const cleanups = scrambleCleanups.current;
- return () => {
- cleanups.forEach((fn) => fn());
- };
- }, []);
-
- useGSAP(
- () => {
- const isMobile = window.innerWidth < 768;
- const cardY = isMobile ? 16 : 30;
-
- gsap.set(".ps-card", { opacity: 0, y: cardY, scale: 0.97 });
-
- const tl = gsap.timeline({
- scrollTrigger: {
- trigger: ".ps-section",
- start: "top 75%",
- },
- defaults: { overwrite: "auto" },
- });
-
- const cardEls = gsap.utils.toArray(".ps-card");
- cardEls.forEach((card, i) => {
- tl.to(
- card,
- {
- opacity: 1,
- y: 0,
- scale: 1,
- duration: 0.9,
- ease: "power3.out",
- onComplete() {
- const metricEl = card.querySelector(".ps-metric");
-
- if (!metricEl) return;
- const finalText = metricEl.dataset.final;
- const delayTimer = setTimeout(() => {
- const cleanup = scrambleText(metricEl, finalText);
- scrambleCleanups.current.push(cleanup);
- }, 200);
-
- scrambleCleanups.current.push(() => clearTimeout(delayTimer));
- },
- },
- 0.3 + i * 0.15,
- );
- });
- },
- { scope: containerRef },
- );
-
- return (
-
-
-
-
-
- You're in
-
-
- good hands
-
-
-
- Autumn is trusted by some of fastest-growing teams. Open source core,
- self-host ready.{" "}
-
- We'll help you go live quickly
- {" "}
- and get back to what's important.
-
-
-
-
-
- {cards.map((card, i) => (
-
-
-
-
-
- {card.metric}
-
-
-
- {card.label}
-
-
-
-
- {card.description}
-
-
- {card.badge && (
-
- {card.badge}
-
- )}
-
- {card.clipart && (
-
- )}
-
- ))}
-
-
-
- );
-}
diff --git a/apps/website/components/production-scale.tsx b/apps/website/components/production-scale.tsx
new file mode 100755
index 000000000..375e01224
--- /dev/null
+++ b/apps/website/components/production-scale.tsx
@@ -0,0 +1,223 @@
+"use client";
+
+import { useGSAP } from "@gsap/react";
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+import Image from "next/image";
+import { useEffect, useRef } from "react";
+
+gsap.registerPlugin(ScrollTrigger);
+
+const SCRAMBLE_CHARS =
+ "!@#$%^&*()_+-=[]{}|;:,.<>?0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
+
+function scrambleText(
+ el: HTMLElement,
+ finalText: string,
+ { charDuration = 60, cycleSpeed = 30 } = {},
+) {
+ const len = finalText.length;
+ let settled = 0;
+ const timeouts: Array> = [];
+
+ const cycleInterval = setInterval(() => {
+ const display = finalText
+ .split("")
+ .map((ch, i) =>
+ i < settled
+ ? ch
+ : SCRAMBLE_CHARS[Math.floor(Math.random() * SCRAMBLE_CHARS.length)],
+ )
+ .join("");
+ el.textContent = display;
+ }, cycleSpeed);
+
+ for (let i = 0; i < len; i++) {
+ const t = setTimeout(
+ () => {
+ settled = i + 1;
+ if (settled === len) {
+ clearInterval(cycleInterval);
+ el.textContent = finalText;
+ }
+ },
+ (i + 1) * charDuration,
+ );
+ timeouts.push(t);
+ }
+
+ return function cleanup() {
+ clearInterval(cycleInterval);
+ timeouts.forEach(clearTimeout);
+ el.textContent = finalText;
+ };
+}
+
+const cards = [
+ {
+ bg: "#A175FF",
+ icon: "/images/production/uptime2.svg",
+ metric: "1 Billion +",
+ label: "Monthly events",
+ description:
+ "Autumn handles billions of billing events monthly for some of your favorite apps.",
+ clipart: true,
+ },
+ {
+ bg: "#FFE8FA",
+ icon: "/images/production/latency.svg",
+ metric: "<50ms",
+ label: "US latency",
+ description:
+ "Every billing check resolves in under 50ms. Your users never wait for a gate.",
+ clipart: true,
+ },
+ {
+ bg: "#D698FF",
+ icon: "/images/production/uptiime.svg",
+ metric: "10 minutes",
+ label: "Support SLA",
+ description:
+ "Billing is critical. We're loved for our rapid response times.",
+ clipart: true,
+ },
+ {
+ bg: "#F55DD0",
+ icon: "/images/production/churn.svg",
+ metric: "Zero",
+ label: "Churn rate",
+ description: "Unless their company shuts down, our customers stay with us.",
+ clipart: true,
+ },
+];
+
+export default function ProductionScale() {
+ const containerRef = useRef(null);
+ const scrambleCleanups = useRef void>>([]);
+
+ useEffect(() => {
+ const cleanups = scrambleCleanups.current;
+ return () => {
+ cleanups.forEach((fn) => fn());
+ };
+ }, []);
+
+ useGSAP(
+ () => {
+ const isMobile = window.innerWidth < 768;
+ const cardY = isMobile ? 16 : 30;
+
+ gsap.set(".ps-card", { opacity: 0, y: cardY, scale: 0.97 });
+
+ const tl = gsap.timeline({
+ scrollTrigger: {
+ trigger: ".ps-section",
+ start: "top 75%",
+ },
+ defaults: { overwrite: "auto" },
+ });
+
+ const cardEls = gsap.utils.toArray(".ps-card");
+ cardEls.forEach((card, i) => {
+ tl.to(
+ card,
+ {
+ opacity: 1,
+ y: 0,
+ scale: 1,
+ duration: 0.9,
+ ease: "power3.out",
+ onComplete() {
+ const metricEl = card.querySelector(".ps-metric");
+ if (!metricEl) return;
+ const finalText = metricEl.dataset.final;
+ if (!finalText) return;
+ const delayTimer = setTimeout(() => {
+ const cleanup = scrambleText(metricEl, finalText);
+ scrambleCleanups.current.push(cleanup);
+ }, 200);
+
+ scrambleCleanups.current.push(() => clearTimeout(delayTimer));
+ },
+ },
+ 0.3 + i * 0.15,
+ );
+ });
+ },
+ { scope: containerRef },
+ );
+
+ return (
+
+
+
+
+
+ You're in
+
+
+ good hands
+
+
+
+ Autumn is trusted by some of fastest-growing teams. Open source
+ core, self-host ready.{" "}
+
+ We'll help you go live quickly
+ and get back to what's
+ important.
+
+
+
+
+
+ {cards.map((card, i) => (
+
+
+
+
+
+ {card.metric}
+
+
+
+ {card.label}
+
+
+
+
+ {card.description}
+
+
+ {card.clipart && (
+
+ )}
+
+ ))}
+
+
+
+ );
+}
diff --git a/apps/website/components/section-divider.jsx b/apps/website/components/section-divider.jsx
deleted file mode 100644
index 956c72c7b..000000000
--- a/apps/website/components/section-divider.jsx
+++ /dev/null
@@ -1,19 +0,0 @@
-export default function SectionDivider({ title }) {
- return (
- <>
-
-
- >
- );
-}
diff --git a/apps/website/components/section-divider.tsx b/apps/website/components/section-divider.tsx
new file mode 100644
index 000000000..d6aab349f
--- /dev/null
+++ b/apps/website/components/section-divider.tsx
@@ -0,0 +1,19 @@
+export default function SectionDivider({ title }: { title: string }) {
+ return (
+ <>
+
+
+ >
+ );
+}
diff --git a/apps/website/components/solution-animation.jsx b/apps/website/components/solution-animation.jsx
deleted file mode 100644
index fd1bfefda..000000000
--- a/apps/website/components/solution-animation.jsx
+++ /dev/null
@@ -1,54 +0,0 @@
-"use client";
-
-import { useEffect, useRef } from "react";
-import lottie, { AnimationItem } from "lottie-web";
-import gsap from "gsap";
-import { ScrollTrigger } from "gsap/ScrollTrigger";
-import desktopAnimationData from "@/public/animation/solution-desktop.json";
-import mobileAnimationData from "@/public/animation/solution-mobile.json";
-
-gsap.registerPlugin(ScrollTrigger);
-
-export default function SolutionAnimation() {
- const containerRef = useRef(null);
- const animRef = useRef(null);
-
- useEffect(() => {
- if (!containerRef.current) return;
-
- const isMobile = window.innerWidth < 768;
- const animationData = isMobile ? mobileAnimationData : desktopAnimationData;
-
- const anim = lottie.loadAnimation({
- container: containerRef.current,
- renderer: "svg",
- loop: false,
- autoplay: false,
- animationData,
- });
-
- animRef.current = anim;
-
- const totalFrames = anim.totalFrames;
-
- const loopStart = Math.floor(totalFrames * 0.2);
-
- anim.addEventListener("complete", () => {
- anim.loop = true;
- anim.playSegments([loopStart, totalFrames], true);
- });
-
- const st = ScrollTrigger.create({
- trigger: containerRef.current,
- start: "top 75%", // Plays when the top of the animation reaches 75% viewport height
- onEnter: () => anim.play(),
- });
-
- return () => {
- st.kill();
- anim.destroy();
- };
- }, []);
-
- return
;
-}
diff --git a/apps/website/components/solution-animation.tsx b/apps/website/components/solution-animation.tsx
new file mode 100644
index 000000000..b83183d50
--- /dev/null
+++ b/apps/website/components/solution-animation.tsx
@@ -0,0 +1,54 @@
+"use client";
+
+import gsap from "gsap";
+import { ScrollTrigger } from "gsap/ScrollTrigger";
+import lottie, { type AnimationItem } from "lottie-web";
+import { useEffect, useRef } from "react";
+import desktopAnimationData from "@/public/animation/solution-desktop.json";
+import mobileAnimationData from "@/public/animation/solution-mobile.json";
+
+gsap.registerPlugin(ScrollTrigger);
+
+export default function SolutionAnimation() {
+ const containerRef = useRef(null);
+ const animRef = useRef(null);
+
+ useEffect(() => {
+ if (!containerRef.current) return;
+
+ const isMobile = window.innerWidth < 768;
+ const animationData = isMobile ? mobileAnimationData : desktopAnimationData;
+
+ const anim = lottie.loadAnimation({
+ container: containerRef.current,
+ renderer: "svg",
+ loop: false,
+ autoplay: false,
+ animationData,
+ });
+
+ animRef.current = anim;
+
+ const totalFrames = anim.totalFrames;
+
+ const loopStart = Math.floor(totalFrames * 0.2);
+
+ anim.addEventListener("complete", () => {
+ anim.loop = true;
+ anim.playSegments([loopStart, totalFrames], true);
+ });
+
+ const st = ScrollTrigger.create({
+ trigger: containerRef.current,
+ start: "top 75%", // Plays when the top of the animation reaches 75% viewport height
+ onEnter: () => anim.play(),
+ });
+
+ return () => {
+ st.kill();
+ anim.destroy();
+ };
+ }, []);
+
+ return
;
+}
diff --git a/apps/website/components/solution.jsx b/apps/website/components/solution.jsx
deleted file mode 100644
index 976d6a181..000000000
--- a/apps/website/components/solution.jsx
+++ /dev/null
@@ -1,68 +0,0 @@
-import Image from "next/image";
-import React from "react";
-import SolutionAnimation from "./solution-animation";
-
-const dbItems = [
- { text: "SUBSCRIPTION STATE", active: true },
- { text: "CREDIT BALANCES & ROLLOVERS", active: false },
- { text: "FEATURE ENTITLEMENTS", active: false },
- { text: "WEBHOOK HANDLING", active: false },
- { text: "USAGE RESETS & PRORATION", active: false },
-];
-
-const appItems = [
- { text: "PRODUCT FEATURES", icon: "grid" },
- { text: "USER INTERFACE", icon: "crossfade" },
- { text: "BUSINESS LOGIC", icon: "box-grid" },
-];
-
-const stripeItems = [
- { text: "MOVES MONEY", icon: "grid" },
- { text: "INVOICING", icon: "crossfade" },
- { text: "CARD PROCESSING", icon: "box-grid" },
-];
-
-const iconSrc = {
- grid: "/images/solutions/grid.svg",
- crossfade: "/images/solutions/crossfade.svg",
- "box-grid": "/images/solutions/box-grid.svg",
-};
-
-export default function Solution() {
- return (
-
-
-
-
- {/* Heading */}
-
-
- Replace it all with
- Autumn
-
-
- Autumn is a database purpose-built for billing state. Configure your
- pricing
-
- in the dashboard.{" "}
-
- Three API calls handle everything else.
-
-
-
-
-
-
- );
-}
diff --git a/apps/website/components/solution.tsx b/apps/website/components/solution.tsx
new file mode 100644
index 000000000..6bb00c58f
--- /dev/null
+++ b/apps/website/components/solution.tsx
@@ -0,0 +1,68 @@
+import Image from "next/image";
+import React from "react";
+import SolutionAnimation from "./solution-animation";
+
+const dbItems = [
+ { text: "SUBSCRIPTION STATE", active: true },
+ { text: "CREDIT BALANCES & ROLLOVERS", active: false },
+ { text: "FEATURE ENTITLEMENTS", active: false },
+ { text: "WEBHOOK HANDLING", active: false },
+ { text: "USAGE RESETS & PRORATION", active: false },
+];
+
+const appItems = [
+ { text: "PRODUCT FEATURES", icon: "grid" },
+ { text: "USER INTERFACE", icon: "crossfade" },
+ { text: "BUSINESS LOGIC", icon: "box-grid" },
+];
+
+const stripeItems = [
+ { text: "MOVES MONEY", icon: "grid" },
+ { text: "INVOICING", icon: "crossfade" },
+ { text: "CARD PROCESSING", icon: "box-grid" },
+];
+
+const iconSrc = {
+ grid: "/images/solutions/grid.svg",
+ crossfade: "/images/solutions/crossfade.svg",
+ "box-grid": "/images/solutions/box-grid.svg",
+};
+
+export default function Solution() {
+ return (
+
+
+
+
+ {/* Heading */}
+
+
+ Replace it all with
+ Autumn
+
+
+ Autumn is a database purpose-built for billing state. Configure your
+ pricing
+
+ in the dashboard.{" "}
+
+ Three API calls handle everything else.
+
+
+
+
+
+
+ );
+}
diff --git a/apps/website/components/testimonials.jsx b/apps/website/components/testimonials.jsx
deleted file mode 100755
index ffbfb8bee..000000000
--- a/apps/website/components/testimonials.jsx
+++ /dev/null
@@ -1,222 +0,0 @@
-"use client";
-import {
- IconArrowLeft,
- IconArrowRight,
- IconQuotes,
- PixelatedPattern,
-} from "@/app/constant";
-import { useEffect, useRef, useState } from "react";
-
-const testimonialsData = [
- {
- id: 1,
- quote:
- "Literally cannot imagine going without it. Thank you. We had some pretty crazy usage-based limitations for different features, as well as a free trial.",
- author: "DANIEL EDRISIAR",
- },
- {
- id: 2,
- quote: "Amazing product. Amazing founders.",
- author: "NIZZY",
- },
- {
- id: 3,
- quote:
- "Autumn is awesome. We've been happy customers since the very beginning - it was a no-brainer to be honest. The founders are in true founder mode.",
- author: "MAX PRILUTSKIY",
- },
- {
- id: 4,
- quote: "What migrating to Autumn does (scroll!)",
- author: "Ben Y",
- },
- {
- id: 5,
- quote:
- "Autumn fixed stripe. Trust me. Save you at least a week and potentially months of Stripe integration time. I wish we could have discovered Autumn earlier.",
- author: "Benny Kok",
- },
- {
- id: 6,
- quote:
- "@autumnpricing is so good it ruined every other tool for me. nothing else even feels right anymore",
- author: "Can Vardar",
- },
-];
-
-const Testimonials = () => {
- const videoRef = useRef(null);
- const scrollRef = useRef(null);
- const progressRef = useRef(null);
- const [canScrollLeft, setCanScrollLeft] = useState(false);
- const [canScrollRight, setCanScrollRight] = useState(true);
-
- const handleScroll = () => {
- if (scrollRef.current) {
- const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;
- setCanScrollLeft(scrollLeft > 0);
- setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 1);
-
- if (progressRef.current) {
- const maxScroll = scrollWidth - clientWidth;
- const progress = maxScroll > 0 ? (scrollLeft / maxScroll) * 100 : 0;
- progressRef.current.style.transform = `translateX(${progress}%)`;
- }
- }
- };
- useEffect(() => {
- if (videoRef.current) {
- videoRef.current.playbackRate = 0.4;
- }
- }, []);
-
- useEffect(() => {
- handleScroll();
- window.addEventListener("resize", handleScroll);
- return () => window.removeEventListener("resize", handleScroll);
- }, []);
-
- const scrollByAmount = (amount) => {
- if (scrollRef.current) {
- scrollRef.current.scrollBy({ left: amount, behavior: "smooth" });
- }
- };
-
- return (
-
-
-
-
- Built for
- teams
-
- that move fast
-
-
- scrollByAmount(-400)}
- disabled={!canScrollLeft}
- className="group p-1.5 bg-transparent cursor-pointer flex items-center justify-center transition-all duration-300 border border-[#292929]"
- aria-label="Previous testimonials"
- >
-
-
-
- scrollByAmount(400)}
- disabled={!canScrollRight}
- className="group p-1.5 bg-transparent cursor-pointer flex items-center justify-center transition-all duration-300 border border-[#292929]"
- aria-label="Next testimonials"
- >
-
-
-
-
-
-
-
-
-
- {testimonialsData.map((testimonial) => (
-
- {/* Hover Pixelated Pattern (Masked) */}
-
- {/* Purple Glow Gradient Overlay */}
-
-
-
-
- {testimonial.quote}
-
-
-
- {testimonial.author}
-
-
- ))}
-
-
-
-
-
-
- scrollByAmount(-400)}
- disabled={!canScrollLeft}
- className="p-1.5 bg-transparent flex items-center justify-center border border-[#292929]"
- aria-label="Previous testimonials"
- >
-
-
-
- scrollByAmount(400)}
- disabled={!canScrollRight}
- className="p-1.5 bg-transparent flex items-center justify-center border border-[#292929]"
- aria-label="Next testimonials"
- >
-
-
-
-
-
-
-
-
-
- );
-};
-
-export default Testimonials;
diff --git a/apps/website/components/testimonials.tsx b/apps/website/components/testimonials.tsx
new file mode 100755
index 000000000..5f89701d2
--- /dev/null
+++ b/apps/website/components/testimonials.tsx
@@ -0,0 +1,222 @@
+"use client";
+import { useEffect, useRef, useState } from "react";
+import {
+ IconArrowLeft,
+ IconArrowRight,
+ IconQuotes,
+ PixelatedPattern,
+} from "@/app/constant";
+
+const testimonialsData = [
+ {
+ id: 1,
+ quote:
+ "Literally cannot imagine going without it. Thank you. We had some pretty crazy usage-based limitations for different features, as well as a free trial.",
+ author: "DANIEL EDRISIAR",
+ },
+ {
+ id: 2,
+ quote: "Amazing product. Amazing founders.",
+ author: "NIZZY",
+ },
+ {
+ id: 3,
+ quote:
+ "Autumn is awesome. We've been happy customers since the very beginning - it was a no-brainer to be honest. The founders are in true founder mode.",
+ author: "MAX PRILUTSKIY",
+ },
+ {
+ id: 4,
+ quote: "What migrating to Autumn does (scroll!)",
+ author: "Ben Y",
+ },
+ {
+ id: 5,
+ quote:
+ "Autumn fixed stripe. Trust me. Save you at least a week and potentially months of Stripe integration time. I wish we could have discovered Autumn earlier.",
+ author: "Benny Kok",
+ },
+ {
+ id: 6,
+ quote:
+ "@autumnpricing is so good it ruined every other tool for me. nothing else even feels right anymore",
+ author: "Can Vardar",
+ },
+];
+
+const Testimonials = () => {
+ const videoRef = useRef(null);
+ const scrollRef = useRef(null);
+ const progressRef = useRef(null);
+ const [canScrollLeft, setCanScrollLeft] = useState(false);
+ const [canScrollRight, setCanScrollRight] = useState(true);
+
+ const handleScroll = () => {
+ if (scrollRef.current) {
+ const { scrollLeft, scrollWidth, clientWidth } = scrollRef.current;
+ setCanScrollLeft(scrollLeft > 0);
+ setCanScrollRight(scrollLeft + clientWidth < scrollWidth - 1);
+
+ if (progressRef.current) {
+ const maxScroll = scrollWidth - clientWidth;
+ const progress = maxScroll > 0 ? (scrollLeft / maxScroll) * 100 : 0;
+ progressRef.current.style.transform = `translateX(${progress}%)`;
+ }
+ }
+ };
+ useEffect(() => {
+ if (videoRef.current) {
+ videoRef.current.playbackRate = 0.4;
+ }
+ }, []);
+
+ useEffect(() => {
+ handleScroll();
+ window.addEventListener("resize", handleScroll);
+ return () => window.removeEventListener("resize", handleScroll);
+ }, []);
+
+ const scrollByAmount = (amount: number) => {
+ if (scrollRef.current) {
+ scrollRef.current.scrollBy({ left: amount, behavior: "smooth" });
+ }
+ };
+
+ return (
+
+
+
+
+ Built for
+ teams
+
+ that move fast
+
+
+ scrollByAmount(-400)}
+ disabled={!canScrollLeft}
+ className="group p-1.5 bg-transparent cursor-pointer flex items-center justify-center transition-all duration-300 border border-[#292929]"
+ aria-label="Previous testimonials"
+ >
+
+
+
+ scrollByAmount(400)}
+ disabled={!canScrollRight}
+ className="group p-1.5 bg-transparent cursor-pointer flex items-center justify-center transition-all duration-300 border border-[#292929]"
+ aria-label="Next testimonials"
+ >
+
+
+
+
+
+
+
+
+
+ {testimonialsData.map((testimonial) => (
+
+ {/* Hover Pixelated Pattern (Masked) */}
+
+ {/* Purple Glow Gradient Overlay */}
+
+
+
+
+ {testimonial.quote}
+
+
+
+ {testimonial.author}
+
+
+ ))}
+
+
+
+
+
+
+ scrollByAmount(-400)}
+ disabled={!canScrollLeft}
+ className="p-1.5 bg-transparent flex items-center justify-center border border-[#292929]"
+ aria-label="Previous testimonials"
+ >
+
+
+
+ scrollByAmount(400)}
+ disabled={!canScrollRight}
+ className="p-1.5 bg-transparent flex items-center justify-center border border-[#292929]"
+ aria-label="Next testimonials"
+ >
+
+
+
+
+
+
+
+
+
+ );
+};
+
+export default Testimonials;
diff --git a/apps/website/lib/blogUtils.js b/apps/website/lib/blogUtils.js
deleted file mode 100644
index d6daffe74..000000000
--- a/apps/website/lib/blogUtils.js
+++ /dev/null
@@ -1,62 +0,0 @@
-import fs from "fs";
-import path from "path";
-import matter from "gray-matter";
-
-const CONTENT_DIR = path.join(process.cwd(), "content", "blog");
-
-export function getAllPosts() {
- if (!fs.existsSync(CONTENT_DIR)) return [];
-
- const files = fs
- .readdirSync(CONTENT_DIR)
- .filter((file) => file.endsWith(".mdx"));
-
- const posts = files.map((filename) => {
- const filePath = path.join(CONTENT_DIR, filename);
- const raw = fs.readFileSync(filePath, "utf-8");
- const { data } = matter(raw);
-
- return {
- slug: data.slug || filename.replace(/\.mdx$/, ""),
- title: data.title || "Untitled",
- description: data.description || "",
- date: data.date || null,
- author: data.author || "Autumn Team",
- image: data.image || null,
- };
- });
-
- return posts.sort((a, b) => {
- if (!a.date || !b.date) return 0;
- return new Date(b.date) - new Date(a.date);
- });
-}
-
-export function getPostBySlug({ slug }) {
- if (!fs.existsSync(CONTENT_DIR)) return null;
-
- const files = fs
- .readdirSync(CONTENT_DIR)
- .filter((file) => file.endsWith(".mdx"));
-
- for (const filename of files) {
- const filePath = path.join(CONTENT_DIR, filename);
- const raw = fs.readFileSync(filePath, "utf-8");
- const { data, content } = matter(raw);
- const fileSlug = data.slug || filename.replace(/\.mdx$/, "");
-
- if (fileSlug === slug) {
- return {
- slug: fileSlug,
- title: data.title || "Untitled",
- description: data.description || "",
- date: data.date || null,
- author: data.author || "Autumn Team",
- image: data.image || null,
- source: content,
- };
- }
- }
-
- return null;
-}
diff --git a/apps/website/lib/blogUtils.ts b/apps/website/lib/blogUtils.ts
new file mode 100644
index 000000000..f14c06fb8
--- /dev/null
+++ b/apps/website/lib/blogUtils.ts
@@ -0,0 +1,75 @@
+import fs from "fs";
+import matter from "gray-matter";
+import path from "path";
+
+const CONTENT_DIR = path.join(process.cwd(), "content", "blog");
+
+export type BlogPostSummary = {
+ slug: string;
+ title: string;
+ description: string;
+ date: string | null;
+ author: string;
+ image: string | null;
+};
+
+export type BlogPost = BlogPostSummary & {
+ source: string;
+};
+
+export function getAllPosts(): BlogPostSummary[] {
+ if (!fs.existsSync(CONTENT_DIR)) return [];
+
+ const files = fs
+ .readdirSync(CONTENT_DIR)
+ .filter((file) => file.endsWith(".mdx"));
+
+ const posts = files.map((filename) => {
+ const filePath = path.join(CONTENT_DIR, filename);
+ const raw = fs.readFileSync(filePath, "utf-8");
+ const { data } = matter(raw);
+
+ return {
+ slug: data.slug || filename.replace(/\.mdx$/, ""),
+ title: data.title || "Untitled",
+ description: data.description || "",
+ date: data.date || null,
+ author: data.author || "Autumn Team",
+ image: data.image || null,
+ };
+ });
+
+ return posts.sort((a, b) => {
+ if (!a.date || !b.date) return 0;
+ return new Date(b.date).getTime() - new Date(a.date).getTime();
+ });
+}
+
+export function getPostBySlug({ slug }: { slug: string }): BlogPost | null {
+ if (!fs.existsSync(CONTENT_DIR)) return null;
+
+ const files = fs
+ .readdirSync(CONTENT_DIR)
+ .filter((file) => file.endsWith(".mdx"));
+
+ for (const filename of files) {
+ const filePath = path.join(CONTENT_DIR, filename);
+ const raw = fs.readFileSync(filePath, "utf-8");
+ const { data, content } = matter(raw);
+ const fileSlug = data.slug || filename.replace(/\.mdx$/, "");
+
+ if (fileSlug === slug) {
+ return {
+ slug: fileSlug,
+ title: data.title || "Untitled",
+ description: data.description || "",
+ date: data.date || null,
+ author: data.author || "Autumn Team",
+ image: data.image || null,
+ source: content,
+ };
+ }
+ }
+
+ return null;
+}
diff --git a/apps/website/lib/types.ts b/apps/website/lib/types.ts
new file mode 100644
index 000000000..262c727ee
--- /dev/null
+++ b/apps/website/lib/types.ts
@@ -0,0 +1,34 @@
+import type {
+ ComponentPropsWithoutRef,
+ ComponentType,
+ CSSProperties,
+ PropsWithChildren,
+ RefAttributes,
+} from "react";
+
+export type PageStyle = CSSProperties & {
+ "--page-pad"?: string;
+};
+
+export type SvgIconProps = ComponentPropsWithoutRef<"svg">;
+export type ImgProps = ComponentPropsWithoutRef<"img">;
+
+export type PixelIconComponent = ComponentType<
+ SvgIconProps & Partial>
+>;
+
+export type PixelAnimationHandle = {
+ play: () => void;
+ reverse: () => void;
+};
+
+export type PixelHoverHandle = {
+ restart: () => void;
+ reverse: () => void;
+};
+
+export type LayoutProps = PropsWithChildren;
+
+export type BlogParams = Promise<{
+ slug: string;
+}>;
diff --git a/apps/website/lib/utils.ts b/apps/website/lib/utils.ts
new file mode 100644
index 000000000..5bebefe56
--- /dev/null
+++ b/apps/website/lib/utils.ts
@@ -0,0 +1,2 @@
+export const cn = (...inputs: Array) =>
+ inputs.filter(Boolean).join(" ");
diff --git a/apps/website/tailwind.config.mjs b/apps/website/tailwind.config.mjs
new file mode 100644
index 000000000..875cc2eeb
--- /dev/null
+++ b/apps/website/tailwind.config.mjs
@@ -0,0 +1,7 @@
+import typography from "@tailwindcss/typography";
+
+const config = {
+ plugins: [typography],
+};
+
+export default config;
diff --git a/apps/website/tsconfig.json b/apps/website/tsconfig.json
new file mode 100644
index 000000000..e1771a45f
--- /dev/null
+++ b/apps/website/tsconfig.json
@@ -0,0 +1,50 @@
+{
+ "compilerOptions": {
+ "target": "ES2017",
+ "lib": [
+ "dom",
+ "dom.iterable",
+ "esnext"
+ ],
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "module": "esnext",
+ "moduleResolution": "bundler",
+ "esModuleInterop": true,
+ "resolveJsonModule": true,
+ "isolatedModules": true,
+ "jsx": "react-jsx",
+ "incremental": true,
+ "typeRoots": [
+ "../../node_modules/@types"
+ ],
+ "types": [
+ "node",
+ "react",
+ "react-dom"
+ ],
+ "plugins": [
+ {
+ "name": "next"
+ }
+ ],
+ "paths": {
+ "@/*": [
+ "./*"
+ ]
+ },
+ "allowJs": true
+ },
+ "include": [
+ "next-env.d.ts",
+ "**/*.ts",
+ "**/*.tsx",
+ ".next/types/**/*.ts",
+ ".next/dev/types/**/*.ts",
+ "**/*.mts"
+ ],
+ "exclude": [
+ "node_modules"
+ ]
+}