This commit is contained in:
rosetta
2026-01-30 05:01:05 +02:00
commit 83f38dc63f
327 changed files with 18725 additions and 0 deletions

View File

@@ -0,0 +1,168 @@
.wrapper {
box-sizing: border-box;
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8));
width: 100%;
height: 100%;
position: fixed;
top: 0;
left: 0;
display: flex;
align-items: center;
align-content: center;
justify-content: center;
overflow: auto;
padding: 60px 20px 30px;
}
.title {
font-family:
Greycliff CF,
var(--mantine-font-family);
font-size: 62px;
font-weight: 900;
line-height: 1.1;
margin-top: 70px;
padding: 0;
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
@media (max-width: $mantine-breakpoint-sm) {
font-size: 42px;
line-height: 1.2;
}
}
.description {
margin-top: var(--mantine-spacing-xl);
font-size: 24px;
@media (max-width: $mantine-breakpoint-sm) {
font-size: 18px;
}
}
.controls {
margin-top: calc(var(--mantine-spacing-xl) * 2);
@media (max-width: $mantine-breakpoint-sm) {
margin-top: var(--mantine-spacing-xl);
}
}
.control {
height: 54px;
padding-left: 38px;
padding-right: 38px;
@media (max-width: $mantine-breakpoint-sm) {
height: 54px;
padding-left: 18px;
padding-right: 18px;
flex: 1;
}
}
.carousel {
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
height: 340px;
}
.slideContent {
display: flex;
flex-direction: column;
align-items: center;
text-align: center;
flex: 1;
justify-content: flex-start;
animation: fadeInSlide 0.5s ease-in-out;
width: 100%;
}
@keyframes fadeInSlide {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
.iconWrapper {
width: 160px;
height: 160px;
border-radius: 50%;
display: flex;
align-items: center;
justify-content: center;
margin-bottom: 32px;
transition: all 0.5s ease;
animation: scaleIn 0.5s ease-in-out;
}
@keyframes scaleIn {
from {
opacity: 0;
transform: scale(0.8);
}
to {
opacity: 1;
transform: scale(1);
}
}
.title {
font-size: 24px;
font-weight: 600;
margin-bottom: 16px;
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
animation: fadeIn 0.5s ease-in-out 0.1s both;
min-height: 30px;
}
@keyframes fadeIn {
from {
opacity: 0;
}
to {
opacity: 1;
}
}
.description {
font-size: 15px;
color: light-dark(var(--mantine-color-gray-6), var(--mantine-color-dark-2));
line-height: 1.5;
max-width: 300px;
animation: fadeIn 0.5s ease-in-out 0.2s both;
min-height: 68px;
}
.dots {
margin: 20px 0;
gap: 8px;
}
.dot {
width: 8px;
height: 8px;
border-radius: 50%;
background-color: light-dark(var(--mantine-color-gray-4), var(--mantine-color-dark-4));
border: none;
cursor: pointer;
transition: all 0.3s ease;
padding: 0;
}
.dot:hover {
background-color: light-dark(var(--mantine-color-gray-5), var(--mantine-color-dark-3));
}
.dotActive {
width: 24px;
border-radius: 4px;
background-color: light-dark(var(--mantine-color-blue-6), var(--mantine-color-blue-4));
}

View File

@@ -0,0 +1,160 @@
import { useEffect, useState } from 'react';
import { Button, Group, Text, Flex } from '@mantine/core';
import { IconShieldLock, IconBolt, IconDeviceMobile } from '@tabler/icons-react';
import classes from './Introduction.module.css';
import { useNavigate } from 'react-router-dom';
import useWindow, { ElectronTheme } from '@/app/hooks/useWindow';
import { AnimatedButton } from '@/app/components/AnimatedButton/AnimatedButton';
const slides = [
{
title: 'Security',
description: 'Your data is protected by modern encryption and stays only on your device',
icon: IconShieldLock,
color: '#4CAF50',
},
{
title: 'Speed',
description: 'Instant synchronization and fast performance without delays',
icon: IconBolt,
color: '#FF9800',
},
{
title: 'Simple Interface',
description: 'Intuitive design that is clear at first glance',
icon: IconDeviceMobile,
color: '#2196F3',
},
];
export function Introduction() {
const navigate = useNavigate();
const {setSize, setResizeble, setTheme} = useWindow();
const [activeSlide, setActiveSlide] = useState(0);
const [touchStart, setTouchStart] = useState<number | null>(null);
const [touchEnd, setTouchEnd] = useState<number | null>(null);
// Минимальное расстояние свайпа (в px)
const minSwipeDistance = 50;
useEffect(() => {
setSize(385, 555);
setResizeble(false);
setTheme(ElectronTheme.SYSTEM);
}, []);
const handleGetStarted = () => {
navigate('/create-seed');
};
const goToSlide = (index: number) => {
setActiveSlide(index);
};
const nextSlide = () => {
setActiveSlide((prev) => (prev + 1) % slides.length);
};
const prevSlide = () => {
setActiveSlide((prev) => (prev - 1 + slides.length) % slides.length);
};
const onTouchStart = (e: React.TouchEvent) => {
setTouchEnd(null);
setTouchStart(e.targetTouches[0].clientX);
};
const onTouchMove = (e: React.TouchEvent) => {
setTouchEnd(e.targetTouches[0].clientX);
};
const onTouchEnd = () => {
if (!touchStart || !touchEnd) return;
const distance = touchStart - touchEnd;
const isLeftSwipe = distance > minSwipeDistance;
const isRightSwipe = distance < -minSwipeDistance;
if (isLeftSwipe) {
nextSlide();
} else if (isRightSwipe) {
prevSlide();
}
};
const handleCarouselClick = () => {
nextSlide();
};
const CurrentIcon = slides[activeSlide].icon;
return (
<div className={classes.wrapper}>
<Flex h={'100%'} w={'100%'} direction={'column'} justify={'space-between'} align={'center'}>
<div>
<div
className={classes.carousel}
onTouchStart={onTouchStart}
onTouchMove={onTouchMove}
onTouchEnd={onTouchEnd}
onClick={handleCarouselClick}
style={{ cursor: 'pointer', userSelect: 'none' }}
>
<div className={classes.slideContent} key={activeSlide}>
<div
className={classes.iconWrapper}
style={{ backgroundColor: `${slides[activeSlide].color}20` }}
>
<CurrentIcon
size={80}
stroke={1.5}
color={slides[activeSlide].color}
/>
</div>
<Text size={'sm'} className={classes.title} fw={500}>
{slides[activeSlide].title}
</Text>
<Text mt={'xs'} size={'xs'} c={'dimmed'} className={classes.description}>
{slides[activeSlide].description}
</Text>
</div>
<Group justify="center" className={classes.dots}>
{slides.map((_, index) => (
<button
key={index}
className={`${classes.dot} ${index === activeSlide ? classes.dotActive : ''}`}
onClick={(e) => {
e.stopPropagation();
goToSlide(index);
}}
aria-label={`Go to slide ${index + 1}`}
/>
))}
</Group>
</div>
</div>
<Flex gap={'xs'} direction={'column'} w={'100%'}>
<AnimatedButton
fullWidth
size="md"
animated={['#2DA5FF', '#87DBFF']}
onClick={handleGetStarted}
>
Get Started
</AnimatedButton>
<Button
fullWidth
variant={'transparent'}
size="md"
onClick={() => navigate('/exists-seed')}
>
Already have an account
</Button>
</Flex>
</Flex>
</div>
);
}