What Others Say (#8)

This commit is contained in:
Nazareno Oviedo
2022-04-04 12:28:11 -03:00
committed by GitHub
parent c9278a1075
commit c5efb3d192
12 changed files with 523 additions and 53 deletions
+26 -1
View File
@@ -65,6 +65,31 @@ const ArrowLink = ({
)
}
const ArrowGreater = ({
className,
fill,
inverted
}: {
className?: string
fill?: string
inverted?: boolean
}) => {
return (
<svg
className={className}
viewBox="0 0 14 9"
fill="none"
style={{ transform: inverted ? 'rotate(180deg)' : 'rotate(0deg)' }}
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M0 8.5v-8l14 4.01216L0 8.5Z"
fill={fill || 'var(--color-white)'}
/>
</svg>
)
}
const ArrowSlider = ({
className,
fill,
@@ -98,4 +123,4 @@ const ArrowSlider = ({
)
}
export { Arrow, ArrowDotted, ArrowLink, ArrowSlider }
export { Arrow, ArrowDotted, ArrowGreater, ArrowLink, ArrowSlider }
@@ -0,0 +1,137 @@
import 'keen-slider/keen-slider.min.css'
import clsx from 'clsx'
import { useKeenSlider } from 'keen-slider/react'
import { useRealViewport } from 'next-real-viewport'
import { useEffect, useRef } from 'react'
import { ArrowGreater } from '~/components/icons/arrow'
import { Container } from '~/components/layout/container'
import Section from '~/components/layout/section'
import Heading from '~/components/primitives/heading'
import { CUSTOM_EASE, DURATION, gsap, Observer } from '~/lib/gsap'
import Item from './item'
import { testimonials } from './testimonials'
import s from './what-others-say.module.scss'
const WhatOthersSay = () => {
const carouselRef = useRef<HTMLDivElement>(null)
const { vw } = useRealViewport()
const [ref] = useKeenSlider<HTMLDivElement>({
// loop: true,
mode: 'free-snap',
slides: {
perView: 1.2,
spacing: 12
}
})
useEffect(() => {
if (!vw) return
const images = carouselRef.current?.querySelectorAll(
'.carousel__item'
) as NodeListOf<HTMLDivElement>
const radius = 44 * vw
const progress = {
value: 0
}
Observer.create({
target: carouselRef.current,
type: 'wheel,pointer',
onPress: (_self) => {
if (!carouselRef.current) return
carouselRef.current.style.cursor = 'grabbing'
},
onRelease: (_self) => {
if (!carouselRef.current) return
carouselRef.current.style.cursor = 'grab'
},
onChange: (self) => {
gsap.killTweensOf(progress)
const p =
self.event.type === 'wheel'
? self.deltaY * -0.0005
: self.deltaX * 0.0025
gsap.to(progress, {
duration: DURATION * 2,
ease: CUSTOM_EASE,
value: `+=${p}`
})
}
})
const animate = () => {
images.forEach((image, index) => {
const theta = index / images.length - progress.value
const x = -Math.sin(theta * Math.PI * 2) * radius
const y = Math.cos(theta * Math.PI * 2) * radius
image.style.transform = `translate3d(${x}px, 0px, ${y}px) rotateY(${
360 * -theta
}deg)`
})
}
gsap.ticker.add(animate)
}, [vw])
return (
<Section className={s['section']}>
<Container>
<div className={s['header']}>
<span>34°35'59″S 58°22'</span>
<Heading as="h2" variant="md" centered>
What <br className="hide-on-mobile" /> others say
</Heading>
<span>
<ArrowGreater className={s['arrow']} />
{testimonials.length} testimonials
<ArrowGreater className={s['arrow']} inverted />
</span>
</div>
<div className={s['carousel']} ref={carouselRef}>
<div className={s['image']}>
<img src="/images/head.png" alt="" />
</div>
{testimonials.map((testimonial, index) => {
return (
<Item
className="carousel__item"
image={testimonial.image}
key={index}
logo={testimonial.logo}
name={testimonial.name}
position={testimonial.position}
>
{testimonial.text}
</Item>
)
})}
</div>
</Container>
<div ref={ref} className={clsx('keen-slider', s['slider'])}>
<div className={s['image']}>
<img src="/images/head.png" alt="" />
</div>
{testimonials.map((testimonial, index) => {
return (
<Item
className="keen-slider__slide"
image={testimonial.image}
key={index}
logo={testimonial.logo}
name={testimonial.name}
position={testimonial.position}
>
{testimonial.text}
</Item>
)
})}
</div>
</Section>
)
}
export default WhatOthersSay
@@ -0,0 +1,43 @@
import clsx from 'clsx'
import * as React from 'react'
import s from './item.module.scss'
type ItemProps = {
children: React.ReactNode
className?: string
image: string
logo: React.ReactNode
name: string
position: string
}
const Item = ({
children,
className,
image,
logo,
name,
position
}: ItemProps) => {
return (
<div className={clsx(s['item'], className)}>
<div className={s['item__header']}>
<div>
<img src={image} alt="" />
<div>
<p>
{name}
<br />
<span>{position}</span>
</p>
</div>
</div>
<div className={s['item__header__logo']}>{logo}</div>
</div>
<p>{children}</p>
</div>
)
}
export default Item
@@ -0,0 +1,75 @@
@import '~/css/helpers';
.item {
position: absolute;
z-index: 5;
top: 50%;
left: 50%;
width: tovw(420px);
margin: tovw(-210px) 0 0 tovw(-210px);
padding: tovw(24px, 'default', 22px);
transform: translate3d(0, 0, tovw(-10px));
transform-origin: 50% 50%;
color: var(--color-white);
border: tovw(1.5px, 'default', 1px) solid var(--color-grey-light);
border-radius: tovw(8px, 'default', 8px);
background-color: rgb(0 0 0 / 0.9);
@media screen and (max-width: 800px) {
position: relative;
top: initial;
left: initial;
width: max-content;
height: max-content !important;
min-height: auto !important;
margin: 0;
transform: none;
}
&__header {
display: flex;
align-items: center;
justify-content: space-between;
margin-bottom: tovw(24px, 'default', 20px);
> div {
display: flex;
align-items: center;
}
p {
font-family: var(--font-dm-mono);
font-size: tovw(12px, 'default', 12px);
line-height: 1.1;
margin: 0;
letter-spacing: tovw(-0.6px, 'default', -0.6px);
text-transform: uppercase;
span {
letter-spacing: tovw(-1px, 'default', -1px);
color: #a0a0a0;
}
}
&__logo {
width: tovw(48px, 'default', 48px);
height: tovw(48px, 'default', 48px);
}
}
img {
overflow: hidden;
width: tovw(48px, 'default', 48px);
height: tovw(48px, 'default', 48px);
margin-right: tovw(16px, 'default', 16px);
border-radius: 100%;
}
p {
font-size: tovw(16px, 'default', 16px);
margin: 0;
@media screen and (max-width: 800px) {
line-height: 1.6;
}
}
}
@@ -0,0 +1,88 @@
import Square from '~/components/logos/square'
export const testimonials = [
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
},
{
image: '/images/testimonial-01.jpg',
name: 'JAYDON BATOR',
position: 'CO-Founder',
logo: <Square />,
text: 'We were looking to add in-app notifications to Quaestor when we came across Knock. Their well-documented APIs and composable React components made it super simple for us to get notifications up and running in minutes.'
}
]
@@ -0,0 +1,106 @@
@import '~/css/helpers';
.section {
position: relative;
margin-top: tovw(24px, 'default', 16px);
padding: tovw(104px, 'default', 72px) 0;
}
.header {
display: flex;
align-items: center;
justify-content: space-between;
width: 100%;
max-width: tovw(1076px, 'default', 700px);
margin: 0 auto;
text-transform: capitalize;
@media screen and (max-width: 800px) {
flex-direction: column;
margin-bottom: tovw(24px, 'mobile');
gap: tovw(24px, 'mobile');
}
span {
font-family: var(--font-dm-mono);
font-size: tovw(18px, 'default', 16px);
letter-spacing: tovw(-0.8px, 'default', -0.8px);
text-transform: uppercase;
@media screen and (max-width: 800px) {
&:first-of-type {
display: none;
}
}
}
.arrow {
display: inline-block;
width: tovw(14px, 'default', 14px);
height: tovw(8px, 'default', 8px);
margin: 0 tovw(12px, 'default', 12px);
vertical-align: middle;
&:first-of-type {
margin-left: 0;
}
&:last-of-type {
margin-right: 0;
}
}
}
.carousel {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100vh;
cursor: grab;
user-select: none;
transform: rotateX(-15deg) scale(0.85) translateY(0%);
transform-style: preserve-3d;
perspective: tovw(3000px);
@media screen and (max-width: 800px) {
display: none;
}
}
.image {
position: relative;
z-index: -1;
width: tovw(531px, 'default', 336px);
height: tovw(817px, 'default', 518px);
user-select: none;
transform: scale(1.18);
pointer-events: none;
}
.slider {
height: tovw(636px, 'mobile');
padding-left: tovw(16px, 'mobile');
align-items: center;
@media screen and (min-width: 800px) {
display: none !important;
}
.image {
position: absolute;
z-index: -1;
width: 85%;
height: auto;
user-select: none;
transform: none;
left: tovw(10px, 'mobile');
pointer-events: none;
img {
width: 100%;
opacity: 0.7;
height: auto;
margin: 0 auto;
}
}
}
Binary file not shown.
+3
View File
@@ -1,6 +1,7 @@
import gsap from 'gsap'
import { CSSRulePlugin } from 'gsap/dist/CSSRulePlugin'
import { CustomEase } from 'gsap/dist/CustomEase'
import { Observer } from 'gsap/dist/Observer'
import { ScrollSmoother } from 'gsap/dist/ScrollSmoother'
import { ScrollTrigger } from 'gsap/dist/ScrollTrigger'
import { SplitText } from 'gsap/dist/SplitText'
@@ -8,6 +9,7 @@ import { SplitText } from 'gsap/dist/SplitText'
gsap.registerPlugin(
CSSRulePlugin,
CustomEase,
Observer,
ScrollSmoother,
ScrollTrigger,
SplitText
@@ -161,6 +163,7 @@ export {
CUSTOM_EASE,
DURATION,
gsap,
Observer,
ScrollSmoother,
ScrollTrigger,
SplitText
+2
View File
@@ -4,6 +4,7 @@ import Benefits from '~/components/sections/homepage/benefits'
import Hero from '~/components/sections/homepage/hero'
import SeenIn from '~/components/sections/homepage/seen-in'
import TrustedBy from '~/components/sections/homepage/trusted-by'
import WhatOthersSay from '~/components/sections/homepage/what-others-say'
import { Page } from './_app'
@@ -15,6 +16,7 @@ const HomePage: Page = () => {
<TrustedBy />
<Benefits />
<SeenIn />
<WhatOthersSay />
</PageLayout>
)
}