first commit
This commit is contained in:
86
modules/contribute/contribute.module.scss
Normal file
86
modules/contribute/contribute.module.scss
Normal file
@@ -0,0 +1,86 @@
|
||||
.reviewCard {
|
||||
margin-top: 48px;
|
||||
border-bottom-left-radius: 40px !important;
|
||||
border-bottom-right-radius: 40px !important;
|
||||
overflow: unset !important;
|
||||
--on-background: #303309;
|
||||
--on-background-secondary: #616825;
|
||||
--background: #e6f45d;
|
||||
}
|
||||
|
||||
.reviewCardContent {
|
||||
margin: -36px;
|
||||
margin-top: 36px !important;
|
||||
}
|
||||
|
||||
.googlePlayButton {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 48px;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 48px;
|
||||
max-width: 1500px;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.cardsRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.stores {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.additionalStores {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
|
||||
&>*:first-child {
|
||||
flex-grow: 1;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 980px) {
|
||||
.cardsRow {
|
||||
flex-direction: column;
|
||||
}
|
||||
.reviewCard {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 730px) {
|
||||
.main {
|
||||
padding: 24px;
|
||||
gap: 24px;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.main {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 16px;
|
||||
}
|
||||
.homeLink {
|
||||
margin: 0 auto;
|
||||
margin-top: 44px;
|
||||
}
|
||||
.description {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
216
modules/contribute/header/header.module.scss
Normal file
216
modules/contribute/header/header.module.scss
Normal file
@@ -0,0 +1,216 @@
|
||||
.header {
|
||||
min-height: 30vh;
|
||||
padding: 220px 78px;
|
||||
padding-top: 330px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
max-width: 1300px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-weight: 600;
|
||||
font-size: 2rem;
|
||||
max-width: 780px;
|
||||
color: #4F3527;
|
||||
line-height: 150%;
|
||||
|
||||
.appName {
|
||||
font-weight: 700;
|
||||
color: #361400;
|
||||
}
|
||||
|
||||
.hobbyProject {
|
||||
}
|
||||
|
||||
.notPlanEarnMoney {
|
||||
margin: 0px 20px;
|
||||
|
||||
span {
|
||||
box-shadow: -16px 0px 0px 2px #FF005C, 16px 0px 0px 2px #FF005C;
|
||||
background-color: #FF005C;
|
||||
color: #fff;
|
||||
box-decoration-break: clone;
|
||||
border-radius: 9px;
|
||||
}
|
||||
}
|
||||
|
||||
.contributeArrowWrapper {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.contributeArrow {
|
||||
position: relative;
|
||||
display: inline;
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
top: 13px;
|
||||
left: 26px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.chipsContainer {
|
||||
flex-grow: 1;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
transform: rotate(-25deg);
|
||||
width: max-content;
|
||||
}
|
||||
|
||||
.chip {
|
||||
padding: 12px 32px;
|
||||
padding-right: 22px;
|
||||
border-radius: 40px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-direction: row;
|
||||
gap: 9px;
|
||||
font-size: 1.62rem;
|
||||
font-weight: 600;
|
||||
width: fit-content;
|
||||
|
||||
.icon {
|
||||
display: flex;
|
||||
}
|
||||
}
|
||||
|
||||
.openSourceChip {
|
||||
background-color: #DDF9D0;
|
||||
color: #339704;
|
||||
transform: translateX(-50px);
|
||||
|
||||
svg {
|
||||
color: #339704;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.madeWithLoveChip {
|
||||
background-color: #FFEEF2;
|
||||
color: #861118;
|
||||
transform: translateX(50px);
|
||||
|
||||
svg {
|
||||
color: #FF003D;
|
||||
width: 35px;
|
||||
height: 35px;
|
||||
}
|
||||
}
|
||||
|
||||
.gradientContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% + 300px);
|
||||
z-index: -100;
|
||||
}
|
||||
|
||||
.transitionToBackgound {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(180deg, #ffffff00 0%, #ffffff 300px);
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 1250px) {
|
||||
.chipsContainer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
left: -60px;
|
||||
margin: auto;
|
||||
top: 230px;
|
||||
flex-direction: row;
|
||||
transform: rotate(356deg);
|
||||
z-index: -1;
|
||||
}
|
||||
.openSourceChip {
|
||||
transform: translate(90px, -20px) rotate(-15deg);
|
||||
}
|
||||
.madeWithLoveChip {
|
||||
transform: translateX(0px);
|
||||
}
|
||||
.header {
|
||||
padding-top: 390px;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 940px) {
|
||||
.header {
|
||||
padding: 200px 48px;
|
||||
padding-top: 360px;
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: justify;
|
||||
max-width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.header {
|
||||
padding: 130px 48px;
|
||||
padding-top: 360px;
|
||||
}
|
||||
|
||||
.description {
|
||||
font-size: 1.5rem;
|
||||
line-height: 170%;
|
||||
|
||||
.contributeArrow {
|
||||
div {
|
||||
top: 8px;
|
||||
}
|
||||
svg {
|
||||
width: auto;
|
||||
height: 150px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 640px) {
|
||||
.header {
|
||||
padding: 110px 36px;
|
||||
padding-top: 360px;
|
||||
}
|
||||
|
||||
.chipsContainer {
|
||||
top: 200px;
|
||||
scale: 0.7;
|
||||
}
|
||||
|
||||
.description {
|
||||
.contributeArrow {
|
||||
div {
|
||||
top: 10px;
|
||||
}
|
||||
svg {
|
||||
width: auto;
|
||||
height: 130px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
127
modules/contribute/header/header.tsx
Normal file
127
modules/contribute/header/header.tsx
Normal file
@@ -0,0 +1,127 @@
|
||||
import styles from './header.module.scss';
|
||||
import { TopBar } from '@/components/topBar';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import { clsx } from 'clsx';
|
||||
import LoveIcon from '@/assets/icons/love.svg';
|
||||
import OpenSourceIcon from '@/assets/icons/open-source.svg';
|
||||
import { Gradients } from '@/components/gradients';
|
||||
import ContributeArrow from '@/assets/images/contribute-arrow.svg';
|
||||
import ContributeArrowInvert from '@/assets/images/contribute-arrow-invert.svg';
|
||||
import { useEffect, useRef, useState } from 'react';
|
||||
import useMediaQuery from '@/utils/useMediaQuery';
|
||||
|
||||
export const locales: LocalesMap = {
|
||||
ru: {
|
||||
appName: 'RideBus',
|
||||
description: (
|
||||
<>
|
||||
<span className={styles.appName}>RideBus</span> — это <span className={styles.hobbyProject}>хобби-проект</span>{' '}
|
||||
и я <span className={styles.notPlanEarnMoney}><span>не планирую на нём зарабатывать</span></span>,{' '}
|
||||
если вы хотите помочь, вот способ, как это <span className={styles.contributeArrowWrapper}>сделать<Arrow /></span>
|
||||
</>
|
||||
),
|
||||
openSource: 'Open source',
|
||||
madeWithLove: 'Сделано с',
|
||||
},
|
||||
en: {
|
||||
appName: 'RideBus',
|
||||
description: (
|
||||
<>
|
||||
<span className={styles.appName}>RideBus</span> is <span className={styles.hobbyProject}>a hobby project</span>{' '}
|
||||
and I <span className={styles.notPlanEarnMoney}><span>do not plan to earn money on it</span></span>,{' '}
|
||||
if you want to help, here is a way how to do <span className={styles.contributeArrowWrapper}>it <Arrow /></span>
|
||||
</>
|
||||
),
|
||||
openSource: 'Open source',
|
||||
madeWithLove: 'Made with',
|
||||
},
|
||||
};
|
||||
|
||||
function Arrow() {
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const [isInvertArrow, setIsInvertArrow] = useState(false);
|
||||
const breakpoint640 = useMediaQuery('(max-width: 640px)');
|
||||
const breakpoint800 = useMediaQuery('(max-width: 800px)');
|
||||
|
||||
useEffect(() => {
|
||||
const arrowWidth = breakpoint640 ? 260 : breakpoint800 ? 300 : 460;
|
||||
|
||||
const handleResize = () => {
|
||||
if (rootRef.current) {
|
||||
const { left } = rootRef.current.getBoundingClientRect();
|
||||
|
||||
if (window.innerWidth < left + arrowWidth) {
|
||||
setIsInvertArrow(true);
|
||||
} else {
|
||||
setIsInvertArrow(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
window.addEventListener('resize', handleResize);
|
||||
|
||||
return () => {
|
||||
window.removeEventListener('resize', handleResize);
|
||||
};
|
||||
}, [breakpoint640, breakpoint800]);
|
||||
|
||||
let left = isInvertArrow ? '-130px' : '26px';
|
||||
|
||||
if (breakpoint640) {
|
||||
left = isInvertArrow ? '-95px' : '16px';
|
||||
}
|
||||
|
||||
if (breakpoint800) {
|
||||
left = isInvertArrow ? '-95px' : '16px';
|
||||
}
|
||||
|
||||
return (
|
||||
<span ref={rootRef} className={styles.contributeArrow}>
|
||||
<span style={{ width: '140px', display: 'inline-block' }} />
|
||||
<div style={{ left }}>
|
||||
{isInvertArrow ? <ContributeArrowInvert /> : <ContributeArrow />}
|
||||
</div>
|
||||
</span>
|
||||
)
|
||||
}
|
||||
|
||||
type ChipProps = {
|
||||
className?: string;
|
||||
label: string;
|
||||
icon: any;
|
||||
};
|
||||
|
||||
function Chip(props: ChipProps) {
|
||||
const { className, label, icon } = props;
|
||||
|
||||
return (
|
||||
<div className={clsx(styles.chip, className)}>
|
||||
<span>{label}</span>
|
||||
<span className={styles.icon}>{icon}</span>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
export function Header() {
|
||||
const t = useLocale(locales);
|
||||
return (
|
||||
<header className={styles.header}>
|
||||
<TopBar notHide />
|
||||
<div className={styles.content}>
|
||||
<p className={styles.description}>{t('description')}</p>
|
||||
<div className={styles.chipsContainer}>
|
||||
<Chip className={styles.openSourceChip} label={t('openSource')} icon={(<OpenSourceIcon />)} />
|
||||
<Chip className={styles.madeWithLoveChip} label={t('madeWithLove')} icon={(<LoveIcon />)} />
|
||||
</div>
|
||||
</div>
|
||||
<div className={styles.gradientContainer}>
|
||||
<div
|
||||
className={styles.transitionToBackgound}
|
||||
/>
|
||||
<Gradients />
|
||||
</div>
|
||||
</header>
|
||||
);
|
||||
}
|
||||
1
modules/contribute/header/index.tsx
Normal file
1
modules/contribute/header/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './header';
|
||||
21
modules/contribute/locales.tsx
Normal file
21
modules/contribute/locales.tsx
Normal file
@@ -0,0 +1,21 @@
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
|
||||
export const locales: LocalesMap = {
|
||||
ru: {
|
||||
pageTitle: 'RideBus - Вклад',
|
||||
pageDescription:
|
||||
'RideBus — Приложение, которое поможет вам удобно смотреть расписание общественного транспорта',
|
||||
appName: 'RideBus',
|
||||
reviewTitle: 'Оставьте отзыв',
|
||||
reviewDescription:
|
||||
'Буду благодарен, если вы оставите свой отзыв о приложении. Отзывы очень важны и помогают улучшать приложение.',
|
||||
},
|
||||
en: {
|
||||
pageTitle: 'RideBus - Contribute',
|
||||
pageDescription: 'RideBus — An app that helps you conveniently view public transportation schedules',
|
||||
appName: 'RideBus',
|
||||
reviewTitle: 'Leave a review',
|
||||
reviewDescription:
|
||||
'I would be grateful if you could leave your feedback on the application. Reviews are very important and help us improve application.',
|
||||
},
|
||||
};
|
||||
1
modules/contribute/shareCard/index.tsx
Normal file
1
modules/contribute/shareCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './shareCard';
|
||||
49
modules/contribute/shareCard/shareCard.module.scss
Normal file
49
modules/contribute/shareCard/shareCard.module.scss
Normal file
@@ -0,0 +1,49 @@
|
||||
.card {
|
||||
--on-background: #59321c;
|
||||
--on-background-secondary: #866655;
|
||||
--background: #ffefe9;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 8px;
|
||||
margin: -8px;
|
||||
margin-top: 0;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
.heartContiner {
|
||||
position: absolute;
|
||||
bottom: -18%;
|
||||
right: -17%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.heartImage {
|
||||
width: 420px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 980px) {
|
||||
.card {
|
||||
height: min-content;
|
||||
}
|
||||
.heartContiner {
|
||||
bottom: -32%;
|
||||
right: -4%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 820px) {
|
||||
.heartContiner {
|
||||
bottom: -42%;
|
||||
right: -4%;
|
||||
}
|
||||
|
||||
.heartImage {
|
||||
width: 300px;
|
||||
}
|
||||
.content {
|
||||
margin-top: 0;
|
||||
}
|
||||
}
|
||||
60
modules/contribute/shareCard/shareCard.tsx
Normal file
60
modules/contribute/shareCard/shareCard.tsx
Normal file
@@ -0,0 +1,60 @@
|
||||
import styles from './shareCard.module.scss';
|
||||
import { Card } from '@/components/card';
|
||||
import clsx from 'clsx';
|
||||
import useLocale, { LocalesMap } from '@/utils/useLocale';
|
||||
import { CursorEffect } from '@/components/cursor';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
appName: 'RideBus',
|
||||
slogan: 'Приложение, которое поможет вам удобно смотреть расписание общественного транспорта',
|
||||
title: 'Расскажите друзьям',
|
||||
description:
|
||||
'Мне очень важно, чтобы мои проекты приносили пользу. Если вы расскажете своим друзьям о своем опыте использования этого приложения и как оно помогло вам, я буду вам очень признателен.',
|
||||
},
|
||||
en: {
|
||||
appName: 'RideBus',
|
||||
slogan: 'An app that helps you conveniently view public transportation schedules',
|
||||
title: 'Tell your friends',
|
||||
description:
|
||||
'It is very important to me that my projects bring benefits. If you tell your friends about your experience using this application and how it helped you, I will be very grateful to you.',
|
||||
},
|
||||
};
|
||||
|
||||
export function ShareCard() {
|
||||
const t = useLocale(locales);
|
||||
|
||||
const ShareCard = (
|
||||
<Card
|
||||
className={clsx(styles.card)}
|
||||
title={t('title')}
|
||||
subtitle={t('description')}
|
||||
backdropOnText
|
||||
classes={{ subtitle: styles.description, content: styles.content }}
|
||||
onClick={() => {
|
||||
if (navigator.share) {
|
||||
navigator
|
||||
.share({
|
||||
title: t('appName'),
|
||||
text: t('slogan'),
|
||||
url: 'https://ridebus.by/',
|
||||
})
|
||||
.then(() => {
|
||||
console.log('Thanks for sharing!');
|
||||
})
|
||||
.catch(console.error);
|
||||
}
|
||||
}}
|
||||
>
|
||||
</Card>
|
||||
);
|
||||
if (typeof window !== 'undefined' && (navigator.share as any)) {
|
||||
return (
|
||||
<CursorEffect cursorPadding={8} cursorBorderRadius={36}>
|
||||
{ShareCard}
|
||||
</CursorEffect>
|
||||
);
|
||||
} else {
|
||||
return ShareCard;
|
||||
}
|
||||
}
|
||||
132
modules/home/citiesCard/citiesCard.module.scss
Normal file
132
modules/home/citiesCard/citiesCard.module.scss
Normal file
@@ -0,0 +1,132 @@
|
||||
.card {
|
||||
--on-background: #122F60;
|
||||
--on-background-secondary: #2C4678;
|
||||
--background: #D8E2FF;
|
||||
width: 100%;
|
||||
height: 580px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mockupPhoneContainer {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.backdropImage {
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
height: 650px;
|
||||
}
|
||||
|
||||
.ridebusImageMask {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
width: 640px;
|
||||
height: 640px;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: 50%;
|
||||
bottom: -140px;
|
||||
right: -140px;
|
||||
}
|
||||
|
||||
.ridebusImageAnchor {
|
||||
bottom: 320px;
|
||||
left: 320px;
|
||||
position: absolute;
|
||||
}
|
||||
|
||||
.ridebusImage {
|
||||
z-index: 1;
|
||||
bottom: -400px;
|
||||
left: -400px;
|
||||
position: absolute;
|
||||
width: 800px;
|
||||
height: 800px;
|
||||
}
|
||||
|
||||
.screenshot {
|
||||
z-index: 1;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
position: absolute;
|
||||
width: auto;
|
||||
height: 650px;
|
||||
}
|
||||
|
||||
.smallCitiesLine {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
right: -50px;
|
||||
bottom: 260px;
|
||||
transform-origin: right center;
|
||||
transform: rotate(4deg);
|
||||
|
||||
svg {
|
||||
width: 220px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
.bigCitiesLine {
|
||||
z-index: 1;
|
||||
position: absolute;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
right: -280px;
|
||||
bottom: 100px;
|
||||
transform-origin: right center;
|
||||
transform: rotate(356deg);
|
||||
gap: 9px;
|
||||
|
||||
svg {
|
||||
width: 260px;
|
||||
height: auto;
|
||||
}
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.card {
|
||||
height: 490px;
|
||||
}
|
||||
.ridebusImageMask {
|
||||
width: 440px;
|
||||
height: 440px;
|
||||
bottom: -70px;
|
||||
right: -110px;
|
||||
}
|
||||
.ridebusImageAnchor {
|
||||
bottom: 220px;
|
||||
left: 220px;
|
||||
}
|
||||
.smallCitiesLine {
|
||||
bottom: 200px;
|
||||
}
|
||||
.bigCitiesLine {
|
||||
bottom: 50px;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@media (max-width: 800px) {
|
||||
.card {
|
||||
height: 390px;
|
||||
}
|
||||
.smallCitiesLine {
|
||||
bottom: 160px;
|
||||
|
||||
svg {
|
||||
width: 170px;
|
||||
}
|
||||
}
|
||||
.bigCitiesLine svg {
|
||||
width: 200px;
|
||||
}
|
||||
}
|
||||
144
modules/home/citiesCard/citiesCard.tsx
Normal file
144
modules/home/citiesCard/citiesCard.tsx
Normal file
@@ -0,0 +1,144 @@
|
||||
import { useRef } from 'react';
|
||||
import styles from './citiesCard.module.scss';
|
||||
import { Card } from '@/components/card';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import CityPolotskSmallEn from '@/assets/images/city-polotsk-small-en.svg';
|
||||
import CityNovopolotskSmallEn from '@/assets/images/city-novopolotsk-small-en.svg';
|
||||
import CityBigEn from '@/assets/images/city-big-en.svg';
|
||||
import CityPolotskSmallRu from '@/assets/images/city-polotsk-small-ru.svg';
|
||||
import CityNovopolotskSmallRu from '@/assets/images/city-novopolotsk-small-ru.svg';
|
||||
import CityBigRu from '@/assets/images/city-big-ru.svg';
|
||||
import maskUrl from '@/assets/images/cities-star-mask.svg?url';
|
||||
import ridebusImage from '@/assets/images/cities-backdrop.jpg';
|
||||
import Image from 'next/image';
|
||||
import useFrame from '@/utils/useFrame';
|
||||
import useScroll from '@/utils/useScroll';
|
||||
import { useRouter } from 'next/router';
|
||||
import useMediaQuery from '@/utils/useMediaQuery';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
title: 'Поддержка нескольких городов',
|
||||
description:
|
||||
'Поддерживаются города Полоцк и Новополоцк, но мы планируем добавлять расписание общественного транспорта и для других городов Беларуси',
|
||||
},
|
||||
en: {
|
||||
title: 'Multi-city support',
|
||||
description:
|
||||
'The cities of Polotsk and Novopolotsk are supported, but we plan to add public transportation schedules for other cities of Belarus as well',
|
||||
},
|
||||
};
|
||||
|
||||
function SmallCitiesLine() {
|
||||
const router = useRouter();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const breakpoint800 = useMediaQuery('(max-width: 800px)');
|
||||
|
||||
const cityWidth = breakpoint800 ? 170 : 220;
|
||||
const swapWidth = cityWidth * 1;
|
||||
|
||||
useScroll((scrollY, delta, time) => {
|
||||
|
||||
const offset = -(scrollY / 6) - (time / 30);
|
||||
|
||||
if (rootRef.current) {
|
||||
rootRef.current.style.transform = `rotate(3deg) translate3D(${offset % swapWidth + cityWidth}px, 0px, 0px)`;
|
||||
}
|
||||
}, {}, [cityWidth]);
|
||||
|
||||
const CityPolotsk = router.locale === 'ru' ? CityPolotskSmallRu : CityPolotskSmallEn;
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className={styles.smallCitiesLine}>
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
<CityPolotsk />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
function BigCitiesLine() {
|
||||
const router = useRouter();
|
||||
const rootRef = useRef<HTMLDivElement>(null);
|
||||
const breakpoint800 = useMediaQuery('(max-width: 800px)');
|
||||
|
||||
const cityWidth = (breakpoint800 ? 200 : 260) + 9;
|
||||
const swapWidth = cityWidth * 1;
|
||||
|
||||
useScroll((scrollY, delta, time) => {
|
||||
|
||||
const offset = (scrollY / 6) + (time / 30);
|
||||
|
||||
if (rootRef.current) {
|
||||
rootRef.current.style.transform = `rotate(356deg) translate3D(${offset % swapWidth}px, 0px, 0px)`;
|
||||
}
|
||||
}, {}, [cityWidth]);
|
||||
|
||||
const CityNovopolotsk = router.locale === 'ru' ? CityNovopolotskSmallRu : CityNovopolotskSmallEn;
|
||||
|
||||
return (
|
||||
<div ref={rootRef} className={styles.bigCitiesLine}>
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
<CityNovopolotsk />
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function CitiesCard() {
|
||||
const t = useLocale(locales);
|
||||
const ridebusImageMaskRef = useRef<HTMLDivElement>(null);
|
||||
const ridebusImageRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
useFrame((delta, time) => {
|
||||
if (ridebusImageMaskRef.current && ridebusImageRef.current) {
|
||||
ridebusImageMaskRef.current.style.transform = `rotate(${-(time * 0.005) % 360}deg) translate3D(0px, 0px, 0px)`;
|
||||
ridebusImageRef.current.style.transform = `rotate(${(time * 0.005) % 360}deg) translate3D(0px, 0px, 0px)`;
|
||||
}
|
||||
});
|
||||
|
||||
return (
|
||||
<Card
|
||||
className={styles.card}
|
||||
title={t('title')}
|
||||
subtitle={t('description')}
|
||||
backdropOnText
|
||||
classes={{ subtitle: styles.description, title: styles.title }}
|
||||
>
|
||||
<div className={styles.mockupPhoneContainer}>
|
||||
<div
|
||||
className={styles.ridebusImageMask}
|
||||
ref={ridebusImageMaskRef}
|
||||
style={{
|
||||
maskImage: `url(${maskUrl.src})`,
|
||||
}}>
|
||||
<div ref={ridebusImageRef} className={styles.ridebusImageAnchor}>
|
||||
<Image
|
||||
className={styles.ridebusImage}
|
||||
height={800}
|
||||
width={800}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={ridebusImage}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<SmallCitiesLine />
|
||||
<BigCitiesLine />
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
1
modules/home/citiesCard/index.tsx
Normal file
1
modules/home/citiesCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './citiesCard';
|
||||
54
modules/home/contributeCard/contributeCard.module.scss
Normal file
54
modules/home/contributeCard/contributeCard.module.scss
Normal file
@@ -0,0 +1,54 @@
|
||||
.cardWrapper {
|
||||
flex-grow: 1;
|
||||
}
|
||||
|
||||
.card {
|
||||
--on-background: #2C4678;
|
||||
--on-background-secondary: #455E91;
|
||||
--background: #F3F3FA;
|
||||
flex-grow: 1;
|
||||
position: relative;
|
||||
min-height: 240px;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
text-align: center;
|
||||
cursor: none;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
|
||||
.cardContent {
|
||||
flex-grow: 0 !important;
|
||||
margin: 0 !important;
|
||||
}
|
||||
|
||||
.circlesContainer {
|
||||
position: absolute;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.circlesBackground {
|
||||
position: absolute;
|
||||
width: 40%;
|
||||
padding: 40%;
|
||||
border-radius: 50%;
|
||||
background-color: #D8E2FF;
|
||||
}
|
||||
|
||||
.circlesFroreground {
|
||||
position: absolute;
|
||||
width: 25%;
|
||||
padding: 25%;
|
||||
border-radius: 50%;
|
||||
background-color: #AEC6FF;
|
||||
}
|
||||
77
modules/home/contributeCard/contributeCard.tsx
Normal file
77
modules/home/contributeCard/contributeCard.tsx
Normal file
@@ -0,0 +1,77 @@
|
||||
import styles from './contributeCard.module.scss';
|
||||
import { Card } from '@/components/card';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import Link from 'next/link';
|
||||
import { animated, useSpring } from '@react-spring/web';
|
||||
import { useRef } from 'react';
|
||||
import { CursorEffect } from '@/components/cursor';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
title: 'Вклад',
|
||||
description: 'Узнайте, как вы можете помочь проекту',
|
||||
},
|
||||
en: {
|
||||
title: 'Contribute',
|
||||
description: 'Find out how you can help the project',
|
||||
},
|
||||
};
|
||||
|
||||
export function ContributeCard() {
|
||||
const t = useLocale(locales);
|
||||
const cardRef = useRef(null);
|
||||
|
||||
const [springsBackground, apiBackground] = useSpring(() => ({
|
||||
scale: 1,
|
||||
config: { mass: 4, tension: 350, friction: 30 },
|
||||
}));
|
||||
const [springsForeground, apiForeground] = useSpring(() => ({
|
||||
scale: 1,
|
||||
config: { mass: 2, tension: 350, friction: 20 },
|
||||
}));
|
||||
|
||||
const handleEnter = () => {
|
||||
apiForeground.start({
|
||||
scale: 1.5,
|
||||
});
|
||||
apiBackground.start({
|
||||
scale: 1.15,
|
||||
});
|
||||
};
|
||||
const handleLeave = () => {
|
||||
apiForeground.start({
|
||||
scale: 1,
|
||||
});
|
||||
apiBackground.start({
|
||||
scale: 1,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<CursorEffect className={styles.cardWrapper} cursorBorderRadius={36} cursorPadding={12}>
|
||||
<Link href="/contribute">
|
||||
<Card
|
||||
ref={cardRef}
|
||||
className={styles.card}
|
||||
title={t('title')}
|
||||
subtitle={t('description')}
|
||||
classes={{ content: styles.cardContent, title: styles.title }}
|
||||
onMouseEnter={handleEnter}
|
||||
onMouseLeave={handleLeave}
|
||||
>
|
||||
<div className={styles.circlesContainer}>
|
||||
<animated.div
|
||||
className={styles.circlesBackground}
|
||||
style={{ ...springsBackground }}
|
||||
/>
|
||||
<animated.div
|
||||
className={styles.circlesFroreground}
|
||||
style={{ ...springsForeground }}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
</Link>
|
||||
</CursorEffect>
|
||||
);
|
||||
}
|
||||
1
modules/home/contributeCard/index.tsx
Normal file
1
modules/home/contributeCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './contributeCard';
|
||||
116
modules/home/freeCard/freeCard.module.scss
Normal file
116
modules/home/freeCard/freeCard.module.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
.root {
|
||||
height: auto;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 440px;
|
||||
height: 580px;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.freeForever, .wihthoutAds {
|
||||
--on-background: #000000;
|
||||
--background: #F3FF6C;
|
||||
width: 100%;
|
||||
position: relative;
|
||||
border-radius: 100px;
|
||||
z-index: -1;
|
||||
flex-shrink: 0;
|
||||
overflow: unset;
|
||||
}
|
||||
|
||||
.freeForever {
|
||||
padding: 48px 16px;
|
||||
}
|
||||
|
||||
|
||||
.wihthoutAds {
|
||||
--on-background: #861118;
|
||||
--background: #FFEEF2;
|
||||
padding: 22px 16px;
|
||||
}
|
||||
|
||||
|
||||
.freeForeverTitle, .wihthoutAdsTitle {
|
||||
left: 0;
|
||||
right: 0;
|
||||
text-align: center;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.freeForeverTitleExclamationPoint {
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: -46px;
|
||||
left: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.wihthoutAdsTitleOutline {
|
||||
position: relative;
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
top: -46px;
|
||||
left: -260px;
|
||||
}
|
||||
}
|
||||
|
||||
.imageCard {
|
||||
background-color: #FFF0E8;
|
||||
border-radius: 36px;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.backdropImage, .screenshotImage, .shadowImage {
|
||||
position: absolute;
|
||||
top: 0px;
|
||||
right: -110px;
|
||||
width: 660px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.screenshotImage {
|
||||
z-index: 2;
|
||||
}
|
||||
|
||||
.shadowImage {
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.root {
|
||||
height: 490px;
|
||||
width: 360px;
|
||||
}
|
||||
.backdropImage, .screenshotImage, .shadowImage {
|
||||
width: 540px;
|
||||
right: -98px;
|
||||
}
|
||||
|
||||
.freeForeverTitle, .wihthoutAdsTitle {
|
||||
font-size: 1.5rem;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 980px) {
|
||||
.root {
|
||||
width: 100%;
|
||||
z-index: 1;
|
||||
}
|
||||
.backdropImage, .screenshotImage, .shadowImage {
|
||||
right: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 510px) {
|
||||
.backdropImage, .screenshotImage, .shadowImage {
|
||||
right: -105px;
|
||||
}
|
||||
}
|
||||
78
modules/home/freeCard/freeCard.tsx
Normal file
78
modules/home/freeCard/freeCard.tsx
Normal file
@@ -0,0 +1,78 @@
|
||||
import styles from './freeCard.module.scss';
|
||||
import clsx from 'clsx';
|
||||
import { Card } from '@/components/card';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import Image from 'next/image';
|
||||
import backdropImage from '@/assets/images/free-backdrop.png';
|
||||
import screenshotEnImage from '@/assets/images/free-screenshot-en.png';
|
||||
import screenshotRuImage from '@/assets/images/free-screenshot-ru.png';
|
||||
import shadowImage from '@/assets/images/free-shadow.png';
|
||||
import ExclamationPoint from '@/assets/images/free-exclamation-point.svg';
|
||||
import Outline from '@/assets/images/free-outline.svg';
|
||||
import { Fragment } from 'react';
|
||||
import { useRouter } from 'next/router';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
freeForever: 'Бесплатно навсегда',
|
||||
wihthoutAds: 'Без рекламы',
|
||||
},
|
||||
en: {
|
||||
freeForever: 'Free forever',
|
||||
wihthoutAds: 'Without ads',
|
||||
},
|
||||
};
|
||||
|
||||
export function FreeCard() {
|
||||
const router = useRouter();
|
||||
const t = useLocale(locales);
|
||||
|
||||
return (
|
||||
<div className={styles.root}>
|
||||
<Image
|
||||
className={clsx(styles.screenshotImage)}
|
||||
width={695}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={router.locale === 'ru' ? screenshotRuImage : screenshotEnImage}
|
||||
/>
|
||||
<div className={styles.imageCard}>
|
||||
<Image
|
||||
className={clsx(styles.backdropImage)}
|
||||
width={695}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={backdropImage}
|
||||
/>
|
||||
</div>
|
||||
<Image
|
||||
className={clsx(styles.shadowImage)}
|
||||
width={695}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={shadowImage}
|
||||
/>
|
||||
<Card
|
||||
className={styles.freeForever}
|
||||
title={
|
||||
<Fragment>
|
||||
{t('freeForever')}
|
||||
<span className={styles.freeForeverTitleExclamationPoint}><ExclamationPoint /></span>
|
||||
</Fragment>
|
||||
}
|
||||
classes={{ title: styles.freeForeverTitle }}
|
||||
/>
|
||||
<Card
|
||||
className={styles.wihthoutAds}
|
||||
title={
|
||||
<Fragment>
|
||||
{t('wihthoutAds')}
|
||||
<span className={styles.wihthoutAdsTitleOutline}><Outline /></span>
|
||||
</Fragment>
|
||||
}
|
||||
classes={{ title: styles.wihthoutAdsTitle }}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
1
modules/home/freeCard/index.tsx
Normal file
1
modules/home/freeCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './freeCard';
|
||||
145
modules/home/header.tsx
Normal file
145
modules/home/header.tsx
Normal file
@@ -0,0 +1,145 @@
|
||||
import styles from '@/modules/home/home.module.scss';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import clsx from 'clsx';
|
||||
import { manrope, caveat } from '@/styles/fonts';
|
||||
import ThinStarBig from '@/assets/images/thin-star-big.svg';
|
||||
import ThickRoundStar5 from '@/assets/images/thick-round-star-5.svg';
|
||||
import thickRoundStar6Url from '@/assets/images/thick-round-star-6.svg?url';
|
||||
import ridebusFlat from '@/assets/images/ridebus-flat.jpg';
|
||||
import Image from 'next/image';
|
||||
import { Gradients } from '@/components/gradients';
|
||||
import { useEffect, useRef, Fragment } from 'react';
|
||||
import useScroll from '@/utils/useScroll';
|
||||
import { TopBar } from '@/components/topBar';
|
||||
import useFrame from '@/utils/useFrame';
|
||||
import useMediaQuery from '@/utils/useMediaQuery';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
slogan_line: (
|
||||
<>
|
||||
<span>Приложение,</span>
|
||||
<span>которое</span>
|
||||
<span>поможет</span>
|
||||
<div>
|
||||
<span>вам</span>
|
||||
<div className={styles.thinStarBig}>
|
||||
<ThinStarBig />
|
||||
</div>
|
||||
</div>
|
||||
<span>с</span>
|
||||
<span className={styles.spendWord}>лёгкостью</span>
|
||||
<span>смотреть</span>
|
||||
<span>расписание</span>
|
||||
<span className={clsx(caveat.className, styles.wiselyWord)}>транспорта</span>
|
||||
</>
|
||||
),
|
||||
},
|
||||
en: {
|
||||
slogan_line: (
|
||||
<>
|
||||
<span>Small</span>
|
||||
<span>app</span>
|
||||
<span>that</span>
|
||||
<span>helps</span>
|
||||
<div>
|
||||
<span>you</span>
|
||||
<div className={styles.thinStarBig}>
|
||||
<ThinStarBig />
|
||||
</div>
|
||||
</div>
|
||||
<span className={styles.spendWord}>conveniently</span>
|
||||
<span>view</span>
|
||||
<span className={clsx(caveat.className, styles.wiselyWord)}>
|
||||
public
|
||||
</span>
|
||||
<span className={clsx(caveat.className, styles.wiselyWord)}>
|
||||
transport
|
||||
</span>
|
||||
<span className={clsx(caveat.className, styles.wiselyWord)}>
|
||||
schedules
|
||||
</span>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
|
||||
export default function Header() {
|
||||
const t = useLocale(locales);
|
||||
const sloganLineRef = useRef<HTMLSpanElement>(null);
|
||||
const transitionToBackgoundRef = useRef<HTMLDivElement>(null);
|
||||
const thickRoundStar6MaskRef = useRef<HTMLDivElement>(null);
|
||||
const thickRoundStar6ImageRef = useRef<HTMLImageElement>(null);
|
||||
const thickRoundStar5 = useRef<HTMLDivElement>(null);
|
||||
const breakpoint600 = false;//useMediaQuery('(max-width: 600px)');
|
||||
|
||||
useScroll((scrollY, deltaTime, time) => {
|
||||
const childs: HTMLSpanElement[] = [
|
||||
...Array.from(sloganLineRef.current?.children || []),
|
||||
] as HTMLSpanElement[];
|
||||
|
||||
childs.forEach((child, index) => {
|
||||
const rawOffset = Math.min(-scrollY * 2 + index * 50, 0);
|
||||
const offset = Math.max(-Math.pow(rawOffset / 50, 2), -500);
|
||||
const rotateDirection = index % 2 === 0 ? 1 : -1;
|
||||
|
||||
child.style.transform = `translate3D(0px, ${offset}px, 0px) rotate(${(offset / 10) * rotateDirection}deg)`;
|
||||
});
|
||||
|
||||
if (transitionToBackgoundRef.current && scrollY + 300 < window.innerHeight) {
|
||||
transitionToBackgoundRef.current.style.height = `${scrollY + 300}px`;
|
||||
}
|
||||
|
||||
if (thickRoundStar6MaskRef.current && scrollY - 300 < window.innerHeight) {
|
||||
thickRoundStar6MaskRef.current.style.transform = `translate3D(0px, ${scrollY / 6}px, 0px) rotate(${scrollY / 8 + (time * 0.01) % 360}deg)`;
|
||||
}
|
||||
|
||||
if (thickRoundStar6ImageRef.current && scrollY - 300 < window.innerHeight) {
|
||||
thickRoundStar6ImageRef.current.style.transform = `rotate(${-scrollY / 8 - (time * 0.01) % 360}deg) translate3d(0,0,0)`;
|
||||
}
|
||||
|
||||
if (thickRoundStar5.current && scrollY - 300 < window.innerHeight) {
|
||||
thickRoundStar5.current.style.transform = `translate3D(-50%, -50%,0) rotate(${-scrollY / 6 - 10}deg)`;
|
||||
}
|
||||
}, { accelerator: 0.06 });
|
||||
|
||||
return (
|
||||
<Fragment>
|
||||
<header className={styles.header}>
|
||||
<TopBar />
|
||||
<div
|
||||
className={styles.thickRoundStar6}
|
||||
ref={thickRoundStar6MaskRef}
|
||||
style={{ maskImage: `url(${thickRoundStar6Url.src})` }}
|
||||
>
|
||||
<Image
|
||||
ref={thickRoundStar6ImageRef}
|
||||
height={400}
|
||||
width={400}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={ridebusFlat}
|
||||
/>
|
||||
</div>
|
||||
<h1 className={clsx(styles.slogan, manrope.className)}>
|
||||
<span ref={sloganLineRef} className={styles.line1}>
|
||||
{t('slogan_line')}
|
||||
</span>
|
||||
</h1>
|
||||
<div className={styles.gradientContainer}>
|
||||
<div
|
||||
ref={transitionToBackgoundRef}
|
||||
className={styles.transitionToBackgound}
|
||||
/>
|
||||
{!breakpoint600 && <Gradients />}
|
||||
</div>
|
||||
</header>
|
||||
<div className={styles.thickRoundStar5}>
|
||||
<div ref={thickRoundStar5}>
|
||||
<ThickRoundStar5 />
|
||||
</div>
|
||||
</div>
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
277
modules/home/home.module.scss
Normal file
277
modules/home/home.module.scss
Normal file
@@ -0,0 +1,277 @@
|
||||
.noiseOverlay {
|
||||
position: absolute;
|
||||
top: -500px;
|
||||
left: -500px;
|
||||
width: calc(100% + 1000px);
|
||||
height: calc(100% + 1000px);
|
||||
z-index: 10000;
|
||||
opacity: 1;
|
||||
mix-blend-mode: color-burn;
|
||||
background-repeat: repeat;
|
||||
background-size: 1024px;
|
||||
pointer-events: none;
|
||||
will-change: auto;
|
||||
}
|
||||
|
||||
.header {
|
||||
min-height: 100vh;
|
||||
padding: 24px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
transform: translate3D(0, 0, 0);
|
||||
}
|
||||
|
||||
.slogan {
|
||||
max-width: 1300px;
|
||||
width: 100%;
|
||||
padding: 0px 50px;
|
||||
text-align: justify;
|
||||
margin-top: 8px;
|
||||
font-size: 4rem;
|
||||
font-weight: 600;
|
||||
color: var(--on-background);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 56px;
|
||||
|
||||
.line1,
|
||||
.line2 {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 16px 34px;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
|
||||
span {
|
||||
color: #000;
|
||||
}
|
||||
}
|
||||
|
||||
.thinStarBig {
|
||||
position: relative;
|
||||
color: #0058FF;
|
||||
z-index: -1;
|
||||
vertical-align: super;
|
||||
height: 0;
|
||||
display: inline-block;
|
||||
transform: translateY(-12px) translateX(-3px);
|
||||
|
||||
svg {
|
||||
position: absolute;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
}
|
||||
}
|
||||
|
||||
.spendWord {
|
||||
font-weight: 600;
|
||||
color: #1d4fac !important;
|
||||
}
|
||||
|
||||
.wiselyWord {
|
||||
font-size: 6rem;
|
||||
line-height: 80%;
|
||||
font-weight: 600;
|
||||
color: #715573 !important;
|
||||
padding-right: 20px;
|
||||
margin-right: -20px;
|
||||
}
|
||||
}
|
||||
|
||||
.header p {
|
||||
max-width: 530px;
|
||||
text-align: center;
|
||||
margin-top: 8px;
|
||||
font-size: 1.14rem;
|
||||
font-weight: 500;
|
||||
color: var(--on-background-secondary);
|
||||
}
|
||||
|
||||
.thickRoundStar6 {
|
||||
position: absolute;
|
||||
top: 20vh;
|
||||
left: -125px;
|
||||
z-index: 0;
|
||||
mask-repeat: no-repeat;
|
||||
mask-size: contain;
|
||||
mask-position: 50%;
|
||||
width: 250px;
|
||||
height: 250px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
position: absolute;
|
||||
width: 400px;
|
||||
height: 400px;
|
||||
object-fit: contain;
|
||||
}
|
||||
}
|
||||
|
||||
.thickRoundStar5 {
|
||||
position: relative;
|
||||
max-width: 1500px;
|
||||
width: 100%;
|
||||
margin: auto;
|
||||
z-index: 100000;
|
||||
|
||||
div {
|
||||
position: absolute;
|
||||
top: -50px;
|
||||
right: 50px;
|
||||
transform: translate(-50%, -50%) rotate(-10deg);
|
||||
color: #9f7ea1;
|
||||
}
|
||||
}
|
||||
|
||||
.gradientContainer {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: calc(100% + 300px);
|
||||
z-index: -100;
|
||||
}
|
||||
|
||||
.transitionToBackgound {
|
||||
content: '';
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 300px;
|
||||
background: linear-gradient(180deg, #ffffff00 0%, #ffffff 300px);
|
||||
transform: translate3D(0, 0, 0);
|
||||
}
|
||||
|
||||
.backdrop {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: #fff;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
.main {
|
||||
padding: 48px;
|
||||
padding-top: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 48px;
|
||||
max-width: 1500px;
|
||||
width: 100%;
|
||||
align-self: center;
|
||||
}
|
||||
|
||||
.cardsRow {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 48px;
|
||||
}
|
||||
|
||||
.card {
|
||||
flex-basis: 50%;
|
||||
}
|
||||
|
||||
.storesBlock {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 1240px) {
|
||||
.main {
|
||||
padding: 36px;
|
||||
gap: 36px;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 36px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 980px) {
|
||||
.slogan {
|
||||
font-size: 3rem;
|
||||
gap: 16px;
|
||||
|
||||
.wiselyWord {
|
||||
font-size: 4.6rem;
|
||||
}
|
||||
}
|
||||
|
||||
.cardsRow {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.howItsWorkCard {
|
||||
height: 550px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.mockupPhoneContainer {
|
||||
top: 25%;
|
||||
}
|
||||
|
||||
.mockupPhone {
|
||||
width: 700px;
|
||||
height: 600px;
|
||||
}
|
||||
|
||||
.screenshotMain {
|
||||
width: 176px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.main {
|
||||
padding: 24px;
|
||||
gap: 24px;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 730px) {
|
||||
.main {
|
||||
padding: 24px;
|
||||
gap: 24px;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 640px) {
|
||||
.thickRoundStar6 {
|
||||
width: 125px;
|
||||
height: 125px;
|
||||
left: -62px;
|
||||
top: 8vh;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 500px) {
|
||||
.main {
|
||||
padding: 16px;
|
||||
gap: 16px;
|
||||
}
|
||||
.slogan {
|
||||
font-size: 2rem;
|
||||
padding: 0px 12px;
|
||||
gap: 16px;
|
||||
}
|
||||
.wiselyWord {
|
||||
font-size: 3rem !important;
|
||||
}
|
||||
.cardsRow {
|
||||
gap: 16px;
|
||||
}
|
||||
}
|
||||
82
modules/home/howItsWorkCard/howItsWorkCard.module.css
Normal file
82
modules/home/howItsWorkCard/howItsWorkCard.module.css
Normal file
@@ -0,0 +1,82 @@
|
||||
.card {
|
||||
--on-background: #00276e;
|
||||
--on-background-secondary: #376ed3;
|
||||
--background: #dbe8ff;
|
||||
width: 100%;
|
||||
height: 650px;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.mockupPhoneContainer {
|
||||
position: absolute;
|
||||
top: 4%;
|
||||
right: 5%;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.mockupPhone {
|
||||
z-index: 1;
|
||||
position: relative;
|
||||
width: 939px;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.screenshotMain {
|
||||
position: absolute;
|
||||
top: 20.1%;
|
||||
right: 27.92%;
|
||||
transform: rotate(6.54deg);
|
||||
width: 266.9px;
|
||||
height: auto;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 3rem !important;
|
||||
}
|
||||
|
||||
.description {
|
||||
padding: 8px;
|
||||
margin: -8px;
|
||||
margin-top: 0;
|
||||
padding-top: 6px;
|
||||
}
|
||||
|
||||
@media (max-width: 1180px) {
|
||||
.card {
|
||||
height: 560px;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 2rem !important;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 800px) {
|
||||
.card {
|
||||
height: 440px;
|
||||
padding: 32px;
|
||||
}
|
||||
|
||||
.mockupPhoneContainer {
|
||||
top: 16%;
|
||||
right: -14%;
|
||||
}
|
||||
|
||||
.mockupPhone {
|
||||
width: 723.338px;
|
||||
height: 508.2px;
|
||||
}
|
||||
|
||||
.screenshotMain {
|
||||
width: 206.1px;
|
||||
right: 27.9%;
|
||||
top: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 480px) {
|
||||
.mockupPhoneContainer {
|
||||
right: -32%;
|
||||
}
|
||||
}
|
||||
80
modules/home/howItsWorkCard/howItsWorkCard.tsx
Normal file
80
modules/home/howItsWorkCard/howItsWorkCard.tsx
Normal file
@@ -0,0 +1,80 @@
|
||||
import Image from 'next/image';
|
||||
import phoneMockupLightImage from '@/assets/images/phone-mockup-light.png';
|
||||
import screenshotEnImage from '@/assets/images/screenshot-en.png';
|
||||
import screenshotRuImage from '@/assets/images/screenshot-ru.png';
|
||||
import styles from './howItsWorkCard.module.css';
|
||||
import { Card } from '@/components/card';
|
||||
import useLocale from '@/utils/useLocale';
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
import { useRouter } from 'next/router';
|
||||
import clsx from 'clsx';
|
||||
import { useRef } from 'react';
|
||||
import useScroll from '@/utils/useScroll';
|
||||
|
||||
const locales: LocalesMap = {
|
||||
ru: {
|
||||
title: 'Как это работает?',
|
||||
description:
|
||||
'Записывая каждую трату, вы отрезвляете разум и даете представление о том, сколько и как вы можете потратить.',
|
||||
},
|
||||
en: {
|
||||
title: 'How its work?',
|
||||
description:
|
||||
'Writing down each spending sobers the mind and gives you an idea of how much and how you can spend',
|
||||
},
|
||||
};
|
||||
|
||||
export function HowItsWorkCard() {
|
||||
const router = useRouter();
|
||||
const t = useLocale(locales);
|
||||
const ref = useRef<any>();
|
||||
const imageRef = useRef<any>();
|
||||
|
||||
useScroll((scrollY) => {
|
||||
if (!imageRef.current || !ref.current) return;
|
||||
|
||||
const rootNode = ref.current;
|
||||
const imageNode = imageRef.current;
|
||||
|
||||
const scrollOffsetCard = -Math.min(scrollY - rootNode.offsetTop + window.innerHeight - 300, 0);
|
||||
|
||||
rootNode.style.transform = `translate3D(0px, ${Math.expm1(scrollOffsetCard / 60)}px, 0px) rotate(${-Math.expm1(scrollOffsetCard / 120)}deg)`;
|
||||
rootNode.style.transformOrigin = 'right top';
|
||||
|
||||
const scrollOffset = rootNode.offsetTop - scrollY;
|
||||
|
||||
imageNode.style.transform = `translate3D(0px, ${-(scrollOffset / 16)}px, 0px)`;
|
||||
}, {
|
||||
accelerator: 0.5,
|
||||
});
|
||||
|
||||
return (
|
||||
<Card
|
||||
ref={ref}
|
||||
className={styles.card}
|
||||
title={t('title')}
|
||||
subtitle={t('description')}
|
||||
backdropOnText
|
||||
classes={{ subtitle: styles.description, title: styles.title }}
|
||||
>
|
||||
<div ref={imageRef} className={styles.mockupPhoneContainer}>
|
||||
<Image
|
||||
className={clsx(styles.mockupPhone)}
|
||||
height={1200}
|
||||
width={1708}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={phoneMockupLightImage}
|
||||
/>
|
||||
<Image
|
||||
className={styles.screenshotMain}
|
||||
height={2340}
|
||||
width={1080}
|
||||
placeholder="blur"
|
||||
alt=""
|
||||
src={router.locale !== 'ru' ? screenshotEnImage : screenshotRuImage}
|
||||
/>
|
||||
</div>
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
1
modules/home/howItsWorkCard/index.tsx
Normal file
1
modules/home/howItsWorkCard/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export * from './howItsWorkCard';
|
||||
13
modules/home/locales.tsx
Normal file
13
modules/home/locales.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import { LocalesMap } from '@/utils/useLocale';
|
||||
|
||||
export const locales: LocalesMap = {
|
||||
ru: {
|
||||
pageTitle: 'RideBus',
|
||||
pageDescription:
|
||||
'RideBus — Приложение, которое поможет вам удобно смотреть расписание общественного транспорта',
|
||||
},
|
||||
en: {
|
||||
pageTitle: 'RideBus',
|
||||
pageDescription: 'RideBus — An app that helps you conveniently view public transportation schedules',
|
||||
},
|
||||
};
|
||||
Reference in New Issue
Block a user