From fae6c3caf00fc9aac2af5c8a7ac8f8603c4bf6a2 Mon Sep 17 00:00:00 2001 From: Gina Lee Date: Thu, 14 Nov 2024 02:42:22 +0800 Subject: [PATCH 1/4] Add contribute page specific component --- .../contribute/ContributeContent.jsx | 167 ++++++++++++++ .../contribute/ContributeContent.module.scss | 214 ++++++++++++++++++ 2 files changed, 381 insertions(+) create mode 100644 src/components/contribute/ContributeContent.jsx create mode 100644 src/components/contribute/ContributeContent.module.scss diff --git a/src/components/contribute/ContributeContent.jsx b/src/components/contribute/ContributeContent.jsx new file mode 100644 index 00000000..6e567ef8 --- /dev/null +++ b/src/components/contribute/ContributeContent.jsx @@ -0,0 +1,167 @@ +import { useEffect, useRef, useState } from "react"; +import * as Tabs from "@radix-ui/react-tabs"; +import gsap from "gsap"; +import { useGSAP } from "@gsap/react"; +import Image from "next/image"; +import { useTranslation } from "next-i18next"; + +import Button from "@/components/solutions/Button"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import { AnimatedText } from "@/components/shared/Text"; + +import styles from "./ContributeContent.module.scss"; + +const ContributeContent = ({ tabs }) => { + const container = useRef(null); + const [value, setValue] = useState("tab1"); + let tabsContent = useRef(null); + const { t } = useTranslation(); + + useGSAP( + () => { + if (tabsContent.current) { + const activeTab = tabsContent.current.find( + (tab) => tab.getAttribute("data-state") === "active", + ); + gsap.fromTo( + activeTab, + { + autoAlpha: 0, + y: 100, + }, + { + autoAlpha: 1, + y: 0, + overwrite: true, + }, + ); + } + }, + { scope: container, dependencies: [value] }, + ); + + useEffect(() => { + tabsContent.current = gsap.utils.toArray(".hero-tab-content"); + }, []); + + return ( + <> + + + setValue("tab1")} + > + {t("contribute.tabs.0.trigger")} + + setValue("tab2")} + > + {t("contribute.tabs.1.trigger")} + + + + +
+
+
+ + {tabs[0].callout.title} + + {tabs[0].callout.text} + +
+ +
+ {tabs[0].callout.title} +
+
+
+ +
+
+ {tabs[0].details.map((detail, i) => ( +
+ +
{detail.title}
+

{detail.text}

+
+
+ ))} +
+
+
+ + +
+
+
+ + {tabs[1].callout.title} + + {tabs[1].callout.text} + +
+ +
+ {tabs[1].callout.title} +
+
+
+ +
+
+ {tabs[1].details.map((detail, i) => ( +
+ +
{detail.title}
+

{detail.text}

+
+
+ ))} +
+
+
+
+ + ); +}; + +export default ContributeContent; diff --git a/src/components/contribute/ContributeContent.module.scss b/src/components/contribute/ContributeContent.module.scss new file mode 100644 index 00000000..c7f019a3 --- /dev/null +++ b/src/components/contribute/ContributeContent.module.scss @@ -0,0 +1,214 @@ +@import "~@/scss/solutions/_variables.scss"; + +.TabsRoot { + display: flex; + flex-direction: column; +} + +.TabsList { + flex-shrink: 0; + display: flex; + border-radius: 12px; + border: 4px solid var(--grey-500); + background-color: var(--grey-500); + max-width: 343px; + width: 100%; + margin: 0 auto; +} + +.TabsTrigger { + font-family: inherit; + background-color: var(--grey-500); + padding: 0 20px; + height: 45px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 16px; + line-height: 1; + color: var(--grey-250); + user-select: none; + font-weight: 700; + + &[data-state="active"] { + color: var(--grey-100); + background: var(--grey-300); + border-radius: 4px; + } + + &:focus { + position: relative; + } +} + +.TabsContent { + flex-grow: 1; + outline: none; + max-width: 1068px; + margin: 0 auto; +} + +.TabCallout { + padding-top: 64px; + + @include breakpoint(lg) { + padding-top: 150px; + } +} + +.CalloutContainer { + padding: 0 24px; + + @include breakpoint(md) { + padding: 0 40px; + } + + @include breakpoint(xl) { + padding: 0; + } +} + +.TextWrapper { + text-align: center; + + & > *:not(a) { + padding: 0; + margin: 0; + } + + h2 { + font-size: 32px; + font-weight: 700; + line-height: 1.11; + letter-spacing: -0.32px; + } + + p { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; + margin-top: 24px; + color: #a2a1b2; + } + + a { + width: fit-content; + margin: 24px auto 0; + } + + @include breakpoint(lg) { + text-align: left; + + h2 { + font-size: 56px; + line-height: 1; + letter-spacing: -1.28px; + } + + p { + font-size: 24px; + line-height: 1.15; + letter-spacing: -0.84px; + margin-top: 16px; + color: #a2a1b2; + } + + a { + margin-top: 48px; + margin-left: 0; + } + } +} + +.CalloutContainer { + display: grid; + grid-template-columns: 1fr; + gap: 40px; + align-items: center; + justify-content: center; + + @include breakpoint(lg) { + grid-template-columns: 0.85fr 1fr; + gap: 150px; + } +} + +.MediaWrapper { + aspect-ratio: 1 / 1; + position: relative; + width: 100%; + display: block; +} + +.TabsDetails { + padding: 64px 0 128px; + + @include breakpoint(lg) { + padding: 80px 0 128px; + } +} + +.DetailsContainer { + padding: 0 24px; + + display: grid; + grid-template-columns: 1fr; + grid-gap: 40px; + + @include breakpoint(md) { + padding: 0 40px; + } + + @include breakpoint(lg) { + grid-template-columns: repeat(3, 1fr); + grid-row-gap: 50px; + grid-column-gap: 80px; + } + + @include breakpoint(xl) { + padding: 0; + } +} + +.Detail { + text-align: center; + & > * { + padding: 0; + margin: 0; + } + + h5 { + color: #eaebf0; + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; + } + + p { + color: #6c6a81; + font-size: 18px; + font-style: normal; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; + } + + @include breakpoint(lg) { + text-align: left; + h5 { + color: #fff; + font-size: 20px; + line-height: 1.12; + letter-spacing: -0.48px; + } + + p { + font-size: 20px; + line-height: 1.12; + letter-spacing: -0.48px; + } + } +} From 74969b6432a1b6f34d3edfb84705c7963d3b4c98 Mon Sep 17 00:00:00 2001 From: Gina Lee Date: Thu, 14 Nov 2024 02:47:15 +0800 Subject: [PATCH 2/4] Add home page specific components --- src/components/home/CardsSlider.module.scss | 249 ++++++++++++ src/components/home/CardsSlider.tsx | 187 +++++++++ src/components/home/Companies.module.scss | 115 ++++++ src/components/home/Companies.tsx | 119 ++++++ src/components/home/Events.jsx | 190 +++++++++ src/components/home/Events.module.scss | 364 ++++++++++++++++++ src/components/home/Hero.module.scss | 115 ++++++ src/components/home/Hero.tsx | 83 ++++ src/components/home/JoinCommunity.jsx | 125 ++++++ src/components/home/JoinCommunity.module.scss | 145 +++++++ src/components/home/Solutions.module.scss | 109 ++++++ src/components/home/Solutions.tsx | 161 ++++++++ src/components/home/Stats.jsx | 100 +++++ src/components/home/Stats.module.scss | 152 ++++++++ 14 files changed, 2214 insertions(+) create mode 100644 src/components/home/CardsSlider.module.scss create mode 100644 src/components/home/CardsSlider.tsx create mode 100644 src/components/home/Companies.module.scss create mode 100644 src/components/home/Companies.tsx create mode 100644 src/components/home/Events.jsx create mode 100644 src/components/home/Events.module.scss create mode 100644 src/components/home/Hero.module.scss create mode 100644 src/components/home/Hero.tsx create mode 100644 src/components/home/JoinCommunity.jsx create mode 100644 src/components/home/JoinCommunity.module.scss create mode 100644 src/components/home/Solutions.module.scss create mode 100644 src/components/home/Solutions.tsx create mode 100644 src/components/home/Stats.jsx create mode 100644 src/components/home/Stats.module.scss diff --git a/src/components/home/CardsSlider.module.scss b/src/components/home/CardsSlider.module.scss new file mode 100644 index 00000000..40cf6ab2 --- /dev/null +++ b/src/components/home/CardsSlider.module.scss @@ -0,0 +1,249 @@ +@import "~@/scss/solutions/_variables.scss"; + +$homeSliderAnimationTime: 0.3s; + +.Title { + font-size: 40px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.01em; + text-align: center; + color: var(--white); + text-transform: capitalize; + margin: 0; + padding: 64px 24px 0; + + strong { + background: var(--gradient-2); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + @include breakpoint(md) { + font-size: 56px; + padding-top: 80px; + } + + @include breakpoint(lg) { + padding-top: 128px; + } +} + +.Carousel { + position: relative; + width: 100%; + + @include breakpoint(lg) { + padding-bottom: 4rem; + } +} + +.CarouselContainer { + display: flex; + width: 100%; + padding: { + top: 64px; + bottom: 64px; + } + + overflow-x: scroll; + overscroll-behavior-x: auto; + scroll-behavior: smooth; + scrollbar-width: none; /*FireFox*/ + -ms-overflow-style: -ms-autohiding-scrollbar; /*IE10+*/ + + /*Chrome, Safari, Edge*/ + &::-webkit-scrollbar { + display: none; + } + + @include breakpoint(md) { + padding: { + top: 80px; + bottom: 80px; + } + } + + @include breakpoint(lg) { + padding: { + top: 96px; + bottom: 40px; + } + } +} + +.Cards { + display: flex; + justify-content: start; + gap: 16px; + padding-left: 16px; + max-width: 80rem; + margin: 0 auto; + + &:hover { + .CardWrapper:not(:hover) { + opacity: 0.5 !important; + transform: scale(0.98) !important; + } + } +} + +.CardWrapper { + border-radius: 1.5rem; + transition: + opacity 0.275s ease-in-out, + transform 0.275s ease-in-out; + transition-delay: 0.15s; + + &:last-child { + padding-right: 5%; + + @include breakpoint(md) { + padding-right: 33%; + } + } +} + +.Card { + height: 27rem; + width: 18rem; + overflow: hidden; + display: flex; + flex-direction: column; + align-items: start; + justify-content: start; + position: relative; + z-index: 10; + border-radius: 20px; + color: var(--white); + perspective: 100vh; + + transition: + transform 0.3s cubic-bezier(0.76, 0, 0.24, 1), + box-shadow 0.3s ease-out; + + &:hover { + cursor: pointer; + transform: scale(1.03) translate(0, -8px) rotateX(4deg); + + img { + transform: scale(1); + } + } + + .CardContent { + position: relative; + z-index: 40; + padding: 24px; + + .CardTitle { + text-transform: capitalize; + font-size: 20px; + font-weight: 700; + line-height: 1.21; + text-align: left; + transition: transform $homeSliderAnimationTime $easeInOutQuart; + margin-bottom: 8px; + + @include breakpoint(md) { + font-size: 28px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.01em; + text-align: left; + } + } + + .CardText { + text-transform: uppercase; + font-size: 13px; + font-weight: 400; + line-height: 1.2; + letter-spacing: 0.02em; + text-align: left; + font-family: var(--abc-mono-font); + transition: transform $homeSliderAnimationTime $easeInOutQuart; + + @include breakpoint(md) { + font-size: 19px; + font-weight: 400; + line-height: 1.15; + letter-spacing: 0.02em; + text-align: left; + } + + span { + display: block; + } + } + } + + .ArrowWrapper { + position: absolute; + bottom: 24px; + right: 24px; + background: var(--white); + border-radius: 50%; + display: flex; + justify-content: center; + align-items: center; + width: 40px; + height: 40px; + z-index: 40; + transition: transform $homeSliderAnimationTime $easeInOutQuart; + + svg { + transition: 0.2s ease-in; + } + } + + .CardImage { + object-fit: cover; + position: absolute; + z-index: 10; + inset: 0px; + transform: scale(1.05); + transition: transform $homeSliderAnimationTime $easeInOutQuart; + + &.Loading { + filter: blur(4px); + } + } + + @include breakpoint(md) { + height: 34rem; + width: 22rem; + } +} + +.Arrows { + display: flex; + justify-content: center; + gap: 16px; + padding-bottom: 64px; + + button { + position: relative; + z-index: 40; + height: 40px; + width: 40px; + border-radius: 50%; + background-color: var(--grey-300); + display: flex; + align-items: center; + justify-content: center; + transition: background-color 0.1s ease; + transition: + background 0.3s ease-in-out, + transform 0.15s $easeInOutQuart; + + &:disabled { + background-color: var(--grey-500); + + svg path { + stroke: var(--grey-300); + } + } + } +} diff --git a/src/components/home/CardsSlider.tsx b/src/components/home/CardsSlider.tsx new file mode 100644 index 00000000..f292e924 --- /dev/null +++ b/src/components/home/CardsSlider.tsx @@ -0,0 +1,187 @@ +import { useEffect, useState, useRef } from "react"; +import { motion } from "framer-motion"; +import Image, { ImageProps } from "next/image"; +import classNames from "classnames"; +import { Trans } from "next-i18next"; +import { useInView } from "react-intersection-observer"; + +import CaretIcon from "@/components/icons/Caret"; +import { AnimatedText } from "@/components/shared/Text"; + +import styles from "./CardsSlider.module.scss"; + +interface CarouselProps { + titleKey: string; + items: JSX.Element[]; + initialScroll?: number; +} + +type Card = { + src: string; + title: string; + text1: string; + text2: string; + url: string; +}; + +export const Carousel = ({ + titleKey, + items, + initialScroll = 0, +}: CarouselProps) => { + const carouselRef = useRef(null); + const [canScrollLeft, setCanScrollLeft] = useState(false); + const [canScrollRight, setCanScrollRight] = useState(true); + + useEffect(() => { + if (carouselRef.current) { + carouselRef.current.scrollLeft = initialScroll; + checkScrollability(); + } + }, [initialScroll]); + + const checkScrollability = () => { + if (carouselRef.current) { + const { scrollLeft, scrollWidth, clientWidth } = carouselRef.current; + setCanScrollLeft(scrollLeft > 0); + setCanScrollRight(scrollLeft < scrollWidth - clientWidth); + } + }; + + const scrollLeft = () => { + if (carouselRef.current) { + carouselRef.current.scrollBy({ left: -304, behavior: "smooth" }); + } + }; + + const scrollRight = () => { + if (carouselRef.current) { + carouselRef.current.scrollBy({ left: 304, behavior: "smooth" }); + } + }; + + const { ref, inView } = useInView({ + triggerOnce: true, + threshold: 0.5, + }); + + return ( +
+ + + + +
+
+
+ {items.map((item, index) => ( + + {item} + + ))} +
+
+ +
+ + +
+
+
+ ); +}; + +// Card component is exported for use in other files +// eslint-disable-next-line no-unused-vars +export const Card = ({ + card, + layout = false, +}: { + card: Card; + layout?: boolean; +}) => { + return ( + <> + +
+ + {card.title} + + + {card.text1} + {card.text2} + +
+
+ +
+ +
+ + ); +}; + +export const BlurImage = ({ + height, + width, + src, + className, + alt, + ...rest +}: ImageProps) => { + const [isLoading, setLoading] = useState(true); + return ( + setLoading(false)} + src={src} + width={width} + height={height} + blurDataURL={typeof src === "string" ? src : undefined} + alt={alt} + {...rest} + /> + ); +}; diff --git a/src/components/home/Companies.module.scss b/src/components/home/Companies.module.scss new file mode 100644 index 00000000..1965e725 --- /dev/null +++ b/src/components/home/Companies.module.scss @@ -0,0 +1,115 @@ +@import "~@/scss/solutions/_variables.scss"; + +.Companies { + padding: 128px 0 64px; + + .ContentWrapper { + padding: 0 24px; + } + + h2 { + text-align: center; + font-size: 32px; + font-weight: 700; + line-height: 1.11; + letter-spacing: -0.01em; + text-align: center; + margin: 0; + } + + p { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + color: var(--grey-250); + margin-top: 24px; + } + + @include breakpoint(md) { + padding-bottom: 90px; + + .ContentWrapper { + padding: 0 40px; + } + + h2 { + font-size: 40px; + } + + p { + font-size: 20px; + } + } + + @include breakpoint(lg) { + padding-top: 128px; + padding-bottom: 128px; + h2 { + font-size: 56px; + } + + p { + font-size: 24px; + max-width: 570px; + margin: 24px auto 40px; + } + } + + @include breakpoint(xl) { + h2 { + font-size: 56px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + text-align: center; + } + + p { + font-size: 28px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + text-align: center; + } + } +} + +.MobileMarquee { + margin-top: 64px; + + img { + width: 100%; + } + + .MoreLogo { + width: auto; + margin: 0 auto; + } + + @include breakpoint(lg) { + display: none; + } +} + +.DesktopStatic { + display: none; + + img { + width: 100%; + } + + .StaticRow { + display: flex; + justify-content: center; + gap: 40px; + align-items: center; + } + + @include breakpoint(lg) { + display: flex; + flex-direction: column; + gap: 10px; + } +} diff --git a/src/components/home/Companies.tsx b/src/components/home/Companies.tsx new file mode 100644 index 00000000..8ce8e04a --- /dev/null +++ b/src/components/home/Companies.tsx @@ -0,0 +1,119 @@ +import { Fragment } from "react"; +import classNames from "classnames"; +import Image from "next/image"; +import { useTranslation } from "next-i18next"; +import Marquee from "react-fast-marquee"; + +import { AnimatedText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./Companies.module.scss"; + +import visaSvg from "../../../assets/home/companies/visa.svg"; +import paypalSvg from "../../../assets/home/companies/paypal-usd.svg"; +import stripeSvg from "../../../assets/home/companies/stripe.svg"; +import awsSvg from "../../../assets/home/companies/aws.svg"; +import mastercardSvg from "../../../assets/home/companies/mastercard.svg"; +import shopifySvg from "../../../assets/home/companies/shopify.svg"; +import googleCloudSvg from "../../../assets/home/companies/google-cloud.svg"; +import basicsSvg from "../../../assets/home/companies/basics.svg"; +import moreSvg from "../../../assets/home/companies/100-more.svg"; + +const Companies = () => { + const { t } = useTranslation(); + + const mobileLogos = [ + { src: visaSvg, alt: "Visa" }, + { src: paypalSvg, alt: "PayPal" }, + { src: stripeSvg, alt: "Stripe" }, + { src: awsSvg, alt: "AWS" }, + { src: mastercardSvg, alt: "MasterCard" }, + { src: shopifySvg, alt: "Shopify" }, + { src: googleCloudSvg, alt: "Google Cloud" }, + { src: basicsSvg, alt: "Basics" }, + ]; + + const desktopLogos = [ + { src: visaSvg, alt: "Visa" }, + { src: paypalSvg, alt: "PayPal" }, + { src: stripeSvg, alt: "Stripe" }, + { src: awsSvg, alt: "AWS" }, + { src: mastercardSvg, alt: "MasterCard" }, + { src: shopifySvg, alt: "Shopify" }, + { src: googleCloudSvg, alt: "Google Cloud" }, + { src: basicsSvg, alt: "Basics" }, + { src: moreSvg, alt: "100s more" }, + ]; + + return ( +
+
+ + {t("home.companies.title")} + + + {t("home.companies.subtitle")} + +
+ +
+
+ + {[...mobileLogos, ...mobileLogos].map((logo, index) => ( + + {logo.alt} + + ))} + + + + {"100s + +
+ +
+
+ {desktopLogos.slice(0, 4).map((logo, index) => ( + + {logo.alt} + + ))} +
+
+ {desktopLogos.slice(4).map((logo, index) => ( + + {logo.alt} + + ))} +
+
+
+
+ ); +}; + +export default Companies; diff --git a/src/components/home/Events.jsx b/src/components/home/Events.jsx new file mode 100644 index 00000000..fed73f3b --- /dev/null +++ b/src/components/home/Events.jsx @@ -0,0 +1,190 @@ +import Image from "next/image"; +import Link from "next/link"; +import { useTranslation } from "next-i18next"; +import classNames from "classnames"; + +import { AnimatedText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import FormattedDate from "../shared/FormattedDate"; +import styles from "./Events.module.scss"; + +import defaultImg from "../../../assets/events/solana-community-event.jpg"; + +const MainEventCard = ({ event }) => { + const { t } = useTranslation(); + if (!event) return null; + + const startDate = new Date(event?.schedule?.from).getDay(); + const endDate = new Date(event?.schedule?.to).getDay(); + + const date = + startDate === endDate ? ( + + ) : ( + <> + {" "} +
+ + - + + + ); + + const eventUrl = + event.platform === "external" ? event.key : event.rsvp || event.lumaUrl; + + return ( +
+
+ {event?.img?.primary?.alt +
+ +
+
+ {event?.img?.primary?.alt +
+ +
+

{event.title}

+ +
+ {date} + +
+ + + {event.description} + +
+
+ + + {t("events.detail.action")} + +
+
+ ); +}; + +const CommunityEventCard = ({ event, key }) => { + const startDate = new Date(event?.schedule?.from).getDay(); + const endDate = new Date(event?.schedule?.to).getDay(); + + const date = + startDate === endDate ? ( + + ) : ( + <> + {" "} + + - + + + ); + + const content = ( + <> +
+ {event?.img?.primary?.alt +
+ +
+

{event.title}

+

+ {date} +

+

{event.venue.city}

+
+ + ); + + return ( +
+ {event?.rsvp ? {content} : { content }} +
+ ); +}; + +const Events = ({ events, communityEvents }) => { + const { t } = useTranslation(); + + return ( +
+
+ {events.map((event) => ( + + ))} +
+ +
+ + {t("events.community.heading")} + + +
+ {communityEvents.map((event, index) => ( + + + + ))} +
+
+
+ ); +}; + +export default Events; diff --git a/src/components/home/Events.module.scss b/src/components/home/Events.module.scss new file mode 100644 index 00000000..25f621af --- /dev/null +++ b/src/components/home/Events.module.scss @@ -0,0 +1,364 @@ +@import "../../scss/solutions/_variables.scss"; + +$container-padding-x: 24px; + +.MainEvents { + padding: 0 $container-padding-x; + display: flex; + flex-direction: column; + gap: 40px; + padding-bottom: 40px; + + .MainEventCard { + $card-padding: 24px; + + position: relative; + padding: $card-padding; + border-radius: 8px; + overflow: hidden; + + .CardBackground { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + transform: scale(3); + filter: blur(36px); + + img { + object-fit: cover; + } + } + + .CardContent { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: 24px; + } + + .EventImage { + width: 100%; + height: 100%; + border-radius: 16px; + overflow: hidden; + + img { + width: 100%; + height: 100%; + } + } + + .EventDetails { + font-family: var(--abc-mono-font); + text-transform: uppercase; + } + + .EventDetailTitle { + font-size: 15px; + font-weight: 400; + line-height: 1.15; + letter-spacing: 0.02em; + text-align: left; + margin: 0; + } + + .EventDetailsDivider { + display: none; + } + + .EventDetailDate, + .EventDetailText { + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; + text-align: left; + display: block; + margin-top: 16px; + } + + .EventDetailDate { + br { + display: none; + } + } + + .EventButton { + background: var(--grey-450); + font-size: 15px; + font-weight: 700; + line-height: 1.3; + padding: 12px 40px; + border-radius: 8px; + max-width: max-content; + color: var(--white); + cursor: pointer; + } + } +} + +.CommunityEvents { + max-width: 1068px; + margin: 0 auto; + + @include breakpoint(md) { + padding: 0 24px; + } + + .EventsWrapper { + @include breakpoint(lg) { + display: flex; + + > div { + &:first-child { + padding-left: 0; + } + &:not(:first-child) { + border-left: 1px solid var(--grey-450); + } + &:last-child { + padding-right: 0; + } + } + + .CommunityEvent { + flex: 1; + padding: 16px 16px 0; + + .EventImage { + width: 128px; + min-width: 128px; + height: 128px; + } + + img { + width: 128px; + width: 100%; + height: 100%; + object-fit: cover; + } + + h4, + p { + font-size: 15px; + font-weight: 400; + line-height: 1; + letter-spacing: 0.02em; + text-align: left; + } + } + } + } + + h3 { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: left; + margin: 0; + padding: 0 24px 40px; + text-transform: capitalize; + + @include breakpoint(md) { + padding: 0 0 16px; + } + + @include breakpoint(xl) { + font-size: 24px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: left; + } + } + + .CommunityEvent { + border-top: 1px solid var(--grey-450); + padding: 24px; + + a { + color: var(--white); + } + + &:hover { + img { + transform: scale(1.05); + } + } + + &, + > a { + display: flex; + gap: 16px; + } + + .EventImage { + width: 128px; + min-width: 128px; + height: 128px; + overflow: hidden; + border-radius: 8px; + } + + img { + width: 128px; + width: 100%; + height: 100%; + object-fit: cover; + border-radius: 8px; + transition: transform 0.3s cubic-bezier(0.76, 0, 0.24, 1); + } + + h4, + p { + font-family: var(--abc-mono-font); + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; + text-align: left; + margin: 0; + text-transform: uppercase; + } + + p { + color: var(--grey-250); + } + + .CommunityEventDetails { + display: flex; + flex-direction: column; + justify-content: flex-end; + gap: 4px; + } + } +} + +@include breakpoint(sm) { + .MainEvents { + display: grid; + grid-template-columns: repeat(2, 1fr); + + .MainEventCard { + &:first-child { + .EventDetailsDivider { + display: block; + font-family: var(--abc-mono-font); + font-size: 15px; + font-weight: 400; + line-height: 1.15; + letter-spacing: 0.02em; + } + + .EventDetailDateText { + gap: 12px; + } + } + + &:not(:first-child) { + .EventDetailDateText { + gap: 24px; + } + } + + .EventDetailDateText { + display: flex; + align-items: flex-start; + margin-top: 16px; + + .EventDetailDate, + .EventDetailText { + margin-top: 0; + } + + .EventDetailDate { + br { + display: block; + } + } + } + } + } +} + +@include breakpoint(md) { + .Events { + padding-top: 128px; + padding-bottom: 90px; + } +} + +@include breakpoint(lg) { + .Events { + padding-top: 128px; + padding-bottom: 80px; + } + + .MainEvents { + display: grid; + grid-template-columns: repeat(3, 1fr); + + .MainEventCard { + &:first-child { + grid-column: span 2; + } + + .CardContent { + height: 100%; + } + + .EventImage { + width: calc(min(100%, 208px)); + height: calc(min(100%, 208px)); + border-radius: 16px; + overflow: hidden; + + img { + width: 100%; + height: 100%; + } + } + + .EventDetails { + flex: 1; + display: flex; + flex-direction: column; + justify-content: flex-end; + } + + .EventDetailDate, + .EventDetailText { + font-size: 15px; + line-height: 1.15; + } + + .EventButton { + font-size: 18px; + line-height: 1.1; + } + } + } +} + +@include breakpoint(xl) { + .MainEvents { + .MainEventCard { + .EventDetailTitle { + font-size: 19px; + line-height: 1.15; + } + + &:first-child { + .EventDetailTitle { + font-size: 40px; + line-height: 1; + } + } + } + } +} diff --git a/src/components/home/Hero.module.scss b/src/components/home/Hero.module.scss new file mode 100644 index 00000000..b4914c37 --- /dev/null +++ b/src/components/home/Hero.module.scss @@ -0,0 +1,115 @@ +@import "~@/scss/solutions/variables.scss"; + +.Hero { + padding: 0 24px; + overflow: hidden; + height: calc(100vh - var(--header-height)); + display: flex; + justify-content: center; + position: relative; + max-height: calc(100vh - var(--header-height)); + + @include breakpoint(md) { + padding: 64px 40px; + } + + .ContentWrapper { + position: relative; + z-index: 2; + max-width: 846px; + margin: 0 auto; + display: flex; + flex-direction: column; + justify-content: center; + } + + .HeroTitle { + font-size: 36px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + text-align: center; + margin: 0; + + .KernOut { + letter-spacing: 1px; + } + + @include breakpoint(md) { + font-size: 64px; + } + + @include breakpoint(lg) { + font-size: 80px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.02em; + text-align: center; + } + } + + .HeroSubtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + text-align: center; + margin: 24px auto 0; + color: var(--grey-200); + + @include breakpoint(md) { + font-size: 24px; + } + + @include breakpoint(lg) { + font-size: 28px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + text-align: center; + } + } + + .HeroButtons { + display: flex; + flex-direction: column; + gap: 16px; + width: max-content; + margin: 32px auto 0; + + @include breakpoint(md) { + margin-top: 24px; + } + } + + .VideoWrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + z-index: 0; + overflow: hidden; + height: calc(100vh - var(--header-height)); + max-height: calc(100vh - var(--header-height)); + + .Gradient { + position: absolute; + width: 100%; + height: 100%; + background: var(--black); + z-index: 1; + opacity: 0.5; + } + + video, + img { + position: relative; + width: 100%; + height: 100%; + object-fit: cover; + z-index: 0; + height: calc(100vh - var(--header-height)); + filter: hue-rotate(40deg); + max-height: calc(100vh - var(--header-height)); + } + } +} diff --git a/src/components/home/Hero.tsx b/src/components/home/Hero.tsx new file mode 100644 index 00000000..32f62771 --- /dev/null +++ b/src/components/home/Hero.tsx @@ -0,0 +1,83 @@ +import { useEffect, useRef } from "react"; +import { useTranslation, Trans } from "next-i18next"; + +import useReducedMotion from "@/hooks/useReducedMotion"; + +import Button from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "@/components/home/Hero.module.scss"; + +const Hero = () => { + const { t } = useTranslation(); + const [prefersReducedMotion] = useReducedMotion(); + + const videoRef = useRef(null); + + useEffect(() => { + if (videoRef.current && prefersReducedMotion) { + videoRef.current.pause(); + } + }, [prefersReducedMotion]); + + return ( +
+
+ + {/* {t("home.hero.title")} */} + , + }} + /> + + + + {t("home.hero.subtitle")} + + + +
+
+
+
+ +
+
+ +
+
+ ); +}; + +export default Hero; diff --git a/src/components/home/JoinCommunity.jsx b/src/components/home/JoinCommunity.jsx new file mode 100644 index 00000000..0a13cf97 --- /dev/null +++ b/src/components/home/JoinCommunity.jsx @@ -0,0 +1,125 @@ +import { useRef } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { useTranslation } from "next-i18next"; +import gsap from "gsap"; +import { useGSAP } from "@gsap/react"; +import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; + +import Text, { AnimatedText } from "@/components/shared/Text"; + +import styles from "./JoinCommunity.module.scss"; + +import MobileHeroImage from "../../../assets/home/join-community.png"; +import Image1 from "../../../assets/home/join-community-1.png"; +import Image2 from "../../../assets/home/join-community-2.png"; +import Image3 from "../../../assets/home/join-community-3.png"; +import Image4 from "../../../assets/home/join-community-4.png"; +import Image5 from "../../../assets/home/join-community-5.png"; +import Image6 from "../../../assets/home/join-community-6.png"; + +gsap.registerPlugin(ScrollTrigger, useGSAP); + +const JoinCommunity = ({ title, links }) => { + const { t } = useTranslation(); + + const container = useRef(null); + + const images = [Image1, Image2, Image4, Image6, Image5, Image3]; + + useGSAP( + () => { + let Imgs = gsap.utils.toArray(".join-community-img"), + mm = gsap.matchMedia(); + + mm.add("(min-width:768px)", () => { + const pinTl = gsap.timeline({ + scrollTrigger: { + trigger: container.current, + start: "top top", + end: "+=500px", + scrub: 1, + pin: true, + pinSpacing: "margin", + }, + defaults: { ease: "none" }, + }); + + pinTl.from(Imgs, { + top: "50%", + left: "50%", + transform: "translate(-50%, -50%)", + }); + + pinTl.from( + ".join-commuity-main-title , .links", + { + autoAlpha: 0, + y: 50, + stagger: 0.1, + }, + "<50%", + ); + }); + }, + { scope: container }, + ); + + return ( +
+ {t("home.join-community.hero-alt")} + +
+ {images.map((image, index) => ( + {t("home.join-community.hero-alt")} + ))} +
+ +
+ + {title} + + + + {title} + + +
+ {links.map((link, index) => ( + + {link.text} + + ))} +
+
+
+ ); +}; + +export default JoinCommunity; diff --git a/src/components/home/JoinCommunity.module.scss b/src/components/home/JoinCommunity.module.scss new file mode 100644 index 00000000..4f297b6d --- /dev/null +++ b/src/components/home/JoinCommunity.module.scss @@ -0,0 +1,145 @@ +@import "~@/scss/solutions/_variables.scss"; + +.JoinCommunity { + padding: 64px 0; + position: relative; +} + +.MobileHeroImage { + position: relative; + width: 100%; +} + +.ImgsWrapper { + display: none; + position: absolute; + inset: 0; + width: 100%; + height: 100%; + img { + position: absolute; + transform-origin: center center; + will-change: auto; + } + img:nth-child(1) { + top: 5%; + left: 20%; + max-width: 278px; + } + img:nth-child(2) { + top: 20%; + left: 70%; + max-width: 300px; + } + img:nth-child(3) { + top: 60%; + left: 80%; + max-width: 84px; + } + img:nth-child(4) { + top: 70%; + left: 60%; + max-width: 200px; + } + img:nth-child(5) { + top: 70%; + left: 20%; + max-width: 340px; + } + img:nth-child(6) { + top: 40%; + left: 10%; + max-width: 135px; + } +} + +.Content { + padding: 0 24px; + + .Title { + margin: 24px auto 0; + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + will-change: auto; + } + + .Links { + display: flex; + gap: 20px; + margin-top: 40px; + justify-content: center; + will-change: auto; + + a { + color: var(--white); + transition: color 0.3s; + text-transform: uppercase; + font-family: var(--abc-mono-font); + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; + text-align: center; + + &:hover { + transform: translateY(-2px); + transition: transform 0.3s; + } + } + } +} + +.DesktopTitle { + display: none; +} + +@include breakpoint(md) { + .MobileTitle { + display: none; + } + + .DesktopTitle { + display: block; + } + + .JoinCommunity { + height: 100vh; + } + + .MobileHeroImage { + display: none; + } + + .Content { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + } + + .ImgsWrapper { + display: block; + } +} + +@include breakpoint(lg) { + .Content { + .Title { + font-size: 64px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.02em; + margin-top: 0; + } + + .Links { + a { + font-size: 15px; + line-height: 1.15; + } + } + } +} diff --git a/src/components/home/Solutions.module.scss b/src/components/home/Solutions.module.scss new file mode 100644 index 00000000..5185b3f0 --- /dev/null +++ b/src/components/home/Solutions.module.scss @@ -0,0 +1,109 @@ +@import "~@/scss/solutions/_variables.scss"; + +.Solutions { + padding: 0 0 64px; +} + +.CardsWrapper { + display: flex; + flex-direction: column; + gap: 35px; + padding: 0 24px; +} + +.Card { + padding: 24px; + background: var(--grey-500); + border-radius: 16px; + overflow: hidden; + + @include breakpoint(md) { + height: 100%; + } + + a { + display: flex; + flex-direction: column; + color: var(--white); + height: 100%; + + &:hover { + img { + transform: scale(1.05); + } + } + } + + .TitleWrapper { + display: flex; + justify-content: space-between; + align-items: center; + } + + .CaretIconWrapper { + background: var(--white); + border-radius: 50%; + overflow: hidden; + width: 24px; + height: 24px; + display: flex; + justify-content: center; + align-items: center; + + @include breakpoint(md) { + width: 32px; + height: 32px; + } + + svg { + width: 14px; + + @include breakpoint(md) { + width: 20px; + } + } + } + + h3 { + font-size: 20px; + line-height: 1.2; + margin: 0; + + @include breakpoint(md) { + font-size: 28px; + } + } + + p { + font-size: 18px; + color: var(--grey-300); + font-weight: bold; + line-height: 1.16; + letter-spacing: -0.01em; + margin-bottom: 24px; + margin-top: 8px; + max-width: 100%; + margin-left: 0; + + @include breakpoint(md) { + flex: 1; + font-size: 20px; + } + } +} + +.ImageWrapper { + position: relative; + border-radius: 28px; + overflow: hidden; + + @include breakpoint(md) { + border-radius: 34px; + } + + img { + width: 100%; + height: auto; + transition: transform 0.3s ease; + } +} diff --git a/src/components/home/Solutions.tsx b/src/components/home/Solutions.tsx new file mode 100644 index 00000000..2fd0c105 --- /dev/null +++ b/src/components/home/Solutions.tsx @@ -0,0 +1,161 @@ +import { useTranslation } from "next-i18next"; +import CardsSlider from "@/components/shared/CardsSlider"; +import Text from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import Image from "next/image"; +import Link from "next/link"; +import classNames from "classnames"; +import styles from "./Solutions.module.scss"; +import CaretIcon from "@/components/icons/Caret"; + +const Card = ({ title, text, mobileImg, desktopImgSrc, href }) => ( + +
+ +
+ + {title} + +
+ +
+
+ + + {text} + + +
+ {title} + {title} +
+ +
+
+); + +const Solutions = () => { + const { t } = useTranslation(); + + const cardsData = [ + { + title: t("home.solutions.payments.title"), + text: t("home.solutions.payments.text"), + mobileImg: { + src: "/src/img/home/solutions/payments-mb.svg", + width: 290.76, + height: 248, + }, + desktopImgSrc: "/src/img/home/solutions/payments-dt.svg", + href: "/solutions/payments", + }, + { + title: t("home.solutions.defi.title"), + text: t("home.solutions.defi.text"), + mobileImg: { + src: "/src/img/home/solutions/defi-mb.png", + width: 290.76, + height: 248, + }, + desktopImgSrc: "/src/img/home/solutions/defi-dt.png", + href: "/solutions/defi", + }, + { + title: t("home.solutions.gaming.title"), + text: t("home.solutions.gaming.text"), + mobileImg: { + src: "/src/img/home/solutions/gaming-mb.png", + width: 294, + height: 255, + }, + desktopImgSrc: "/src/img/home/solutions/gaming-dt.png", + href: "/solutions/gaming", + }, + { + title: t("home.solutions.creative.title"), + text: t("home.solutions.creative.text"), + mobileImg: { + src: "/src/img/home/solutions/creative-mb.png", + width: 294, + height: 319, + }, + desktopImgSrc: "/src/img/home/solutions/creative-dt.png", + href: "/solutions/creative", + }, + { + title: t("home.solutions.institutional-finance.title"), + text: t("home.solutions.institutional-finance.text"), + mobileImg: { + src: "/src/img/home/solutions/institutional-finance-mb.png", + width: 294, + height: 250.76, + }, + desktopImgSrc: "/src/img/home/solutions/institutional-finance-dt.png", + href: "/solutions/institutional-finance", + }, + { + title: t("home.solutions.loyalty.title"), + text: t("home.solutions.loyalty.text"), + mobileImg: { + src: "/src/img/home/solutions/loyalty-dt.png", + width: 294, + height: 277.69, + }, + desktopImgSrc: "/src/img/home/solutions/loyalty-dt.png", + href: "/solutions/loyalty", + }, + { + title: t("home.solutions.wallets.title"), + text: t("home.solutions.wallets.text"), + mobileImg: { + src: "/src/img/home/solutions/wallets-mb.png", + width: 294, + height: 250.76, + }, + desktopImgSrc: "/src/img/home/solutions/wallets-dt.png", + href: "/solutions/wallets", + }, + ]; + + const cards = cardsData.map((card, index) => ( + + )); + + return ( +
+
+ {cards} +
+ +
+ ); +}; + +export default Solutions; diff --git a/src/components/home/Stats.jsx b/src/components/home/Stats.jsx new file mode 100644 index 00000000..41f45d7e --- /dev/null +++ b/src/components/home/Stats.jsx @@ -0,0 +1,100 @@ +import { useRef } from "react"; +import gsap from "gsap"; +import { useGSAP } from "@gsap/react"; +import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; + +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./Stats.module.scss"; + +gsap.registerPlugin(ScrollTrigger, useGSAP); + +const Stats = ({ stats }) => { + const container = useRef(); + + useGSAP( + () => { + let statsElements = gsap.utils.toArray(".single-stat"), + pinDuration = statsElements.length * 500, + lastIndex = statsElements.length - 1, + paginationDots = gsap.utils.toArray(".pagination .dot"), + paginationDotsBG = gsap.utils.toArray(".pagination .dotBG"), + mm = gsap.matchMedia(); + + mm.add("(min-width:768px)", () => { + const pinTl = gsap.timeline({ + scrollTrigger: { + trigger: container.current, + start: "center center", + end: `+=${pinDuration}`, + scrub: 1, + pin: true, + pinSpacing: "margin", + invalidateOnRefresh: false, + }, + defaults: { ease: "none" }, + }); + + statsElements.forEach((stat, i) => { + pinTl.set(paginationDots[i], { height: "40px" }); + pinTl.to(paginationDotsBG[i], { height: "100%" }); + + pinTl.fromTo( + stat, + { + opacity: i === 0 ? 1 : 0, + }, + { + keyframes: [ + { opacity: 1 }, + { + opacity: i !== lastIndex && 0, + }, + ], + }, + "<", + ); + + pinTl.set(paginationDots[i], { height: "7px" }); + pinTl.set(paginationDotsBG[i], { height: "0%" }); + }); + }); + }, + { scope: container }, + ); + + return ( +
+
+ {stats.length && + stats.map((stat, index) => ( +
+ +
+ ))} +
+
+ {stats.length && + stats.map((stat, index) => )} +
+
+ ); +}; + +const MotionStat = ({ stat }) => { + return ( +
+ + {stat.title} + + + {stat.value} + + + {stat.subtext} + +
+ ); +}; + +export default Stats; diff --git a/src/components/home/Stats.module.scss b/src/components/home/Stats.module.scss new file mode 100644 index 00000000..d0de5dfe --- /dev/null +++ b/src/components/home/Stats.module.scss @@ -0,0 +1,152 @@ +@import "~@/scss/solutions/_variables.scss"; + +.StatsSection { + padding: 64px 24px; + position: relative; + background: linear-gradient(0deg, #3a1b60 0%, transparent 100%); + background-size: 100% 50%; + background-repeat: no-repeat; + background-position: bottom; + overflow: hidden; +} + +.StatsWrapper { + position: relative; + z-index: 1; + display: flex; + flex-direction: column; + gap: 40px; +} + +.Stat { + text-align: center; + display: flex; + flex-direction: column; + gap: 8px; + opacity: 1; + + .Title { + font-size: 32px; + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.01em; + color: var(--white); + margin: 0; + } + + .Value { + font-size: 72px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + margin: 0; + background: var(--gradient-11); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + + &::selection { + -webkit-text-fill-color: var(--black); + } + } + + .Subtext { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + } +} + +.Pagination { + position: absolute; + left: 20px; + top: 50%; + transform: translateY(-50%); + display: none; + gap: 8px; + z-index: 1; + justify-content: center; + align-items: center; + flex-direction: column; + + .dot { + position: relative; + display: block; + width: 8px; + height: 8px; + border-radius: 100px; + background: #6c6a81; + overflow: hidden; + transition: 0.5s ease; + } + + .dot .dotBG { + position: absolute; + width: 100%; + height: 0; + background: #fff; + border-radius: 100px; + } +} + +@include breakpoint(md) { + .StatsSection { + background-color: var(--black); + height: 100vh; + background-image: url("/src/img/home/home-stat-bg.png"); + background-repeat: no-repeat; + background-size: cover; + background-position: bottom; + } + + .StatsWrapper { + height: 100%; + position: relative; + } + + .Stat { + height: 100%; + width: 100%; + justify-content: center; + gap: 0; + + &:not(:first-child) { + position: absolute; + } + + .Title { + font-size: min(6vh, 64px); + } + + .Value { + font-size: min(15vh, 164px); + margin: 24px auto 4px; + } + + .Subtext { + font-size: min(3vh, 28px); + margin-bottom: 0; + } + } + + .Pagination { + display: flex; + } +} + +@include breakpoint(xl) { + .Stat { + .Title { + font-size: 56px; + } + + .Value { + font-size: 144px; + } + + .Subtext { + font-size: 24px; + } + } +} From 2b65a5a288e025cf902641322c4e82ea6975a369 Mon Sep 17 00:00:00 2001 From: Gina Lee Date: Thu, 14 Nov 2024 02:48:40 +0800 Subject: [PATCH 3/4] Add learn/wallets page specific component --- src/components/learn/WalletsHero.jsx | 91 +++++++++ src/components/learn/WalletsHero.module.scss | 193 +++++++++++++++++++ 2 files changed, 284 insertions(+) create mode 100644 src/components/learn/WalletsHero.jsx create mode 100644 src/components/learn/WalletsHero.module.scss diff --git a/src/components/learn/WalletsHero.jsx b/src/components/learn/WalletsHero.jsx new file mode 100644 index 00000000..705f1eb9 --- /dev/null +++ b/src/components/learn/WalletsHero.jsx @@ -0,0 +1,91 @@ +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "next-i18next"; +import classNames from "classnames"; + +import useReducedMotion from "@/hooks/useReducedMotion"; + +import Button from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; + +import styles from "./WalletsHero.module.scss"; + +import { MotionSlideIn } from "@/components/shared/Motions"; + +const WalletsHero = () => { + const { t } = useTranslation(); + + const [active, setActive] = useState(false); + const videoRef = useRef(null); + const [prefersReducedMotion] = useReducedMotion(); + + useEffect(() => { + setTimeout(() => setActive(true), 500); + }, []); + + useEffect(() => { + prefersReducedMotion && videoRef.current.pause(); + }, [prefersReducedMotion]); + + return ( +
+
+
+ + {t("learn-wallets.hero.kicker")} + + + + {t("learn-wallets.hero.title")} + + + + {t("learn-wallets.hero.subtitle")} + + + +
+
+ +
+ +
+
+ ); +}; + +export default WalletsHero; diff --git a/src/components/learn/WalletsHero.module.scss b/src/components/learn/WalletsHero.module.scss new file mode 100644 index 00000000..5621205e --- /dev/null +++ b/src/components/learn/WalletsHero.module.scss @@ -0,0 +1,193 @@ +@import "~@/scss/solutions/_variables.scss"; + +.WalletsHero { + padding: 64px 0; + display: flex; + flex-direction: column; + gap: 40px; + + .ContentWrapper { + padding: 0 24px; + + .ContentInnerWrapper { + display: flex; + flex-direction: column; + gap: 24px; + } + + p, + h1 { + margin-top: 0; + margin-bottom: 0; + } + } + + .Kicker { + font-size: 16px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + text-align: center; + color: var(--blue); + } + + .Title { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + } + + .Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + text-align: center; + color: var(--grey-250); + } + + .Buttons { + display: flex; + flex-direction: column; + gap: 16px; + width: max-content; + margin: 0 auto; + } +} + +.VideoWrapper { + display: flex; + align-items: center; + opacity: 0; + transition: opacity $duration-standard $easeInQuart; + transition-delay: 300ms; + position: relative; + + &:after { + content: ""; + display: block; + position: absolute; + height: 100%; + width: 100%; + background: linear-gradient( + 360deg, + rgba(15, 10, 22, 0) 10.27%, + rgb(13 8 23) 95.48% + ); + top: 0; + left: 0; + + @include breakpoint(xl) { + background: linear-gradient( + 270deg, + rgba(15, 10, 22, 0) 10.27%, + rgb(13, 8, 23) 95.48% + ); + } + } + + &.Active { + opacity: 1; + } + + video { + width: 100%; + height: 100%; + object-fit: cover; + } +} + +@include breakpoint(md) { + .WalletsHero { + padding: 80px 0; + + .ContentWrapper { + .ContentInnerWrapper { + @include max-width(650px); + } + } + + .Title { + font-size: 64px; + } + + .Subtitle { + font-size: 24px; + } + + .Buttons { + flex-direction: row; + } + } +} + +@include breakpoint(xl) { + .WalletsHero { + padding: 92px 0; + flex-direction: row; + justify-content: space-between; + overflow: hidden; + position: relative; + height: 81vh; + + .ContentWrapper { + flex: 1; + position: relative; + z-index: 1; + + .ContentInnerWrapper { + justify-content: center; + @include max-width(1068px); + height: 100%; + + p { + max-width: 519px; + margin-left: 0; + } + } + + * { + text-align: left; + } + } + + .VideoWrapper { + flex: 1; + height: 100%; + max-width: 60%; + margin-left: 0; + margin-right: auto; + position: absolute; + right: 0; + top: 0; + } + + .Kicker { + margin-left: 0; + font-size: 22px; + } + + .Title { + line-height: 1; + letter-spacing: -0.02em; + max-width: 519px; + } + + .Subtitle { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.02em; + max-width: 100%; + } + + .Buttons { + flex-direction: row; + margin-left: 0; + + a { + padding: 12px; + } + } + } +} From 60d867a34c07e87f1930a4136d15f15cc66db59c Mon Sep 17 00:00:00 2001 From: Gina Lee Date: Thu, 14 Nov 2024 02:58:06 +0800 Subject: [PATCH 4/4] Add solution pages specific components --- src/components/solutions/BasicCallout.jsx | 37 ++ .../solutions/BasicCallout.module.scss | 94 ++++ src/components/solutions/Button.module.scss | 98 ++++ src/components/solutions/Button.tsx | 31 ++ .../solutions/DetailsSection.module.scss | 108 +++++ src/components/solutions/DetailsSection.tsx | 54 +++ .../solutions/DeveloperResources.module.scss | 168 +++++++ .../solutions/DeveloperResources.tsx | 108 +++++ src/components/solutions/EcosystemSlider.jsx | 89 ++++ .../solutions/EcosystemSlider.module.scss | 236 ++++++++++ .../EcosystemSliderWithTabs.module.scss | 226 +++++++++ .../solutions/EcosystemSliderWithTabs.tsx | 155 +++++++ .../solutions/FooterCallout.module.scss | 184 ++++++++ src/components/solutions/FooterCallout.tsx | 103 +++++ src/components/solutions/Layout.module.scss | 122 +++++ .../solutions/LongformItem.module.scss | 362 +++++++++++++++ src/components/solutions/LongformItem.tsx | 124 +++++ .../solutions/LottieHeroWithTabs.module.scss | 151 ++++++ .../solutions/LottieHeroWithTabs.tsx | 191 ++++++++ .../MediaOptionSelection.module.scss | 132 ++++++ .../solutions/MediaOptionSelection.tsx | 69 +++ src/components/solutions/Stats.jsx | 121 +++++ src/components/solutions/Stats.module.scss | 174 +++++++ .../solutions/SuccessStories.module.scss | 194 ++++++++ src/components/solutions/SuccessStories.tsx | 128 ++++++ .../solutions/VideoBgHero.module.scss | 188 ++++++++ src/components/solutions/VideoBgHero.tsx | 115 +++++ .../blinks-and-actions/BlinksHero.jsx | 83 ++++ .../blinks-and-actions/BlinksHero.module.scss | 124 +++++ src/components/solutions/gaming/GamesKit.jsx | 89 ++++ .../solutions/gaming/GamesKit.module.scss | 172 +++++++ .../solutions/gaming/GamingSlider.module.scss | 186 ++++++++ .../solutions/gaming/GamingSlider.tsx | 202 ++++++++ .../solutions/gaming/GamingVideoHero.jsx | 119 +++++ .../gaming/GamingVideoHero.module.scss | 116 +++++ .../solutions/gaming/TVMert.module.scss | 79 ++++ src/components/solutions/gaming/TVMert.tsx | 106 +++++ src/components/solutions/layout.tsx | 21 + .../solutions/loyalty/LoyaltyHero.jsx | 53 +++ .../solutions/loyalty/LoyaltyHero.module.scss | 114 +++++ .../EcosystemToggle.module.scss | 326 +++++++++++++ .../token-extensions/EcosystemToggle.tsx | 133 ++++++ .../wallets-explorer/CollapsibleContent.jsx | 43 ++ .../CollapsibleContent.module.scss | 23 + .../solutions/wallets-explorer/Filters.jsx | 433 ++++++++++++++++++ .../wallets-explorer/Filters.module.scss | 346 ++++++++++++++ .../solutions/wallets-explorer/WalletCard.jsx | 307 +++++++++++++ .../wallets-explorer/WalletCard.module.scss | 176 +++++++ .../wallets-explorer/WalletFilters.jsx | 115 +++++ .../WalletFilters.module.scss | 65 +++ .../solutions/wallets-explorer/Wallets.jsx | 61 +++ .../wallets-explorer/Wallets.module.scss | 13 + .../wallets-explorer/WalletsLayout.jsx | 72 +++ .../WalletsLayout.module.scss | 92 ++++ .../solutions/wallets/WalletTitle.jsx | 35 ++ .../solutions/wallets/WalletsDetails.jsx | 65 +++ .../wallets/WalletsExploreSolutions.jsx | 62 +++ .../solutions/wallets/WalletsHero.jsx | 103 +++++ .../solutions/wallets/WalletsHero.module.scss | 147 ++++++ 59 files changed, 7843 insertions(+) create mode 100644 src/components/solutions/BasicCallout.jsx create mode 100644 src/components/solutions/BasicCallout.module.scss create mode 100644 src/components/solutions/Button.module.scss create mode 100644 src/components/solutions/Button.tsx create mode 100644 src/components/solutions/DetailsSection.module.scss create mode 100644 src/components/solutions/DetailsSection.tsx create mode 100644 src/components/solutions/DeveloperResources.module.scss create mode 100644 src/components/solutions/DeveloperResources.tsx create mode 100644 src/components/solutions/EcosystemSlider.jsx create mode 100644 src/components/solutions/EcosystemSlider.module.scss create mode 100644 src/components/solutions/EcosystemSliderWithTabs.module.scss create mode 100644 src/components/solutions/EcosystemSliderWithTabs.tsx create mode 100644 src/components/solutions/FooterCallout.module.scss create mode 100644 src/components/solutions/FooterCallout.tsx create mode 100644 src/components/solutions/Layout.module.scss create mode 100644 src/components/solutions/LongformItem.module.scss create mode 100644 src/components/solutions/LongformItem.tsx create mode 100644 src/components/solutions/LottieHeroWithTabs.module.scss create mode 100644 src/components/solutions/LottieHeroWithTabs.tsx create mode 100644 src/components/solutions/MediaOptionSelection.module.scss create mode 100644 src/components/solutions/MediaOptionSelection.tsx create mode 100644 src/components/solutions/Stats.jsx create mode 100644 src/components/solutions/Stats.module.scss create mode 100644 src/components/solutions/SuccessStories.module.scss create mode 100644 src/components/solutions/SuccessStories.tsx create mode 100644 src/components/solutions/VideoBgHero.module.scss create mode 100644 src/components/solutions/VideoBgHero.tsx create mode 100644 src/components/solutions/blinks-and-actions/BlinksHero.jsx create mode 100644 src/components/solutions/blinks-and-actions/BlinksHero.module.scss create mode 100644 src/components/solutions/gaming/GamesKit.jsx create mode 100644 src/components/solutions/gaming/GamesKit.module.scss create mode 100644 src/components/solutions/gaming/GamingSlider.module.scss create mode 100644 src/components/solutions/gaming/GamingSlider.tsx create mode 100644 src/components/solutions/gaming/GamingVideoHero.jsx create mode 100644 src/components/solutions/gaming/GamingVideoHero.module.scss create mode 100644 src/components/solutions/gaming/TVMert.module.scss create mode 100644 src/components/solutions/gaming/TVMert.tsx create mode 100644 src/components/solutions/layout.tsx create mode 100644 src/components/solutions/loyalty/LoyaltyHero.jsx create mode 100644 src/components/solutions/loyalty/LoyaltyHero.module.scss create mode 100644 src/components/solutions/token-extensions/EcosystemToggle.module.scss create mode 100644 src/components/solutions/token-extensions/EcosystemToggle.tsx create mode 100644 src/components/solutions/wallets-explorer/CollapsibleContent.jsx create mode 100644 src/components/solutions/wallets-explorer/CollapsibleContent.module.scss create mode 100644 src/components/solutions/wallets-explorer/Filters.jsx create mode 100644 src/components/solutions/wallets-explorer/Filters.module.scss create mode 100644 src/components/solutions/wallets-explorer/WalletCard.jsx create mode 100644 src/components/solutions/wallets-explorer/WalletCard.module.scss create mode 100644 src/components/solutions/wallets-explorer/WalletFilters.jsx create mode 100644 src/components/solutions/wallets-explorer/WalletFilters.module.scss create mode 100644 src/components/solutions/wallets-explorer/Wallets.jsx create mode 100644 src/components/solutions/wallets-explorer/Wallets.module.scss create mode 100644 src/components/solutions/wallets-explorer/WalletsLayout.jsx create mode 100644 src/components/solutions/wallets-explorer/WalletsLayout.module.scss create mode 100644 src/components/solutions/wallets/WalletTitle.jsx create mode 100644 src/components/solutions/wallets/WalletsDetails.jsx create mode 100644 src/components/solutions/wallets/WalletsExploreSolutions.jsx create mode 100644 src/components/solutions/wallets/WalletsHero.jsx create mode 100644 src/components/solutions/wallets/WalletsHero.module.scss diff --git a/src/components/solutions/BasicCallout.jsx b/src/components/solutions/BasicCallout.jsx new file mode 100644 index 00000000..870c35f1 --- /dev/null +++ b/src/components/solutions/BasicCallout.jsx @@ -0,0 +1,37 @@ +import classNames from "classnames"; +import { Trans } from "next-i18next"; + +import { AnimatedText } from "@/components/shared/Text"; + +import styles from "./BasicCallout.module.scss"; + +const BasicCallout = ({ titleContent, subtitleKey, className, id }) => { + return ( +
+
+
+ {titleContent && ( + + {titleContent} + + )} + + {subtitleKey && ( + + + + )} +
+
+
+ ); +}; + +export default BasicCallout; diff --git a/src/components/solutions/BasicCallout.module.scss b/src/components/solutions/BasicCallout.module.scss new file mode 100644 index 00000000..08a52929 --- /dev/null +++ b/src/components/solutions/BasicCallout.module.scss @@ -0,0 +1,94 @@ +@import "../../scss/solutions/variables"; + +.BasicCallout { + padding: 64px 24px; + + * { + margin: 0; + padding: 0; + } +} + +.MediaBlock { + position: relative; + aspect-ratio: 7 / 9; + margin-top: 40px; + + img { + object-fit: contain; + } +} + +.Title { + font-size: 40px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.01em; + text-align: center; + color: var(--white); +} + +.Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: center; + color: var(--grey-250); + margin: 24px auto 0; +} + +@include breakpoint(md) { + .Title { + font-size: 56px; + } + + .WithImage { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 40px; + align-items: center; + + h2, + p { + text-align: left; + } + } + + .MediaBlock { + margin-top: 0; + } +} + +@include breakpoint(lg) { + .BasicCallout { + padding: 128px; + } + + .Title { + font-size: 72px; + line-height: 1.08; + letter-spacing: -0.03em; + margin-bottom: 24px; + } + + .Subtitle { + font-size: 24px; + font-weight: 500; + line-height: 1.25; + text-align: center; + max-width: 800px; + } + + .ButtonWrapper { + margin-top: 40px; + } + + .WithImage { + p { + font-size: 32px; + line-height: 1.15; + letter-spacing: -0.64px; + } + } +} diff --git a/src/components/solutions/Button.module.scss b/src/components/solutions/Button.module.scss new file mode 100644 index 00000000..c533c14d --- /dev/null +++ b/src/components/solutions/Button.module.scss @@ -0,0 +1,98 @@ +@import "../../scss/solutions/_variables.scss"; + +.Button { + padding: 12px 24px; + border-radius: 8px; + color: var(--white); + text-align: center; + font-size: 18px; + font-weight: 700; + line-height: 1.32; + position: relative; + cursor: pointer; + display: flex; + justify-content: center; + transition: + transform 0.3s $easeInOutQuart, + box-shadow 0.5s ease-out; + overflow: hidden; + box-shadow: 0 0 0 rgba(#80ecff, 0); + + span { + position: relative; + z-index: 2; + transition: color 0.15s ease-in-out; + } + + /* These are the two backgrounds (before/after hover tied to before/after pseudo elements) */ + &.primary, + &.secondary { + &:before, + &:after { + content: ""; + display: block; + position: absolute; + left: 0; + top: 0; + bottom: 0; + right: 0; + z-index: 1; + border-radius: 8px; + } + } + + // Primary & secondary buttons start with different background colors + &.primary { + &:before { + background: var(--purple); + } + } + + &.secondary { + &:before { + background: var(--grey-450); + } + } + + // Default hide :after and show on hover + &:after { + opacity: 0; + background: var(--purple); + background: var(--gradient-3); + + // animation styling + transition: + opacity 0.3s ease-in-out, + transform 0.2s $easeInOutQuart, + filter 0.35s $easeDefault; + transform: translate(0, 60%) scale(1.2, 1); + filter: blur(15px); + } + + /* On hover, fade in the :after to show the new background. */ + &:hover { + transform: translate(0, -2px); + box-shadow: 0px 2px 12px rgba(#80ecff, 0.5); + &:after { + opacity: 1; + transform: translate(0, 0) scale(1.2, 1); + filter: blur(0); + border-radius: 8px; + } + + span { + color: var(--black); + } + } + + &:active { + transition: transform 0.2s $easeInOutQuart; + transform: translate(0, 0); + box-shadow: 0 1px 6px rgba(#80ecff, 0.35); + + &:after { + transition: opacity 0.15s; + opacity: 0.5; + } + } +} diff --git a/src/components/solutions/Button.tsx b/src/components/solutions/Button.tsx new file mode 100644 index 00000000..2d58d514 --- /dev/null +++ b/src/components/solutions/Button.tsx @@ -0,0 +1,31 @@ +import { AnchorHTMLAttributes } from "react"; +import Link from "next/link"; +import classNames from "classnames"; +import styles from "./Button.module.scss"; + +export interface ButtonProps extends AnchorHTMLAttributes { + text: string; + url?: string; + theme?: "primary" | "secondary"; + classes?: string; +} + +const Button = ({ + text, + url, + theme = "primary", + classes, + ...props +}: ButtonProps) => { + return ( + + {text} + + ); +}; + +export default Button; diff --git a/src/components/solutions/DetailsSection.module.scss b/src/components/solutions/DetailsSection.module.scss new file mode 100644 index 00000000..8517aab4 --- /dev/null +++ b/src/components/solutions/DetailsSection.module.scss @@ -0,0 +1,108 @@ +@import "../../scss/solutions/_variables.scss"; + +.Content { + * { + padding: 0; + margin: 0; + } +} + +.DetailsSection { + display: grid; + grid-template-columns: 1fr; + grid-gap: 40px; + padding: 0 24px 64px; + + a { + svg { + transition: transform 0.25s $easeInOutQuart; + } + + &:hover svg { + transform: translate(4px, 0); + } + } +} + +.Detail { + text-align: center; + + h3, + p { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + color: var(--white); + margin-top: 0; + margin-bottom: 0; + } + + h3 { + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + + svg { + box-sizing: content-box; + width: 22px; + } + } + + p { + color: var(--grey-300); + + a { + color: var(--grey-300); + text-decoration: underline; + } + } +} + +@include breakpoint(md) { + .DetailsSection { + grid-template-columns: 1fr 1fr; + grid-row-gap: 40px; + grid-column-gap: 112px; + padding: 64px 40px; + } + + .Detail { + text-align: left; + + h3, + p { + font-size: 20px; + margin-left: 0; + max-width: 100%; + } + + h3 { + justify-content: flex-start; + + svg { + width: 24px; + } + } + } +} + +@include breakpoint(xl) { + .Content { + padding: 0 0 128px; + } + + .DetailsSection { + grid-row-gap: 80px; + grid-column-gap: 224px; + } + + .Detail { + h3, + p { + font-size: 20px; + line-height: 1.12; + letter-spacing: -0.02em; + } + } +} diff --git a/src/components/solutions/DetailsSection.tsx b/src/components/solutions/DetailsSection.tsx new file mode 100644 index 00000000..3857e735 --- /dev/null +++ b/src/components/solutions/DetailsSection.tsx @@ -0,0 +1,54 @@ +import { ReactNode } from "react"; +import Link from "next/link"; +import classNames from "classnames"; +import styles from "./DetailsSection.module.scss"; +import CaretIcon from "@/components/icons/Caret"; +import { AnimatedText } from "../shared/Text"; + +interface DetailProps { + title: string; + text?: string; + url?: string; + arrow?: boolean; +} + +export const Detail = ({ url, title, text, arrow = false }: DetailProps) => ( +
+ {url ? ( + + + {title} + {arrow && } + + + ) : ( + + {title} + + )} + + {text && ( + + {text} + + )} +
+); + +const DetailsSection = ({ + className, + children, +}: { + className?: string; + children: ReactNode; +}) => { + return ( +
+ {children} +
+ ); +}; + +export default DetailsSection; diff --git a/src/components/solutions/DeveloperResources.module.scss b/src/components/solutions/DeveloperResources.module.scss new file mode 100644 index 00000000..9ef69186 --- /dev/null +++ b/src/components/solutions/DeveloperResources.module.scss @@ -0,0 +1,168 @@ +@import "../../scss/solutions/_variables.scss"; + +.DeveloperResources { + padding: 80px 0; + background-color: var(--grey-500); + background-image: url("/solutions/loyalty/gradient-mobile.webp"); + background-size: contain; + background-position: top center; + background-repeat: no-repeat; +} + +.Container { + padding: 0 24px; + margin: 0 auto; + display: flex; + flex-direction: column; + align-items: center; +} + +.TitleBlock { + text-align: center; + + h2 { + font-size: 40px; + font-weight: 400; + line-height: 1.08; + text-transform: uppercase; + font-family: var(--abc-mono-font); + } + + p { + color: var(--grey-250); + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; + margin: 24px auto 0; + } + + .ButtonWrapper { + a { + max-width: max-content; + margin: 40px auto 64px; + } + } +} + +.LinksWrapper { + margin-top: 40px; + display: flex; + flex-direction: column; + gap: 32px; + list-style-type: none; + padding: 0; + margin: 40px 0 64px; + + li { + text-align: left; + } + + a { + color: var(--grey-100); + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + display: flex; + align-items: center; + justify-content: center; + gap: 4px; + cursor: pointer; + + svg { + width: 24px; + height: 24px; + transition: 0.2s ease-in; + } + + &:hover { + svg { + transform: translateX(4px); + } + } + } + + span { + color: #6c6a81; + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.26px; + text-transform: uppercase; + display: block; + margin-top: 8px; + } +} + +.ImageWrapper { + position: relative; + display: flex; + justify-content: end; +} + +.CustomMedia { + max-width: 400px; +} + +@include breakpoint(md) { + .DeveloperResources { + background-image: url("/solutions/loyalty/gradient-tablet.webp"); + } + + .TitleBlock { + h2 { + font-size: 48px; + } + } +} + +@include breakpoint(xl) { + .DeveloperResources { + padding: 128px 0; + background-image: url("/solutions/loyalty/gradient-desktop.webp"); + background-position: top left; + } + + .Container { + display: grid; + grid-template-columns: 0.8fr 1fr; + grid-column-gap: 96px; + grid-row-gap: 40px; + align-items: center; + max-width: 1064px; + padding: 0; + } + + .TitleBlock { + text-align: left; + + h2 { + font-size: 56px; + } + + p { + font-size: 24px; + color: var(--grey-300); + margin-left: 0; + max-width: 386px; + } + } + + .LinksWrapper { + margin: 40px 0 0; + width: 100%; + gap: 16px; + + a { + justify-content: flex-start; + } + + span { + font-size: 15px; + line-height: 1.15; + letter-spacing: 0.3px; + margin-top: 10px; + } + } +} diff --git a/src/components/solutions/DeveloperResources.tsx b/src/components/solutions/DeveloperResources.tsx new file mode 100644 index 00000000..dd1f3371 --- /dev/null +++ b/src/components/solutions/DeveloperResources.tsx @@ -0,0 +1,108 @@ +import Link from "next/link"; +import Image from "next/image"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; + +import Button from "@/components/solutions/Button"; +import { AnimatedText } from "@/components/shared/Text"; +import CaretIcon from "@/components/icons/Caret"; + +import styles from "./DeveloperResources.module.scss"; + +import resourcesImage from "../../../assets/solutions/developer-resources.svg"; + +interface DeveloperResourcesLinkProps { + title: string; + text?: string; + link?: string; +} + +export const DeveloperResourcesLink = ({ + title, + text, + link, +}: DeveloperResourcesLinkProps) => ( +
  • + {link && ( + + {title} + + + )} + {text && {text}} +
  • +); + +interface DeveloperResourcesProps { + title: string; + subtitle?: string; + links: React.ReactNode; + id: string; + buttonText?: string; + buttonUrl?: string; + image?: string; + className?: string; + imageClassName?: string; + media?: React.ReactNode; +} + +const DeveloperResources = ({ + title, + subtitle, + links, + id, + buttonText, + buttonUrl, + image, + className, + imageClassName, + media, +}: DeveloperResourcesProps) => { + return ( +
    +
    +
    + + + + + {subtitle && ( + + + + )} + + {links &&
      {links}
    } + + {buttonText && buttonUrl && ( +
    +
    + )} +
    + +
    + {media ? ( +
    {media}
    + ) : ( + {title} + )} +
    +
    +
    + ); +}; + +export default DeveloperResources; diff --git a/src/components/solutions/EcosystemSlider.jsx b/src/components/solutions/EcosystemSlider.jsx new file mode 100644 index 00000000..0d54c625 --- /dev/null +++ b/src/components/solutions/EcosystemSlider.jsx @@ -0,0 +1,89 @@ +import Link from "next/link"; +import Image from "next/image"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; +import { ChevronRight } from "react-feather"; +import Marquee from "react-fast-marquee"; + +import CardsSlider from "@/components/shared/CardsSlider"; +import { AnimatedText, GradientText } from "@/components/shared/Text"; + +import styles from "./EcosystemSlider.module.scss"; + +export const Card = ({ img, url, title, text, className }) => { + const Content = () => ( + <> +
    + {title} +
    +
    + {title} + {url && } +
    +

    {text}

    + + ); + + return ( +
    + {url ? ( + + + + ) : ( + + )} +
    + ); +}; + +const EcosystemSlider = ({ + titleKey, + textKey, + cards, + className, + titleBlockClassName, +}) => { + return ( +
    +
    + {titleKey && ( + + + ), + }} + /> + + )} + + {textKey && ( + + + + )} +
    + +
    + +
    + +
    + +
    + {cards.map((card, index) => ( +
    + {card} +
    + ))} +
    +
    +
    +
    + ); +}; + +export default EcosystemSlider; diff --git a/src/components/solutions/EcosystemSlider.module.scss b/src/components/solutions/EcosystemSlider.module.scss new file mode 100644 index 00000000..847329aa --- /dev/null +++ b/src/components/solutions/EcosystemSlider.module.scss @@ -0,0 +1,236 @@ +@import "../../scss/solutions/_variables.scss"; + +.EcosystemSlider { + padding: 64px 0; + position: relative; +} + +.Title { + font-size: 40px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.03em; + text-align: center; + color: var(--white); + text-transform: capitalize; + margin: 0; + padding: 0 24px; + + strong { + background: var(--gradient-2); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } +} + +.Text { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: center; + color: var(--grey-250); + max-width: 698px; + padding: 0 24px; + margin: 24px auto 0; + + @include breakpoint(md) { + font-size: 24px; + } + + @include breakpoint(xl) { + font-size: 32px; + font-weight: 500; + line-height: 1.25; + text-align: center; + max-width: 886px; + } +} + +.Card { + width: 12rem; + + a { + svg, + h5 { + transition: 0.2s ease-in; + } + + svg { + width: 20px; + } + + img { + transition: transform 0.2s ease-in; + } + + .ImageWrapper { + overflow: hidden; + border-radius: 16px; + } + + &:hover { + svg { + transform: translateX(4px); + } + + img { + transform: scale(1.05); + } + } + } + + h5 { + font-size: 20px; + text-transform: capitalize; + margin-block: 24px 16px !important; + display: flex; + align-items: center; + color: var(--white); + gap: 12px; + } + + p { + font-size: 16px; + color: var(--grey-300); + margin: 0px; + line-height: 1.12; + font-weight: 700; + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + max-height: min(calc(100vw * 0.5), 220px); + border-radius: 16px; + } +} + +.MobileCarousel { + overflow: hidden; + position: relative; + + * { + outline: none !important; + } + + .CarouselArrows { + padding: 32px 0 0; + display: flex; + justify-content: center; + gap: 16px; + width: 100%; + + button { + background: var(--grey-500); + border: none; + border-radius: 50%; + padding: 8px; + cursor: pointer; + transition: background 0.3s; + width: 40px; + height: 40px; + display: flex; + justify-content: center; + align-items: center; + + &.PrevArrow { + transform: rotate(180deg); + } + + &:hover { + background: var(--grey-300); + } + + img { + width: 24px; + height: 24px; + } + + svg { + width: 16px; + width: 16px; + } + } + } +} + +.DesktopMarquee { + position: relative; + margin-top: 80px; + + &:before, + &:after { + content: ""; + width: 300px; + height: 100%; + position: absolute; + left: 0; + top: 0; + z-index: 2; + } + + &:before { + background: linear-gradient( + 90deg, + #0f0a16 0.5%, + rgba(15, 10, 22, 0) 99.21% + ); + } + + &:after { + right: 0 !important; + top: 0; + left: unset; + background: linear-gradient( + 272deg, + #0f0a16 0.5%, + rgba(15, 10, 22, 0) 99.21% + ); + } + + .Cards { + display: flex; + gap: 16px; + margin: 0 8px; + } + + .Card { + max-width: 238px; + padding: 0; + } + + @include breakpoint(xl) { + margin-top: 96px; + } +} + +@include breakpoint(md) { + .Title { + font-size: 56px; + } +} + +@include breakpoint(lg) { + .Title { + font-size: 64px; + } +} + +@include breakpoint(xl) { + .EcosystemSlider { + padding: 128px 0; + } + + .Title { + font-size: 80px; + } + + .Text { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.64px; + } +} diff --git a/src/components/solutions/EcosystemSliderWithTabs.module.scss b/src/components/solutions/EcosystemSliderWithTabs.module.scss new file mode 100644 index 00000000..2538c8c9 --- /dev/null +++ b/src/components/solutions/EcosystemSliderWithTabs.module.scss @@ -0,0 +1,226 @@ +@import "../../scss/solutions/_variables.scss"; + +.EcosystemSliderWithTabs { + padding: 64px 0; + position: relative; +} + +.Title { + font-size: 40px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.01em; + text-align: center; + color: var(--white); + text-transform: capitalize; + margin: 0; + padding: 0 24px; + + strong { + background: var(--gradient-2); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } +} + +.TabsRoot { + margin-top: 64px; + + @include breakpoint(xl) { + margin-top: 80px; + } +} + +.TabsList { + flex-shrink: 0; + display: flex; + border-radius: 12px; + border: 4px solid var(--grey-450); + background-color: var(--grey-450); + max-width: 343px; + width: 100%; + margin: 0 auto; +} + +.TabsTrigger { + font-family: inherit; + background-color: var(--grey-450); + padding: 0 20px; + height: 45px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 15px; + line-height: 1; + color: var(--grey-250); + user-select: none; + font-weight: 700; + + &[data-state="active"] { + color: var(--white); + background: var(--grey-300); + border-radius: 4px; + } + + &:focus { + position: relative; + } +} + +.TabsContent { + flex-grow: 1; + padding: 0; + outline: none; + transition: opacity $duration-shorter $easeInQuart; + transition: all 1s; + min-height: 320px; + position: relative; + + &[hidden] { + display: block !important; + opacity: 0; + position: absolute; + top: 0; + } + + &[data-state="inactive"] { + opacity: 0; + } + + &[data-state="active"] { + opacity: 1; + } +} + +.Card { + width: 164px; + + a { + svg { + transition: 0.2s ease-in; + width: 20px; + } + + &:hover { + svg { + transform: translateX(4px); + } + } + } + + h5 { + font-size: 19.42px; + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + text-align: left; + text-transform: capitalize; + margin-block: 24px 16px !important; + display: flex; + align-items: center; + color: var(--grey-100); + gap: 12px; + } + + p { + color: var(--grey-300); + margin: 0px; + font-size: 14.56px; + font-weight: 700; + line-height: 1.3; + text-align: left; + } + + img { + width: 100%; + height: 100%; + object-fit: cover; + max-height: min(calc(100vw * 0.5), 220px); + border-radius: 16px; + } +} + +.MobileCarousel { + overflow: hidden; + position: relative; +} + +.DesktopMarquee { + position: relative; + margin-top: 80px; + + &:before, + &:after { + content: ""; + width: 300px; + height: 100%; + position: absolute; + left: 0; + top: 0; + z-index: 2; + } + + &:before { + background: linear-gradient( + 90deg, + #0f0a16 0.5%, + rgba(15, 10, 22, 0) 99.21% + ); + } + + &:after { + right: 0 !important; + top: 0; + left: unset; + background: linear-gradient( + 272deg, + #0f0a16 0.5%, + rgba(15, 10, 22, 0) 99.21% + ); + } + + .Cards { + display: flex; + gap: 16px; + margin: 0 8px; + } + + .Card { + width: 12rem; + padding: 0; + } + + @include breakpoint(xl) { + margin-top: 40px; + } +} + +@include breakpoint(md) { + .Title { + font-size: 56px; + } +} + +@include breakpoint(lg) { + .Title { + font-size: 64px; + } +} + +@include breakpoint(xl) { + .EcosystemSliderWithTabs { + padding: 128px 0; + } + + .Title { + font-size: 80px; + } + + .Text { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.64px; + } +} diff --git a/src/components/solutions/EcosystemSliderWithTabs.tsx b/src/components/solutions/EcosystemSliderWithTabs.tsx new file mode 100644 index 00000000..1626fbe9 --- /dev/null +++ b/src/components/solutions/EcosystemSliderWithTabs.tsx @@ -0,0 +1,155 @@ +import { useState, ReactNode, FC } from "react"; +import Link from "next/link"; +import Image from "next/image"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; +import * as Tabs from "@radix-ui/react-tabs"; +import { ChevronRight } from "react-feather"; +import Marquee from "react-fast-marquee"; + +import CardsSlider from "@/components/shared/CardsSlider"; +import { AnimatedText, GradientText } from "@/components/shared/Text"; + +import styles from "./EcosystemSliderWithTabs.module.scss"; + +export const Card = ({ img, url, title, text }) => { + const Content = () => ( + <> + {title} +
    + {title} + +
    +

    {text}

    + + ); + + return ( +
    + {url ? ( + + + + ) : ( + + )} +
    + ); +}; + +interface EcosystemSliderWithTabsProps { + titleKey: string; + tab1Title: string; + tab2Title: string; + tab1Cards: ReactNode[]; + tab2Cards: ReactNode[]; + className?: string; +} + +const EcosystemSliderWithTabs: FC = ({ + titleKey, + tab1Title, + tab2Title, + tab1Cards, + tab2Cards, + className, +}) => { + const [value, setValue] = useState("tab1"); + + return ( +
    +
    + + + ), + }} + /> + +
    + + + + setValue("tab1")} + > + {tab1Title} + + setValue("tab2")} + > + {tab2Title} + + + + +
    + +
    + +
    + +
    + {tab1Cards.map((card, index) => ( +
    + {card} +
    + ))} +
    +
    +
    +
    + + +
    + +
    + +
    + +
    + {tab2Cards.map((card, index) => ( +
    + {card} +
    + ))} +
    +
    +
    +
    +
    +
    + ); +}; + +export default EcosystemSliderWithTabs; diff --git a/src/components/solutions/FooterCallout.module.scss b/src/components/solutions/FooterCallout.module.scss new file mode 100644 index 00000000..a1a55e03 --- /dev/null +++ b/src/components/solutions/FooterCallout.module.scss @@ -0,0 +1,184 @@ +@import "../../scss/solutions/_variables.scss"; + +.FooterCallout { + border-top: 1px solid var(--grey-450); + border-bottom: 1px solid var(--grey-450); + background: var(--grey-500); + position: relative; +} + +.TopSection, +.ButtonLargeWrapper { + padding-left: 24px; + padding-right: 24px; +} + +.TopSection { + padding-top: 64px; + padding-bottom: 64px; +} + +.Title { + color: var(--grey-100); + text-align: center; + font-size: 32px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.03em; + margin: 0; +} + +.TextBtnWrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 24px; +} + +.Text { + color: var(--grey-300); + text-align: center; + font-size: 18px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.01em; + margin: 40px auto 0; + max-width: 100%; + + strong { + color: var(--grey-100); + display: block; + margin-bottom: 0; + } +} + +.Button { + font-size: 15px; + font-weight: 700; + line-height: 1.3; + text-align: left; + background: var(--purple); + color: var(--white); + border-radius: 8px; + padding: 12px 30px; + display: block; + width: fit-content; + + &:hover { + background: var(--gradient-3); + color: var(--black); + transition: background 1s ease-out; + } +} + +.ButtonLargeWrapper { + padding: 40px 24px; + border-top: 1px solid var(--grey-450); +} + +.ButtonLarge { + text-align: center; + font-size: 20px; + font-weight: 700; + line-height: 1.2; + padding: 40px 24px; + background-color: var(--purple); + color: var(--white); + border-radius: 8px; + width: 100%; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + position: relative; + + svg { + margin-top: 24px; + transition: 0.2s ease-in; + } + + strong { + background: var(--gradient-6); + background-clip: text; + -webkit-text-fill-color: transparent; + text-align: center; + } + + @media (max-width: #{$screen-xl-min - 1}) { + padding: 24px; + display: block; + + svg { + margin: 0; + margin-left: 4px; + } + } + + @include breakpoint(sm) { + text-align: left; + } + + @include breakpoint(xl) { + grid-column: 1 / -1; + margin-top: 0; + flex-direction: row; + justify-content: flex-start; + padding: 40px 50px; + gap: 8px; + font-size: 28px; + + svg { + margin-top: 0; + position: absolute; + right: 48px; + } + + &:before { + display: none; + } + } +} + +@include breakpoint(md) { + .Title { + font-size: 48px; + } +} + +@include breakpoint(xl) { + .TopSection { + display: grid; + grid-template-columns: auto auto; + justify-content: space-between; + padding-top: 80px; + padding-bottom: 80px; + } + + .TextBtnWrapper { + align-items: flex-start; + max-width: 331px; + align-self: flex-end; + } + + .Title { + text-align: left; + font-size: 56px; + max-width: 458px; + } + + .Text { + font-size: 20px; + margin-top: 0; + text-align: left; + } + + .ButtonLargeWrapper { + border: none; + padding: 80px 0; + + svg { + width: 40px; + height: 40px; + } + } +} diff --git a/src/components/solutions/FooterCallout.tsx b/src/components/solutions/FooterCallout.tsx new file mode 100644 index 00000000..b6d1436c --- /dev/null +++ b/src/components/solutions/FooterCallout.tsx @@ -0,0 +1,103 @@ +import Link from "next/link"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; + +import Button from "@/components/solutions/Button"; +import CaretIcon from "@/components/icons/Caret"; +import { AnimatedText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./FooterCallout.module.scss"; + +interface FooterCalloutProps { + title?: string; + text?: string; + btnText?: string; + btnUrl?: string; + btnLargeText?: string; + btnLargeUrl?: string; + className?: string; + topSectionClassName?: string; + buttonLargeClassName?: string; +} + +const FooterCallout = ({ + title, + text, + btnText, + btnUrl, + btnLargeText, + btnLargeUrl, + className, + topSectionClassName, + buttonLargeClassName, +}: FooterCalloutProps) => { + return ( +
    +
    +
    + {title && ( + + {title} + + )} + +
    + {text && ( + + + + )} + + {btnText && btnUrl && ( + +
    +
    + + {btnLargeText && btnLargeUrl && ( +
    + + {btnLargeUrl.includes("@") ? ( + + + + + ) : ( + + + + + )} + +
    + )} +
    +
    + ); +}; + +export default FooterCallout; diff --git a/src/components/solutions/Layout.module.scss b/src/components/solutions/Layout.module.scss new file mode 100644 index 00000000..2ac6cacc --- /dev/null +++ b/src/components/solutions/Layout.module.scss @@ -0,0 +1,122 @@ +@import "../../scss/solutions/_variables.scss"; + +.Layout { + :global { + // --- Colors --- + --white: #fff; + --grey-100: #eaebf0; + --grey-200: #d0d0dc; + --grey-250: #a2a1b2; + --grey-300: #6c6a81; + --grey-400: #504d61; + --grey-450: #322f43; + --grey-500: #1d1a23; + --black: #0f0a16; + + --pink: #eb54bc; + --purple: #9945ff; + --blue: #64a8f2; + --blue-light: #80ecff; + --green: #14f195; + + // --- Gradient variables --- + --gradient-1: linear-gradient( + 90deg, + #64a8f2 0%, + #9945ff 49.61%, + #eb54bc 100% + ); + --gradient-2: linear-gradient( + 90deg, + #80ecff 0.17%, + #64a8f2 50.54%, + #9945ff 99.77% + ); + --gradient-3: linear-gradient( + 90deg, + #14f195 -6.38%, + #80ecff 51.28%, + #64a8f2 106.12% + ); + --gradient-4: linear-gradient(90deg, #14f195 0%, #64a8f2 50%, #9945ff 100%); + --gradient-5: linear-gradient( + 45deg, + #9945ff 10.43%, + #8752f3 30.84%, + #5497d5 49.4%, + #43b4ca 58.68%, + #28e0b9 69.81%, + #14f195 93.01% + ); + --gradient-6: linear-gradient( + 90deg, + #14f195 -6.38%, + #80ecff 30.93%, + #64a8f2 69.37%, + #64a8f2 106.68% + ); + --gradient-7: linear-gradient(90deg, #9945ff 0%, #64a8f2 50%, #14f195 100%); + --gradient-8: linear-gradient( + 270deg, + #9945ff 0%, + #eb54bc 50.57%, + #ff754a 100% + ); + --gradient-9: linear-gradient( + 90deg, + #ffffff, + #ffffff, + #eb54bc, + #9945ff, + #eb54bc, + #64a8f2, + #ffffff + ); + --gradient-10: linear-gradient( + 90deg, + #64a8f2 0%, + #9945ff 21.95%, + #eb54bc 32.2%, + #ffffff 61.46% + ); + --gradient-11: linear-gradient( + 90deg, + #64a8f2 0%, + #9945ff 41.95%, + #eb54bc 82.2% + ); + + // --- Fonts --- + --abc-mono-font: "ABC Mono", monospace, sans-serif; + + background: var(--black); + + // --- Breakpoint utility classes --- + // Common containers use this max width + .page-width { + @include max-width(1068px); + box-sizing: content-box; + + @include breakpoint(sm) { + padding-left: 40px; + padding-right: 40px; + } + } + } +} + +// --- Overrides --- + +.Header { + &, + nav { + background: var(--black); + } +} + +.Footer { + background: var(--grey-500); + margin-top: 0; + border-radius: 0; + border: none; +} diff --git a/src/components/solutions/LongformItem.module.scss b/src/components/solutions/LongformItem.module.scss new file mode 100644 index 00000000..3669f539 --- /dev/null +++ b/src/components/solutions/LongformItem.module.scss @@ -0,0 +1,362 @@ +@import "../../scss/solutions/_variables.scss"; + +.LongformItem { + padding: 64px 24px; + display: flex; + flex-direction: column; + gap: 40px; + align-items: center; + max-width: 900px; + margin: { + left: auto; + right: auto; + } + + img.desktop-image { + display: none !important; + } + + a { + color: var(--white); + } + + &[data-media-desktop-placement="above"] { + gap: 24px; + + .MediaComponent { + position: relative; + + svg { + max-height: initial; + } + + &:after { + content: ""; + position: absolute; + display: block; + width: 100%; + height: 30%; + background: linear-gradient( + 180deg, + rgba(15, 10, 22, 0) 2.47%, + #0f0a16 79.46% + ); + bottom: 0; + left: 0; + z-index: 1; + } + } + } + + &[data-media-desktop-placement="below"] { + gap: 40px; + flex-direction: column-reverse; + + .MediaComponent { + position: relative; + + svg { + max-height: initial; + } + + &:after { + content: ""; + position: absolute; + display: block; + width: 100%; + height: 30%; + background: linear-gradient( + 180deg, + rgba(15, 10, 22, 0) 2.47%, + #0f0a16 79.46% + ); + bottom: 0; + left: 0; + z-index: 1; + } + } + } +} + +.MediaComponent { + height: auto; + width: min(80vw, 100%); + display: flex; + @include max-width(1020px); + justify-content: center; + + [role="button"] { + cursor: initial; + } + + svg { + max-height: 70vh; + } +} + +@media (max-width: #{$screen-md-min - 1}) { + .MediaComponent { + svg { + max-height: 65vh; + } + } +} + +.Title { + font-size: 32px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + margin: 0; + width: 100%; + text-align: center; + + a { + color: var(--white); + } +} + +.Subtitle { + font-size: 16px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + color: var(--grey-300); + margin: 0; + + a { + color: var(--grey-300) !important; + text-decoration: underline; + } +} + +.TextBlock { + text-align: center; + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + gap: 16px; +} + +.SeeMoreWrapper { + margin-top: 40px; + width: 100%; + + .SeeMoreItemsWrapper { + display: flex; + flex-direction: column; + gap: 24px; + padding-top: 24px; + } + + .LongformSeeMoreItem { + font-size: 16px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + color: var(--grey-300); + + strong, + a { + color: var(--grey-250); + } + } +} + +@media (min-width: $screen-md-min) and (max-width: #{$screen-xl-min - 1}) { + .LongformItem { + &[data-media-desktop-placement="left"], + &[data-media-desktop-placement="right"] { + .MediaComponent { + max-width: 300px; + } + } + } +} + +@media (max-width: #{$screen-xl-min - 1}) { + .LongformItem { + max-width: 768px !important; + } +} + +@include breakpoint(md) { + .LongformItem { + padding-top: 64px; + padding-bottom: 64px; + + &[data-media-desktop-placement="above"] { + .SeeMoreWrapper { + .CollapsibleTrigger { + justify-content: center; + } + + .LongformSeeMoreItem { + text-align: center; + } + } + } + + &[data-media-desktop-placement="left"], + &[data-media-desktop-placement="right"] { + > * { + flex: 1; + } + + .MediaComponent { + display: flex; + width: auto; + grid-template-columns: 1fr 1fr; + gap: 40px; + align-items: center; + justify-content: center; + } + + .TextBlock { + max-width: 422px; + gap: 24px; + + * { + text-align: left; + } + } + + .SeeMoreWrapper { + margin-top: 24px; + } + } + + &[data-media-desktop-placement="left"] { + flex-direction: row; + .MediaComponent { + margin-left: 0; + } + } + + &[data-media-desktop-placement="right"] { + flex-direction: row-reverse; + .MediaComponent { + justify-content: flex-end; + } + } + } + + .Title { + font-size: 40px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + } + + .Subtitle { + @include max-width(600px); + font-size: 20px; + } + + .SeeMoreWrapper { + .CollapsibleTrigger { + justify-content: flex-start; + } + + .CollapsibleTrigger, + .LongformSeeMoreItem { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: left; + } + + .LongformSeeMoreItem { + strong, + a { + color: var(--grey-250); + } + + p { + max-width: 100%; + } + } + } +} + +@include breakpoint(lg) { + .LongformItem { + &[data-media-desktop-placement="left"], + &[data-media-desktop-placement="right"] { + gap: 64px; + } + } +} + +@include breakpoint(xl) { + .LongformItem { + &[data-media-desktop-placement="above"] { + .TextBlock { + &[data-text-content-desktop-direction="column"] { + flex-direction: column; + + .Title { + text-align: center; + } + + .Subtitle { + text-align: center; + } + } + } + + .SeeMoreWrapper { + .CollapsibleTrigger { + justify-content: flex-start; + } + + .LongformSeeMoreItem { + text-align: left; + } + } + } + + &[data-media-desktop-placement="right"] { + .MediaComponent { + margin-right: 0; + } + } + } + + .Title { + font-size: 48px; + text-align: left; + } + + .Subtitle { + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.03em; + text-align: left; + } + + .TextBlock { + &[data-text-content-desktop-direction="row"] { + flex-direction: row; + max-width: initial; + gap: 132px; + align-items: flex-start; + + .Title { + flex: 1; + } + + .SubTextBlock { + flex: 1.2; + } + + .LongformSeeMoreItem { + text-align: left; + } + } + } +} diff --git a/src/components/solutions/LongformItem.tsx b/src/components/solutions/LongformItem.tsx new file mode 100644 index 00000000..159e1536 --- /dev/null +++ b/src/components/solutions/LongformItem.tsx @@ -0,0 +1,124 @@ +/** + * LongformItem component is a versatile layout component designed to display + * media and text content in a structured format. It supports various configurations + * for media placement, text direction, and collapsible content sections. + * + * @component + * @param {ReactNode} [mediaComponent] - The media component to be displayed (e.g., image, video). + * @param {"left" | "right"} [mediaDesktopPlacement] - The placement of the media component on desktop view. If not specified, the media will be placed on top. + * @param {ReactNode} [titleComponent] - The title component to be displayed. + * @param {ReactNode} [subtitleComponent] - The subtitle component to be displayed. + * @param {"row" | "column"} [textContentDesktopDirection] - The direction of the text content on desktop view. Defaults to "column". + * @param {string} [seeMoreTitle] - The title for the collapsible "See More" section. + * @param {ReactNode[]} [seeMoreItems] - The items to be displayed within the "See More" section. + * @param {string} [className] - Additional class names to apply to the component. + * + * @example + * } + * mediaDesktopPlacement="left" + * titleComponent={

    Title

    } + * subtitleComponent={

    Subtitle

    } + * textContentDesktopDirection="column" + * seeMoreTitle="See More" + * seeMoreItems={[

    Item 1

    ,

    Item 2

    ]} + * className="custom-class" + * /> + */ + +import type { ReactNode } from "react"; +import classNames from "classnames"; + +import Text from "@/components/shared/Text"; +import CollapsibleContent from "@/components/shared/CollapsibleContent"; + +import styles from "./LongformItem.module.scss"; + +interface LongformItemProps { + mediaComponent?: ReactNode; + mediaDesktopPlacement?: "left" | "right" | "above"; + titleComponent?: ReactNode; + subtitleComponent?: ReactNode; + textContentDesktopDirection?: "row" | "column"; + seeMoreTitle?: string; + seeMoreItems?: ReactNode[]; + className?: string; + mediaClassName?: string; + customContent?: ReactNode; +} + +export const LongformSeeMoreItem = ({ children }: { children: ReactNode }) => { + return
    {children}
    ; +}; + +const LongformItem = ({ + mediaComponent, + mediaDesktopPlacement = "above", + titleComponent, + subtitleComponent, + textContentDesktopDirection = "column", + seeMoreTitle, + seeMoreItems, + className, + mediaClassName, + customContent, +}: LongformItemProps) => { + return ( +
    + {mediaComponent && ( +
    + {mediaComponent} +
    + )} + +
    + {customContent ? ( +
    {customContent}
    + ) : ( + <> + {titleComponent && ( + + {titleComponent} + + )} + +
    + {subtitleComponent && ( + + {subtitleComponent} + + )} + + {seeMoreTitle && seeMoreItems && ( +
    + +
    + {seeMoreItems.map((item, index) => ( +
    + {item} +
    + ))} +
    +
    +
    + )} +
    + + )} +
    +
    + ); +}; + +export default LongformItem; diff --git a/src/components/solutions/LottieHeroWithTabs.module.scss b/src/components/solutions/LottieHeroWithTabs.module.scss new file mode 100644 index 00000000..633e6afd --- /dev/null +++ b/src/components/solutions/LottieHeroWithTabs.module.scss @@ -0,0 +1,151 @@ +@import "~@/scss/solutions/_variables.scss"; + +.GradientBgWrapper { + position: relative; + width: 100%; + height: 100%; +} + +.GradientBg { + width: 100% !important; + height: auto !important; + z-index: 0; + position: absolute; +} + +.TabsRoot { + display: flex; + flex-direction: column; + padding: 24px 0; + margin-top: 40px; + position: relative; + + @include breakpoint(lg) { + padding-bottom: 9rem; + } +} + +.TabsList { + flex-shrink: 0; + display: flex; + border-radius: 12px; + border: 4px solid var(--grey-500); + background-color: var(--grey-500); + max-width: 343px; + width: 100%; + margin: 20px auto; +} + +.TabsTrigger { + font-family: inherit; + background-color: var(--grey-500); + padding: 0 16px; + height: 45px; + flex: 1; + display: flex; + align-items: center; + justify-content: center; + font-size: 14px; + line-height: 1; + color: var(--grey-250); + user-select: none; + font-weight: 700; + + &[data-state="active"] { + color: var(--grey-100); + background: var(--grey-300); + border-radius: 8px; + } + + &:focus { + position: relative; + } +} + +.TabsContent { + flex-grow: 1; + padding: 0; + outline: none; + transition: + opacity $duration-shorter $easeInQuart, + transform $duration-shorter $easeInQuart; + + &[hidden] { + display: block !important; + opacity: 0; + transform: translateY($anim-translate-y); + } + + &[data-state="inactive"] { + opacity: 0; + } + + &[data-state="active"] { + opacity: 1; + transform: translateY(0); + } +} + +.Title { + font-size: 44px; + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.02em; + text-align: center; + color: var(--white); + @include max-width(950px); + padding: 0 24px; +} + +.Subtitle { + font-size: 24px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: center; + margin-top: 20px; + color: var(--grey-250); + max-width: 771px; + margin: 20px auto 0; + padding: 0 24px; +} + +.LottieWrapper { + width: 100%; + max-width: 300px; + margin: 40px auto 0; +} + +@include breakpoint(md) { + .Title { + font-size: 64px; + } +} + +@include breakpoint(lg) { + .Title { + font-size: 80px; + padding: 0 72px; + } + + .LottieWrapper { + max-width: 1000px; + margin-top: 100px; + padding: 0 24px; + } +} + +@include breakpoint(xl) { + .TabsRoot { + margin-top: 4rem; + } + + .Title { + line-height: 1; + } + + .Subtitle { + font-size: 28px; + line-height: 1.25; + } +} diff --git a/src/components/solutions/LottieHeroWithTabs.tsx b/src/components/solutions/LottieHeroWithTabs.tsx new file mode 100644 index 00000000..35179224 --- /dev/null +++ b/src/components/solutions/LottieHeroWithTabs.tsx @@ -0,0 +1,191 @@ +import { useState, ReactNode } from "react"; +import Image from "next/image"; +import classNames from "classnames"; +import * as Tabs from "@radix-ui/react-tabs"; +import Lottie from "react-lottie"; + +import { OpacityInText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "@/components/solutions/LottieHeroWithTabs.module.scss"; + +interface LottieHeroWithTabsProps { + tabs: { + buttonTitle: string; + content: { + title: string; + subtitle: string; + lottieMobile: ReactNode; + lottieDesktop: ReactNode; + }; + }; + tabListAriaLabel: string; + className?: string; +} + +const LottieHeroWithTabs = ({ + tabs, + tabListAriaLabel, + className, +}: LottieHeroWithTabsProps) => { + const [value, setValue] = useState("tab1"); + + return ( + <> + + +
    + Gradient background + Gradient background +
    +
    + + +
    + Gradient background + Gradient background +
    +
    + + + + {tabs[0].content.title} + + + + {tabs[0].content.subtitle} + + + + + + {tabs[1].content.title} + + + {tabs[1].content.subtitle} + + + + + + setValue("tab1")} + > + {tabs[0].buttonTitle} + + setValue("tab2")} + > + {tabs[1].buttonTitle} + + + + + + +
    + +
    +
    + +
    +
    +
    + + + +
    + +
    +
    + +
    +
    +
    +
    + + ); +}; + +export default LottieHeroWithTabs; diff --git a/src/components/solutions/MediaOptionSelection.module.scss b/src/components/solutions/MediaOptionSelection.module.scss new file mode 100644 index 00000000..7dc09644 --- /dev/null +++ b/src/components/solutions/MediaOptionSelection.module.scss @@ -0,0 +1,132 @@ +@import "~@/scss/solutions/_variables.scss"; + +.MediaOptionSelection { + display: flex; + flex-direction: column; + padding: 64px 24px; + gap: 40px; + + @include breakpoint(md) { + flex-direction: row; + } + + @include breakpoint(lg) { + padding: { + top: 84px; + bottom: 84px; + } + } +} + +.MediaWrapper { + @include breakpoint(md) { + flex: 1; + + svg { + height: 100%; + width: auto; + } + + div { + display: flex; + justify-content: center; + } + } +} + +.ContentWrapper { + @include breakpoint(md) { + width: 300px; + display: flex; + flex-direction: column; + justify-content: center; + } + + @include breakpoint(lg) { + width: 423px; + } + + h3 { + color: var(--grey-250); + font-weight: 700; + font-size: 24px; + line-height: 1.12; + text-align: center; + letter-spacing: -0.02em; + margin: 0; + margin-bottom: 16px; + + @include breakpoint(md) { + text-align: left; + line-height: 1.15; + letter-spacing: -0.03em; + margin-bottom: 28px; + } + + @include breakpoint(lg) { + font-size: 28px; + } + } + + ul { + display: flex; + flex-direction: column; + gap: 16px; + margin-bottom: 24px; + padding: 0; + + @include breakpoint(md) { + gap: 0; + } + + @include breakpoint(lg) { + margin-bottom: 40px; + } + } + + li { + font-weight: 700; + font-size: 18px; + line-height: 1.16; + text-align: center; + letter-spacing: -0.01em; + margin: 0; + list-style: none; + + @include breakpoint(md) { + text-align: left; + line-height: 1.12; + letter-spacing: -0.02em; + padding: 18px 16px; + + &.Active { + background: var(--grey-500); + border-radius: 8px; + } + } + + @include breakpoint(lg) { + font-size: 24px; + } + } + + a { + width: max-content; + margin: 0 auto; + + @media (max-width: #{$screen-lg-min - 1}) { + font-size: 15px; + } + + @include breakpoint(md) { + margin-left: 0; + } + + @include breakpoint(lg) { + padding: { + left: 45px; + right: 45px; + } + } + } +} diff --git a/src/components/solutions/MediaOptionSelection.tsx b/src/components/solutions/MediaOptionSelection.tsx new file mode 100644 index 00000000..fb09d146 --- /dev/null +++ b/src/components/solutions/MediaOptionSelection.tsx @@ -0,0 +1,69 @@ +import { useState, FC, ReactNode, useRef } from "react"; +import clsx from "clsx"; +import Button from "@/components/solutions/Button"; +import styles from "./MediaOptionSelection.module.scss"; +import { AnimatedText } from "../shared/Text"; + +interface MediaOption { + label: string; + media: ReactNode; +} + +interface MediaOptionSelectionProps { + options: MediaOption[]; + title?: string; + buttonText?: string; + buttonUrl?: string; +} + +const MediaOptionSelection: FC = ({ + options, + title, + buttonText, + buttonUrl, +}: MediaOptionSelectionProps) => { + const [selectedIndex, setSelectedIndex] = useState(0); + const container = useRef(null); + + const handleSelect = (index: number) => { + setSelectedIndex(index); + + if (container.current) { + container.current.scrollIntoView({ behavior: "smooth" }); + } + }; + + return ( +
    +
    {options[selectedIndex].media}
    + +
    + {title && {title}} + +
      + {options.map((option, index) => ( +
    • + +
    • + ))} +
    + + {buttonText && buttonUrl && ( +
    +
    + ); +}; + +export default MediaOptionSelection; diff --git a/src/components/solutions/Stats.jsx b/src/components/solutions/Stats.jsx new file mode 100644 index 00000000..666244c2 --- /dev/null +++ b/src/components/solutions/Stats.jsx @@ -0,0 +1,121 @@ +import { useRef } from "react"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; +import gsap from "gsap"; +import { useGSAP } from "@gsap/react"; +import { ScrollTrigger } from "gsap/dist/ScrollTrigger"; +import Link from "next/link"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import { AnimatedText } from "@/components/shared/Text"; + +import styles from "./Stats.module.scss"; + +gsap.registerPlugin(useGSAP, ScrollTrigger); + +const Stats = ({ + titleContent, + subtitleKey, + kickerKey, + kickerUrl, + buttonsComponent, + stats, + className, + buttonsClassName, + statsClassName, +}) => { + const container = useRef(null); + + useGSAP( + () => { + const titles = gsap.utils.toArray(".stats-title"); + + titles.forEach((title) => { + gsap.from(title, { + backgroundPosition: "100% 0%", + ease: "none", + scrollTrigger: { + trigger: title, + start: "top 80%", + end: "top 10%", + scrub: 1, + }, + }); + }); + }, + { scope: container }, + ); + + return ( +
    +
    +
    + {titleContent && ( + + {titleContent} + + )} + + {subtitleKey && ( + + + + )} + + {buttonsComponent && ( + + {buttonsComponent} + + )} +
    + +
    + {kickerKey && !kickerUrl && ( + + {kickerKey} + + )} + + {kickerKey && kickerUrl && ( + + + {kickerKey} + + + )} + + {stats.map((stat, index) => ( +
    +

    + {stat.value} +

    +

    {stat.label}

    +
    + ))} +
    +
    +
    + ); +}; + +export default Stats; diff --git a/src/components/solutions/Stats.module.scss b/src/components/solutions/Stats.module.scss new file mode 100644 index 00000000..6bbe86eb --- /dev/null +++ b/src/components/solutions/Stats.module.scss @@ -0,0 +1,174 @@ +@import "@/scss/solutions/_variables.scss"; + +.Stats { + padding: 64px 24px; + position: relative; +} + +.Title { + font-size: 32px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.03em; + text-align: center; + color: var(--white); + margin-bottom: 0; +} + +.StatsKicker { + color: #6c6a81; + font-size: 13px; + font-style: normal; + font-weight: 700; + line-height: 1.32; + text-align: center; + display: block; + max-width: 100%; + width: 100%; + margin: 0; + + @include breakpoint(xl) { + font-weight: 700; + line-height: 1.32; + text-align: left; + } +} + +.Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: center; + margin: 24px auto 0; + color: var(--grey-250); +} + +.ButtonContainer { + display: flex; + flex-direction: column; + gap: 16px; + margin: 40px auto 0; + width: max-content; + + a { + width: 100%; + } +} + +.StatsContainer { + display: flex; + flex-direction: column; + gap: 40px; + margin-top: 64px; +} + +.Stat { + text-align: center; + display: flex; + flex-direction: column; + gap: 8px; +} + +.StatValue { + font-size: 48px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + margin: 0; + + background-position: 0% 0%; + --bg-size: 700%; + background: var(--gradient-9) 0 0 / var(--bg-size) 100%; + color: transparent; + background-clip: text; + -webkit-background-clip: text; + -webkit-text-fill-color: transparent; + + &::selection { + -webkit-text-fill-color: var(--black); + } +} + +.StatLabel { + font-size: 24px; + font-weight: 500; + line-height: 1.3; + letter-spacing: -0.02em; + color: var(--grey-250); + margin: 0; +} + +@include breakpoint(md) { + .Stats { + padding: 90px 40px; + } + + .Title { + font-size: 40px; + } +} + +@include breakpoint(lg) { + .Title { + font-size: 56px; + } + + .StatValue { + font-size: 64px; + } +} + +@include breakpoint(xl) { + .Stats { + padding: 128px; + } + + .Container { + display: flex; + gap: 66px; + } + + .ContentContainer { + flex: 1; + } + + .Title { + font-size: 56px; + line-height: 1; + text-align: left; + max-width: 473px; + } + + .Subtitle { + font-size: 24px; + line-height: 1.15; + text-align: left; + max-width: 400px; + margin-left: 0; + } + + .ButtonContainer { + margin-left: 0; + } + + .StatsContainer { + margin-top: 0; + width: min-content; + min-width: 327px; + gap: 48px; + } + + .Stat { + text-align: left; + gap: 4px; + } + + .StatValue { + font-size: 72px; + } + + .StatLabel { + font-size: 24px; + } +} diff --git a/src/components/solutions/SuccessStories.module.scss b/src/components/solutions/SuccessStories.module.scss new file mode 100644 index 00000000..a92b6efb --- /dev/null +++ b/src/components/solutions/SuccessStories.module.scss @@ -0,0 +1,194 @@ +@import "../../scss/solutions/_variables.scss"; + +.SuccessStories { + padding: 64px 0; + background: var(--grey-500); + + &[data-theme="black"] { + background: var(--black); + } +} + +.Container { + padding: 0 24px; +} + +.Title { + color: var(--grey-100); + text-align: center; + font-size: 36px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -1.2px; + margin-bottom: 0; +} + +.CardsWrapper { + display: grid; + grid-gap: 40px; + margin-top: 64px; +} + +.StoryCard { + background: var(--grey-450); + padding: 32px 20px; + display: flex; + flex-direction: column; + gap: 20px; + align-items: flex-start; + border-radius: 8px; + height: 100%; + + strong { + color: var(--white); + } + + a { + font-size: 15px; + line-height: 1.32; + } +} + +.LogoWrapper { + margin: 0 auto; +} + +.MainImageWrapper { + width: 100%; + + img { + border-radius: 8px; + width: 100%; + max-height: 400px; + object-fit: cover; + } +} + +.Text { + margin: 0; + color: var(--grey-250); + font-size: 16px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; +} + +@include breakpoint(sm) { + .Text { + max-width: 100%; + } +} + +@include breakpoint(md) { + .Container { + padding: 0 40px; + } + + .Title { + font-size: 48px; + } + + .CardsWrapper { + &:not(.SingleCard) { + grid-template-columns: 1fr 1fr; + + &[data-cards-count="2"] { + max-width: 50rem; + margin: { + left: auto; + right: auto; + } + } + + .Text { + flex: 1; + } + } + } + + .StoryCard a { + font-size: 18px; + line-height: 1.1; + } + + .Text { + font-size: 18px; + line-height: 1.12; + letter-spacing: -0.02em; + flex: 1; + } +} + +@include breakpoint(lg) { + .SuccessStories { + padding: 128px 0 144px; + } + + .Container { + padding: 0; + } + + .CardsWrapper { + margin-top: 80px; + } + + .StoryCard { + padding: 40px; + } +} + +@include breakpoint(xl) { + .Title { + font-size: 64px; + line-height: 1.04; + letter-spacing: -1.6px; + text-transform: capitalize; + } +} + +.SingleCard { + @include breakpoint(md) { + .StoryCard { + display: grid; + grid-template-columns: 0.84fr 1fr; + grid-template-rows: 1fr auto 1fr; + gap: 24px; + } + + .MainImageWrapper { + grid-row: 1 / span 3; + grid-column: 1; + align-self: center; + } + + .LogoWrapper { + grid-column: 2; + grid-row: 1; + margin-left: 0; + align-self: flex-end; + } + + .Text { + grid-column: 2; + grid-row: 2; + } + + a { + grid-column: 2; + grid-row: 3; + width: fit-content; + } + } + + @include breakpoint(lg) { + .StoryCard { + grid-column-gap: 40px; + } + } + + @include breakpoint(xl) { + .StoryCard { + grid-row-gap: 40px; + } + } +} diff --git a/src/components/solutions/SuccessStories.tsx b/src/components/solutions/SuccessStories.tsx new file mode 100644 index 00000000..066cffa7 --- /dev/null +++ b/src/components/solutions/SuccessStories.tsx @@ -0,0 +1,128 @@ +import { ReactNode } from "react"; +import Image from "next/image"; +import classNames from "classnames"; + +import { AnimatedText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import Button from "./Button"; + +import styles from "./SuccessStories.module.scss"; + +interface StoryCardProps { + logo: string; + logoAlt: string; + mobileImage: string; + desktopImage: string; + imageAlt: string; + text: string; + buttonText: string; + buttonUrl: string; + className?: string; + logoClassName?: string; + mainImageClassName?: string; +} + +export const StoryCard = ({ + logo, + logoAlt, + mobileImage, + desktopImage, + imageAlt, + text, + buttonText, + buttonUrl, + className, + logoClassName, + mainImageClassName, +}: StoryCardProps) => { + return ( +
    +
    + + {logoAlt} + +
    + +
    + + {mobileImage && ( + {imageAlt} + )} + + {imageAlt} + +
    + + + {text} + + + +
    + ); +}; + +interface SuccessStoriesProps { + title: string; + cards: ReactNode[]; + backgroundTheme?: "grey" | "black"; + className?: string; + cardsClassName?: string; + id?: string; +} + +const SuccessStories = ({ + title, + cards, + backgroundTheme = "grey", + className, + cardsClassName, + id, +}: SuccessStoriesProps) => { + return ( +
    +
    + + {title} + + +
    + {cards} +
    +
    +
    + ); +}; + +export default SuccessStories; diff --git a/src/components/solutions/VideoBgHero.module.scss b/src/components/solutions/VideoBgHero.module.scss new file mode 100644 index 00000000..60629926 --- /dev/null +++ b/src/components/solutions/VideoBgHero.module.scss @@ -0,0 +1,188 @@ +@import "~@/scss/solutions/_variables.scss"; + +.ContentWrapper { + padding: 64px 24px; + display: flex; + flex-direction: column; + gap: 24px; + align-items: center; + + h1 { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + margin-bottom: 0; + } + + .Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + text-align: center; + color: var(--grey-250); + margin-bottom: 0; + margin-top: 24px; + max-width: 100%; + } + + .ButtonWrapper { + display: flex; + flex-direction: column; + align-items: center; + width: max-content; + margin: 24px auto 0; + gap: 16px; + min-width: 211px; + + @include breakpoint(lg) { + min-width: initial; + margin-left: 0; + align-items: flex-start; + } + } + + a { + width: 100%; + margin: 0; + + @media (max-width: #{$screen-lg-min - 1}) { + font-size: 15px; + font-weight: 700; + line-height: 1.32; + text-align: left; + } + + @include breakpoint(lg) { + width: auto; + } + } +} + +.Eyebrow { + color: #64a8f2; + font-size: 16px; + font-style: normal; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.32px; + text-align: center; + margin-bottom: 24px; + + @include breakpoint(lg) { + font-size: 22px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.44px; + text-align: left; + } +} + +.VideoWrapper { + position: relative; + + &:after { + content: ""; + display: block; + width: 100%; + height: 50%; + position: absolute; + top: 0; + left: 0; + background: linear-gradient( + 180deg, + #0f0a16 1.64%, + rgba(15, 10, 22, 0) 100% + ); + z-index: 1; + } + + &, + video { + width: 100%; + } +} + +@include breakpoint(sm) { + .ContentWrapper { + padding-top: 90px; + padding-bottom: 90px; + + h1 { + font-size: 64px; + } + + .Subtitle { + font-size: 24px; + } + } +} + +@include breakpoint(lg) { + .VideoBgHero { + display: flex; + min-height: calc(100vh - 76px); + position: relative; + } + + .ContentWrapper { + position: relative; + z-index: 1; + width: 100%; + justify-content: center; + align-items: flex-start; + + .ContentInnerWrapper { + max-width: 519px; + } + + h1, + .Subtitle { + text-align: left; + } + + h1 { + font-size: 72px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.02em; + text-align: left; + } + + .Subtitle { + font-size: 28px; + font-weight: 700; + line-height: 1.15; + margin-top: 24px; + letter-spacing: -0.02em; + text-align: left; + } + } + + .VideoWrapper { + position: absolute; + z-index: 0; + height: 100%; + + &:after { + content: ""; + display: block; + position: absolute; + height: 100%; + width: 70%; + background: linear-gradient( + 270deg, + rgba(15, 10, 22, 0) 3.27%, + rgba(15, 10, 22, 0.84) 30.48% + ); + top: 0; + left: 0; + } + + video { + height: 100%; + object-fit: cover; + } + } +} diff --git a/src/components/solutions/VideoBgHero.tsx b/src/components/solutions/VideoBgHero.tsx new file mode 100644 index 00000000..13a9a839 --- /dev/null +++ b/src/components/solutions/VideoBgHero.tsx @@ -0,0 +1,115 @@ +/** + * VideoBgHero component renders a section with a video background and content overlay. + * + * @param {string} videoSrc - The source URL for the video to be displayed. + * @param {string} [videoSrc720] - The source URL for the video to be displayed on screens with a max-width of 720px. + * @param {string} videoPoster - The URL of the image to be shown while the video is loading or if the video cannot be played. + * @param {string} title - The main title text to be displayed. + * @param {string} [subtitle] - The subtitle text to be displayed. + * @param {string} [eyebrow] - The eyebrow text to be displayed above the title. + * @param {ButtonProps[]} [buttons] - An array of button properties to render buttons within the component. + * @param {string} [classes] - Additional CSS classes to apply to the component. + * + * @returns {JSX.Element} The rendered VideoBgHero component. + */ +import { useEffect, useRef } from "react"; +import classNames from "classnames"; + +import useReducedMotion from "@/hooks/useReducedMotion"; +import Button, { ButtonProps } from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./VideoBgHero.module.scss"; + +interface VideoBgHeroProps { + videoSrc: string; + videoSrc720?: string; + videoPoster: string; + title: string; + subtitle?: string; + eyebrow?: string; + buttons?: ButtonProps[]; + classes?: string; +} + +const VideoBgHero = ({ + videoSrc, + videoSrc720, + videoPoster, + title, + subtitle, + eyebrow, + buttons, + classes, +}: VideoBgHeroProps) => { + const [prefersReducedMotion] = useReducedMotion(); + const videoRef = useRef(null); + + useEffect(() => { + if (videoRef.current && prefersReducedMotion) { + videoRef.current.pause(); + } + }, [prefersReducedMotion]); + + return ( +
    +
    +
    + {eyebrow && ( + + {eyebrow} + + )} + + + {title} + + + {subtitle && ( + + {subtitle} + + )} + + {buttons && ( + +
    + {buttons?.map((button, index) => ( +
    +
    + )} +
    +
    + +
    + +
    +
    + ); +}; + +export default VideoBgHero; diff --git a/src/components/solutions/blinks-and-actions/BlinksHero.jsx b/src/components/solutions/blinks-and-actions/BlinksHero.jsx new file mode 100644 index 00000000..2e40bbbb --- /dev/null +++ b/src/components/solutions/blinks-and-actions/BlinksHero.jsx @@ -0,0 +1,83 @@ +import { useEffect, useRef } from "react"; +import { useTranslation } from "next-i18next"; +import classNames from "classnames"; +import Image from "next/image"; + +import useReducedMotion from "@/hooks/useReducedMotion"; + +import Button from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./BlinksHero.module.scss"; + +const BlinksHero = () => { + const { t } = useTranslation(); + + const videoRef = useRef(null); + const [prefersReducedMotion] = useReducedMotion(); + + useEffect(() => { + prefersReducedMotion && videoRef.current.pause(); + }, [prefersReducedMotion]); + + return ( +
    +
    + + {t("solutions-blinks-and-actions.hero.kicker")} + + + + {t("solutions-blinks-and-actions.hero.title")} + + + + {t("solutions-blinks-and-actions.hero.subtitle")} + + + +
    + + + + +
    + ); +}; + +export default BlinksHero; diff --git a/src/components/solutions/blinks-and-actions/BlinksHero.module.scss b/src/components/solutions/blinks-and-actions/BlinksHero.module.scss new file mode 100644 index 00000000..c8b9c057 --- /dev/null +++ b/src/components/solutions/blinks-and-actions/BlinksHero.module.scss @@ -0,0 +1,124 @@ +@import "../../../scss/solutions/_variables.scss"; + +.BlinksHero { + padding: 64px 24px; + display: flex; + flex-direction: column; + gap: 40px; + + .ContentWrapper { + display: flex; + flex-direction: column; + gap: 24px; + + p, + h1 { + margin-top: 0; + margin-bottom: 0; + } + } + + .Kicker { + font-size: 16px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + text-align: center; + color: var(--blue); + } + + .Title { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + } + + .Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + text-align: center; + color: var(--grey-250); + } + + .Buttons { + display: flex; + flex-direction: column; + gap: 16px; + width: max-content; + margin: 0 auto; + } + + .ImageWrapper { + display: flex; + justify-content: center; + } +} + +@include breakpoint(md) { + .BlinksHero { + padding: 80px 24px; + + .ContentWrapper { + @include max-width(650px); + } + + .Title { + font-size: 64px; + } + + .Subtitle { + font-size: 24px; + } + } +} + +@include breakpoint(xl) { + .BlinksHero { + padding: 92px 24px; + flex-direction: row; + justify-content: space-between; + gap: 128px; + + .ContentWrapper { + max-width: 520px; + justify-content: center; + flex: 1; + + * { + text-align: left; + } + } + + .Kicker { + margin-left: 0; + font-size: 22px; + } + + .Title { + font-size: 64px; + line-height: 1; + letter-spacing: -0.02em; + max-width: 422px; + } + + .Subtitle { + font-size: 32px; + line-height: 1.15; + letter-spacing: -0.02em; + max-width: 100%; + } + + .Buttons { + margin-left: 0; + min-width: 328px; + + a { + padding: 12px; + width: 100%; + } + } + } +} diff --git a/src/components/solutions/gaming/GamesKit.jsx b/src/components/solutions/gaming/GamesKit.jsx new file mode 100644 index 00000000..04ae56d6 --- /dev/null +++ b/src/components/solutions/gaming/GamesKit.jsx @@ -0,0 +1,89 @@ +import Link from "next/link"; +import classNames from "classnames"; +import { useTranslation, Trans } from "next-i18next"; + +import Button from "@/components/solutions/Button"; +import CaretIcon from "@/components/icons/Caret"; +import { AnimatedText, GradientText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./GamesKit.module.scss"; + +const GamesKit = () => { + const { t } = useTranslation(); + + const ListItem = ({ title, text, url }) => ( +
    +
    + + {title} + + +
    +

    {text}

    +
    + ); + + return ( +
    +
    +
    + + + + Start with the
    + + Solana Games Kit + +
    +
    +
    + + + {t("solutions-gaming.games-kit.subtitle")} + + + +
    + +
    + + + + + + + + + + + + + + + +
    +
    +
    + ); +}; + +export default GamesKit; diff --git a/src/components/solutions/gaming/GamesKit.module.scss b/src/components/solutions/gaming/GamesKit.module.scss new file mode 100644 index 00000000..605182d1 --- /dev/null +++ b/src/components/solutions/gaming/GamesKit.module.scss @@ -0,0 +1,172 @@ +@import "~@/scss/solutions/_variables.scss"; + +.GamesKit { + background: var(--grey-500); + border-top: 1px solid var(--grey-450); + + @include breakpoint(sm) { + padding: 0 40px; + } +} + +.Container { + @include breakpoint(xl) { + display: flex; + padding: 128px 40px; + gap: 225px; + + .TextBlock, + .ListItems { + padding: 0; + } + } +} + +.TextBlock { + padding: 64px 24px; + + h2 { + font-size: 32px; + font-weight: 700; + line-height: 1.11; + letter-spacing: -0.01em; + text-align: center; + margin: 0; + + strong { + @include gradient-text( + linear-gradient(90deg, #64a8f2 0%, #9945ff 49.61%, #eb54bc 100%) + ); + } + } + + p { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + color: var(--grey-250); + margin: 24px 0 40px; + max-width: 100%; + } + + a { + width: max-content; + font-size: 15px; + font-weight: 700; + line-height: 1.32; + margin: 0 auto; + } +} + +.ListItems { + padding: 0 24px 64px; + display: flex; + flex-direction: column; + gap: 32px; + + .ListItem { + h5 { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + margin: 0; + + a { + display: flex; + justify-content: center; + align-items: center; + gap: 4px; + color: var(--grey-100); + + svg { + width: 22px; + transition: 0.2s ease-in; + + @include breakpoint(xl) { + width: 24px; + } + } + + &:hover { + svg { + transform: translateX(4px); + } + } + } + } + + p { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: center; + color: var(--grey-300); + margin: 8px 0 0; + } + } +} + +@include breakpoint(sm) { + .TextBlock { + h2 { + font-size: 40px; + } + } +} + +@include breakpoint(md) { + .TextBlock { + h2 { + font-size: 56px; + } + } +} + +@include breakpoint(xl) { + .TextBlock { + h2 { + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + text-align: left; + } + + p { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: left; + } + + a { + font-size: 18px; + font-weight: 700; + line-height: 1.1; + margin-left: 0; + } + } + + .ListItems { + .ListItem { + h5 a, + p { + text-align: left; + justify-content: flex-start; + } + + h5, + p { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + } + } + } +} diff --git a/src/components/solutions/gaming/GamingSlider.module.scss b/src/components/solutions/gaming/GamingSlider.module.scss new file mode 100644 index 00000000..00cd1167 --- /dev/null +++ b/src/components/solutions/gaming/GamingSlider.module.scss @@ -0,0 +1,186 @@ +@import "~@/scss/solutions/_variables.scss"; + +.Container { + position: relative; + + h5, + p { + margin: 0; + padding: 0; + box-sizing: border-box; + } + + h5 { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: left; + color: var(--grey-100); + margin: 24px 0 0; + display: flex; + gap: 4px; + align-items: center; + + svg { + width: 22px; + + @include breakpoint(md) { + width: 24px; + } + } + + &:hover { + svg { + transform: translateX(4px); + } + } + } + + p { + color: #6c6a81; + font-size: 14px; + line-height: 1.3; + + @include breakpoint(md) { + font-size: 18px; + } + } + + a { + color: var(--white); + } + + @include breakpoint(lg) { + padding: 0; + } +} + +.Slider { + max-width: 1024px; + margin: 0 auto; + position: relative; + padding-bottom: 80px; + + @include breakpoint(lg) { + padding: 0; + + & > div { + padding-top: 48px !important; + padding-bottom: 168px !important; + } + + &:before { + content: ""; + width: 200px; + height: 100%; + position: absolute; + left: -2px; + top: 0; + background: linear-gradient( + 90deg, + #0d0817 20%, + rgba(15, 10, 22, 0) 99.21% + ); + z-index: 10; + pointer-events: none; + } + + &:after { + content: ""; + width: 200px; + height: 100%; + position: absolute; + right: -2px; + top: 0; + background: linear-gradient( + 272deg, + #0d0817 20%, + rgba(15, 10, 22, 0) 99.21% + ); + z-index: 10; + pointer-events: none; + } + } +} + +.Card { + $height: 320px; + + position: relative; + padding: 8px; + min-height: $height; + + @include breakpoint(lg) { + min-height: auto; + } + + img { + border-radius: 16px; + } + + .CardInner { + padding: 24px; + display: block; + border-radius: 8px; + border: 1px solid var(--gradient-6, #504d61); + background: var(--Neutrals-Grey-500, #1d1a23); + min-height: 300px; + position: relative; + + @include breakpoint(sm) { + min-height: 320px; + } + + @include breakpoint(md) { + min-height: 350px; + } + + @include breakpoint(lg) { + padding: 0; + border-radius: 16px; + box-shadow: none !important; + min-height: 0; + } + } + + .CardImageWrapper { + position: relative; + aspect-ratio: 14 / 9; + width: 100%; + height: auto; + } + + .CardContent { + @include breakpoint(lg) { + display: none; + } + } +} + +.TooltipContainer { + position: absolute; + top: 0; + left: 0; + width: calc(100%); + z-index: 12; +} + +.Tooltip { + display: none !important; + + @include breakpoint(lg) { + display: block !important; + } + + position: absolute; + top: -24px; + left: -24px; + width: calc(100% + 48px); + padding: 24px; + border-radius: 16px; + z-index: 10 !important; + border-radius: 8px; + border: 1px solid var(--gradient-6, #504d61); + background: var(--Neutrals-Grey-500, #1d1a23); +} diff --git a/src/components/solutions/gaming/GamingSlider.tsx b/src/components/solutions/gaming/GamingSlider.tsx new file mode 100644 index 00000000..5bcc5a0f --- /dev/null +++ b/src/components/solutions/gaming/GamingSlider.tsx @@ -0,0 +1,202 @@ +"use client"; + +import { useState } from "react"; +import Link from "next/link"; +import { useTranslation } from "next-i18next"; +import Image from "next/image"; +import Slider from "react-slick"; +import "slick-carousel/slick/slick.css"; +import "slick-carousel/slick/slick-theme.css"; + +import styles from "./GamingSlider.module.scss"; +import CaretIcon from "@/components/icons/Caret"; + +const GamingSlider = () => { + const { t } = useTranslation(); + const [tooltip, setTooltip] = useState<{ + title: string; + content: string; + img: string; + url: string; + boxShadow: string; + } | null>(null); + + const handleMouseEnter = (card: { + title: string; + content: string; + img: string; + url: string; + boxShadow: string; + }) => { + console.log("Hovering over card:", card); + setTooltip(card); + }; + + const handleMouseLeave = () => { + console.log("Mouse left card"); + setTooltip(null); + }; + + const cards = [ + { + title: t("solutions-gaming.cards.star-atlas.title"), + content: t("solutions-gaming.cards.star-atlas.content"), + img: "/solutions/gaming/slider/row1-card1.jpg", + url: "https://play.staratlas.com/", + boxShadow: "#3079A6B2", + }, + { + title: t("solutions-gaming.cards.aurory.title"), + content: t("solutions-gaming.cards.aurory.content"), + img: "/solutions/gaming/slider/row1-card2.jpg", + url: "https://aurory.io/", + boxShadow: "#AE7272B2", + }, + { + title: t("solutions-gaming.cards.stepn-go.title"), + content: t("solutions-gaming.cards.stepn-go.content"), + img: "/solutions/gaming/slider/row1-card3.jpg", + url: "https://stepngo.com/", + boxShadow: "#7E5CDCB2", + }, + { + title: t("solutions-gaming.cards.br1.title"), + content: t("solutions-gaming.cards.br1.content"), + img: "/solutions/gaming/slider/row1-card4.jpg", + url: "https://www.br1game.com/", + boxShadow: "#22ABC1B2", + }, + { + title: t("solutions-gaming.cards.nyan-heros.title"), + content: t("solutions-gaming.cards.nyan-heros.content"), + img: "/solutions/gaming/slider/row1-card5.jpg", + url: "https://nyanheroes.com/", + boxShadow: "#3079A6B2", + }, + { + title: t("solutions-gaming.cards.photo-finish-live.title"), + content: t("solutions-gaming.cards.photo-finish-live.content"), + img: "/solutions/gaming/slider/row2-card1.jpg", + url: "https://photofinish.live/", + boxShadow: "#206E72B2", + }, + { + title: t("solutions-gaming.cards.genopets.title"), + content: t("solutions-gaming.cards.genopets.content"), + img: "/solutions/gaming/slider/row2-card2.jpg", + url: "https://www.genopets.me/", + boxShadow: "#A1937DB2", + }, + { + title: t("solutions-gaming.cards.portals.title"), + content: t("solutions-gaming.cards.portals.content"), + img: "/solutions/gaming/slider/row2-card3.jpg", + url: "https://theportal.to/", + boxShadow: "#29729FB2", + }, + { + title: t("solutions-gaming.cards.low-life-forms.title"), + content: t("solutions-gaming.cards.low-life-forms.content"), + img: "/solutions/gaming/slider/row2-card4.jpg", + url: "https://www.rtrigger.com/#lowlifeforms", + boxShadow: "#9F51EBB2", + }, + { + title: t("solutions-gaming.cards.mixmob.title"), + content: t("solutions-gaming.cards.mixmob.content"), + img: "/solutions/gaming/slider/row2-card5.jpg", + url: "https://uprising.mixmob.io/", + boxShadow: "#2D42C3B2", + }, + ]; + + const settings = { + className: "center", + centerMode: true, + infinite: true, + centerPadding: "140px", + arrows: true, + slidesToShow: 3, + speed: 500, + rows: 2, + slidesPerRow: 1, + responsive: [ + { + breakpoint: 992, + settings: { + slidesToShow: 2, + centerPadding: "48px", + }, + }, + { + breakpoint: 639, + settings: { + slidesToShow: 1, + centerPadding: "56px", + }, + }, + { + breakpoint: 480, + settings: { + slidesToShow: 1, + centerPadding: "32px", + }, + }, + ], + }; + + return ( +
    + + {cards.map((card, index) => ( +
    handleMouseEnter(card)} + onMouseLeave={handleMouseLeave} + > + +
    + {card.title} +
    +
    +
    + {card.title} + +
    +

    {card.content}

    +
    + + + {tooltip && tooltip.title === card.title && ( + +
    + {tooltip.title} +
    +
    +
    + {tooltip.title} + +
    +

    {tooltip.content}

    +
    + + )} +
    + ))} +
    +
    + ); +}; + +export default GamingSlider; diff --git a/src/components/solutions/gaming/GamingVideoHero.jsx b/src/components/solutions/gaming/GamingVideoHero.jsx new file mode 100644 index 00000000..8e02d42a --- /dev/null +++ b/src/components/solutions/gaming/GamingVideoHero.jsx @@ -0,0 +1,119 @@ +import { useRef, useEffect } from "react"; +import { useTranslation } from "next-i18next"; +import useReducedMotion from "../../../hooks/useReducedMotion"; + +import Button from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./GamingVideoHero.module.scss"; + +const GamingVideoHero = () => { + const { t } = useTranslation(); + const video1Ref = useRef(null); + const video2Ref = useRef(null); + const [prefersReducedMotion] = useReducedMotion(); + + const videos = [ + { + src_720: + "https://player.vimeo.com/progressive_redirect/playback/1009457872/rendition/720p/file.mp4?loc=external&signature=9c3fab67abf9c4a84a9db0c3cc16ffaeab06ad83e2c8100aa5b524d0184f9954", + src_1080: + "https://player.vimeo.com/progressive_redirect/playback/1009457872/rendition/1080p/file.mp4?loc=external&signature=86bd2ccd9bc532bff002c5e4eeb2cd892842aba7ac17b8f06936c5bfeab95012", + poster: "/solutions/gaming/hero-video-1.webp", + }, + { + src_720: + "https://player.vimeo.com/progressive_redirect/playback/1009457866/rendition/720p/file.mp4?loc=external&signature=f945644cad9795580cb0b90ce6d3ca744fdd13a5faf7eeafd1fb35b46739a7bd", + src_1080: + "https://player.vimeo.com/progressive_redirect/playback/1009457866/rendition/1080p/file.mp4?loc=external&signature=f77cc386fce4caa083968ed91504145a93b448a51d4b8e22b2d444484722d128", + poster: "/solutions/gaming/hero-video-2.webp", + }, + ]; + + const handleVideoEnd = () => { + video1Ref.current.style.opacity = 0; + video2Ref.current.style.opacity = 1; + }; + + useEffect(() => { + if (video1Ref.current && prefersReducedMotion) { + video1Ref.current.pause(); + } + }, [prefersReducedMotion]); + + useEffect(() => { + if (video1Ref.current && video2Ref.current) { + video1Ref.current.play(); + video2Ref.current.play(); + } + }, [video1Ref, video2Ref]); + + return ( +
    +
    + + + + +
    +
    + +
    + + {t("solutions-gaming.hero.kicker")} + + + {t("solutions-gaming.hero.title")} + + + {t("solutions-gaming.hero.subtitle")} + + + +
    +
    +
    +
    +
    + ); +}; + +export default GamingVideoHero; diff --git a/src/components/solutions/gaming/GamingVideoHero.module.scss b/src/components/solutions/gaming/GamingVideoHero.module.scss new file mode 100644 index 00000000..fdde4f21 --- /dev/null +++ b/src/components/solutions/gaming/GamingVideoHero.module.scss @@ -0,0 +1,116 @@ +@import "~@/scss/solutions/_variables.scss"; + +.GamingVideoHero { + height: calc(100vh - 76px); + position: relative; + + video { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + object-fit: cover; + object-position: center; + } + + .Video2 { + opacity: 0; + } +} + +.VideoWrapper { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + .VideoOverlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background: rgba(0, 0, 0, 0.4); + z-index: 1; + } +} + +.TextBlock { + position: absolute; + top: 50%; + left: 50%; + transform: translate(-50%, -50%); + z-index: 2; + text-align: center; + width: 100%; + padding: 24px; + + .Kicker { + font-size: 16px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + text-align: center; + text-transform: uppercase; + margin: 0 0 24px; + display: block; + + @include breakpoint(lg) { + font-size: 22px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + } + } + + h1 { + font-size: 48px; + font-weight: 700; + line-height: 1; + letter-spacing: -0.02em; + } + + p { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + margin: 24px auto 0; + max-width: 702px; + } +} + +.Buttons { + display: flex; + flex-direction: column; + width: max-content; + margin: 24px auto 0; + gap: 16px; + + @media (max-width: #{$screen-md-min - 1}) { + a { + font-size: 15px; + line-height: 1.32; + } + } + + @include breakpoint(lg) { + display: grid; + grid-template-columns: 1fr 1fr; + } +} + +@include breakpoint(xl) { + .TextBlock { + h1 { + font-size: 96px; + } + + p { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.02em; + } + } +} diff --git a/src/components/solutions/gaming/TVMert.module.scss b/src/components/solutions/gaming/TVMert.module.scss new file mode 100644 index 00000000..2281efca --- /dev/null +++ b/src/components/solutions/gaming/TVMert.module.scss @@ -0,0 +1,79 @@ +@import "../../../scss/solutions/_variables.scss"; + +.TVMert { + position: relative; + padding: 64px 0; + + .Title { + font-size: 40px; + font-weight: 700; + line-height: 1.06; + letter-spacing: -0.01em; + text-align: center; + padding: 0 24px; + margin: 0 auto 24px; + position: relative; + z-index: 2; + + --gradient: linear-gradient( + 90deg, + #3f37c9 4.6%, + #46dcf8 31.37%, + #ffffff 50.45% + ); + } +} + +.VideoWrapper { + position: relative; + width: 100vw; + height: calc(100vw * 0.5); + transform: scale(1.25); + + @include breakpoint(sm) { + transform: scale(1); + } + + @include breakpoint(md) { + margin-top: -5%; + position: relative; + z-index: 1; + } + + video { + position: absolute; + top: 0; + left: 50%; + transform: translateX(-50%); + width: 100%; + height: 100%; + object-fit: contain; + object-position: center; + opacity: 0; + transition: opacity $duration-standard; + + &.Active { + opacity: 1; + } + } +} + +@include breakpoint(md) { + .TVMert { + .Title { + font-size: 80px; + margin-bottom: 0; + letter-spacing: -0.03em; + } + } + + .VideoWrapper { + width: 100%; + } +} + +@include breakpoint(xl) { + .TVMert { + padding: 128px 0 0; + } +} diff --git a/src/components/solutions/gaming/TVMert.tsx b/src/components/solutions/gaming/TVMert.tsx new file mode 100644 index 00000000..c03f0e74 --- /dev/null +++ b/src/components/solutions/gaming/TVMert.tsx @@ -0,0 +1,106 @@ +import { useRef, useEffect } from "react"; +import { Trans } from "next-i18next"; +import classNames from "classnames"; + +import { AnimatedText, GradientText } from "@/components/shared/Text"; + +import useReducedMotion from "@/hooks/useReducedMotion"; + +import styles from "./TVMert.module.scss"; + +const TVMert = () => { + const video1Ref = useRef(null); + const video2Ref = useRef(null); + const [prefersReducedMotion] = useReducedMotion(); + + const videos = [ + { + src_720: + "https://player.vimeo.com/progressive_redirect/playback/1009649838/rendition/720p/file.mp4?loc=external&signature=0c0ffaab9ea401d986555187c28100a88594ccc69d8317f295f06cb618815e4c", + src_1080: + "https://player.vimeo.com/progressive_redirect/playback/1009649838/rendition/1080p/file.mp4?loc=external&signature=8769962746274f024137d46578b7ee5630fe33ea5d845d5ee8e342697ab82dac", + poster: "/solutions/gaming/mert-tv-1.jpeg", + }, + { + src_720: + "https://player.vimeo.com/progressive_redirect/playback/1009649823/rendition/720p/file.mp4?loc=external&signature=9917c37382359cac1733ee757060f86b07cff29c8d646842962d56321c52d544", + src_1080: + "https://player.vimeo.com/progressive_redirect/playback/1009649823/rendition/1080p/file.mp4?loc=external&signature=b8f8a4574f9ee500b74e63279e83c0da87258c7c306f0ba879d051f264f85f10", + poster: "/solutions/gaming/mert-tv-2.jpeg", + }, + ]; + + const handleVideoEnd = () => { + video1Ref.current.style.opacity = 0; + video2Ref.current.style.opacity = 1; + }; + + useEffect(() => { + if (video1Ref.current && prefersReducedMotion) { + video1Ref.current.pause(); + } + }, [prefersReducedMotion, video1Ref]); + + useEffect(() => { + if (video1Ref.current && video2Ref.current) { + video1Ref.current.play(); + video2Ref.current.play(); + setTimeout(() => { + video1Ref.current?.classList.add(styles.Active); + }, 500); + } + }, [video1Ref, video2Ref]); + + return ( +
    + + , + }} + /> + + +
    + + + +
    +
    + ); +}; + +export default TVMert; diff --git a/src/components/solutions/layout.tsx b/src/components/solutions/layout.tsx new file mode 100644 index 00000000..f8a7406e --- /dev/null +++ b/src/components/solutions/layout.tsx @@ -0,0 +1,21 @@ +import { ReactNode } from "react"; +import classNames from "classnames"; +import Header from "../Header"; +import Footer from "../Footer"; +import styles from "./Layout.module.scss"; + +interface LayoutProps { + headerClassName?: string; + children: ReactNode; +} + +const Layout = ({ headerClassName, children }: LayoutProps) => { + return ( +
    +
    +
    {children}
    +
    +
    + ); +}; +export default Layout; diff --git a/src/components/solutions/loyalty/LoyaltyHero.jsx b/src/components/solutions/loyalty/LoyaltyHero.jsx new file mode 100644 index 00000000..e7a81283 --- /dev/null +++ b/src/components/solutions/loyalty/LoyaltyHero.jsx @@ -0,0 +1,53 @@ +import Lottie from "react-lottie"; +import { useTranslation } from "next-i18next"; + +import Text from "@/components/shared/Text"; +import Button from "@/components/solutions/Button"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./LoyaltyHero.module.scss"; + +const LoyaltyHero = ({ heroLottie }) => { + const { t } = useTranslation(); + + return ( +
    +
    + + {t("solutions-loyalty.hero.eyebrow")} + + + {t("solutions-loyalty.hero.title")} + + + {t("solutions-loyalty.hero.subtitle")} + + +
    +
    +
    + + + + +
    + ); +}; + +export default LoyaltyHero; diff --git a/src/components/solutions/loyalty/LoyaltyHero.module.scss b/src/components/solutions/loyalty/LoyaltyHero.module.scss new file mode 100644 index 00000000..53b4078f --- /dev/null +++ b/src/components/solutions/loyalty/LoyaltyHero.module.scss @@ -0,0 +1,114 @@ +@import "@/scss/solutions/_variables.scss"; + +.Hero { + padding: 64px 24px; + + .HeroTitle { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + text-wrap: initial; + } + + .HeroSubtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + text-align: center; + color: #a2a1b2; + margin: 24px 0 40px; + max-width: 100%; + } + + .Eyebrow { + color: #64a8f2; + text-align: center; + margin-bottom: 24px; + text-transform: uppercase; + font-size: 16px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + } + + .ButtonWrapper { + display: flex; + flex-direction: column; + width: max-content; + margin: 24px auto 40px; + gap: 16px; + + @media (max-width: #{$screen-xl-min - 1}) { + a { + font-size: 15px; + line-height: 1.32; + } + } + + @include breakpoint(lg) { + display: grid; + grid-template-columns: 1fr 1fr; + margin-left: 0; + + a { + width: 100%; + flex-direction: column; + } + } + } + + @include breakpoint(lg) { + display: flex; + gap: 80px; + max-width: 1080px; + margin: 0 auto; + align-items: center; + padding: 128px 40px; + box-sizing: content-box; + + * { + flex-basis: 50%; + } + + .HeroTitle { + font-size: 64px; + text-align: left; + } + + .HeroSubtitle { + font-size: 24px; + text-align: left; + } + + .Eyebrow { + text-align: left; + } + } + + @include breakpoint(xl) { + .HeroTitle { + font-size: 64px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.02em; + text-align: left; + max-width: 400px; + } + + .HeroSubtitle { + font-size: 24px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + } + + .Eyebrow { + font-size: 20px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.44px; + } + } +} diff --git a/src/components/solutions/token-extensions/EcosystemToggle.module.scss b/src/components/solutions/token-extensions/EcosystemToggle.module.scss new file mode 100644 index 00000000..060c700b --- /dev/null +++ b/src/components/solutions/token-extensions/EcosystemToggle.module.scss @@ -0,0 +1,326 @@ +@import "~@/scss/solutions/_variables.scss"; + +.TitleBlock { + padding-top: 64px; + + @include breakpoint(lg) { + padding-top: 80px; + } +} + +.Title { + font-size: 40px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.03em; + text-align: center; + color: var(--white); + text-transform: capitalize; + margin: 0; + padding: 0 24px; + + strong { + background: var(--gradient-2); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; + } + + @include breakpoint(lg) { + font-size: 72px; + line-height: 1.08; + letter-spacing: -0.03em; + margin-bottom: 24px; + } +} + +.Text { + font-size: 20px; + font-weight: 700; + line-height: 1.12; + letter-spacing: -0.02em; + text-align: center; + color: var(--grey-250); + max-width: 698px; + padding: 0 24px; + margin: 24px auto 0; + + @include breakpoint(md) { + font-size: 24px; + } + + @include breakpoint(lg) { + font-size: 32px; + font-weight: 500; + line-height: 1.25; + text-align: center; + max-width: 886px; + } +} + +.MainCollapsible { + & > button[data-state="open"] { + animation: hide 300ms ease; + visibility: hidden; + transition: visibility 300ms ease; + } +} + +.CollapsibleContentWrapper { + margin-top: 64px; + margin-bottom: 64px; + + @include breakpoint(lg) { + margin-top: 124px; + margin-bottom: 80px; + } + + .ToggleBtn { + background: var(--grey-450); + font-size: 15px; + color: white; + line-height: 1.3; + font-weight: 700; + padding: 12px; + border-radius: 8px; + margin-bottom: 24px; + + svg path { + stroke: var(--white); + } + + @include breakpoint(md) { + font-size: 16px; + justify-content: center; + } + + @include breakpoint(lg) { + justify-content: center; + } + } + + .EcosystemPreviewContent { + position: relative; + // overflow: hidden; + + &:after { + content: ""; + display: block; + height: 100%; + width: 100%; + position: absolute; + top: 0; + opacity: 1; + background: linear-gradient(0deg, #0f0a16 0%, rgba(15, 10, 22, 0.6) 100%); + transform: translateY(0%); + transition: + opacity 0.3s ease-in-out, + background 0.5s ease-in-out, + transform 0s ease-in-out; + } + } + + div[data-state="closed"] { + .EcosystemPreviewContent { + &:after { + opacity: 1; + background: linear-gradient( + 0deg, + #0f0a16 0%, + rgba(15, 10, 22, 0.6) 100% + ); + transform: translateY(0%); + } + } + } + + div[data-state="open"] { + .EcosystemPreviewContent { + &:after { + opacity: 0; + background: transparent; + transform: translateY(-100%); + } + } + } + + .EcosystemItem { + position: relative; + // .AccordionContent { + // opacity: 0; + // } + + .AccordionContent[data-state="closed"] { + animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1); + } + + .AccordionContent[data-state="open"] { + animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1) forwards; + + @include breakpoint(lg) { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: auto; + } + } + + h3 { + margin: 0; + line-height: 1; + display: flex; + } + + button { + color: var(--grey-400); + letter-spacing: -0.01em; + font-size: 18px; + line-height: 1.16; + padding: 6px 0; + width: 100%; + + @include breakpoint(md) { + font-size: 20px; + } + + @include breakpoint(lg) { + font-size: 28px; + } + + svg { + display: none; + } + + &[data-state="open"] { + color: var(--white); + } + } + + .ItemToggleBtn { + display: flex; + justify-content: center; + + &:hover { + color: var(--white); + transition: color 0.1s ease-in; + } + + @include breakpoint(lg) { + justify-content: flex-start; + } + } + + .EcosystemItemContent { + display: flex; + flex-direction: column; + justify-content: center; + gap: 8px; + text-align: center; + padding: 6px; + position: relative; + + @include breakpoint(lg) { + align-items: flex-end; + text-align: left; + } + + .EcosystemItemContentInner { + display: flex; + flex-direction: column; + gap: 8px; + + @include breakpoint(lg) { + width: 329px; + gap: 40px; + position: relative; + } + } + + h4 { + font-size: 13px; + line-height: 1.32; + color: var(--grey-100); + margin: 0; + font-weight: bold; + + @include breakpoint(md) { + font-size: 15px; + } + + @include breakpoint(lg) { + font-size: 18px; + } + } + + p { + font-size: 13px; + line-height: 1.32; + color: var(--grey-300); + margin: 0; + font-weight: bold; + width: 75%; + max-width: 500px; + margin: 0 auto; + + @include breakpoint(md) { + font-size: 15px; + } + + @include breakpoint(lg) { + max-width: 100%; + width: 100%; + margin: 0 auto; + font-size: 18px; + } + } + + a { + font-size: 13px; + line-height: 1.1; + color: var(--white); + max-width: 75%; + margin: 8px auto; + + @include breakpoint(md) { + font-size: 15px; + } + + @include breakpoint(lg) { + max-width: 100%; + width: 100%; + margin: 0 auto; + } + } + } + } +} + +@keyframes hide { + from { + opacity: 1; + } + to { + opacity: 0; + } +} + +@keyframes slideDown { + from { + height: 0; + opacity: 0; + } + to { + height: var(--radix-accordion-content-height); + opacity: 1; + } +} + +@keyframes slideUp { + from { + height: var(--radix-accordion-content-height); + } + to { + height: 0; + } +} diff --git a/src/components/solutions/token-extensions/EcosystemToggle.tsx b/src/components/solutions/token-extensions/EcosystemToggle.tsx new file mode 100644 index 00000000..3330d7aa --- /dev/null +++ b/src/components/solutions/token-extensions/EcosystemToggle.tsx @@ -0,0 +1,133 @@ +import { ReactNode, Fragment } from "react"; +import classNames from "classnames"; +import { Trans } from "next-i18next"; +import * as Accordion from "@radix-ui/react-accordion"; + +import Text, { AnimatedText, GradientText } from "@/components/shared/Text"; +import CollapsibleContent from "@/components/shared/CollapsibleContent"; + +import styles from "./EcosystemToggle.module.scss"; + +interface EcosystemItem { + label: string; + content: ReactNode; +} + +const AccordionItem = ({ value, label, content }) => { + return ( + + + + {label} + + + + + {content} + + + ); +}; + +const EcosystemToggle = ({ titleKey, textKey, toggleLabel, items }) => { + return ( +
    +
    + {titleKey && ( + + + ), + }} + /> + + )} + + {textKey && ( + + + + )} +
    + +
    + + + {items.slice(0, 3).map((item: EcosystemItem, index: number) => ( + + + + ))} +
    + } + > + {items + .slice(3) + .map( + ( + item: { label: string; content?: ReactNode }, + index: number, + ) => ( + + + + ), + )} + {/* {items + .slice(3) + .map( + ( + item: { label: string; content?: ReactNode }, + index: number, + ) => ( + + + + ), + )} */} + + +
    +
    + ); +}; + +export const EcosystemItemContentWrap = ({ + children, +}: { + children: ReactNode; +}) => ( +
    +
    {children}
    +
    +); + +export const EcosystemItemContentTitle = ({ text }: { text: string }) => ( + {text} +); + +export const EcosystemItemContentText = ({ text }: { text: string }) => ( + {text} +); + +export default EcosystemToggle; diff --git a/src/components/solutions/wallets-explorer/CollapsibleContent.jsx b/src/components/solutions/wallets-explorer/CollapsibleContent.jsx new file mode 100644 index 00000000..4f6da03c --- /dev/null +++ b/src/components/solutions/wallets-explorer/CollapsibleContent.jsx @@ -0,0 +1,43 @@ +import { useState } from "react"; +import * as Collapsible from "@radix-ui/react-collapsible"; +import classNames from "classnames"; + +import CaretIcon from "@/components/icons/Caret"; + +import styles from "./CollapsibleContent.module.scss"; + +const CollapsibleContent = ({ + label, + defaultOpen, + children, + icon, + classes, +}) => { + const [open, setOpen] = useState(defaultOpen || false); + + return ( + + {label && ( + + + + )} + + {children} + + ); +}; + +export default CollapsibleContent; diff --git a/src/components/solutions/wallets-explorer/CollapsibleContent.module.scss b/src/components/solutions/wallets-explorer/CollapsibleContent.module.scss new file mode 100644 index 00000000..0e473028 --- /dev/null +++ b/src/components/solutions/wallets-explorer/CollapsibleContent.module.scss @@ -0,0 +1,23 @@ +.CollapsibleTrigger { + border-bottom: 1px solid var(--grey-400); + display: flex; + justify-content: space-between; + align-items: center; + width: 100%; +} + +.Label { + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; + color: var(--blue); + text-transform: uppercase; + padding: 19px 0; + margin: 0; + text-align: left; +} + +.IconArrowDown { + transform: rotate(180deg); +} diff --git a/src/components/solutions/wallets-explorer/Filters.jsx b/src/components/solutions/wallets-explorer/Filters.jsx new file mode 100644 index 00000000..da66b5f6 --- /dev/null +++ b/src/components/solutions/wallets-explorer/Filters.jsx @@ -0,0 +1,433 @@ +import { useState } from "react"; +import Image from "next/image"; +import { useTranslation } from "next-i18next"; + +import CollapsibleContent from "@/components/solutions/wallets-explorer/CollapsibleContent"; + +import XIcon from "../../../../assets/wallets/x.inline.svg"; + +import styles from "./Filters.module.scss"; + +const CloseButton = ({ onClick }) => { + return ( + + ); +}; + +const ToggleFormField = ({ key, label, id, checked, onChange }) => ( +
    +

    + {label} +

    +
    + + +
    +
    +); + +export const MobileFilters = ({ + filterState, + toggleFilterActiveState, + resetWalletsAndFilters, +}) => { + const { t } = useTranslation(); + + const [open, setOpen] = useState(false); + + // Active filters count + const activeFiltersCount = filterState.filter( + (filter) => filter.checked, + ).length; + + // Categories + const beginnerFilters = filterState.filter( + (filter) => filter.category === "beginner", + ); + const advancedFilters = filterState.filter( + (filter) => filter.category === "advanced", + ); + + // Advanced filters are further categorized into subcategories + const walletTypeFilters = advancedFilters.filter( + (filter) => filter.subCategory === "wallet_type", + ); + const financialTransactionFilters = advancedFilters.filter( + (filter) => filter.subCategory === "financial_transaction", + ); + const assetManagementFilters = advancedFilters.filter( + (filter) => filter.subCategory === "asset_management", + ); + const securityRecoveryFilters = advancedFilters.filter( + (filter) => filter.subCategory === "security_recovery", + ); + const toolingFilters = advancedFilters.filter( + (filter) => filter.subCategory === "tooling", + ); + + const handleMenuToggle = () => { + !open + ? (document.body.style.overflow = "hidden") + : (document.body.style.overflow = "auto"); + setOpen(!open); + }; + + return ( +
    + + +
    +
    +

    + {t("solutions-wallets-explorer.filters")} +

    + +
    + +
    +
    +
    + + {beginnerFilters.length && + beginnerFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + +
    + +
    + + + {walletTypeFilters.length && + walletTypeFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + {financialTransactionFilters.length && + financialTransactionFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + {assetManagementFilters.length && + assetManagementFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + {securityRecoveryFilters.length && + securityRecoveryFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + {toolingFilters.length && + toolingFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + +
    +
    +
    + +
    +
    + + +
    +
    +
    +
    + ); +}; + +export const DesktopFilters = ({ + filterState, + toggleFilterActiveState, + resetWalletsAndFilters, +}) => { + const { t } = useTranslation(); + + // Active filters count + const activeFiltersCount = filterState.filter( + (filter) => filter.checked, + ).length; + + // Categories + const beginnerFilters = filterState.filter( + (filter) => filter.category === "beginner", + ); + const advancedFilters = filterState.filter( + (filter) => filter.category === "advanced", + ); + + // Advanced filters are further categorized into subcategories + const walletTypeFilters = advancedFilters.filter( + (filter) => filter.subCategory === "wallet_type", + ); + const financialTransactionFilters = advancedFilters.filter( + (filter) => filter.subCategory === "financial_transaction", + ); + const assetManagementFilters = advancedFilters.filter( + (filter) => filter.subCategory === "asset_management", + ); + const securityRecoveryFilters = advancedFilters.filter( + (filter) => filter.subCategory === "security_recovery", + ); + const toolingFilters = advancedFilters.filter( + (filter) => filter.subCategory === "tooling", + ); + + return ( +
    +
    +
    +

    + {t("solutions-wallets-explorer.filters")} ({activeFiltersCount}) +

    + +
    + +
    +
    +
    + + {beginnerFilters.length && + beginnerFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + +
    + +
    + + + } + classes={styles.FirstChild} + > + {walletTypeFilters.length && + walletTypeFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + } + classes={styles.Child} + > + {financialTransactionFilters.length && + financialTransactionFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + } + classes={styles.Child} + > + {assetManagementFilters.length && + assetManagementFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + } + classes={styles.Child} + > + {securityRecoveryFilters.length && + securityRecoveryFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + + + } + classes={styles.Child} + > + {toolingFilters.length && + toolingFilters.map((filter, index) => ( + toggleFilterActiveState(filter.id)} + /> + ))} + + +
    +
    +
    +
    +
    + ); +}; diff --git a/src/components/solutions/wallets-explorer/Filters.module.scss b/src/components/solutions/wallets-explorer/Filters.module.scss new file mode 100644 index 00000000..04ded05b --- /dev/null +++ b/src/components/solutions/wallets-explorer/Filters.module.scss @@ -0,0 +1,346 @@ +@import "~@/scss/solutions/_variables.scss"; + +.Fieldsets { + display: flex; + flex-direction: column; + gap: 15px; + padding-bottom: 24px; +} + +.FieldsetsInner { + padding-bottom: 96px; + padding-top: 105px; +} + +.FormField { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + width: 100%; + gap: 5px; + padding: 19px 0; + position: relative; +} + +.FormFieldLabel { + font-size: 13px; + font-weight: 400; + line-height: 1.13; + letter-spacing: 0.02em; + text-align: left; + color: #a2a1b2; + text-transform: uppercase; + margin: 0; +} + +.LabelChecked { + color: #fff !important; +} + +.SwitchWrapper { + display: flex; + align-items: center; + position: relative; +} +.Switch { + position: relative; + display: inline-block; + width: 44px; + height: 24px; + background-color: transparent; + border-radius: 20px; + transition: + background 0.3s, + border 0.3s, + left 0.3s; + border: 1px solid #6c6a81; + cursor: pointer; + + &:after { + content: ""; + position: absolute; + width: 20px; + height: 20px; + border-radius: 50%; + background-color: transparent; + top: 1px; + left: 1px; + transition: all 0.3s; + border: 1px solid #a2a1b2; + } +} + +.Checkbox:checked + .Switch::after { + left: 21px; + background-color: #14f195; + border: 1px solid #14f195; +} +.Checkbox:checked + .Switch { + border: 1px solid #14f195; +} +.Checkbox { + appearance: none; + width: 100%; + height: 100%; + position: absolute; + top: 0; + z-index: 2; + cursor: pointer; +} + +// Mobile +.MobileFiltersMenuToggle { + background: #322f43; + width: 100%; + padding: 8px; + border-radius: 8px; + font-size: 15px; + font-weight: 700; + line-height: 1.32; +} + +.MobileFiltersMenu { + display: none; + height: 100%; + min-height: 100vh; + overflow: scroll; // TODO: body overflow hidden to prevent double scrollbar + box-sizing: content-box; + + // Menu Open State + &[data-open="true"] { + display: block; + position: fixed; + top: 0; + left: 0; + background: #1d1a23; + z-index: 1021; + padding: 0 24px; + width: calc(100% - 48px); // 48px = x-axis padding + } + + header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 56px 0 24px; + position: fixed; + top: 0; + width: calc(100% - 48px); + background: #1d1a23; + z-index: 2; + } + + .HeaderTitle { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.01em; + text-align: left; + margin: 0; + } + + .Actions { + padding: 24px 0; + position: fixed; + bottom: 0; + width: 100%; + left: 0; + background: #1d1a23; + + .ActionsInner { + padding: 0 24px; + display: flex; + gap: 8px; + } + } + + .ActionButton { + width: 100%; + cursor: pointer; + background: #322f43; + border-radius: 8px; + font-size: 15px; + font-weight: 700; + line-height: 1.32; + text-align: center; + padding: 14px; + } +} + +.MobileFilters { + margin-bottom: 40px; + + @include breakpoint(lg) { + display: none; + margin-bottom: 0; + } +} + +.DesktopFilters { + display: none; + + @include breakpoint(lg) { + display: block; + } + + .FormField { + padding: 16px 0; + } + .DesktopFieldsetOne { + background: var(--grey-500); + } + + button { + p { + align-items: center; + padding-top: 24px; + } + } +} + +.DesktopFiltersHeader { + display: flex; + align-items: center; + justify-content: space-between; + padding-left: 16px; + padding-bottom: 16px; +} + +.DesktopActionButton { + font-family: "ABC Mono"; + color: #a2a1b2; + font-size: 15px; + line-height: 1.15; + letter-spacing: 0.3px; + text-transform: uppercase; + border-bottom: 1px solid #a2a1b2; + margin: 0; + padding: 0; + transition: color 0.2s ease; + + &:hover { + color: #9945ff; + } +} + +.DesktopFiltersCount { + font-size: 18px; + font-weight: 700; + line-height: 1.16; + letter-spacing: -0.18px; + margin: 0; + padding: 0; +} + +.DesktopFieldsets { + margin-top: 24px; + + p { + color: #a2a1b2; + font-family: var(--font-mono); + font-size: 15px; + font-weight: 400; + line-height: 1.15; + letter-spacing: 0.02em; + text-transform: uppercase; + } + + button { + padding-top: 8px; + } +} + +.DesktopFieldsetOne { + border-radius: 8px; + background: #1d1a23; + padding: 16px; + + p { + padding: 0; + } +} + +.DesktopFieldsetTwo { + border-radius: 8px; + background: #1d1a23; + padding: 0; + margin-top: 24px; + + button { + border: none; + } +} + +.Parent { + & > button { + padding: 0 16px; + p, + svg { + color: #64a8f2; + } + svg path { + stroke: #64a8f2; + } + svg { + padding: 6px; + box-sizing: content-box; + } + } +} + +.FirstChild { + border-top: 1px solid #504d61; + padding-bottom: 8px; + + button { + p, + svg { + color: #80ecff; + } + } + + svg path { + stroke: #80ecff; + } + + p { + display: flex; + align-items: flex-start; + gap: 6px; + + svg { + margin-bottom: 4px; + } + } + + & > div, + button { + padding: 0 32px; + } +} + +.Child { + padding: 0 16px; + padding-bottom: 8px; + + > * { + padding: 0 16px; + } + + svg path { + stroke: #80ecff; + } + + button { + border-top: 1px solid #504d61; + p, + svg { + color: #80ecff; + } + + p { + display: flex; + gap: 6px; + } + } +} diff --git a/src/components/solutions/wallets-explorer/WalletCard.jsx b/src/components/solutions/wallets-explorer/WalletCard.jsx new file mode 100644 index 00000000..d8d2d375 --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletCard.jsx @@ -0,0 +1,307 @@ +import classNames from "classnames"; +import { useState } from "react"; +import Link from "next/link"; +import * as Collapsible from "@radix-ui/react-collapsible"; +import Image from "next/image"; +import { useTranslation } from "next-i18next"; + +import CaretIcon from "@/components/icons/Caret"; + +import styles from "./WalletCard.module.scss"; + +import ArrowUpRightIcon from "../../../../assets/wallets/arrow-up-right.inline.svg"; + +// Function to group keys into categories +const groupWalletData = (content) => { + const groups = { + "Wallet Type": ["custodial", "non_custodial", "hardware", "mpc"], + "Financial Transactions": ["buy_crypto", "sell_crypto", "staking"], + "Asset Management": ["hold_nfts", "gas_abstraction", "spending_limits"], + "Security and Recovery": [ + "social_recovery", + "open_source", + "private_key_infrastructure", + ], + "Solana Tooling": ["te", "blinks_and_actions", "solana_pay"], + }; + + // Generate content for each group by filtering true values + const groupedContent = Object.entries(groups).map(([groupName, keys]) => { + const filteredKeys = keys.filter((key) => content[key] === true); + return { + groupName, + values: filteredKeys, + }; + }); + + return groupedContent; +}; + +const Tag = ({ text }) => ( +
    + {text} +
    +); + +const Dot = () => ; + +const WalletCard = ({ + index, + walletImage, + name, + websiteUrl, + newToCrypto, + developer, + content, +}) => { + const { t } = useTranslation(); + const [open, setOpen] = useState(true); + + const tags = []; + if (newToCrypto) tags.push(t("solutions-wallets-explorer.tags.beginner")); + if (developer) tags.push(t("solutions-wallets-explorer.tags.developer")); + + const groupedContent = groupWalletData(content); + + const localize = (value) => + t(`solutions-wallets-explorer.wallet-filters.${value}`); + + // Map of group names to their corresponding icons + const groupIcons = { + "Wallet Type": ( + {t("solutions-wallets-explorer.filter-icons.wallet-type-icon-alt")} + ), + "Financial Transactions": ( + {t( + ), + "Asset Management": ( + {t( + ), + "Security and Recovery": ( + {t( + ), + "Solana Tooling": ( + {t( + ), + }; + + const MobileCollapsibleContentRow = ({ + groupName, + values, + maxValuesShown, + }) => { + if (values.length === 0) return null; + + if (values.length === maxValuesShown) { + return ( +
    + {groupIcons[groupName]} +

    + {values.map((value, index) => ( + + {localize(value)} + {index === values.length - 1 ? null : } + + ))} +

    +
    + ); + } + + if (values.length > maxValuesShown - 1) { + return ( +
    + {groupIcons[groupName]} +

    + {values.slice(0, maxValuesShown).map((value, index) => ( + + {localize(value)} + + + ))} + +{values.length - maxValuesShown} +

    +
    + ); + } + + return ( +
    + {groupIcons[groupName]} +

    {localize(values[0])}

    +
    + ); + }; + + const MobileTags = () => ( +
    + {tags.length > 0 && ( +
    + + {tags.length > 1 && ( + +{tags.length - 1} + )} +
    + )} +
    + ); + + const DesktopTags = () => ( +
    + {tags.length > 0 && ( +
    + {tags.map((tag, index) => ( + + ))} +
    + )} +
    + ); + + return ( + +
    + + + + +
    +
    + + {name} + +
    + +
    + + {name} + + + + + + +
    + + + + + +
    + +
    + {groupedContent.map((group, index) => { + if (group.values.length === 0) return null; + return ( +
    + {groupIcons[group.groupName]} +

    + {group.values.map((value, index) => ( + + {localize(value)} + {index === group.values.length - 1 ? null : } + + ))} +

    +
    + ); + })} +
    +
    + + + {t("solutions-wallets-explorer.visit-website")} + + +
    +
    +
    +
    + ); +}; + +export default WalletCard; diff --git a/src/components/solutions/wallets-explorer/WalletCard.module.scss b/src/components/solutions/wallets-explorer/WalletCard.module.scss new file mode 100644 index 00000000..eccf50f1 --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletCard.module.scss @@ -0,0 +1,176 @@ +@import "~@/scss/solutions/_variables.scss"; + +.WalletCard { + border-bottom: 1px solid #322f43; + padding: 20px 0; + position: relative; + + @include breakpoint(lg) { + padding: 40px 0; + } +} + +.WalletCardContainer { + display: flex; + gap: 15px; +} + +.CollapsibleTrigger { + position: absolute; + right: 0; + top: 50%; + transform: translateY(-50%); +} + +.WalletCardImage { + width: 64px; + min-width: 64px; + overflow: hidden; + border-radius: 8px; + + img { + width: 100%; + } +} + +.WalletCardContent { + display: flex; + flex-direction: column; + gap: 12px; +} + +.WalletCardTitle { + font-size: 18px; + font-weight: 700; + line-height: 1.1; + margin-bottom: 0; + color: #fff; + transition: 0.2s all ease; + + @include breakpoint(lg) { + font-size: 24px; + } +} + +.TagsWrapper { + display: flex; + gap: 10px; +} + +.Tags { + display: flex; + gap: 12px; + align-items: center; + font-family: var(--font-mono); +} + +.Tag { + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; + text-transform: uppercase; + color: #a2a1b2; + padding: 8px 12px; + border-radius: 40px; + background: #322f43; + + @include breakpoint(lg) { + line-height: 1.15; + } +} + +.ExtraTagsCount { + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.02em; +} + +.WalletCardLink { + color: #fff; + font-family: "ABC Mono"; + font-size: 13px; + font-weight: 400; + line-height: 1.4; + letter-spacing: 0.26px; + text-transform: uppercase; + margin-top: 12px; + transition: color 0.2s ease; + display: flex; + align-items: center; + gap: 4px; + + @media (min-width: 768px) { + font-size: 15px; + line-height: 1.15; + letter-spacing: 0.3px; + margin-top: 28px; + } +} + +.MobileContent { + @include breakpoint(lg) { + display: none; + } +} + +.DesktopContent { + display: none; + @include breakpoint(lg) { + display: block; + } +} + +.GroupedContent { + display: flex; + align-items: center; + gap: 16px; + + & + .GroupedContent { + margin-top: 12px; + } + + .GroupedContentText { + margin-bottom: 0; + color: #a2a1b2; + font-size: 13px; + font-weight: 700; + line-height: 1.32; + + &, + .ValueGroup { + display: flex; + align-items: center; + gap: 8px; + } + + @include breakpoint(lg) { + font-size: 15px; + line-height: 1.32; + } + } + + svg { + width: 17px; + height: 17px; + } +} + +.Dot { + font-size: 6px; +} + +.MobileTags { + @include breakpoint(lg) { + display: none; + } +} + +.DesktopTags { + display: none; + + @include breakpoint(lg) { + display: block; + } +} diff --git a/src/components/solutions/wallets-explorer/WalletFilters.jsx b/src/components/solutions/wallets-explorer/WalletFilters.jsx new file mode 100644 index 00000000..3e8da024 --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletFilters.jsx @@ -0,0 +1,115 @@ +import { useState } from "react"; + +import { MobileFilters, DesktopFilters } from "./Filters"; + +import Wallets from "./Wallets"; + +import styles from "./WalletFilters.module.scss"; + +const WalletFilters = ({ + filterData, + currentFilters, + setFilters, + updateWallets, + walletData, +}) => { + /** + * Returns all filter data with an extra key named `checked` set to false as the initial state of a filter + * @returns Object + */ + const setFilterInitialState = () => { + return filterData.map((filter) => ({ ...filter, checked: false })); + }; + + const [filterState, setFilterState] = useState(setFilterInitialState()); + + // Accepts the index of the filter and changes its checked state, + // then updates filterState to use the updated state + const toggleFilterActiveState = (index) => { + console.log({ index }); + const filters = [...filterState]; + filters[index].checked = !filters[index].checked; + + setFilterState(filters); + + const activeFilters = { ...currentFilters }; + const filterKey = filters[index].filterKey; + + // Add the filter as true to the filters state or remove the filter if its been unchecked + if (filters[index].checked) { + if (!(filterKey in activeFilters)) { + activeFilters[filterKey] = true; + } + } else { + if (filterKey in activeFilters) { + delete activeFilters[filterKey]; + } + } + + const showAllButton = document.querySelector( + 'button[data-role="show-all"]', + ); + + setFilters(activeFilters); + + // Handle case where a user checks a filter and then unchecks that same filter. If we have no filters active, + // show all wallets and set the "All wallets" button as active. If we have filters at least one filter active, + // remove active state from "All wallets" and show filtered wallets + if (Object.keys(activeFilters).length > 0) { + if ( + showAllButton && + showAllButton.classList.contains(`${styles["wallet-filter--active"]}`) + ) { + showAllButton.classList.remove(`${styles["wallet-filter--active"]}`); + } + + // Filters are active - show filtered results + updateWallets(activeFilters); + } else { + if ( + showAllButton && + !showAllButton.classList.contains(`${styles["wallet-filter--active"]}`) + ) { + showAllButton.classList.add(`${styles["wallet-filter--active"]}`); + } + + // No filters are active - show all + updateWallets({}); + } + }; + + const resetWalletsAndFilters = () => { + setFilterState(setFilterInitialState()); // Reset all filters to unchecked + setFilters({}); // Remove all current filters + updateWallets({}); // Pass empty filters object to show all wallets + + const showAllButton = document.querySelector( + 'button[data-role="show-all"]', + ); + + if ( + showAllButton && + !showAllButton.classList.contains(`${styles["wallet-filter--active"]}`) + ) { + showAllButton.classList.add(`${styles["wallet-filter--active"]}`); + } + }; + + return ( +
    + + + +
    + ); +}; + +export default WalletFilters; diff --git a/src/components/solutions/wallets-explorer/WalletFilters.module.scss b/src/components/solutions/wallets-explorer/WalletFilters.module.scss new file mode 100644 index 00000000..7bd6538b --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletFilters.module.scss @@ -0,0 +1,65 @@ +@import "~@/scss/solutions/_variables.scss"; + +.WalletFilters { + padding: 0 24px 64px; + max-width: 1068px; + margin: 0 auto; + + @include breakpoint(lg) { + display: grid; + grid-template-columns: 330px 1fr; + gap: 40px; + } +} + +.wallet-filter-section { + display: flex; + flex-flow: row wrap; + justify-content: flex-start; + align-items: center; + gap: 5px; + max-width: 1280px; + margin: 0 auto; + padding: 0 20px; + position: relative; + z-index: 10; +} + +.wallet-filter { + display: inline-block; + background-color: #504d61; + color: #fff; + transition: all 0.3s ease; + border-radius: 30px; + padding: 7px 12px; + font-size: 12px; + text-transform: uppercase; + min-width: 50px; + text-align: center; + font-family: "Diatype", monotype; + line-height: 13px; + border: 1px solid #504d61; + + &:hover { + background-color: #1d1a23; + cursor: pointer; + } +} + +.wallet-filter--active { + background-color: #1d1a23; + color: #fff; +} + +.wallet-filter + .wallet-filter-input:checked { + background-color: #1d1a23; +} + +.wallet-filter.active { + background-color: #1d1a23; + color: #fff; +} + +.wallet-filter-input { + appearance: none; +} diff --git a/src/components/solutions/wallets-explorer/Wallets.jsx b/src/components/solutions/wallets-explorer/Wallets.jsx new file mode 100644 index 00000000..63c89bbd --- /dev/null +++ b/src/components/solutions/wallets-explorer/Wallets.jsx @@ -0,0 +1,61 @@ +import { useTranslation } from "next-i18next"; + +import { walletData } from "../../../data/wallets/wallet-data"; + +import WalletCard from "./WalletCard"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +import styles from "./Wallets.module.scss"; + +const Wallets = ({ filteredWalletData }) => { + const { t } = useTranslation(); + + return ( + <> +
    + +

    + {t("solutions-wallets-explorer.showing")}{" "} + + {filteredWalletData.length} / {walletData.length} + {" "} + {t("solutions-wallets-explorer.wallets")} +

    +
    + +
    + {filteredWalletData.length ? ( + filteredWalletData.map((wallet, key) => { + const { ...walletDataContent } = wallet; + + return ( + + + + ); + }) + ) : ( +
    +

    + {t("solutions-wallets-explorer.no-results")} +

    +
    + )} +
    +
    + + ); +}; + +export default Wallets; diff --git a/src/components/solutions/wallets-explorer/Wallets.module.scss b/src/components/solutions/wallets-explorer/Wallets.module.scss new file mode 100644 index 00000000..3f351fb4 --- /dev/null +++ b/src/components/solutions/wallets-explorer/Wallets.module.scss @@ -0,0 +1,13 @@ +.WalletsCount { + font-size: 15px; + font-weight: 700; + line-height: 1.32; + text-align: left; + padding: 0 0 20px; + color: #6c6a81; + margin-bottom: 0; + + .Count { + color: white; + } +} diff --git a/src/components/solutions/wallets-explorer/WalletsLayout.jsx b/src/components/solutions/wallets-explorer/WalletsLayout.jsx new file mode 100644 index 00000000..f5564537 --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletsLayout.jsx @@ -0,0 +1,72 @@ +import { useState } from "react"; +import { useTranslation } from "next-i18next"; +import Lottie from "react-lottie"; + +import { walletData } from "../../../data/wallets/wallet-data"; +import { walletFiltersData } from "../../../data/wallets/wallet-filters"; + +import WalletFilters from "./WalletFilters"; +import { OpacityInText } from "@/components/shared/Text"; + +import styles from "./WalletsLayout.module.scss"; + +import * as walletHeroLottie from "../../../../assets/wallets/wallet-finder.json"; + +const WalletsLayout = () => { + const { t } = useTranslation(); + + const [filters, setFilters] = useState({}); + const [wallets, setWallets] = useState(walletData); + + const updateWalletsBasedOnFilters = (filters) => { + if (Object.keys(filters).length !== 0) { + // Only return wallets that match every key/value pair inside the filters object + const updatedWallets = walletData.filter((obj) => + Object.keys(filters).every((key) => obj[key] === filters[key]), + ); + + setWallets(updatedWallets); + } else { + setWallets(walletData); + } + }; + + return ( +
    +
    +
    +
    + + {t("solutions-wallets-explorer.hero.title")} + + + + {t("solutions-wallets-explorer.hero.text")} + +
    + +
    + +
    +
    +
    + + +
    + ); +}; + +export default WalletsLayout; diff --git a/src/components/solutions/wallets-explorer/WalletsLayout.module.scss b/src/components/solutions/wallets-explorer/WalletsLayout.module.scss new file mode 100644 index 00000000..0a8d7167 --- /dev/null +++ b/src/components/solutions/wallets-explorer/WalletsLayout.module.scss @@ -0,0 +1,92 @@ +@import "~@/scss/solutions/_variables.scss"; + +.WalletLayout { + background: #0f0a16; +} + +.Hero { + padding: 64px 0; + position: relative; + background-image: url("/src/img/solana-wallets/hero-mb.svg"); + background-size: cover; + background-repeat: no-repeat; + background-position: center; + + @media (min-width: 640px) { + padding: 128px 0; + background-image: url("/src/img/solana-wallets/hero-dt.svg"); + background-position: right center; + } + + .HeroContainer { + padding: 0 24px; + display: grid; + grid-template-columns: 1fr; + grid-gap: 40px; + align-items: center; + justify-content: center; + max-width: 1080px; + margin: 0 auto; + + @include breakpoint(md) { + grid-template-columns: 0.8fr 1fr; + } + + @include breakpoint(lg) { + padding: 40px; + } + } + + h2 { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + margin: 0; + } + + p { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + text-align: center; + color: #a2a1b2; + margin: 24px 0 0; + max-width: 100%; + } + + @include breakpoint(md) { + h2, + p { + text-align: left; + } + h2 { + font-size: 48px; + } + } + + @include breakpoint(lg) { + h2 { + font-size: 64px; + font-weight: 700; + line-height: 1.04; + letter-spacing: -0.02em; + text-align: left; + } + p { + font-size: 24px; + font-weight: 700; + line-height: 1.15; + letter-spacing: -0.02em; + text-align: left; + } + } +} + +.LottieContainer { + height: 407px; + [role="button"] { + cursor: initial; + } +} diff --git a/src/components/solutions/wallets/WalletTitle.jsx b/src/components/solutions/wallets/WalletTitle.jsx new file mode 100644 index 00000000..3c3fd1f0 --- /dev/null +++ b/src/components/solutions/wallets/WalletTitle.jsx @@ -0,0 +1,35 @@ +import classNames from "classnames"; +import { Trans } from "next-i18next"; + +import { motion } from "framer-motion"; +import { useInView } from "react-intersection-observer"; + +import { animations, easeFunctions, durations } from "@/constants/animations"; + +const transition = { + duration: durations.slower, + ease: easeFunctions.easeInQuart, +}; + +const WalletTitle = ({ styleName, text }) => { + const { ref, inView } = useInView({ + triggerOnce: true, + threshold: 0.25, + }); + + return ( + +

    + +

    +
    + ); +}; + +export default WalletTitle; diff --git a/src/components/solutions/wallets/WalletsDetails.jsx b/src/components/solutions/wallets/WalletsDetails.jsx new file mode 100644 index 00000000..8b28458b --- /dev/null +++ b/src/components/solutions/wallets/WalletsDetails.jsx @@ -0,0 +1,65 @@ +import classNames from "classnames"; +import DetailsSection, { Detail } from "@/components/solutions/DetailsSection"; +import { useTranslation, Trans } from "next-i18next"; +import { useInView } from "react-intersection-observer"; +import { MotionSlideIn } from "@/components/shared/Motions"; + +const WalletsDetails = ({ styles }) => { + const { t } = useTranslation(); + const { ref: inViewRef, inView } = useInView({ + triggerOnce: true, + threshold: 0.5, + }); + + return ( +
    + +

    + +

    +
    + + + + + + + + + + + +
    + ); +}; + +export default WalletsDetails; diff --git a/src/components/solutions/wallets/WalletsExploreSolutions.jsx b/src/components/solutions/wallets/WalletsExploreSolutions.jsx new file mode 100644 index 00000000..444df853 --- /dev/null +++ b/src/components/solutions/wallets/WalletsExploreSolutions.jsx @@ -0,0 +1,62 @@ +import classNames from "classnames"; +import { useTranslation, Trans } from "next-i18next"; +import Button from "@/components/solutions/Button"; +import { MotionSlideIn } from "@/components/shared/Motions"; +import Text, { AnimatedText, GradientText } from "@/components/shared/Text"; + +const WalletsExploreSolutions = ({ styles }) => { + const { t } = useTranslation(); + + return ( +
    + + + ), + }} + /> + + + + {t("solutions-wallets.explore.subtitle")} + + +
    + +

    {t("solutions-wallets.explore.cards.new.title")}

    +

    {t("solutions-wallets.explore.cards.new.subtitle")}

    +
    + + + + {t("solutions-wallets.explore.cards.embed.title")} + + + {t("solutions-wallets.explore.cards.embed.subtitle")} + + +
    + + +
    + ); +}; + +export default WalletsExploreSolutions; diff --git a/src/components/solutions/wallets/WalletsHero.jsx b/src/components/solutions/wallets/WalletsHero.jsx new file mode 100644 index 00000000..0379e75e --- /dev/null +++ b/src/components/solutions/wallets/WalletsHero.jsx @@ -0,0 +1,103 @@ +import { useEffect, useRef, useState } from "react"; +import { useTranslation } from "next-i18next"; +import classNames from "classnames"; + +import useReducedMotion from "@/hooks/useReducedMotion"; + +import Button from "@/components/solutions/Button"; +import { OpacityInText } from "@/components/shared/Text"; + +import styles from "./WalletsHero.module.scss"; + +import { MotionSlideIn } from "@/components/shared/Motions"; + +const WalletsHero = () => { + const { t } = useTranslation(); + + const [active, setActive] = useState(false); + const videoRef = useRef(null); + const [prefersReducedMotion] = useReducedMotion(); + + useEffect(() => { + setTimeout(() => setActive(true), 500); + }, []); + + useEffect(() => { + prefersReducedMotion && videoRef.current.pause(); + }, [prefersReducedMotion]); + + return ( +
    +
    + + {t("solutions-wallets.hero.kicker")} + + + + {t("solutions-wallets.hero.title")} + + + + {t("solutions-wallets.hero.subtitle")} + + + +
    + +
    + +
    +
    + ); +}; + +export default WalletsHero; diff --git a/src/components/solutions/wallets/WalletsHero.module.scss b/src/components/solutions/wallets/WalletsHero.module.scss new file mode 100644 index 00000000..56bceb42 --- /dev/null +++ b/src/components/solutions/wallets/WalletsHero.module.scss @@ -0,0 +1,147 @@ +@import "~@/scss/solutions/_variables.scss"; + +.WalletsHero { + padding: 64px 24px; + display: flex; + flex-direction: column; + gap: 40px; + + .ContentWrapper { + display: flex; + flex-direction: column; + gap: 24px; + + p, + h1 { + margin-top: 0; + margin-bottom: 0; + } + } + + .Kicker { + font-size: 16px; + font-weight: 800; + line-height: 1.25; + letter-spacing: -0.02em; + text-align: center; + color: var(--blue); + } + + .Title { + font-size: 36px; + font-weight: 700; + line-height: 1.14; + letter-spacing: -0.01em; + text-align: center; + } + + .Subtitle { + font-size: 20px; + font-weight: 700; + line-height: 1.2; + text-align: center; + color: var(--grey-250); + } + + .Buttons { + display: flex; + flex-direction: column; + gap: 16px; + width: max-content; + margin: 0 auto; + } +} + +.VideoWrapper { + display: flex; + align-items: center; + opacity: 0; + transition: opacity $duration-standard $easeInQuart; + transition-delay: 300ms; + + &.Active { + opacity: 1; + } + + video { + width: 100%; + height: 100%; + object-fit: contain; + max-width: 600px; + margin: 0 auto; + } +} + +@include breakpoint(md) { + .WalletsHero { + padding: 80px 24px; + + .ContentWrapper { + @include max-width(650px); + } + + .Title { + font-size: 64px; + } + + .Subtitle { + font-size: 24px; + } + + .Buttons { + flex-direction: row; + } + } +} + +@include breakpoint(xl) { + .WalletsHero { + padding: 92px 24px; + flex-direction: row; + justify-content: space-between; + gap: 128px; + + .ContentWrapper { + max-width: 520px; + justify-content: center; + flex: 1; + + * { + text-align: left; + } + } + + .VideoWrapper { + flex: 2; + height: 670px; + max-width: 500px; + } + + .Kicker { + margin-left: 0; + font-size: 22px; + } + + .Title { + line-height: 1; + letter-spacing: -0.02em; + max-width: 422px; + } + + .Subtitle { + font-size: 28px; + line-height: 1.15; + letter-spacing: -0.02em; + max-width: 100%; + } + + .Buttons { + flex-direction: row; + margin-left: 0; + + a { + padding: 12px; + } + } + } +}