Лендинг
This commit is contained in:
24
.gitignore
vendored
Normal file
24
.gitignore
vendored
Normal file
@@ -0,0 +1,24 @@
|
||||
# Logs
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
|
||||
node_modules
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
|
||||
# Editor directories and files
|
||||
.vscode/*
|
||||
!.vscode/extensions.json
|
||||
.idea
|
||||
.DS_Store
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
23
eslint.config.js
Normal file
23
eslint.config.js
Normal file
@@ -0,0 +1,23 @@
|
||||
import js from '@eslint/js'
|
||||
import globals from 'globals'
|
||||
import reactHooks from 'eslint-plugin-react-hooks'
|
||||
import reactRefresh from 'eslint-plugin-react-refresh'
|
||||
import tseslint from 'typescript-eslint'
|
||||
import { defineConfig, globalIgnores } from 'eslint/config'
|
||||
|
||||
export default defineConfig([
|
||||
globalIgnores(['dist']),
|
||||
{
|
||||
files: ['**/*.{ts,tsx}'],
|
||||
extends: [
|
||||
js.configs.recommended,
|
||||
tseslint.configs.recommended,
|
||||
reactHooks.configs.flat.recommended,
|
||||
reactRefresh.configs.vite,
|
||||
],
|
||||
languageOptions: {
|
||||
ecmaVersion: 2020,
|
||||
globals: globals.browser,
|
||||
},
|
||||
},
|
||||
])
|
||||
BIN
favicon.ico
Normal file
BIN
favicon.ico
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 353 KiB |
13
index.html
Normal file
13
index.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>Rosetta.IM</title>
|
||||
<link rel="icon" href="favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
<script type="module" src="/src/main.tsx"></script>
|
||||
</body>
|
||||
</html>
|
||||
3871
package-lock.json
generated
Normal file
3871
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "rosetta-landing",
|
||||
"private": true,
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vite",
|
||||
"build": "tsc -b && vite build",
|
||||
"lint": "eslint .",
|
||||
"preview": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@mantine/core": "^8.3.9",
|
||||
"@mantine/hooks": "^8.3.9",
|
||||
"@mantinex/dev-icons": "^2.0.0",
|
||||
"@tabler/icons": "^3.35.0",
|
||||
"@tabler/icons-react": "^3.35.0",
|
||||
"buffer": "^6.0.3",
|
||||
"events": "^3.3.0",
|
||||
"react": "^19.2.0",
|
||||
"react-dom": "^19.2.0",
|
||||
"react-icons": "^5.5.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "^9.39.1",
|
||||
"@types/events": "^3.0.3",
|
||||
"@types/node": "^24.10.1",
|
||||
"@types/react": "^19.2.5",
|
||||
"@types/react-dom": "^19.2.3",
|
||||
"@vitejs/plugin-react": "^5.1.1",
|
||||
"babel-plugin-react-compiler": "^1.0.0",
|
||||
"eslint": "^9.39.1",
|
||||
"eslint-plugin-react-hooks": "^7.0.1",
|
||||
"eslint-plugin-react-refresh": "^0.4.24",
|
||||
"globals": "^16.5.0",
|
||||
"postcss": "^8.5.6",
|
||||
"postcss-preset-mantine": "^1.18.0",
|
||||
"postcss-simple-vars": "^7.0.1",
|
||||
"typescript": "~5.9.3",
|
||||
"typescript-eslint": "^8.46.4",
|
||||
"vite": "^7.2.4"
|
||||
}
|
||||
}
|
||||
14
postcss.config.cjs
Normal file
14
postcss.config.cjs
Normal file
@@ -0,0 +1,14 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
'postcss-preset-mantine': {},
|
||||
'postcss-simple-vars': {
|
||||
variables: {
|
||||
'mantine-breakpoint-xs': '36em',
|
||||
'mantine-breakpoint-sm': '48em',
|
||||
'mantine-breakpoint-md': '62em',
|
||||
'mantine-breakpoint-lg': '75em',
|
||||
'mantine-breakpoint-xl': '88em',
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
20
src/App.tsx
Normal file
20
src/App.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
// Import styles of packages that you've installed.
|
||||
// All packages except `@mantine/hooks` require styles imports
|
||||
import '@mantine/core/styles.css';
|
||||
|
||||
import { MantineProvider } from '@mantine/core';
|
||||
import { HeroTitle } from './components/HeroTitle/HeroTitle';
|
||||
import { DownloadCenter } from './components/DownloadCenter/DownloadCenter';
|
||||
import { FeaturesGrid } from './components/FeaturesGrid/FeaturesGrid';
|
||||
import './style.css'
|
||||
import { MessageSteps } from './components/MessageSteps/MessageSteps';
|
||||
|
||||
|
||||
export default function App() {
|
||||
return <MantineProvider>
|
||||
<HeroTitle></HeroTitle>
|
||||
<FeaturesGrid></FeaturesGrid>
|
||||
<MessageSteps></MessageSteps>
|
||||
<DownloadCenter></DownloadCenter>
|
||||
</MantineProvider>;
|
||||
}
|
||||
12
src/components/Download/Download.module.css
Normal file
12
src/components/Download/Download.module.css
Normal file
@@ -0,0 +1,12 @@
|
||||
.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;
|
||||
}
|
||||
}
|
||||
50
src/components/Download/Download.tsx
Normal file
50
src/components/Download/Download.tsx
Normal file
@@ -0,0 +1,50 @@
|
||||
import { Button } from "@mantine/core";
|
||||
import classes from './Download.module.css';
|
||||
import { RosettaLogo } from "../RosettaLogo/RosettaLogo";
|
||||
|
||||
interface DownloadProps {
|
||||
href: string;
|
||||
}
|
||||
|
||||
export function Download(props: DownloadProps) {
|
||||
/*const protocol = useMemo(() => {
|
||||
let protocol = new Protocol('ws://127.0.0.1:3000');
|
||||
protocol.addSupportedPacket(new PacketRequestUpdate());
|
||||
protocol.addSupportedPacket(new PacketKernelUpdate());
|
||||
return protocol;
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if(!protocol){
|
||||
return;
|
||||
}
|
||||
const platform = getPlatformTranslated();
|
||||
let packet = new PacketRequestUpdate();
|
||||
packet.setPlatform(platform);
|
||||
packet.setKernelVersion("0.0.0");
|
||||
packet.setArch("x64");
|
||||
protocol.sendPacket(packet);
|
||||
}, [protocol]);
|
||||
|
||||
protocol.waitPacket(0x0D, (packet : PacketKernelUpdate) => {
|
||||
console.info(packet);
|
||||
});*/
|
||||
|
||||
return (
|
||||
<Button.Group>
|
||||
<Button
|
||||
size="xl"
|
||||
component="a"
|
||||
className={classes.control}
|
||||
variant={'default'}
|
||||
href={props.href}
|
||||
leftSection={
|
||||
<RosettaLogo size={25}></RosettaLogo>
|
||||
}
|
||||
>
|
||||
Get Rosetta
|
||||
</Button>
|
||||
</Button.Group>
|
||||
);
|
||||
|
||||
}
|
||||
41
src/components/DownloadCenter/DownloadCenter.module.css
Normal file
41
src/components/DownloadCenter/DownloadCenter.module.css
Normal file
@@ -0,0 +1,41 @@
|
||||
.wrapper {
|
||||
min-height: 100vh;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding-top: var(--mantine-spacing-xl);
|
||||
padding-bottom: var(--mantine-spacing-xl);
|
||||
background-color: #0066ff05;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-size: 34px;
|
||||
font-weight: 500;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
font-size: 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
max-width: 600px;
|
||||
margin: auto;
|
||||
}
|
||||
|
||||
.card {
|
||||
width: 300px;
|
||||
height: 300px;
|
||||
border: 1px solid light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-5));
|
||||
}
|
||||
|
||||
.cardTitle {
|
||||
&::after {
|
||||
content: '';
|
||||
display: block;
|
||||
background-color: var(--mantine-color-blue-filled);
|
||||
width: 45px;
|
||||
height: 2px;
|
||||
margin-top: var(--mantine-spacing-sm);
|
||||
}
|
||||
}
|
||||
160
src/components/DownloadCenter/DownloadCenter.tsx
Normal file
160
src/components/DownloadCenter/DownloadCenter.tsx
Normal file
@@ -0,0 +1,160 @@
|
||||
import {
|
||||
Button,
|
||||
Card,
|
||||
Container,
|
||||
Flex,
|
||||
Text,
|
||||
Title,
|
||||
useComputedColorScheme,
|
||||
useMantineTheme,
|
||||
} from '@mantine/core';
|
||||
import classes from './DownloadCenter.module.css';
|
||||
import { RosettaLogo } from '../RosettaLogo/RosettaLogo';
|
||||
import { useEffect, useState } from 'react';
|
||||
import { FaApple, FaWindows } from 'react-icons/fa';
|
||||
import { FcLinux } from 'react-icons/fc';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
import { IconDownload } from '@tabler/icons-react';
|
||||
|
||||
interface DownloadFeature {
|
||||
platform: string;
|
||||
arch: string;
|
||||
link: string;
|
||||
version: string;
|
||||
}
|
||||
|
||||
interface UpdateItem {
|
||||
platform: string;
|
||||
arch: string;
|
||||
version: string;
|
||||
downloadUrl: string;
|
||||
}
|
||||
|
||||
const fetchUpdates = async (): Promise<DownloadFeature[]> => {
|
||||
try {
|
||||
const response = await fetch('https://sdu.rosetta.im/updates/all');
|
||||
const data = await response.json();
|
||||
|
||||
return (data.items || []).map((item: UpdateItem) => ({
|
||||
platform: item.platform,
|
||||
arch: item.arch,
|
||||
version: item.version,
|
||||
link: new URL(item.downloadUrl, 'https://sdu.rosetta.im').toString(),
|
||||
}));
|
||||
} catch (error) {
|
||||
console.error('Failed to fetch updates:', error);
|
||||
return [];
|
||||
}
|
||||
};
|
||||
|
||||
export function DownloadCenter() {
|
||||
const theme = useMantineTheme();
|
||||
const colorScheme = useComputedColorScheme();
|
||||
const [kernels, setKernels] = useState<DownloadFeature[]>([]);
|
||||
const [targetPlatforms, setTargetPlatforms] = useState<string[]>(['darwin', 'linux', 'win32']);
|
||||
|
||||
useEffect(() => {
|
||||
fetchUpdates().then(setKernels);
|
||||
}, []);
|
||||
|
||||
const switchTarget = (value: string) => {
|
||||
let targetPlatforms: string[] = [];
|
||||
switch(value){
|
||||
case 'All':
|
||||
targetPlatforms = ['darwin', 'linux', 'win32'];
|
||||
break;
|
||||
case 'Windows':
|
||||
targetPlatforms = ['win32'];
|
||||
break;
|
||||
case 'macOS':
|
||||
targetPlatforms = ['darwin'];
|
||||
break;
|
||||
case 'Linux':
|
||||
targetPlatforms = ['linux'];
|
||||
break;
|
||||
}
|
||||
setTargetPlatforms(targetPlatforms);
|
||||
}
|
||||
|
||||
const translatePlatformToOsName = (platform: string) => {
|
||||
switch(platform){
|
||||
case 'darwin':
|
||||
return 'macOS';
|
||||
case 'linux':
|
||||
return 'Linux';
|
||||
case 'win32':
|
||||
return 'Windows';
|
||||
default:
|
||||
return platform;
|
||||
}
|
||||
}
|
||||
|
||||
const translatePlatformAndArchToCpuName = (platform : string, arch: string) => {
|
||||
switch(platform){
|
||||
case 'darwin':
|
||||
return arch === 'arm64' ? 'Apple Silicon' : 'Intel';
|
||||
case 'linux':
|
||||
return arch === 'arm64' ? 'ARM64' : 'x64';
|
||||
case 'win32':
|
||||
return arch === 'arm64' ? 'ARM64' : 'x64';
|
||||
default:
|
||||
return arch;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const features = kernels.filter((v) => targetPlatforms.includes(v.platform)).map((feature) => (
|
||||
<Card key={feature.link} radius="md" className={classes.card} padding="xl">
|
||||
<Flex direction={'column'} align="center" justify={'center'}>
|
||||
{feature.platform === 'darwin' && <FaApple size={50} color={
|
||||
colorScheme == 'dark' ? 'white' : 'black'
|
||||
} />}
|
||||
{feature.platform === 'linux' && <FcLinux size={50} />}
|
||||
{feature.platform === 'win32' && <FaWindows size={50} color={theme.colors.blue[6]} />}
|
||||
<Text fz="lg" ta={'center'} fw={500} mt="md">
|
||||
{translatePlatformToOsName(feature.platform)} - {translatePlatformAndArchToCpuName(feature.platform, feature.arch)}
|
||||
</Text>
|
||||
<Text c="dimmed" fz="sm" ta={'center'} mt="xs">
|
||||
{feature.platform == 'darwin' && <>
|
||||
{feature.arch == 'arm64' && <>
|
||||
Download this version if you have a Mac with Apple Silicon (M1, M2 chips).
|
||||
</>}
|
||||
{feature.arch == 'x64' && <>
|
||||
Download this version if you have a Mac with an Intel processor, which was most likely manufactured before 2020.
|
||||
</>}
|
||||
</>}
|
||||
{feature.platform == 'linux' && <>This version is for Linux and comes as an AppImage for the GUI versions.</>}
|
||||
{feature.platform == 'win32' && <>Download this version of you have computer on Windows 10 or later.</>}
|
||||
</Text>
|
||||
<Button component={'a'} href={feature.link} variant={'subtle'} mt={'sm'} leftSection={
|
||||
<IconDownload size={16}></IconDownload>
|
||||
}>Get {feature.version} now</Button>
|
||||
</Flex>
|
||||
</Card>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Container id={'kernels'} mb={'xl'} size="lg" mt={'xl'}>
|
||||
<Flex justify="center" align="center" direction="row" gap={'md'}>
|
||||
<Title order={2} className={classes.title} ta="center">
|
||||
Download
|
||||
</Title>
|
||||
<RosettaLogo size={35}></RosettaLogo>
|
||||
</Flex>
|
||||
|
||||
<Text c="dimmed" className={classes.description} ta="center" mt="md">
|
||||
Choose your platform and get started with Rosetta today. Enjoy secure and fast messaging across all your devices.
|
||||
</Text>
|
||||
|
||||
<Flex justify={'center'} mt={'lg'}>
|
||||
<Switch onChange={switchTarget} data={['All', 'Windows', 'macOS', 'Linux']}></Switch>
|
||||
</Flex>
|
||||
|
||||
<Flex gap="xl" justify={'center'} wrap={'wrap'} align={'center'} mt={50}>
|
||||
{features}
|
||||
</Flex>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
29
src/components/FeaturesGrid/FeaturesGrid.module.css
Normal file
29
src/components/FeaturesGrid/FeaturesGrid.module.css
Normal file
@@ -0,0 +1,29 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
background-color: #0066ff05;
|
||||
min-height: 100vh;
|
||||
padding-top: var(--mantine-spacing-xl);
|
||||
padding-bottom: var(--mantine-spacing-xl);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: Outfit, var(--mantine-font-family);
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--mantine-spacing-md);
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
font-size: 28px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
97
src/components/FeaturesGrid/FeaturesGrid.tsx
Normal file
97
src/components/FeaturesGrid/FeaturesGrid.tsx
Normal file
@@ -0,0 +1,97 @@
|
||||
import { IconGauge, IconLock, IconMessage2, IconServer, IconUser, IconUsersGroup } from '@tabler/icons-react';
|
||||
import { Container, Flex, SimpleGrid, Text, ThemeIcon, Title } from '@mantine/core';
|
||||
import classes from './FeaturesGrid.module.css';
|
||||
import { RosettaLogo } from '../RosettaLogo/RosettaLogo';
|
||||
|
||||
export const MOCKDATA = [
|
||||
{
|
||||
icon: IconGauge,
|
||||
title: 'Performance',
|
||||
description:
|
||||
'Despite encryption, the messenger remains fast and convenient.',
|
||||
},
|
||||
{
|
||||
icon: IconUser,
|
||||
title: 'Privacy focused',
|
||||
description:
|
||||
'We encrypt everything, even media files, so that no one can access your files.',
|
||||
},
|
||||
{
|
||||
icon: IconServer,
|
||||
title: 'Server does not save messages',
|
||||
description:
|
||||
'The server does not store messages. All messages are stored only on your devices.',
|
||||
},
|
||||
{
|
||||
icon: IconLock,
|
||||
title: 'Secure by default',
|
||||
description:
|
||||
'The messenger uses asymmetric encryption to hide message keys. ChaCha20 is used for message encryption, and AES-256 is used for encrypting your media files.',
|
||||
},
|
||||
{
|
||||
icon: IconMessage2,
|
||||
title: 'Regular Updates',
|
||||
description:
|
||||
'We strive to update the messenger as often as possible to fix bugs and add new features. This is not an abandoned project.',
|
||||
},
|
||||
{
|
||||
icon: IconUsersGroup,
|
||||
title: 'No trackers or ads',
|
||||
description:
|
||||
'Registration without phone number, email, or other personal data. Only public or private keys. We do not use trackers and do not show ads.',
|
||||
}
|
||||
];
|
||||
|
||||
interface FeatureProps {
|
||||
icon: React.FC<any>;
|
||||
title: React.ReactNode;
|
||||
description: React.ReactNode;
|
||||
}
|
||||
|
||||
export function Feature({ icon: Icon, title, description }: FeatureProps) {
|
||||
return (
|
||||
<div>
|
||||
<ThemeIcon variant="light" size={40} radius={40}>
|
||||
<Icon size={18} stroke={1.5} />
|
||||
</ThemeIcon>
|
||||
<Text mt="sm" mb={7}>
|
||||
{title}
|
||||
</Text>
|
||||
<Text size="sm" c="dimmed" lh={1.6}>
|
||||
{description}
|
||||
</Text>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
export function FeaturesGrid() {
|
||||
const features = MOCKDATA.map((feature, index) => <Feature {...feature} key={index} />);
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Container>
|
||||
<Flex justify={'center'} mb={'lg'} align={'center'}>
|
||||
<RosettaLogo size={50}></RosettaLogo>
|
||||
</Flex>
|
||||
<Title ta="center" className={classes.title}>
|
||||
Here’s what we offer
|
||||
</Title>
|
||||
|
||||
<Container size={560} p={0}>
|
||||
<Text size="sm" className={classes.description}>
|
||||
Rosetta offers a wide range of features that ensure security, privacy, and ease of use. Explore the key features that make Rosetta an excellent choice for messaging.
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
<SimpleGrid
|
||||
mt={60}
|
||||
cols={{ base: 1, sm: 2, md: 3 }}
|
||||
spacing={{ base: 'xl', md: 50 }}
|
||||
verticalSpacing={{ base: 'xl', md: 50 }}
|
||||
>
|
||||
{features}
|
||||
</SimpleGrid>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
59
src/components/HeroTitle/HeroTitle.module.css
Normal file
59
src/components/HeroTitle/HeroTitle.module.css
Normal file
@@ -0,0 +1,59 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
box-sizing: border-box;
|
||||
background-color: light-dark(var(--mantine-color-white), var(--mantine-color-dark-8));
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
.inner {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: Outfit, var(--mantine-font-family);
|
||||
font-size: 62px;
|
||||
font-weight: 500;
|
||||
line-height: 1.1;
|
||||
margin: 0;
|
||||
word-wrap: normal;
|
||||
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;
|
||||
}
|
||||
}
|
||||
27
src/components/HeroTitle/HeroTitle.tsx
Normal file
27
src/components/HeroTitle/HeroTitle.tsx
Normal file
@@ -0,0 +1,27 @@
|
||||
import { Container, Group, Text } from '@mantine/core';
|
||||
import classes from './HeroTitle.module.css';
|
||||
import { Download } from '../Download/Download';
|
||||
|
||||
export function HeroTitle() {
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Container className={classes.inner}>
|
||||
<h1 className={classes.title}>
|
||||
Rosetta is{' '}
|
||||
<Text component="span" variant="gradient" gradient={{ from: 'blue', to: 'cyan' }} inherit>
|
||||
secure
|
||||
</Text>{' '}
|
||||
messaging for everyone
|
||||
</h1>
|
||||
|
||||
<Text className={classes.description} c="dimmed">
|
||||
Rosetta is designed to ensure secure messaging. We use comprehensive encryption, which keeps the app fast and convenient, yet secure.
|
||||
</Text>
|
||||
|
||||
<Group className={classes.controls}>
|
||||
<Download href="#kernels"></Download>
|
||||
</Group>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
28
src/components/MessageSteps/MessageSteps.module.css
Normal file
28
src/components/MessageSteps/MessageSteps.module.css
Normal file
@@ -0,0 +1,28 @@
|
||||
.wrapper {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
flex-direction: column;
|
||||
min-height: 100vh;
|
||||
padding-top: var(--mantine-spacing-xl);
|
||||
padding-bottom: var(--mantine-spacing-xl);
|
||||
}
|
||||
|
||||
.title {
|
||||
font-family: Outfit, var(--mantine-font-family);
|
||||
font-weight: 500;
|
||||
margin-bottom: var(--mantine-spacing-md);
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
font-size: 28px;
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
|
||||
.description {
|
||||
text-align: center;
|
||||
|
||||
@media (max-width: $mantine-breakpoint-sm) {
|
||||
text-align: left;
|
||||
}
|
||||
}
|
||||
45
src/components/MessageSteps/MessageSteps.tsx
Normal file
45
src/components/MessageSteps/MessageSteps.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Container, Flex, Text, Title } from '@mantine/core';
|
||||
import classes from './MessageSteps.module.css';
|
||||
import { RosettaLogo } from '../RosettaLogo/RosettaLogo';
|
||||
import { Switch } from '../Switch/Switch';
|
||||
import { useState } from 'react';
|
||||
import { Sender } from '../Sender/Sender';
|
||||
import { Recipient } from '../Recipient/Recipient';
|
||||
|
||||
|
||||
export function MessageSteps() {
|
||||
const [show, setShow] = useState('Sender');
|
||||
|
||||
return (
|
||||
<div className={classes.wrapper}>
|
||||
<Container>
|
||||
<Flex justify={'center'} mb={'lg'} align={'center'}>
|
||||
<RosettaLogo size={50}></RosettaLogo>
|
||||
</Flex>
|
||||
<Title ta="center" className={classes.title}>
|
||||
How is my message sent?
|
||||
</Title>
|
||||
|
||||
<Container size={560} p={0}>
|
||||
<Text size="sm" className={classes.description}>
|
||||
See why Rosetta is truly secure. Notice how your message is sent and received from you to the recipient. The steps highlighted in blue are what you see, while the steps not highlighted are what happens behind the scenes.
|
||||
</Text>
|
||||
</Container>
|
||||
|
||||
<Flex align={'center'} justify={'center'} mt={'xl'}>
|
||||
<Switch data={[
|
||||
'Sender',
|
||||
'Recipient'
|
||||
]} onChange={(v) => setShow(v)}></Switch>
|
||||
</Flex>
|
||||
|
||||
<Flex justify={'center'} align={'center'} mt={'xl'} gap={'xl'}>
|
||||
<Flex>
|
||||
{show == 'Sender' && <Sender></Sender>}
|
||||
{show == 'Recipient' && <Recipient></Recipient>}
|
||||
</Flex>
|
||||
</Flex>
|
||||
</Container>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
45
src/components/Recipient/Recipient.tsx
Normal file
45
src/components/Recipient/Recipient.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import { Text, Timeline } from "@mantine/core";
|
||||
import { IconDeviceDesktop, IconKey, IconLockAccess, IconMessage, IconMessage2, IconServer } from "@tabler/icons-react";
|
||||
|
||||
export function Recipient() {
|
||||
return (
|
||||
<Timeline reverseActive active={0} bulletSize={40} lineWidth={2}>
|
||||
<Timeline.Item bullet={<IconServer size={20} />} title="Retranslate">
|
||||
<Text c="dimmed" size="sm">
|
||||
Server retranslate message to recipient.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>After retranslate server deletes message from memory.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconMessage size={20} />} title="Receive message">
|
||||
<Text c="dimmed" size="sm">
|
||||
Recipient device receives encrypted message and encrypted ChaCha20 key from server.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Message now encrypted.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconLockAccess size={20} />} title="Decrypting ChaCha20 key">
|
||||
<Text c="dimmed" size="sm">
|
||||
Decrypt ChaCha20 key with recipient private key using RSA-4096 decryption.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Message still encrypted.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconKey size={20} />} title="Decrypting message using ChaCha20">
|
||||
<Text c="dimmed" size="sm">
|
||||
Decrypting text of message or attachments using decrypted ChaCha20 key from previous step.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Message text is available.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconDeviceDesktop size={20} />} title="Save message on your device">
|
||||
<Text c="dimmed" size="sm">
|
||||
Encrypt decrypted text with AES-256 for save in your device.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Need for future access, because message not save on server.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconMessage2 size={20} />} title="Show message">
|
||||
<Text c="dimmed" size="sm">
|
||||
Message available for reading in your chat.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Final step.</Text>
|
||||
</Timeline.Item>
|
||||
</Timeline>
|
||||
)
|
||||
}
|
||||
BIN
src/components/RosettaLogo/512x512.png
Normal file
BIN
src/components/RosettaLogo/512x512.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 14 KiB |
7
src/components/RosettaLogo/RosettaLogo.tsx
Normal file
7
src/components/RosettaLogo/RosettaLogo.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import logo from './512x512.png';
|
||||
|
||||
export function RosettaLogo({size}: {size?: number}) {
|
||||
return (
|
||||
<img src={logo} alt="Rosetta Logo" width={size || 100} height={size || 100} />
|
||||
);
|
||||
}
|
||||
38
src/components/Sender/Sender.tsx
Normal file
38
src/components/Sender/Sender.tsx
Normal file
@@ -0,0 +1,38 @@
|
||||
import { Text, Timeline } from "@mantine/core";
|
||||
import { IconDeviceDesktop, IconKering, IconKey, IconMessage, IconServer } from "@tabler/icons-react";
|
||||
|
||||
export function Sender() {
|
||||
return (
|
||||
<Timeline active={0} bulletSize={40} lineWidth={2}>
|
||||
<Timeline.Item bullet={<IconMessage size={20} />} title="Enter message">
|
||||
<Text c="dimmed" size="sm">
|
||||
You enter message text in chat.
|
||||
</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconDeviceDesktop size={20} />} title="Encrypt for you">
|
||||
<Text c="dimmed" size="sm">
|
||||
Message encrypted for your device using AES-256 encryption.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Need for safety save message on your device.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconKey size={20} />} title="Encrypt for opponent">
|
||||
<Text c="dimmed" size="sm">
|
||||
Encrypt message for recipient using ChaCha20 encryption.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Encrypt message with chacha20 with key.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconKering size={20} />} title="Encrypt ChaCha20 key to send">
|
||||
<Text c="dimmed" size="sm">
|
||||
Encrypt key of previous step with recipient public key using RSA-4096 encryption.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>Needs for send encrypted key to opponent.</Text>
|
||||
</Timeline.Item>
|
||||
<Timeline.Item bullet={<IconServer size={20} />} title="Send encrypted data to server">
|
||||
<Text c="dimmed" size="sm">
|
||||
Send encrypted message and encrypted ChaCha20 key to server for delivery to recipient.
|
||||
</Text>
|
||||
<Text size="xs" mt={4}>See next steps in recipient timeline.</Text>
|
||||
</Timeline.Item>
|
||||
</Timeline>
|
||||
)
|
||||
}
|
||||
37
src/components/Switch/Switch.module.css
Normal file
37
src/components/Switch/Switch.module.css
Normal file
@@ -0,0 +1,37 @@
|
||||
.root {
|
||||
position: relative;
|
||||
background-color: light-dark(var(--mantine-color-gray-0), var(--mantine-color-dark-8));
|
||||
width: fit-content;
|
||||
border-radius: var(--mantine-radius-md);
|
||||
padding: 5px;
|
||||
border: 1px solid light-dark(var(--mantine-color-gray-2), var(--mantine-color-dark-4));
|
||||
}
|
||||
|
||||
.control {
|
||||
padding: 7px 12px;
|
||||
line-height: 1;
|
||||
color: light-dark(var(--mantine-color-gray-7), var(--mantine-color-dark-2));
|
||||
border-radius: var(--mantine-radius-md);
|
||||
font-size: var(--mantine-font-size-sm);
|
||||
transition: color 100ms ease;
|
||||
font-weight: 500;
|
||||
|
||||
@mixin hover {
|
||||
color: light-dark(var(--mantine-color-black), var(--mantine-color-white));
|
||||
background-color: light-dark(var(--mantine-color-gray-1), var(--mantine-color-dark-7));
|
||||
}
|
||||
|
||||
&[data-active] {
|
||||
color: var(--mantine-color-white);
|
||||
}
|
||||
}
|
||||
|
||||
.controlLabel {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.indicator {
|
||||
background-color: var(--mantine-primary-color-filled);
|
||||
border-radius: var(--mantine-radius-md);
|
||||
}
|
||||
51
src/components/Switch/Switch.tsx
Normal file
51
src/components/Switch/Switch.tsx
Normal file
@@ -0,0 +1,51 @@
|
||||
import { useState } from 'react';
|
||||
import { FloatingIndicator, UnstyledButton } from '@mantine/core';
|
||||
import classes from './Switch.module.css';
|
||||
|
||||
interface SwitchProps {
|
||||
data: string[];
|
||||
onChange?: (value: string) => void;
|
||||
}
|
||||
|
||||
export function Switch(props: SwitchProps) {
|
||||
const [rootRef, setRootRef] = useState<HTMLDivElement | null>(null);
|
||||
const [controlsRefs, setControlsRefs] = useState<Record<string, HTMLButtonElement | null>>({});
|
||||
const [active, setActive] = useState(0);
|
||||
|
||||
const setControlRef = (index: number) => (node: HTMLButtonElement) => {
|
||||
controlsRefs[index] = node;
|
||||
setControlsRefs(controlsRefs);
|
||||
};
|
||||
|
||||
const changeActive = (index: number) => {
|
||||
setActive(index);
|
||||
if(!props.onChange){
|
||||
return;
|
||||
}
|
||||
props.onChange(props.data[index]);
|
||||
}
|
||||
|
||||
const controls = props.data.map((item, index) => (
|
||||
<UnstyledButton
|
||||
key={item}
|
||||
className={classes.control}
|
||||
ref={setControlRef(index)}
|
||||
onClick={() => changeActive(index)}
|
||||
mod={{ active: active === index }}
|
||||
>
|
||||
<span className={classes.controlLabel}>{item}</span>
|
||||
</UnstyledButton>
|
||||
));
|
||||
|
||||
return (
|
||||
<div className={classes.root} ref={setRootRef}>
|
||||
{controls}
|
||||
|
||||
<FloatingIndicator
|
||||
target={controlsRefs[active]}
|
||||
parent={rootRef}
|
||||
className={classes.indicator}
|
||||
/>
|
||||
</div>
|
||||
);
|
||||
}
|
||||
7
src/main.tsx
Normal file
7
src/main.tsx
Normal file
@@ -0,0 +1,7 @@
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import App from './App.tsx'
|
||||
|
||||
|
||||
createRoot(document.getElementById('root')!).render(
|
||||
<App />
|
||||
)
|
||||
3
src/style.css
Normal file
3
src/style.css
Normal file
@@ -0,0 +1,3 @@
|
||||
*{
|
||||
scroll-behavior: smooth;
|
||||
}
|
||||
28
tsconfig.app.json
Normal file
28
tsconfig.app.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
||||
"target": "ES2022",
|
||||
"useDefineForClassFields": true,
|
||||
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
||||
"module": "ESNext",
|
||||
"types": ["vite/client"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["src"]
|
||||
}
|
||||
7
tsconfig.json
Normal file
7
tsconfig.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"files": [],
|
||||
"references": [
|
||||
{ "path": "./tsconfig.app.json" },
|
||||
{ "path": "./tsconfig.node.json" }
|
||||
]
|
||||
}
|
||||
26
tsconfig.node.json
Normal file
26
tsconfig.node.json
Normal file
@@ -0,0 +1,26 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
||||
"target": "ES2023",
|
||||
"lib": ["ES2023"],
|
||||
"module": "ESNext",
|
||||
"types": ["node"],
|
||||
"skipLibCheck": true,
|
||||
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"verbatimModuleSyntax": true,
|
||||
"moduleDetection": "force",
|
||||
"noEmit": true,
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"erasableSyntaxOnly": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedSideEffectImports": true
|
||||
},
|
||||
"include": ["vite.config.ts"]
|
||||
}
|
||||
13
vite.config.ts
Normal file
13
vite.config.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
// https://vite.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [
|
||||
react({
|
||||
babel: {
|
||||
plugins: [['babel-plugin-react-compiler']],
|
||||
},
|
||||
}),
|
||||
],
|
||||
})
|
||||
Reference in New Issue
Block a user