animate on scroll
This commit is contained in:
@@ -1,8 +1,16 @@
|
||||
import { useRef } from 'react'
|
||||
import Image from 'next/image'
|
||||
import Link from 'next/link'
|
||||
import { MotionValue, motion, useMotionValue, useSpring, useTransform } from 'framer-motion'
|
||||
import {
|
||||
MotionValue,
|
||||
motion,
|
||||
useInView,
|
||||
useMotionValue,
|
||||
useSpring,
|
||||
useTransform,
|
||||
} from 'framer-motion'
|
||||
import { useBreakpoint } from 'common'
|
||||
import { DEFAULT_TRANSITION } from '~/lib/animations'
|
||||
|
||||
function MagnifiedProducts() {
|
||||
let mouseX = useMotionValue(Infinity)
|
||||
@@ -13,15 +21,16 @@ function MagnifiedProducts() {
|
||||
onMouseLeave={() => mouseX.set(Infinity)}
|
||||
className="mx-auto w-full max-w-md grid grid-cols-3 md:flex items-center justify-center gap-y-8 md:gap-4 px-4"
|
||||
>
|
||||
{Object.entries(products).map(([key, product]) => (
|
||||
<AppIcon mouseX={mouseX} product={product} key={key} />
|
||||
{Object.entries(products).map(([key, product], i) => (
|
||||
<AppIcon mouseX={mouseX} product={product} index={i} key={key} />
|
||||
))}
|
||||
</motion.div>
|
||||
)
|
||||
}
|
||||
|
||||
function AppIcon({ mouseX, product }: { mouseX: MotionValue; product: any }) {
|
||||
function AppIcon({ mouseX, product, index }: { mouseX: MotionValue; product: any; index: number }) {
|
||||
let ref = useRef<HTMLDivElement>(null)
|
||||
const isInView = useInView(ref, { margin: '20%', once: true })
|
||||
const isMobile = useBreakpoint(768)
|
||||
|
||||
let distance = useTransform(mouseX, (val) => {
|
||||
@@ -30,18 +39,36 @@ function AppIcon({ mouseX, product }: { mouseX: MotionValue; product: any }) {
|
||||
return val - bounds.x - bounds.width / 2
|
||||
})
|
||||
|
||||
let widthSync = useTransform(distance, [-150, 0, 150], [75, 130, 75])
|
||||
let widthSync = useTransform(distance, [-150, 0, 150], [75, 110, 75])
|
||||
let width = useSpring(widthSync, { mass: 0.1, stiffness: 150, damping: 12 })
|
||||
|
||||
const initial = { x: 0 }
|
||||
const xDelta = 120
|
||||
const animate = {
|
||||
x: index > 2 ? (index - 2) * xDelta - xDelta / 2 : index * -xDelta - xDelta / 2,
|
||||
transition: { ...DEFAULT_TRANSITION, delay: 0.2 },
|
||||
}
|
||||
|
||||
return (
|
||||
<motion.div ref={ref} className="relative mx-auto w-[150px] bg-transparent group">
|
||||
<motion.div
|
||||
ref={ref}
|
||||
className="relative md:absolute mx-auto w-[150px] bg-transparent group"
|
||||
initial={initial}
|
||||
animate={!isMobile && isInView ? animate : initial}
|
||||
>
|
||||
<Link href={product.url}>
|
||||
<a className="flex w-full flex-col items-center text-center">
|
||||
<motion.div
|
||||
style={isMobile ? (undefined as any) : { width }}
|
||||
className="relative w-[50px] aspect-square will-change-transform"
|
||||
>
|
||||
<Image src={product.icon} layout="fill" objectFit="contain" />
|
||||
<Image
|
||||
src={product.icon}
|
||||
priority
|
||||
layout="fill"
|
||||
objectFit="contain"
|
||||
lazyBoundary="100px"
|
||||
/>
|
||||
</motion.div>
|
||||
<div className="text-brand-900 flex justify-center relative opacity-70 md:absolute md:bottom-0 md:opacity-0 group-hover:opacity-100 transition-opacity md:translate-y-8 md:-left-20 md:md:-right-20 font-mono uppercase text-center text-xs mt-2">
|
||||
<span>{product.name}</span>
|
||||
|
||||
@@ -1,70 +1,98 @@
|
||||
import Link from 'next/link'
|
||||
import React, { ReactNode } from 'react'
|
||||
import React, { useRef, ReactNode } from 'react'
|
||||
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
|
||||
import { ReactMarkdown } from 'react-markdown/lib/react-markdown'
|
||||
import { Button } from 'ui'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
|
||||
interface Feature {
|
||||
icon: string
|
||||
title: string
|
||||
text: string
|
||||
}
|
||||
interface Props {
|
||||
title: string | ReactNode
|
||||
paragraph: string
|
||||
features: {
|
||||
icon: string
|
||||
title: string
|
||||
text: string
|
||||
}[]
|
||||
features: Feature[]
|
||||
}
|
||||
|
||||
const FeaturesSection = ({ title, paragraph, features }: Props) => {
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { margin: '20%', once: true })
|
||||
|
||||
return (
|
||||
<SectionContainer>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 xl:gap-10 justify-between">
|
||||
<div className="col-span-full lg:col-span-4 gap-2 flex flex-col">
|
||||
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[280px] sm:max-w-xs xl:max-w-[360px] tracking-[-1px]">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-scale-900 mb-4">{paragraph}</p>
|
||||
<Link href="">
|
||||
<a className="">
|
||||
<Button type="default" size="small">
|
||||
Explore documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
<LazyMotion features={domAnimation}>
|
||||
<SectionContainer>
|
||||
<div className="grid grid-cols-1 lg:grid-cols-12 gap-8 xl:gap-10 justify-between">
|
||||
<div className="col-span-full lg:col-span-4 gap-2 flex flex-col">
|
||||
<h2 className="text-2xl sm:text-3xl xl:text-4xl max-w-[280px] sm:max-w-xs xl:max-w-[360px] tracking-[-1px]">
|
||||
{title}
|
||||
</h2>
|
||||
<p className="text-scale-900 mb-4">{paragraph}</p>
|
||||
<Link href="">
|
||||
<a className="">
|
||||
<Button type="default" size="small">
|
||||
Explore documentation
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
</div>
|
||||
<div
|
||||
ref={ref}
|
||||
className="col-span-full lg:col-start-6 lg:col-span-7 space-y-10 lg:space-y-0 flex flex-col lg:grid lg:grid-cols-2 lg:gap-16"
|
||||
>
|
||||
{features.map((feature: Feature, i: number) => (
|
||||
<Feature feature={feature} index={i} isInView={isInView} key={feature.title} />
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="col-span-full lg:col-start-6 lg:col-span-7 space-y-10 lg:space-y-0 flex flex-col lg:grid lg:grid-cols-2 lg:gap-16">
|
||||
{features.map((feature: { icon: string; title: string; text: string }, i: number) => {
|
||||
return (
|
||||
<div className="h-full flex items-start space-x-3 w-full" key={i}>
|
||||
<div className="flex items-center">
|
||||
<div className="relative w-full h-6 flex items-center mx-auto">
|
||||
<svg
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d={feature.icon}
|
||||
stroke="var(--colors-scale12)"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinejoin="bevel"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
<div className="text-sm lg:text-base">
|
||||
<h2 className="text-base">{feature.title}</h2>
|
||||
<ReactMarkdown className="prose pt-1 text-sm text-scale-900">
|
||||
{feature.text}
|
||||
</ReactMarkdown>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</SectionContainer>
|
||||
</LazyMotion>
|
||||
)
|
||||
}
|
||||
|
||||
const Feature = ({
|
||||
feature,
|
||||
index,
|
||||
isInView,
|
||||
}: {
|
||||
feature: Feature
|
||||
index: number
|
||||
isInView: boolean
|
||||
}) => {
|
||||
const initial = INITIAL_BOTTOM
|
||||
const animate = getAnimation({ delay: index * 0.1 })
|
||||
|
||||
return (
|
||||
<m.div
|
||||
className="h-full flex items-start space-x-3 w-full"
|
||||
initial={initial}
|
||||
animate={isInView ? animate : initial}
|
||||
>
|
||||
<div className="flex items-center">
|
||||
<div className="relative w-full h-6 flex items-center mx-auto">
|
||||
<svg
|
||||
width="17"
|
||||
height="17"
|
||||
viewBox="0 0 17 17"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path
|
||||
d={feature.icon}
|
||||
stroke="var(--colors-scale12)"
|
||||
strokeMiterlimit="10"
|
||||
strokeLinejoin="bevel"
|
||||
/>
|
||||
</svg>
|
||||
</div>
|
||||
</div>
|
||||
</SectionContainer>
|
||||
<div className="text-sm lg:text-base">
|
||||
<h2 className="text-base">{feature.title}</h2>
|
||||
<ReactMarkdown className="prose pt-1 text-sm text-scale-900">{feature.text}</ReactMarkdown>
|
||||
</div>
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import React from 'react'
|
||||
import SectionContainer from '../Layouts/SectionContainer'
|
||||
import InteractiveShimmerCard from '../InteractiveShimmerCard'
|
||||
import React, { useRef } from 'react'
|
||||
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import InteractiveShimmerCard from '~/components/InteractiveShimmerCard'
|
||||
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
|
||||
|
||||
interface Highlight {
|
||||
image: string
|
||||
@@ -11,19 +13,35 @@ interface Highlight {
|
||||
const HighlightCards = ({ highlights }: { highlights: Highlight[] }) => {
|
||||
return (
|
||||
<SectionContainer>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{highlights.map((highlight) => (
|
||||
<InteractiveShimmerCard innerClassName="flex flex-col !bg-scale-200">
|
||||
<div className="w-full aspect-square mb-4"></div>
|
||||
<div className="p-8">
|
||||
<h3 className="text-lg text-scale-1200 font-medium mb-2">{highlight.title}</h3>
|
||||
<p className="text-scale-900">{highlight.paragraph}</p>
|
||||
</div>
|
||||
</InteractiveShimmerCard>
|
||||
))}
|
||||
</div>
|
||||
<LazyMotion features={domAnimation}>
|
||||
<div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-4">
|
||||
{highlights.map((highlight, i) => (
|
||||
<HighlightCard highlight={highlight} index={i} key={highlight.title} />
|
||||
))}
|
||||
</div>
|
||||
</LazyMotion>
|
||||
</SectionContainer>
|
||||
)
|
||||
}
|
||||
|
||||
const HighlightCard = ({ highlight, index }: { highlight: Highlight; index: number }) => {
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { once: true })
|
||||
|
||||
const initial = INITIAL_BOTTOM
|
||||
const animate = getAnimation({ delay: 0.4 + index * 0.1 })
|
||||
|
||||
return (
|
||||
<m.div ref={ref} initial={initial} animate={isInView ? animate : initial}>
|
||||
<InteractiveShimmerCard innerClassName="flex flex-col !bg-scale-200">
|
||||
<div className="w-full aspect-square mb-4"></div>
|
||||
<div className="p-8">
|
||||
<h3 className="text-lg text-scale-1200 font-medium mb-2">{highlight.title}</h3>
|
||||
<p className="text-scale-900">{highlight.paragraph}</p>
|
||||
</div>
|
||||
</InteractiveShimmerCard>
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
export default HighlightCards
|
||||
|
||||
@@ -2,6 +2,7 @@ import { Button, IconBookOpen, IconPlayCircle } from 'ui'
|
||||
import Link from 'next/link'
|
||||
import ProductIcon from '../ProductIcon'
|
||||
import Image from 'next/image'
|
||||
import styles from '~/styles/animations.module.css'
|
||||
|
||||
interface Types {
|
||||
h1: string | React.ReactNode
|
||||
@@ -50,11 +51,11 @@ const ProductHeaderCentered = (props: Types) => (
|
||||
)}
|
||||
</div>
|
||||
) : null}
|
||||
</div>
|
||||
<div className={[styles['appear-from-bottom']].join(' ')}>
|
||||
<h1 className="h1 text-3xl md:text-4xl tracking-[-1px]" key={`h1`}>
|
||||
{props.h1}
|
||||
</h1>
|
||||
</div>
|
||||
<div>
|
||||
<p className="p !text-scale-1000">{props.subheader}</p>
|
||||
</div>
|
||||
<div className="flex flex-row md:flex-row pt-8 md:items-center">
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import React, { useState, useEffect, ReactNode } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { motion, useAnimation } from 'framer-motion'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import Link from 'next/link'
|
||||
import { Button, IconArrowUpRight } from 'ui'
|
||||
import CodeBlock from '../CodeBlock/CodeBlock'
|
||||
import CodeBlock from '~/components/CodeBlock/CodeBlock'
|
||||
import { Swiper, SwiperSlide } from 'swiper/react'
|
||||
|
||||
interface TabProps {
|
||||
@@ -18,18 +18,22 @@ const Tab = ({ isActive, label, onClick, progress, intervalDuration }: TabProps)
|
||||
<button
|
||||
onClick={onClick}
|
||||
className={`text-left text-lg flex flex-col group gap-1 transition-all ${
|
||||
// isActive ? 'grow-[1]' : 'grow-[0.5]'
|
||||
isActive ? 'flex-[2] text-scale-1200' : 'flex-[1] text-scale-1100'
|
||||
}`}
|
||||
aria-selected={isActive}
|
||||
role="tab"
|
||||
>
|
||||
<div className="w-full h-[2px] bg-scale-700 group-hover:bg-scale-800 rounded-full overflow-hidden">
|
||||
<motion.div
|
||||
className="bg-brand-900 h-full"
|
||||
style={{ width: `${isActive ? progress : 0}%` }}
|
||||
transition={{ duration: intervalDuration }}
|
||||
/>
|
||||
<div className="relative w-full h-[2px] bg-scale-700 group-hover:bg-scale-800 rounded-full overflow-hidden">
|
||||
{isActive && (
|
||||
<motion.div
|
||||
className={[
|
||||
'absolute inset-0 w-full right-full bg-brand-900 h-full transition-opacity',
|
||||
progress! > 99.7 ? 'opacity-0' : 'opacity-100',
|
||||
].join(' ')}
|
||||
style={{ x: `${progress! - 100}%` }}
|
||||
transition={{ duration: intervalDuration }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
{label}
|
||||
</button>
|
||||
@@ -98,7 +102,7 @@ const TimedTabsSection = ({
|
||||
}, [activeTab, controls])
|
||||
|
||||
useEffect(() => {
|
||||
if (progress >= 100) {
|
||||
if (progress >= 100.9) {
|
||||
setActiveTab((prevActiveTab) => (prevActiveTab === tabs.length - 1 ? 0 : prevActiveTab + 1))
|
||||
}
|
||||
}, [progress])
|
||||
|
||||
@@ -1,8 +1,10 @@
|
||||
import Link from 'next/link'
|
||||
import React, { ReactNode } from 'react'
|
||||
import React, { ReactNode, useRef } from 'react'
|
||||
import { Button } from 'ui'
|
||||
import SectionContainer from '~/components/Layouts/SectionContainer'
|
||||
import InteractiveShimmerCard from '../InteractiveShimmerCard'
|
||||
import { LazyMotion, domAnimation, m, useInView } from 'framer-motion'
|
||||
import { INITIAL_BOTTOM, getAnimation } from '~/lib/animations'
|
||||
|
||||
interface UseCase {
|
||||
img?: string
|
||||
@@ -23,52 +25,77 @@ interface Props {
|
||||
}
|
||||
|
||||
const UseCasesSection = ({ title, paragraph, useCases }: Props) => {
|
||||
const ref = useRef(null)
|
||||
const isInView = useInView(ref, { margin: '20%', once: true })
|
||||
|
||||
return (
|
||||
<SectionContainer className="flex flex-col gap-12">
|
||||
<div className="flex flex-col text-center gap-4 items-center justify-center">
|
||||
<h2 className="heading-gradient text-2xl sm:text-3xl xl:text-4xl">{title}</h2>
|
||||
<p className="mx-auto text-scale-900 lg:w-1/2">{paragraph}</p>
|
||||
</div>
|
||||
<div className="mx-auto w-full max-w-5xl grid gap-4 grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3">
|
||||
{useCases.map((example) => {
|
||||
return (
|
||||
<InteractiveShimmerCard innerClassName="p-4 md:p-8 h-full !bg-scale-200">
|
||||
<div className="h-full flex flex-col gap-4 items-start justify-between">
|
||||
<div className="prose">
|
||||
<div className="flex items-center gap-1">
|
||||
<svg
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 21 21"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d={example.icon} fill="var(--colors-scale11)" />
|
||||
</svg>
|
||||
<h4 className="text-sm md:text-lg m-0">{example.title}</h4>
|
||||
</div>
|
||||
<p className="text-sm text-scale-900 mt-2">{example.description}</p>
|
||||
</div>
|
||||
{example.cta &&
|
||||
(example.cta.isDisabled ? (
|
||||
<Button size="tiny" type="default" disabled className="justify-end">
|
||||
{example.cta.label ?? 'View example'}
|
||||
</Button>
|
||||
) : (
|
||||
<Link href={example.cta.link}>
|
||||
<a>
|
||||
<Button size="tiny" type="default">
|
||||
{example.cta.label ?? 'View example'}
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</InteractiveShimmerCard>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
<LazyMotion features={domAnimation}>
|
||||
<SectionContainer className="flex flex-col gap-12">
|
||||
<div className="flex flex-col text-center gap-4 items-center justify-center">
|
||||
<h2 className="heading-gradient text-2xl sm:text-3xl xl:text-4xl">{title}</h2>
|
||||
<p className="mx-auto text-scale-900 lg:w-1/2">{paragraph}</p>
|
||||
</div>
|
||||
<div
|
||||
ref={ref}
|
||||
className="mx-auto w-full max-w-5xl grid gap-4 grid-cols-2 sm:grid-cols-2 md:grid-cols-2 lg:grid-cols-3 xl:grid-cols-3"
|
||||
>
|
||||
{useCases.map((useCase, i) => {
|
||||
return <UseCase useCase={useCase} isInView={isInView} index={i} key={useCase.title} />
|
||||
})}
|
||||
</div>
|
||||
</SectionContainer>
|
||||
</LazyMotion>
|
||||
)
|
||||
}
|
||||
|
||||
const UseCase = ({
|
||||
useCase,
|
||||
index,
|
||||
isInView,
|
||||
}: {
|
||||
useCase: UseCase
|
||||
index: number
|
||||
isInView: boolean
|
||||
}) => {
|
||||
const initial = INITIAL_BOTTOM
|
||||
const animate = getAnimation({ delay: index * 0.1 })
|
||||
|
||||
return (
|
||||
<m.div initial={initial} animate={isInView ? animate : initial} className="flex h-full">
|
||||
<InteractiveShimmerCard innerClassName="p-4 md:p-8 h-full !bg-scale-200">
|
||||
<div className="h-full flex flex-col gap-4 items-start justify-between">
|
||||
<div className="prose">
|
||||
<div className="flex items-center gap-2">
|
||||
<svg
|
||||
width="21"
|
||||
height="21"
|
||||
viewBox="0 0 21 21"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<path d={useCase.icon} fillRule="evenodd" fill="var(--colors-scale11)" />
|
||||
</svg>
|
||||
<h4 className="text-sm md:text-lg m-0">{useCase.title}</h4>
|
||||
</div>
|
||||
<p className="text-sm text-scale-900 mt-2">{useCase.description}</p>
|
||||
</div>
|
||||
{useCase.cta &&
|
||||
(useCase.cta.isDisabled ? (
|
||||
<Button size="tiny" type="default" disabled className="justify-end">
|
||||
{useCase.cta.label ?? 'View example'}
|
||||
</Button>
|
||||
) : (
|
||||
<Link href={useCase.cta.link}>
|
||||
<a>
|
||||
<Button size="tiny" type="default">
|
||||
{useCase.cta.label ?? 'View example'}
|
||||
</Button>
|
||||
</a>
|
||||
</Link>
|
||||
))}
|
||||
</div>
|
||||
</InteractiveShimmerCard>
|
||||
</m.div>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -90,6 +90,10 @@ export default {
|
||||
},
|
||||
},
|
||||
{
|
||||
// <svg width="21" height="21" viewBox="0 0 21 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
// <path fill-rule="evenodd" clip-rule="evenodd" d="M18.2324 10.2283C18.2324 14.0943 14.6507 17.2283 10.2324 17.2283C8.74079 17.2283 7.3445 16.8711 6.14919 16.2491L2.23242 17.2283L3.57072 14.1056C2.72522 12.9956 2.23242 11.6623 2.23242 10.2283C2.23242 6.36228 5.81414 3.22827 10.2324 3.22827C14.6507 3.22827 18.2324 6.36228 18.2324 10.2283ZM7.23242 9.22827H5.23242V11.2283H7.23242V9.22827ZM15.2324 9.22827H13.2324V11.2283H15.2324V9.22827ZM9.23242 9.22827H11.2324V11.2283H9.23242V9.22827Z" fill="#F2F2F2"/>
|
||||
// </svg>
|
||||
|
||||
icon: 'M18 10C18 13.866 14.4183 17 10 17C8.50836 17 7.11208 16.6428 5.91677 16.0208L2 17L3.3383 13.8773C2.4928 12.7673 2 11.434 2 10C2 6.13401 5.58172 3 10 3C14.4183 3 18 6.13401 18 10ZM7 9H5V11H7V9ZM15 9H13V11H15V9ZM9 9H11V11H9V9Z',
|
||||
title: 'Chatbots',
|
||||
description: 'Enhance chatbot memory with content-based long-term retention.',
|
||||
|
||||
21
apps/www/lib/animations.ts
Normal file
21
apps/www/lib/animations.ts
Normal file
@@ -0,0 +1,21 @@
|
||||
export const DEFAULT_EASE = [0.24, 0.25, 0.05, 1]
|
||||
export const DEFAULT_DURATION = 0.4
|
||||
export const DEFAULT_DELAY = 0
|
||||
export const DEFAULT_TRANSITION = { ease: DEFAULT_EASE, duration: DEFAULT_DURATION }
|
||||
|
||||
interface AnimationProps {
|
||||
delay?: number
|
||||
duration?: number
|
||||
ease?: number
|
||||
}
|
||||
|
||||
export const INITIAL_BOTTOM = { opacity: 0, y: 20 }
|
||||
export const getAnimation = ({ delay, duration, ease }: AnimationProps) => ({
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
transition: {
|
||||
delay: delay ?? DEFAULT_DELAY,
|
||||
ease: ease ?? DEFAULT_EASE,
|
||||
duration: duration ?? DEFAULT_DURATION,
|
||||
},
|
||||
})
|
||||
12
apps/www/styles/animations.module.css
Normal file
12
apps/www/styles/animations.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.appear-from-bottom {
|
||||
opacity: 0;
|
||||
transform: translate3d(0, 10px, 0);
|
||||
animation: animateIn 1s cubic-bezier(0.25, 0.25, 0, 1) 0.1s both;
|
||||
}
|
||||
|
||||
@keyframes animateIn {
|
||||
to {
|
||||
opacity: 1;
|
||||
transform: translate3d(0, 0, 0);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user