Merge pull request #1372 from useautumn/cursor/fix-hero-animation-first-load
fix(website): restore hero entrance animation on first page load
1
apps/website/.browserslistrc
Normal file
@@ -0,0 +1 @@
|
||||
> 0.5%, last 2 versions, Firefox ESR, not dead, not ie 11
|
||||
2
apps/website/.gitattributes
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
45
apps/website/.gitignore
vendored
@@ -1,41 +1,6 @@
|
||||
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||
|
||||
# dependencies
|
||||
/node_modules
|
||||
/.pnp
|
||||
.pnp.*
|
||||
.yarn/*
|
||||
!.yarn/patches
|
||||
!.yarn/plugins
|
||||
!.yarn/releases
|
||||
!.yarn/versions
|
||||
|
||||
# testing
|
||||
/coverage
|
||||
|
||||
# next.js
|
||||
/.next/
|
||||
/out/
|
||||
|
||||
# production
|
||||
/build
|
||||
|
||||
# misc
|
||||
.DS_Store
|
||||
*.pem
|
||||
|
||||
# debug
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.pnpm-debug.log*
|
||||
|
||||
# env files (can opt-in for committing if needed)
|
||||
.env*
|
||||
|
||||
# vercel
|
||||
.vercel
|
||||
|
||||
# typescript
|
||||
node_modules
|
||||
/.next
|
||||
*.tsbuildinfo
|
||||
next-env.d.ts
|
||||
*.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
tsconfig.tsbuildinfo
|
||||
|
||||
@@ -101,6 +101,7 @@ export default async function BlogPostPage({ params }: { params: BlogParams }) {
|
||||
fill
|
||||
className="object-cover"
|
||||
priority
|
||||
sizes="(max-width: 768px) 100vw, 720px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -72,6 +72,7 @@ export default function BlogListingPage() {
|
||||
alt={post.title}
|
||||
fill
|
||||
className="object-cover"
|
||||
sizes="(max-width: 640px) 0px, (max-width: 768px) 140px, 180px"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { motion } from "motion/react";
|
||||
import Image from "next/image";
|
||||
import type { ComponentPropsWithoutRef } from "react";
|
||||
import { forwardRef } from "react";
|
||||
import type { ImgProps, SvgIconProps } from "@/lib/types";
|
||||
@@ -1330,10 +1331,13 @@ export const ProblemBgSvg = (props: ComponentPropsWithoutRef<"div">) => (
|
||||
className="w-full h-auto hidden md:block cursor-pointer transition-all duration-300 hover:brightness-125 hover:drop-shadow-[0_0_15px_rgba(255,255,255,0.15)]"
|
||||
{...props}
|
||||
>
|
||||
{/* eslint-disable-next-line @next/next/no-img-element */}
|
||||
<img
|
||||
src="/images/problems/problembg.svg"
|
||||
<Image
|
||||
src="/images/problems/problembg.png"
|
||||
alt="Problem Background"
|
||||
width={621}
|
||||
height={223}
|
||||
sizes="(max-width: 1280px) 100vw, 720px"
|
||||
loading="lazy"
|
||||
className="w-full h-auto"
|
||||
/>
|
||||
</div>
|
||||
|
||||
1240
apps/website/bun.lock
Normal file
@@ -11,7 +11,7 @@ const AnimatedFooterImage = forwardRef<HTMLDivElement>(
|
||||
>
|
||||
<div className="relative w-full h-full">
|
||||
<Image
|
||||
src="/images/footer/footer.webp"
|
||||
src="/images/footer/footer.avif"
|
||||
alt="footer background"
|
||||
fill
|
||||
sizes="100vw"
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
"use client";
|
||||
|
||||
import Image from "next/image";
|
||||
import { useEffect, useLayoutEffect, useRef, useState } from "react";
|
||||
import React, { 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";
|
||||
|
||||
@@ -12,7 +12,7 @@ const SyntaxHighlighter = RawSyntaxHighlighter as unknown as ((props: {
|
||||
lineNumberStyle?: Record<string, string | number>;
|
||||
showLineNumbers?: boolean;
|
||||
style?: Record<string, Record<string, string | number>>;
|
||||
}) => JSX.Element) & {
|
||||
}) => React.JSX.Element) & {
|
||||
registerLanguage: (name: string, language: unknown) => void;
|
||||
};
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
"use client";
|
||||
import { motion, useMotionValue, useSpring, useTransform } from "framer-motion";
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import type { LayoutProps } from "@/lib/types";
|
||||
import AnimatedFooterImage from "./animated-footer-image";
|
||||
|
||||
@@ -9,9 +9,14 @@ export default function ElasticRecoil({ children }: LayoutProps) {
|
||||
|
||||
const [showFooter, setShowFooter] = useState(false);
|
||||
const [isMobile, setIsMobile] = useState(false);
|
||||
const isMobileRef = useRef(false);
|
||||
|
||||
useEffect(() => {
|
||||
const check = () => setIsMobile(window.innerWidth < 768);
|
||||
const check = () => {
|
||||
const mobile = window.innerWidth < 768;
|
||||
setIsMobile(mobile);
|
||||
isMobileRef.current = mobile;
|
||||
};
|
||||
check();
|
||||
window.addEventListener("resize", check);
|
||||
return () => window.removeEventListener("resize", check);
|
||||
@@ -46,6 +51,7 @@ export default function ElasticRecoil({ children }: LayoutProps) {
|
||||
};
|
||||
|
||||
const handleWheel = (e: WheelEvent) => {
|
||||
if (isMobileRef.current) return;
|
||||
const isAtBottom =
|
||||
window.innerHeight + window.pageYOffset >=
|
||||
document.documentElement.scrollHeight - 5;
|
||||
@@ -77,6 +83,7 @@ export default function ElasticRecoil({ children }: LayoutProps) {
|
||||
let lastTouchY = 0;
|
||||
let atBottomOnStart = false;
|
||||
const handleTouchStart = (e: TouchEvent) => {
|
||||
if (isMobileRef.current) return;
|
||||
isTouching = true;
|
||||
lastTouchY = e.touches[0].clientY;
|
||||
atBottomOnStart =
|
||||
@@ -87,6 +94,7 @@ export default function ElasticRecoil({ children }: LayoutProps) {
|
||||
};
|
||||
|
||||
const handleTouchMove = (e: TouchEvent) => {
|
||||
if (isMobileRef.current) return;
|
||||
const currentY = e.touches[0].clientY;
|
||||
const delta = lastTouchY - currentY;
|
||||
lastTouchY = currentY;
|
||||
@@ -135,7 +143,7 @@ export default function ElasticRecoil({ children }: LayoutProps) {
|
||||
|
||||
return (
|
||||
<div className="relative w-full overflow-hidden">
|
||||
{showFooter && <AnimatedFooterImage />}
|
||||
{showFooter && !isMobile && <AnimatedFooterImage />}
|
||||
<motion.div style={{ y }} className="relative z-10 bg-black">
|
||||
{children}
|
||||
</motion.div>
|
||||
|
||||
@@ -83,7 +83,7 @@ export default function FAQ() {
|
||||
"text-base tracking-[-2%] transition-all duration-400 lg:text-[18px]",
|
||||
isOpen
|
||||
? "font-normal text-white"
|
||||
: "font-light text-[#FFFFFF66] md:group-hover:text-white",
|
||||
: "font-light text-[#FFFFFF80] md:group-hover:text-white",
|
||||
)}
|
||||
>
|
||||
{faq.question}
|
||||
@@ -95,7 +95,7 @@ export default function FAQ() {
|
||||
"h-5 w-5 transition-colors duration-400",
|
||||
isOpen
|
||||
? "text-white"
|
||||
: "text-[#FFFFFF66] md:group-hover:text-white",
|
||||
: "text-[#FFFFFF80] md:group-hover:text-white",
|
||||
)}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -69,7 +69,7 @@ export default function Features() {
|
||||
</div>
|
||||
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
<div className="grid grid-cols-1 gap-[-3px] md:grid-cols-2 lg:grid-cols-3 xl:px-22.75 border-[#292929] [&>*:last-child]:border-b-0 [&>*:nth-last-child(2)]:border-b-0 md:[&>*:nth-last-child(-n+2)]:border-b-0 lg:[&>*:nth-last-child(-n+3)]:border-b-0 *:border-l md:[&>*:nth-child(2n)]:border-l-0 lg:[&>*:nth-child(3n+1)]:border-l lg:[&>*:nth-child(3n+2)]:border-l-0 lg:[&>*:nth-child(3n)]:border-l-0">
|
||||
<div className="grid grid-cols-1 gap-[-3px] md:grid-cols-2 lg:grid-cols-3 xl:px-22.75 border-[#292929] [&>*:last-child]:border-b-0 md:[&>*:nth-last-child(-n+2)]:border-b-0 lg:[&>*:nth-last-child(-n+3)]:border-b-0 *:border-l md:[&>*:nth-child(2n)]:border-l-0 lg:[&>*:nth-child(3n+1)]:border-l lg:[&>*:nth-child(3n+2)]:border-l-0 lg:[&>*:nth-child(3n)]:border-l-0">
|
||||
{featuresData.map((feature, i) => (
|
||||
<FeatureCard key={i} feature={feature} />
|
||||
))}
|
||||
|
||||
@@ -52,10 +52,10 @@ export default function Footer() {
|
||||
className="mt-[50px] relative bg-[#000000] border-t border-[#292929] overflow-hidden text-[#FFFFFF99] grid"
|
||||
>
|
||||
<div className="col-start-1 row-start-1 flex flex-col z-10 w-full relative">
|
||||
<div className="relative w-full flex flex-col justify-between min-h-[400px]">
|
||||
<div className="relative w-full flex flex-col justify-between min-h-[300px] md:min-h-[400px]">
|
||||
<div className="absolute bottom-0 left-0 right-0 h-[300px] sm:h-[550px] lg:h-[400px] pointer-events-none opacity-70 bg-[url('/images/footer/footerbg.svg')] bg-cover sm:bg-contain bg-bottom bg-no-repeat z-0" />
|
||||
|
||||
<div className="flex flex-col lg:flex-row justify-between pt-[32px] md:pt-24 pb-32 md:pb-16 pl-4.5 xl:pl-[90px] pr-4 sm:pr-8 lg:pr-12 gap-10 md:gap-16 lg:gap-8 relative z-10">
|
||||
<div className="flex flex-col lg:flex-row justify-between pt-[32px] md:pt-24 pb-16 md:pb-16 pl-4.5 xl:pl-[90px] pr-4 sm:pr-8 lg:pr-12 gap-10 md:gap-16 lg:gap-8 relative z-10">
|
||||
<div className="flex flex-col max-w-sm">
|
||||
<Link href="/" className="mb-3 md:mb-6 inline-block">
|
||||
<Image
|
||||
|
||||
@@ -28,11 +28,7 @@ const getLoggedInHintCookie = () => {
|
||||
|
||||
export default function Hero() {
|
||||
const containerRef = useRef<HTMLDivElement | null>(null);
|
||||
// Start with the final text so SSR/first paint shows "// 100% open source"
|
||||
// immediately. The typewriter effect only runs on fast desktop hydration
|
||||
// (it intentionally clears and re-scrambles this value); everywhere else
|
||||
// the initial text is left alone.
|
||||
const [displayedText, setDisplayedText] = useState(BADGE_TEXT);
|
||||
const [displayedText, setDisplayedText] = useState("");
|
||||
const [isLoggedIn, setIsLoggedIn] = useState(false);
|
||||
// Gate the xl-only hero visual (background video + AutumnConfig syntax
|
||||
// highlighter) behind an actual viewport check so mobile never downloads
|
||||
@@ -55,18 +51,6 @@ export default function Hero() {
|
||||
|
||||
// Badge typewriter
|
||||
useEffect(() => {
|
||||
// Only run the typewriter scramble on fast desktop hydration. Initial
|
||||
// state is already `BADGE_TEXT`, so bailing here leaves the SSR-rendered
|
||||
// text in place. On mobile, tablets, or slow-hydration desktop this
|
||||
// avoids a jarring "full text -> empty -> scrambled in" flash that
|
||||
// fires many seconds after the page has already been visible.
|
||||
if (
|
||||
window.matchMedia("(max-width: 1023px)").matches ||
|
||||
performance.now() > 300
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
const chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ!@#$%^&*0123456789";
|
||||
let intervalId: ReturnType<typeof setInterval> | null = null;
|
||||
let timeoutId: ReturnType<typeof setTimeout> | null = null;
|
||||
@@ -106,38 +90,38 @@ export default function Hero() {
|
||||
|
||||
useGSAP(
|
||||
() => {
|
||||
// IMPORTANT: do NOT gate `.hero-root` behind opacity:0 here or in JSX.
|
||||
// GSAP ships in a code-split chunk, so if the class were `opacity-0`
|
||||
// by default the hero would stay invisible for the full duration of
|
||||
// the JS chunk download + parse + hydrate on mobile (~several seconds
|
||||
// on Slow 4G). We use `gsap.from()` tweens so the content is visible
|
||||
// by default and only animates if GSAP has loaded by first frame.
|
||||
gsap.set(".hero-root", { opacity: 0 });
|
||||
|
||||
// Skip the entrance animation on non-desktop viewports, or on any
|
||||
// device where hydration landed well after first paint. Running
|
||||
// `.from()` tweens late would visibly hide already-painted content
|
||||
// and animate it back in — a much worse UX than no animation.
|
||||
// Threshold is tight (300ms) so the flash never happens on slow
|
||||
// devices; only near-instant hydration earns the animation.
|
||||
if (
|
||||
window.matchMedia("(max-width: 1023px)").matches ||
|
||||
performance.now() > 300
|
||||
) {
|
||||
return;
|
||||
}
|
||||
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({
|
||||
defaults: { overwrite: "auto" },
|
||||
});
|
||||
|
||||
tl.from(".hero-bg", {
|
||||
opacity: 0,
|
||||
filter: "blur(6px) brightness(1)",
|
||||
scale: 0.97,
|
||||
transformOrigin: "center top",
|
||||
duration: 0.4,
|
||||
ease: "power2.out",
|
||||
})
|
||||
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)",
|
||||
@@ -151,14 +135,13 @@ export default function Hero() {
|
||||
ease: "power2.out",
|
||||
})
|
||||
|
||||
.from(
|
||||
.to(
|
||||
".hero-reveal",
|
||||
{
|
||||
opacity: 0,
|
||||
y: 25,
|
||||
filter: "blur(12px)",
|
||||
scale: 0.96,
|
||||
transformOrigin: "center bottom",
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
filter: "blur(0px)",
|
||||
scale: 1,
|
||||
duration: 1.1,
|
||||
stagger: 0.1,
|
||||
ease: "power3.out",
|
||||
@@ -166,11 +149,11 @@ export default function Hero() {
|
||||
"-=0.2",
|
||||
)
|
||||
|
||||
.from(
|
||||
.to(
|
||||
".hero-cta",
|
||||
{
|
||||
opacity: 0,
|
||||
scale: 0.95,
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
duration: 0.3,
|
||||
stagger: 0.06,
|
||||
ease: "back.out(1.5)",
|
||||
@@ -183,7 +166,7 @@ export default function Hero() {
|
||||
|
||||
return (
|
||||
<div ref={containerRef}>
|
||||
<div className="relative hero-root flex flex-col items-stretch pb-0 md:pb-12 mb-0 bg-[#0F0F0F]">
|
||||
<div className="relative hero-root opacity-0 flex flex-col items-stretch pb-0 md:pb-12 mb-0 bg-[#0F0F0F]">
|
||||
<div className="flex justify-between">
|
||||
<div className="flex flex-col gap-6 px-4 xl:px-22.75 py-8 bg-[#0F0F0F] mt-26">
|
||||
<h4 className="hero-reveal relative uppercase font-mono tracking-[-2%] text-[12px] md:text-sm leading-sm text-white md:text-[#FFFFFF99] bg-[#2c2c2d] w-fit p-2 min-h-[30px] md:min-h-[36px] flex items-center">
|
||||
|
||||
@@ -1,17 +1,23 @@
|
||||
"use client";
|
||||
|
||||
import dynamic from "next/dynamic";
|
||||
import { useEffect } from "react";
|
||||
import FAQ from "./faq";
|
||||
import Features from "./features";
|
||||
import Footer from "./footer";
|
||||
import Hero from "./hero";
|
||||
import Pricing from "./pricing";
|
||||
import PricingModels from "./pricing-models";
|
||||
import Problem from "./problem";
|
||||
import ProductionScale from "./production-scale";
|
||||
import SectionDivider from "./section-divider";
|
||||
import Solution from "./solution";
|
||||
import Testimonials from "./testimonials";
|
||||
|
||||
// All below-fold sections are code-split into separate lazy chunks so the
|
||||
// initial JS bundle only contains the hero. Framer-motion, GSAP ScrollTrigger,
|
||||
// and lottie-web are pulled into these chunks rather than the main bundle.
|
||||
const LogoWall = dynamic(() => import("./logo-wall"));
|
||||
const Problem = dynamic(() => import("./problem"));
|
||||
const Solution = dynamic(() => import("./solution"));
|
||||
const PricingModels = dynamic(() => import("./pricing-models"));
|
||||
const Features = dynamic(() => import("./features"));
|
||||
const Testimonials = dynamic(() => import("./testimonials"));
|
||||
const ProductionScale = dynamic(() => import("./production-scale"));
|
||||
const Pricing = dynamic(() => import("./pricing"));
|
||||
const FAQ = dynamic(() => import("./faq"));
|
||||
const Footer = dynamic(() => import("./footer"));
|
||||
|
||||
function scrollToHash() {
|
||||
const hash = window.location.hash;
|
||||
@@ -36,6 +42,15 @@ export default function HomeSections() {
|
||||
return (
|
||||
<>
|
||||
<Hero />
|
||||
{/*
|
||||
<div className="flex flex-col gap-2.5 bg-[#000000]">
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
<div className="border-t border-[#292929] w-full" />
|
||||
</div>
|
||||
<LogoWall /> */}
|
||||
<SectionDivider title="THE PROBLEM" />
|
||||
<Problem />
|
||||
<SectionDivider title="THE SOLUTION" />
|
||||
|
||||
73
apps/website/components/logo-wall.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
"use client";
|
||||
|
||||
import { cn } from "@/lib/utils";
|
||||
|
||||
const LOGOS = [
|
||||
{ id: 1, name: "Mintlify", src: "/images/logos/mintlify_logo.svg.svg" },
|
||||
{ id: 2, name: "Browser Use", src: "/images/logos/Browser use.svg" },
|
||||
{ id: 3, name: "Firecrawl", src: "/images/logos/Firecrawl.svg.svg" },
|
||||
{ id: 4, name: "Mastra", src: "/images/logos/Mastra.svg.svg" },
|
||||
{ id: 5, name: "T3.chat", src: "/images/logos/T3_svg.svg" },
|
||||
{ id: 6, name: "", src: null },
|
||||
];
|
||||
|
||||
const NUM_MOBILE_COLS = 2;
|
||||
const NUM_DESKTOP_COLS = 3;
|
||||
|
||||
export default function LogoWall() {
|
||||
return (
|
||||
<section className="w-full bg-[#000000]">
|
||||
<div className="flex flex-col md:flex-row">
|
||||
{/* Left heading — full width on mobile, 42% on desktop */}
|
||||
<div className="md:w-[42%] px-4 xl:px-22.75 py-8 md:py-0 flex items-center border-b md:border-b-0 md:border-r border-[#292929]">
|
||||
<div>
|
||||
<p className="font-sans text-[28px] md:text-[36px] xl:text-[40px] font-normal leading-[1.1] tracking-[-0.03em]">
|
||||
<span className="text-[#FFFFFF66]">Trusted by </span>
|
||||
<span className="text-white">AI teams</span>
|
||||
</p>
|
||||
<p className="font-sans text-[28px] md:text-[36px] xl:text-[40px] font-normal leading-[1.1] tracking-[-0.03em] text-white">
|
||||
shipping fast
|
||||
</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{/* Logo grid — 2 cols on mobile, 3 cols on desktop */}
|
||||
<div className="flex-1 grid grid-cols-2 md:grid-cols-3">
|
||||
{LOGOS.map((logo, i) => {
|
||||
const isLastMobileCol = (i + 1) % NUM_MOBILE_COLS === 0;
|
||||
const isLastDesktopCol = (i + 1) % NUM_DESKTOP_COLS === 0;
|
||||
const isLastMobileRow = i >= LOGOS.length - NUM_MOBILE_COLS;
|
||||
const isLastDesktopRow = i >= LOGOS.length - NUM_DESKTOP_COLS;
|
||||
|
||||
return (
|
||||
<div
|
||||
key={logo.id}
|
||||
className={cn(
|
||||
"flex items-center justify-center min-h-[90px] md:min-h-[166px] border-[#292929]",
|
||||
!isLastMobileCol && "border-r",
|
||||
!isLastMobileRow && "border-b",
|
||||
isLastDesktopCol ? "md:border-r-0" : "md:border-r",
|
||||
isLastDesktopRow ? "md:border-b-0" : "md:border-b",
|
||||
)}
|
||||
style={{
|
||||
backgroundImage:
|
||||
"radial-gradient(circle, rgba(255,255,255,0.06) 1px, transparent 1px)",
|
||||
backgroundSize: "14px 14px",
|
||||
}}
|
||||
>
|
||||
{logo.src && (
|
||||
<img
|
||||
src={logo.src}
|
||||
alt={logo.name}
|
||||
className="h-5 md:h-7 w-auto max-w-[110px] md:max-w-[150px] object-contain"
|
||||
loading="lazy"
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
})}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@@ -289,7 +289,7 @@ export default function Navbar({
|
||||
y: 0,
|
||||
pointerEvents: "auto",
|
||||
clipPath: "inset(0% 0 0% 0)",
|
||||
duration: 0.65,
|
||||
duration: 0.2,
|
||||
ease: "power3.inOut",
|
||||
})
|
||||
.to(
|
||||
@@ -298,11 +298,11 @@ export default function Navbar({
|
||||
opacity: 1,
|
||||
filter: "blur(0px)",
|
||||
scale: 1,
|
||||
duration: 0.4,
|
||||
stagger: 0.02,
|
||||
duration: 0.15,
|
||||
stagger: 0.01,
|
||||
ease: "power2.out",
|
||||
},
|
||||
"-=0.35",
|
||||
"-=0.1",
|
||||
);
|
||||
},
|
||||
{ scope: containerRef },
|
||||
|
||||
@@ -59,14 +59,25 @@ const sidebarItems = [
|
||||
] 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",
|
||||
0: "/images/pricing-models/Subscriptions.avif",
|
||||
1: "/images/pricing-models/Subscriptions (1).avif",
|
||||
2: "/images/pricing-models/Trial Configuration.avif",
|
||||
3: "/images/pricing-models/Hybrid Plan Builder.avif",
|
||||
4: "/images/pricing-models/Usage Metering.avif",
|
||||
5: "/images/pricing-models/Hybrid Plan Builder (1).avif",
|
||||
6: "/images/pricing-models/Subscriptions (2).avif",
|
||||
7: "/images/pricing-models/Subscriptions (3).avif",
|
||||
};
|
||||
|
||||
const mobileImages: Record<(typeof sidebarItems)[number]["model"], string> = {
|
||||
0: "/images/pricing-models/Subscriptions-mobile.avif",
|
||||
1: "/images/pricing-models/Subscriptions (1)-mobile.avif",
|
||||
2: "/images/pricing-models/Trial Configuration-mobile.avif",
|
||||
3: "/images/pricing-models/Hybrid Plan Builder-mobile.avif",
|
||||
4: "/images/pricing-models/Usage Metering-mobile.avif",
|
||||
5: "/images/pricing-models/Hybrid Plan Builder (1)-mobile.avif",
|
||||
6: "/images/pricing-models/Subscriptions (2)-mobile.avif",
|
||||
7: "/images/pricing-models/Subscriptions (3)-mobile.avif",
|
||||
};
|
||||
|
||||
export default function PricingModels() {
|
||||
@@ -159,7 +170,7 @@ export default function PricingModels() {
|
||||
src={src}
|
||||
alt="Pricing Model"
|
||||
fill
|
||||
sizes="(max-width: 768px) 100vw, 50vw"
|
||||
sizes="(max-width: 1024px) 100vw, (max-width: 1440px) 40vw, 600px"
|
||||
className="object-contain"
|
||||
priority={key === "0"}
|
||||
/>
|
||||
@@ -194,9 +205,12 @@ export default function PricingModels() {
|
||||
>
|
||||
<div className="relative w-full aspect-3/2.5 sm:aspect-square">
|
||||
<div className="absolute bottom-0 left-0 right-0 z-10 flex items-center justify-center px-3 pt-2">
|
||||
<img
|
||||
src={images[item.model]}
|
||||
<Image
|
||||
src={mobileImages[item.model]}
|
||||
alt="Pricing Model"
|
||||
width={1212}
|
||||
height={1048}
|
||||
sizes="(max-width: 640px) 90vw, (max-width: 768px) 85vw, 400px"
|
||||
className="w-full h-auto max-h-full object-contain object-bottom"
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -52,7 +52,7 @@ export default function Pricing() {
|
||||
>
|
||||
{/* Desktop Background */}
|
||||
<Image
|
||||
src="/images/pricing/pricing.webp"
|
||||
src="/images/pricing/pricing.avif"
|
||||
alt="pricing background desktop"
|
||||
fill
|
||||
className="object-cover absolute z-10 lg:z-50 hidden md:block"
|
||||
@@ -60,7 +60,7 @@ export default function Pricing() {
|
||||
/>
|
||||
{/* Mobile Background */}
|
||||
<Image
|
||||
src="/images/pricing/pricing-mob.webp"
|
||||
src="/images/pricing/pricing-mob.avif"
|
||||
alt="pricing background mobile"
|
||||
fill
|
||||
className="object-cover absolute z-10 lg:z-50 block md:hidden"
|
||||
@@ -86,6 +86,7 @@ export default function Pricing() {
|
||||
index === 1 ? "lg:border-r" : ""
|
||||
} ${index === 2 ? "md:col-span-2 lg:col-span-1 md:w-[calc(50%-16px)] md:justify-self-center lg:w-full lg:justify-self-auto" : ""}`}
|
||||
>
|
||||
|
||||
{plan.isPro && (
|
||||
<div className="hidden lg:block absolute -inset-px z-20 pointer-events-none border border-transparent [border-image:linear-gradient(to_bottom,#A175FF,#000000)_1]"></div>
|
||||
)}
|
||||
|
||||
@@ -70,6 +70,8 @@ export default function ProblemAnimation() {
|
||||
src={src}
|
||||
alt=""
|
||||
className="block max-h-full max-w-[90%] object-contain"
|
||||
loading="lazy"
|
||||
decoding="async"
|
||||
draggable={false}
|
||||
/>
|
||||
</div>
|
||||
|
||||
@@ -74,7 +74,7 @@ export default function Problem() {
|
||||
<div
|
||||
key={i}
|
||||
className={`py-6 pr-9 border-[#292929] border-r
|
||||
${i === 2 ? "col-span-2 xl:col-span-1 border-t xl:border-t-0 border-r-0 xl:border-r" : ""}
|
||||
${i === 2 ? "col-span-2 xl:col-span-1 border-t xl:border-t-0 border-r-0" : ""}
|
||||
${i === 1 ? "border-r-0 xl:border-r" : ""}
|
||||
pl-4 xl:pl-6`}
|
||||
>
|
||||
|
||||
@@ -15,52 +15,72 @@ export default function SolutionAnimation() {
|
||||
|
||||
useEffect(() => {
|
||||
if (!containerRef.current) return;
|
||||
let cancelled = false;
|
||||
|
||||
const isMobile = window.innerWidth < 768;
|
||||
const url = isMobile
|
||||
? "/animation/solution-mobile.json"
|
||||
: "/animation/solution-desktop.json";
|
||||
|
||||
fetch(url)
|
||||
.then((res) => res.json())
|
||||
.then((animationData) => {
|
||||
if (cancelled || !containerRef.current) return;
|
||||
|
||||
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%",
|
||||
onEnter: () => anim.play(),
|
||||
});
|
||||
|
||||
cleanupRef.current = () => {
|
||||
st.kill();
|
||||
anim.destroy();
|
||||
};
|
||||
});
|
||||
|
||||
let cancelled = false;
|
||||
const cleanupRef = { current: () => {} };
|
||||
|
||||
const initAnimation = () => {
|
||||
if (cancelled || !containerRef.current) return;
|
||||
|
||||
fetch(url)
|
||||
.then((res) => res.json())
|
||||
.then((animationData) => {
|
||||
if (cancelled || !containerRef.current) return;
|
||||
|
||||
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%",
|
||||
onEnter: () => anim.play(),
|
||||
});
|
||||
|
||||
cleanupRef.current = () => {
|
||||
st.kill();
|
||||
anim.destroy();
|
||||
};
|
||||
});
|
||||
};
|
||||
|
||||
// Defer the 1.4–2.1 MB Lottie JSON fetch until the element is close to
|
||||
// the viewport. Without this the browser fetches it during initial load
|
||||
// and blocks the main thread while parsing the large JSON blob.
|
||||
const observer = new IntersectionObserver(
|
||||
(entries) => {
|
||||
if (entries[0].isIntersecting) {
|
||||
observer.disconnect();
|
||||
initAnimation();
|
||||
}
|
||||
},
|
||||
{ rootMargin: "200px" },
|
||||
);
|
||||
|
||||
observer.observe(containerRef.current);
|
||||
|
||||
return () => {
|
||||
cancelled = true;
|
||||
observer.disconnect();
|
||||
cleanupRef.current();
|
||||
};
|
||||
}, []);
|
||||
|
||||
@@ -30,7 +30,7 @@ const iconSrc = {
|
||||
|
||||
export default function Solution() {
|
||||
return (
|
||||
<section className="relative w-full bg-[#000000] border-t border-[#292929] overflow-hidden pt-24">
|
||||
<section className="relative w-full bg-[#000000] overflow-hidden pt-24">
|
||||
<div
|
||||
className="absolute inset-0 z-0 pointer-events-none"
|
||||
style={{
|
||||
|
||||
@@ -41,7 +41,6 @@ const testimonialsData = [
|
||||
];
|
||||
|
||||
const Testimonials = () => {
|
||||
const videoRef = useRef<HTMLVideoElement | null>(null);
|
||||
const scrollRef = useRef<HTMLDivElement | null>(null);
|
||||
const progressRef = useRef<HTMLDivElement | null>(null);
|
||||
const [canScrollLeft, setCanScrollLeft] = useState(false);
|
||||
@@ -68,12 +67,6 @@ const Testimonials = () => {
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (videoRef.current) {
|
||||
videoRef.current.playbackRate = 0.4;
|
||||
}
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
handleScroll();
|
||||
window.addEventListener("resize", handleScroll);
|
||||
@@ -143,7 +136,6 @@ const Testimonials = () => {
|
||||
<div className="absolute inset-x-0 bottom-0 h-[120%] pointer-events-none z-0 overflow-hidden">
|
||||
<div className="absolute inset-0 opacity-0 translate-y-6 group-hover:opacity-100 group-hover:translate-y-0 transition-all duration-500 ease-out pointer-events-none z-0 hidden md:block">
|
||||
<video
|
||||
ref={videoRef}
|
||||
src="/images/testimonials/testimonial section.webm"
|
||||
autoPlay
|
||||
loop
|
||||
|
||||
@@ -1,7 +1,79 @@
|
||||
/** @type {import('next').NextConfig} */
|
||||
const nextConfig = {
|
||||
/* config options here */
|
||||
allowedDevOrigins: ['*.ngrok-free.dev']
|
||||
allowedDevOrigins: ["*.ngrok-free.dev"],
|
||||
experimental: {
|
||||
optimizePackageImports: ["framer-motion", "motion", "gsap", "@gsap/react"],
|
||||
optimizeCss: true,
|
||||
},
|
||||
images: {
|
||||
// Serve AVIF to supporting browsers (better compression than WebP),
|
||||
// falling back to WebP. Next.js negotiates via Accept header automatically.
|
||||
// formats: ["image/avif", "image/webp"],
|
||||
},
|
||||
async headers() {
|
||||
return [
|
||||
// Next.js chunks include a content hash in their filename, so they are
|
||||
// safe to cache indefinitely. Vercel sets this automatically on its CDN,
|
||||
// but the explicit header also covers self-hosted deployments.
|
||||
{
|
||||
source: "/_next/static/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value: "public, max-age=31536000, immutable",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Lottie JSON files are large (1.4–2.6 MB each) and change infrequently.
|
||||
// A 1-year cache means repeat visitors skip the expensive re-download.
|
||||
{
|
||||
source: "/animation/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value: "public, max-age=31536000, immutable",
|
||||
},
|
||||
],
|
||||
},
|
||||
// Public images: 7-day TTL with a 1-day stale-while-revalidate window
|
||||
// so content updates eventually propagate without blocking visitors.
|
||||
{
|
||||
source: "/images/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Cache-Control",
|
||||
value: "public, max-age=604800, stale-while-revalidate=86400",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
source: "/(.*)",
|
||||
headers: [
|
||||
{
|
||||
key: "Content-Security-Policy",
|
||||
value: [
|
||||
"default-src 'self'",
|
||||
"script-src 'self' 'unsafe-inline' 'unsafe-eval' https://vercel.live",
|
||||
"style-src 'self' 'unsafe-inline'",
|
||||
"img-src 'self' data: blob:",
|
||||
"font-src 'self' data:",
|
||||
"media-src 'self'",
|
||||
"connect-src 'self' https://*.vercel.app https://vitals.vercel-insights.com",
|
||||
"frame-ancestors 'none'",
|
||||
].join("; "),
|
||||
},
|
||||
{
|
||||
key: "Cross-Origin-Opener-Policy",
|
||||
value: "same-origin",
|
||||
},
|
||||
{
|
||||
key: "X-Frame-Options",
|
||||
value: "DENY",
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
},
|
||||
};
|
||||
|
||||
export default nextConfig;
|
||||
|
||||
9112
apps/website/package-lock.json
generated
BIN
apps/website/public/images/footer/footer-mob.avif
Normal file
|
After Width: | Height: | Size: 46 KiB |
|
Before Width: | Height: | Size: 594 KiB |
BIN
apps/website/public/images/footer/footer.avif
Normal file
|
After Width: | Height: | Size: 284 KiB |
|
Before Width: | Height: | Size: 698 KiB |
|
Before Width: | Height: | Size: 346 KiB |
|
Before Width: | Height: | Size: 791 KiB |
10
apps/website/public/images/logos/Browser use.svg
Normal file
|
After Width: | Height: | Size: 30 KiB |
19
apps/website/public/images/logos/Firecrawl.svg.svg
Normal file
@@ -0,0 +1,19 @@
|
||||
<svg width="142" height="33" viewBox="0 0 142 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5622_2619)">
|
||||
<path d="M19.2725 10.5837C17.9963 10.9625 17.0343 11.8195 16.3297 12.7504C16.1784 12.9502 15.8631 12.8001 15.9232 12.5552C17.2722 7.00807 15.4901 2.3976 9.93481 0.128169C9.65299 0.0126761 9.35962 0.265605 9.43354 0.561267C11.9606 10.7073 1.33165 9.85151 2.67485 21.3534C2.69795 21.5509 2.4762 21.686 2.3145 21.5705C1.81095 21.209 1.24849 20.4549 0.862744 19.9248C0.749558 19.7688 0.504709 19.8127 0.452738 19.9987C0.145523 21.1097 0 22.1561 0 23.1956C0 27.2378 2.07774 30.7961 5.22265 32.8589C5.40282 32.9767 5.63381 32.808 5.57259 32.6013C5.4109 32.0584 5.31967 31.4856 5.31274 30.8931C5.31274 30.5293 5.33584 30.1575 5.39242 29.811C5.52409 28.9402 5.82669 28.1109 6.33486 27.3556C8.07766 24.7397 11.5714 22.2127 11.0135 18.7814C10.9777 18.5642 11.2341 18.4211 11.3958 18.57C13.857 20.8187 14.3444 23.8434 13.9402 26.5564C13.9055 26.792 14.2011 26.9179 14.3501 26.7331C14.7267 26.2619 15.1864 25.8484 15.6864 25.5377C15.8111 25.4603 15.9774 25.5192 16.0248 25.6578C16.3032 26.4674 16.7166 27.2274 17.107 27.9873C17.5736 28.9009 17.8219 29.9438 17.7826 31.0479C17.763 31.585 17.6741 32.1046 17.5251 32.599C17.4615 32.808 17.6902 32.9824 17.8739 32.8623C21.0211 30.7996 23.1 27.2413 23.1 23.1967C23.1 21.7912 22.854 20.4133 22.3885 19.1221C21.4126 16.4138 18.9365 14.38 19.5624 10.8505C19.5924 10.6819 19.4365 10.5352 19.2725 10.5837Z" fill="#9D9D9D"/>
|
||||
<path d="M33.8223 28.0934V9.04395H45.9981V11.8197H36.9547V17.3441H44.4182V20.0382H36.9547V28.0934H33.8223Z" fill="#9D9D9D"/>
|
||||
<path d="M49.4639 12.2009C48.456 12.2009 47.666 11.4661 47.666 10.432C47.666 9.39783 48.456 8.66309 49.4639 8.66309C50.4717 8.66309 51.2616 9.39783 51.2616 10.432C51.2616 11.4661 50.4717 12.2009 49.4639 12.2009ZM47.9657 28.0936V14.1602H50.9075V28.0936H47.9657Z" fill="#9D9D9D"/>
|
||||
<path d="M60.7112 14.1592H61.3649V16.9077H60.0573C57.4424 16.9077 56.598 18.9487 56.598 21.0987V28.0925H53.6562V14.1592H56.2712L56.598 16.2546C57.3062 15.0844 58.423 14.1592 60.7112 14.1592Z" fill="#9D9D9D"/>
|
||||
<path d="M68.994 28.256C64.6086 28.256 61.8574 25.4257 61.8574 21.1532C61.8574 16.8535 64.6086 13.9961 68.7217 13.9961C72.753 13.9961 75.4496 16.5542 75.5314 20.6362C75.5314 20.99 75.5042 21.3709 75.4496 21.7519H64.9354V21.9424C65.0172 24.31 66.5153 25.8612 68.8306 25.8612C70.6283 25.8612 71.9359 24.9631 72.3444 23.412H75.2862C74.7959 26.1606 72.4534 28.256 68.994 28.256ZM65.0444 19.6021H72.4806C72.2354 17.5339 70.7918 16.3637 68.7489 16.3637C66.8694 16.3637 65.2623 17.6155 65.0444 19.6021Z" fill="#9D9D9D"/>
|
||||
<path d="M84.1767 28.256C79.9544 28.256 77.2578 25.5074 77.2578 21.1532C77.2578 16.8535 80.0362 13.9961 84.2584 13.9961C87.8537 13.9961 90.087 15.9827 90.6595 19.1395H87.5815C87.2003 17.5066 86.0288 16.4997 84.2039 16.4997C81.8339 16.4997 80.2813 18.4046 80.2813 21.1532C80.2813 23.8746 81.8339 25.7523 84.2039 25.7523C86.0016 25.7523 87.2003 24.7182 87.5542 23.1126H90.6595C90.1142 26.2694 87.7448 28.256 84.1767 28.256Z" fill="#9D9D9D"/>
|
||||
<path d="M99.8319 14.1592H100.486V16.9077H99.1785C96.5633 16.9077 95.7193 18.9487 95.7193 21.0987V28.0925H92.7773V14.1592H95.3926L95.7193 16.2546C96.4271 15.0844 97.5442 14.1592 99.8319 14.1592Z" fill="#9D9D9D"/>
|
||||
<path d="M107.756 13.9961C111.46 13.9961 113.585 15.765 113.585 19.0578V28.0927H111.025L110.78 26.1061C109.826 27.358 108.491 28.256 106.285 28.256C103.234 28.256 101.191 26.7593 101.191 24.174C101.191 21.3165 103.261 19.711 107.183 19.711H110.671V18.8673C110.671 17.3161 109.554 16.3637 107.62 16.3637C105.877 16.3637 104.705 17.1801 104.487 18.4046H101.6C101.899 15.6833 104.242 13.9961 107.756 13.9961ZM106.775 25.9701C109.227 25.9701 110.643 24.5277 110.671 22.3778V21.888H107.02C105.195 21.888 104.188 22.5683 104.188 23.9835C104.188 25.1536 105.168 25.9701 106.775 25.9701Z" fill="#9D9D9D"/>
|
||||
<path d="M119.34 28.0925L114.791 14.1592H117.896L121.083 24.8268L124.27 14.1592H126.966L130.017 24.8268L133.313 14.1592H136.309L131.679 28.0925H128.519L125.578 18.6222L122.527 28.0925H119.34Z" fill="#9D9D9D"/>
|
||||
<path d="M137.719 28.0934V9.04395H140.661V28.0934H137.719Z" fill="#9D9D9D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5622_2619">
|
||||
<rect width="141.9" height="33" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.2 KiB |
3
apps/website/public/images/logos/Mastra.svg.svg
Normal file
|
After Width: | Height: | Size: 7.4 KiB |
10
apps/website/public/images/logos/T3_svg.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg width="113" height="24" viewBox="0 0 113 24" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5622_2630)">
|
||||
<path d="M16.2113 4.84492H10.8679V23.3657H6.97358V4.8902H0V1.53926H19.4717L16.2113 4.84492ZM77.9774 9.10153C78.566 10.2336 78.8377 11.5468 78.8377 13.1317V23.3657H74.9887V13.6751C74.9887 12.2261 74.6717 11.094 73.9472 10.3242C73.2226 9.55436 72.2717 9.19209 71.0491 9.19209C69.8264 9.19209 68.8755 9.55436 68.1509 10.3242C67.4264 11.0487 67.0642 12.1808 67.0642 13.6298V23.3657H63.1698V4.02983L67.0642 0.0449219V7.51662C67.6076 7.01851 68.2415 6.56568 68.966 6.2487C69.917 5.88643 70.9132 5.66002 72 5.66002C73.2679 5.66002 74.4453 5.93171 75.4868 6.52039L75.5321 6.56568C76.5283 7.10907 77.3434 7.96945 77.9774 9.10153ZM20.1057 4.79964L23.3208 1.53926H33.2377V4.43737L28.3472 9.50907C30.0679 9.73549 31.4717 10.3695 32.4679 11.411L32.5585 11.5015C33.6906 12.6789 34.234 14.2638 34.3245 16.2562C34.3245 18.3845 33.6453 20.1053 32.3321 21.3732C31.0189 22.6411 29.2075 23.2751 26.9434 23.2751C24.8604 23.2751 23.1396 22.6864 21.8264 21.5996C20.4679 20.5128 19.7434 18.9279 19.6528 16.8449V16.6185H23.3208V16.8449C23.4113 17.7959 23.7283 18.5657 24.3623 19.1996C24.9509 19.7883 25.8113 20.1053 26.9434 20.1053C28.0755 20.1053 28.9811 19.7883 29.6151 19.1091C30.2491 18.4298 30.566 17.4789 30.566 16.3015C30.566 15.0789 30.2491 14.1732 29.6151 13.5393C28.9359 12.86 28.0755 12.543 26.9434 12.543H23.7736V9.87134L28.5736 4.79964H20.1057ZM111.758 5.977V9.28266H107.683V19.5619L105.057 22.2789L103.743 23.5921V9.28266H101.796V5.977H103.743V1.76568H107.683V5.977H111.758ZM47.9547 6.83737C49.2226 6.06756 50.6717 5.7053 52.3019 5.7053C54.3849 5.7053 56.1057 6.20341 57.4642 7.19964C58.8226 8.19587 59.7736 9.64492 60.2264 11.5015L60.2717 11.7279H56.1509L56.1057 11.5921C55.7887 10.777 55.3359 10.143 54.7019 9.6902C54.0679 9.23737 53.2981 9.01096 52.3019 9.01096C50.9434 9.01096 49.8566 9.46379 49.0415 10.46C48.2264 11.4562 47.8189 12.8147 47.8189 14.6261C47.8189 16.4374 48.2264 17.8411 49.0415 18.8374C49.8566 19.8336 50.9434 20.3317 52.3019 20.3317C53.2981 20.3317 54.0679 20.1053 54.7019 19.6978C55.3359 19.2449 55.7887 18.611 56.1057 17.7506L56.1509 17.6147H60.2717L60.2264 17.8411C59.7283 19.6072 58.7774 21.011 57.4189 22.0525C56.0151 23.094 54.3396 23.6374 52.3019 23.6374C50.6717 23.6374 49.2226 23.2751 47.9547 22.5053C46.6868 21.7355 45.6906 20.694 44.966 19.3355C44.2415 17.977 43.8792 16.3921 43.8792 14.6261C43.8792 12.86 44.2415 11.2751 44.966 9.91662L45.1019 9.6902C45.7811 8.42228 46.7321 7.42605 47.9547 6.83737ZM89.5698 23.5921C88.1208 23.5921 86.7623 23.1845 85.5396 22.4147C84.317 21.6449 83.366 20.5581 82.6415 19.1996C81.917 17.8411 81.5547 16.2562 81.5547 14.5355C81.5547 12.8147 81.917 11.2751 82.6415 9.91662C83.3208 8.60341 84.317 7.5619 85.5396 6.79209C86.7623 6.02228 88.1208 5.66002 89.6151 5.66002C90.9736 5.66002 92.1509 5.93171 93.1472 6.47511C93.9623 6.88266 94.6868 7.42605 95.2302 8.06002V5.93171H99.0792V23.3204H95.2302V21.1468C94.6868 21.7808 93.9623 22.3242 93.1019 22.777C92.0604 23.3204 90.8377 23.5921 89.5698 23.5921ZM92.6943 9.73549C91.9698 9.32794 91.1547 9.10153 90.2943 9.10153C89.434 9.10153 88.6189 9.28266 87.8943 9.73549C87.1245 10.143 86.5359 10.777 86.083 11.5921C85.6302 12.4072 85.4038 13.4034 85.4038 14.5355C85.4038 15.6676 85.6302 16.6638 86.083 17.5242C86.5359 18.3845 87.1245 19.0185 87.8943 19.4713C88.6642 19.9242 89.434 20.1506 90.2943 20.1506C91.1547 20.1506 91.9698 19.9242 92.6943 19.4713C93.4189 19.0638 94.0528 18.4298 94.5057 17.5695C94.9585 16.7091 95.1849 15.7128 95.1849 14.5808C95.1849 13.5393 95.0038 12.6336 94.6415 11.8638L94.5057 11.6374C94.0528 10.8223 93.4642 10.1883 92.6943 9.73549ZM38.1736 20.0147H41.2981V23.1393H38.1736V20.0147Z" fill="#9D9D9D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5622_2630">
|
||||
<rect width="112.166" height="24" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.8 KiB |
20
apps/website/public/images/logos/mintlify_logo.svg.svg
Normal file
@@ -0,0 +1,20 @@
|
||||
<svg width="143" height="33" viewBox="0 0 143 33" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g clip-path="url(#clip0_5622_2605)">
|
||||
<path d="M25.4002 13.2077V5.38218C25.4002 4.54242 24.7189 3.87305 23.8917 3.87305H16.0697C14.841 3.87305 13.6246 4.11644 12.4932 4.57892C11.3619 5.05357 10.3279 5.73511 9.46422 6.61139L9.40339 6.67224C8.2599 7.82843 7.44485 9.26453 7.03125 10.8467C7.77331 10.652 8.53965 10.5546 9.30604 10.5424C11.3497 10.5181 13.3569 11.1753 14.987 12.4045C16.459 13.4998 17.5781 15.009 18.1864 16.7494C18.8189 18.514 18.8919 20.437 18.4175 22.2503C19.9867 21.8366 21.4344 21.0212 22.5901 19.8772L22.6509 19.8163C23.5145 18.9522 24.2079 17.9177 24.6823 16.7858C25.1568 15.654 25.3879 14.437 25.3879 13.2077H25.4002Z" fill="#9D9D9D"/>
|
||||
<path d="M6.79278 13.0745C6.80489 10.6738 7.76228 8.37012 9.44682 6.64844L2.9389 13.1593C2.91468 13.1836 2.89042 13.1957 2.86618 13.2199C1.2786 14.7961 0.321219 16.9059 0.163673 19.1367C0.0182454 21.2221 0.563588 23.2833 1.72701 25.005C1.83798 25.1692 2.13909 25.2233 2.30875 25.0657L6.29588 21.0888C7.54415 19.84 7.93197 17.9848 7.33813 16.3238C6.96244 15.2933 6.78067 14.19 6.79278 13.0745Z" fill="#9D9D9D"/>
|
||||
<path d="M22.6109 19.8164C21.3627 21.041 19.7993 21.8898 18.1027 22.2656C16.3939 22.6415 14.6245 22.5323 12.9764 21.9504C12.9764 21.9504 12.9643 21.9504 12.9521 21.9504C11.2918 21.3563 9.43767 21.7442 8.18942 22.981L4.20222 26.9577C4.03256 27.1276 4.05679 27.4064 4.26282 27.5398C5.98371 28.6916 8.04399 29.2493 10.1284 29.1038C12.3583 28.9463 14.4549 27.9883 16.0424 26.4L16.1031 26.3394L22.6109 19.8285V19.8164Z" fill="#9D9D9D"/>
|
||||
<path d="M132.324 32.3135H127.441L130.882 24.5465L124 9.07812H128.916L132.926 18.7748C133.066 19.1138 133.547 19.1131 133.686 18.7739L137.665 9.07812H142.614L132.324 32.3135Z" fill="#9D9D9D"/>
|
||||
<path d="M117.49 25.4631V13.0098H114.18V9.07712H117.49V6.94694C117.49 5.00247 118.08 3.47311 119.259 2.35887C120.439 1.24462 121.936 0.6875 123.749 0.6875C124.863 0.6875 125.814 0.807664 126.6 1.04799V5.0134C126.076 4.81676 125.464 4.71845 124.765 4.71845C123.782 4.71845 123.072 4.93692 122.635 5.37388C122.198 5.789 121.979 6.49906 121.979 7.50406V9.07712H126.6V13.0098H121.979V25.4631H117.49Z" fill="#9D9D9D"/>
|
||||
<path d="M110.169 6.38982C109.405 6.38982 108.738 6.11671 108.17 5.57052C107.602 5.00247 107.318 4.32519 107.318 3.53866C107.318 2.75213 107.602 2.08576 108.17 1.53957C108.738 0.971524 109.405 0.6875 110.169 0.6875C110.978 0.6875 111.655 0.971524 112.201 1.53957C112.769 2.08576 113.053 2.75213 113.053 3.53866C113.053 4.32519 112.769 5.00247 112.201 5.57052C111.655 6.11671 110.978 6.38982 110.169 6.38982ZM107.941 25.4631V9.07712H112.431V25.4631H107.941Z" fill="#9D9D9D"/>
|
||||
<path d="M100.117 25.4618V0.882812H104.607V25.4618H100.117Z" fill="#9D9D9D"/>
|
||||
<path d="M95.0441 25.6594C93.2089 25.6594 91.7013 25.1568 90.5215 24.1519C89.3635 23.125 88.7847 21.6503 88.7847 19.7277V13.0094H85.4746V9.07679H88.7847V4.52148H93.2743V9.07679H97.8953V13.0094H93.2743V18.8429C93.2743 19.8478 93.4928 20.5688 93.9298 21.0057C94.3667 21.4208 95.0768 21.6285 96.0599 21.6285C96.7591 21.6285 97.3708 21.5302 97.8953 21.3335V25.2989C97.1086 25.5392 96.1582 25.6594 95.0441 25.6594Z" fill="#9D9D9D"/>
|
||||
<path d="M68.6699 25.4633V9.07729H73.1596V10.275C73.1596 10.4775 73.4353 10.5671 73.5693 10.4153C74.5703 9.28298 75.9958 8.7168 77.846 8.7168C79.8124 8.7168 81.3526 9.37225 82.4668 10.6831C83.603 11.9722 84.171 13.6981 84.171 15.8611V25.4633H79.6812V16.6149C79.6812 15.4133 79.43 14.4847 78.9274 13.8293C78.425 13.1519 77.7149 12.8133 76.7973 12.8133C75.705 12.8133 74.82 13.2066 74.1428 13.993C73.4874 14.7797 73.1596 15.9376 73.1596 17.467V25.4633H68.6699Z" fill="#9D9D9D"/>
|
||||
<path d="M63.0836 6.38982C62.3188 6.38982 61.6525 6.11671 61.0845 5.57052C60.5165 5.00247 60.2324 4.32519 60.2324 3.53866C60.2324 2.75213 60.5165 2.08576 61.0845 1.53957C61.6525 0.971524 62.3188 0.6875 63.0836 0.6875C63.892 0.6875 64.5692 0.971524 65.1155 1.53957C65.6835 2.08576 65.9675 2.75213 65.9675 3.53866C65.9675 4.32519 65.6835 5.00247 65.1155 5.57052C64.5692 6.11671 63.892 6.38982 63.0836 6.38982ZM60.855 25.4631V9.07712H65.3448V25.4631H60.855Z" fill="#9D9D9D"/>
|
||||
<path d="M53.2303 25.4633V16.7132C53.2303 14.1132 52.3782 12.8133 50.6742 12.8133C49.6909 12.8133 48.9044 13.1847 48.3145 13.9276C47.7465 14.6704 47.4406 15.7737 47.3969 17.2375V25.4633H42.9072V16.7132C42.9072 14.1132 42.0551 12.8133 40.3509 12.8133C39.346 12.8133 38.5485 13.2066 37.9586 13.993C37.3687 14.7797 37.0738 15.9376 37.0738 17.467V25.4633H32.584V9.07729H37.0738V10.2868C37.0738 10.4883 37.3446 10.577 37.4771 10.4251C38.4688 9.28623 39.7873 8.7168 41.4324 8.7168C43.5943 8.7168 45.2226 9.60932 46.3172 11.3944C46.4036 11.5352 46.6161 11.533 46.7019 11.3919C47.1752 10.614 47.8329 9.99542 48.6751 9.5361C49.6363 8.9899 50.6086 8.7168 51.5917 8.7168C53.5362 8.7168 55.0436 9.36131 56.1142 10.6503C57.1848 11.9394 57.7201 13.7091 57.7201 15.9594V25.4633H53.2303Z" fill="#9D9D9D"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_5622_2605">
|
||||
<rect width="143" height="33" fill="white"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 42 KiB |
|
After Width: | Height: | Size: 13 KiB |
BIN
apps/website/public/images/pricing-models/Subscriptions (1).avif
Normal file
|
After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 48 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
BIN
apps/website/public/images/pricing-models/Subscriptions (2).avif
Normal file
|
After Width: | Height: | Size: 11 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
After Width: | Height: | Size: 10 KiB |
BIN
apps/website/public/images/pricing-models/Subscriptions (3).avif
Normal file
|
After Width: | Height: | Size: 30 KiB |
|
Before Width: | Height: | Size: 50 KiB |
|
After Width: | Height: | Size: 14 KiB |
BIN
apps/website/public/images/pricing-models/Subscriptions.avif
Normal file
|
After Width: | Height: | Size: 25 KiB |
|
Before Width: | Height: | Size: 54 KiB |
|
After Width: | Height: | Size: 16 KiB |
|
After Width: | Height: | Size: 20 KiB |
|
Before Width: | Height: | Size: 52 KiB |
|
After Width: | Height: | Size: 8.6 KiB |
BIN
apps/website/public/images/pricing-models/Usage Metering.avif
Normal file
|
After Width: | Height: | Size: 16 KiB |
|
Before Width: | Height: | Size: 40 KiB |
BIN
apps/website/public/images/pricing-models/pricing-modal.avif
Normal file
|
After Width: | Height: | Size: 48 KiB |
BIN
apps/website/public/images/pricing-models/pricingbg-mobile.avif
Normal file
|
After Width: | Height: | Size: 88 KiB |
BIN
apps/website/public/images/pricing/pricing-mob.avif
Normal file
|
After Width: | Height: | Size: 135 KiB |
|
Before Width: | Height: | Size: 2.1 MiB |
|
Before Width: | Height: | Size: 542 KiB |
BIN
apps/website/public/images/pricing/pricing.avif
Normal file
|
After Width: | Height: | Size: 213 KiB |
|
Before Width: | Height: | Size: 842 KiB |
19
bun.lock
@@ -1,6 +1,5 @@
|
||||
{
|
||||
"lockfileVersion": 1,
|
||||
"configVersion": 1,
|
||||
"workspaces": {
|
||||
"": {
|
||||
"name": "autumn",
|
||||
@@ -5649,6 +5648,8 @@
|
||||
|
||||
"@autumn/server/@types/node": ["@types/node@25.6.0", "", { "dependencies": { "undici-types": "~7.19.0" } }, "sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview": ["@typescript/native-preview@7.0.0-dev.20260427.1", "", { "optionalDependencies": { "@typescript/native-preview-darwin-arm64": "7.0.0-dev.20260427.1", "@typescript/native-preview-darwin-x64": "7.0.0-dev.20260427.1", "@typescript/native-preview-linux-arm": "7.0.0-dev.20260427.1", "@typescript/native-preview-linux-arm64": "7.0.0-dev.20260427.1", "@typescript/native-preview-linux-x64": "7.0.0-dev.20260427.1", "@typescript/native-preview-win32-arm64": "7.0.0-dev.20260427.1", "@typescript/native-preview-win32-x64": "7.0.0-dev.20260427.1" }, "bin": { "tsgo": "bin/tsgo.js" } }, "sha512-g6L7hed1Y2OGwAzZ+vXoGSvtJUdWUtTqtsn/16+UjYbu3+6pol0cggdWj26SFxI41R+jLfnT2+JGtoXRBdH+RQ=="],
|
||||
|
||||
"@autumn/server/autumn-js": ["autumn-js@0.1.85", "", { "dependencies": { "query-string": "^9.2.2", "rou3": "^0.6.1", "swr": "^2.3.3", "zod": "^4.0.0" }, "peerDependencies": { "better-auth": "^1.3.17", "better-call": "^1.0.12", "convex": "^1.25.4" }, "optionalPeers": ["better-auth", "better-call", "convex"] }, "sha512-PDud/t8z5bDJcD7ptyHzTaoJ0A8zkxvQ4TYcJ48RtgKDdOkVY36D1T6udVLwLDnWw4J5KXwJgEuGxHdd+cuABw=="],
|
||||
|
||||
"@autumn/server/ink": ["ink@6.8.0", "", { "dependencies": { "@alcalzone/ansi-tokenize": "^0.2.4", "ansi-escapes": "^7.3.0", "ansi-styles": "^6.2.1", "auto-bind": "^5.0.1", "chalk": "^5.6.0", "cli-boxes": "^3.0.0", "cli-cursor": "^4.0.0", "cli-truncate": "^5.1.1", "code-excerpt": "^4.0.0", "es-toolkit": "^1.39.10", "indent-string": "^5.0.0", "is-in-ci": "^2.0.0", "patch-console": "^2.0.0", "react-reconciler": "^0.33.0", "scheduler": "^0.27.0", "signal-exit": "^3.0.7", "slice-ansi": "^8.0.0", "stack-utils": "^2.0.6", "string-width": "^8.1.1", "terminal-size": "^4.0.1", "type-fest": "^5.4.1", "widest-line": "^6.0.0", "wrap-ansi": "^9.0.0", "ws": "^8.18.0", "yoga-layout": "~3.2.1" }, "peerDependencies": { "@types/react": ">=19.0.0", "react": ">=19.0.0", "react-devtools-core": ">=6.1.2" }, "optionalPeers": ["@types/react", "react-devtools-core"] }, "sha512-sbl1RdLOgkO9isK42WCZlJCFN9hb++sX9dsklOvfd1YQ3bQ2AiFu12Q6tFlr0HvEUvzraJntQCCpfEoUe9DSzA=="],
|
||||
@@ -6467,6 +6468,8 @@
|
||||
|
||||
"@useautumn/sdk/typescript": ["typescript@5.8.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-p1diW6TqL9L07nNxvRMM7hMMw4c5XOo/1ibL4aAIGmSAt9slTE1Xgw5KWuof2uTOvCg9BY7ZRi+GaF+7sfgPeQ=="],
|
||||
|
||||
"@useautumn/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"@vercel/sdk/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"@vue/compiler-core/entities": ["entities@7.0.1", "", {}, "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA=="],
|
||||
@@ -7287,6 +7290,20 @@
|
||||
|
||||
"@autumn/server/@types/node/undici-types": ["undici-types@7.19.2", "", {}, "sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-arm64": ["@typescript/native-preview-darwin-arm64@7.0.0-dev.20260427.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-8zxaaEgIpHSadCoCAvUsp0C6WDH0dUXix7Mm7IBjh+EhSxI2clhXwPZTqgtDqbowXHeE82BG5mBbQx+CXDwGOQ=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-darwin-x64": ["@typescript/native-preview-darwin-x64@7.0.0-dev.20260427.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-6MjekGfajPtny/bBoBYJ+8dTOlgw6nhSSgJ3Us4R/4L8R90ll803Krz+iz907r1SnYeK5eWubDMV/p1ryLNXkQ=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm": ["@typescript/native-preview-linux-arm@7.0.0-dev.20260427.1", "", { "os": "linux", "cpu": "arm" }, "sha512-3bhv/NxU9FHIN3MSmoplIAkIHF62mlF9l5XooAFawwj8yscvPZih/m5fkYIiP5qGri3828XwGyT1Cksaft6FWQ=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-arm64": ["@typescript/native-preview-linux-arm64@7.0.0-dev.20260427.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-a1yG/vrLaN3dORvaMuNqXz5jcTaTEPBfhmq77vzqRn8As7EdqxtizPosfxB9K1s7PEB8NeGQKqHEQroPUCsPFg=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-linux-x64": ["@typescript/native-preview-linux-x64@7.0.0-dev.20260427.1", "", { "os": "linux", "cpu": "x64" }, "sha512-lqaA9oF9ZSw1jn87+Ncxo0Sf0d65eVXMjAD0z44ne7QKFRgWd+QpvK4AXAG4lxnFR+XdndWlVm6O1/tdvcG7xQ=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-arm64": ["@typescript/native-preview-win32-arm64@7.0.0-dev.20260427.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-ZGXRDC0WPVK/Ky2fZRhy2EcNmdHg22biVYWcWgOUK5tCbJd/KJs3VXk758gn0UbFHEQAR5d7dsvDucCCjZkWpA=="],
|
||||
|
||||
"@autumn/server/@typescript/native-preview/@typescript/native-preview-win32-x64": ["@typescript/native-preview-win32-x64@7.0.0-dev.20260427.1", "", { "os": "win32", "cpu": "x64" }, "sha512-Ut4Hncq1IuSeNIfcPs1s719j8H3ZA+ogsJ53W3s/Wy1UF5BIhu5Hkspdc7TzGgJgYqGJKo/+pr4vsRnbBPdWgQ=="],
|
||||
|
||||
"@autumn/server/autumn-js/zod": ["zod@4.3.6", "", {}, "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg=="],
|
||||
|
||||
"@autumn/server/ink/@alcalzone/ansi-tokenize": ["@alcalzone/ansi-tokenize@0.2.5", "", { "dependencies": { "ansi-styles": "^6.2.1", "is-fullwidth-code-point": "^5.0.0" } }, "sha512-3NX/MpTdroi0aKz134A6RC2Gb2iXVECN4QaAXnvCIxxIm3C3AVB1mkUe8NaaiyvOpDfsrqWhYtj+Q6a62RrTsw=="],
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import "dotenv/config";
|
||||
|
||||
import { ac, ALL_SCOPES, invitation, roles, schemas } from "@autumn/shared";
|
||||
import type { AccessControl } from "better-auth/plugins/access";
|
||||
import { oauthProvider } from "@better-auth/oauth-provider";
|
||||
import {
|
||||
type BetterAuthOptions,
|
||||
@@ -165,7 +166,7 @@ const options = {
|
||||
}),
|
||||
|
||||
organization({
|
||||
ac,
|
||||
ac: ac as AccessControl,
|
||||
roles,
|
||||
creatorRole: "owner",
|
||||
async sendInvitationEmail(data: {
|
||||
|
||||