Base markup has been added

This commit is contained in:
Aidar Shaikhutdin @makeweb.space 2023-05-02 19:05:24 +06:00
parent 1fdc0aae1f
commit 3b52ed4989
41 changed files with 807 additions and 136 deletions

View File

@ -5,7 +5,7 @@ install-local:
npm install
start:
npm run dev
npm run dev -- --host
build:
npm run build

View File

@ -10,6 +10,6 @@
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
<script type="module" src="/src/main.ts"></script>
</body>
</html>

118
package-lock.json generated
View File

@ -9,11 +9,13 @@
"version": "0.0.0",
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react": "^4.0.0",
@ -929,6 +931,20 @@
"node": ">= 8"
}
},
"node_modules/@remix-run/router": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz",
"integrity": "sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w==",
"engines": {
"node": ">=14"
}
},
"node_modules/@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"dev": true
},
"node_modules/@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -961,6 +977,27 @@
"@types/react": "*"
}
},
"node_modules/@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"node_modules/@types/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"dev": true,
"dependencies": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "*"
}
},
"node_modules/@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -2535,6 +2572,36 @@
"node": ">=0.10.0"
}
},
"node_modules/react-router": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz",
"integrity": "sha512-hTm6KKNpj9SDG4syIWRjCU219O0RZY8RUPobCFt9p+PlF7nnkRgMoh2DieTKvw3F3Mw6zg565HGnSv8BuoY5oQ==",
"dependencies": {
"@remix-run/router": "1.6.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8"
}
},
"node_modules/react-router-dom": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.0.tgz",
"integrity": "sha512-Q3mK1c/CYoF++J6ZINz7EZzwlgSOZK/kc7lxIA7PhtWhKju4KfF1WHqlx0kVCIFJAWztuYVpXZeljEbds8z4Og==",
"dependencies": {
"@remix-run/router": "1.6.0",
"react-router": "6.11.0"
},
"engines": {
"node": ">=14"
},
"peerDependencies": {
"react": ">=16.8",
"react-dom": ">=16.8"
}
},
"node_modules/resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
@ -3512,6 +3579,17 @@
"fastq": "^1.6.0"
}
},
"@remix-run/router": {
"version": "1.6.0",
"resolved": "https://registry.npmjs.org/@remix-run/router/-/router-1.6.0.tgz",
"integrity": "sha512-N13NRw3T2+6Xi9J//3CGLsK2OqC8NMme3d/YX+nh05K9YHWGcv8DycHJrqGScSP4T75o8IN6nqIMhVFU8ohg8w=="
},
"@types/history": {
"version": "4.7.11",
"resolved": "https://registry.npmjs.org/@types/history/-/history-4.7.11.tgz",
"integrity": "sha512-qjDJRrmvBMiTx+jyLxvLfJU7UznFuokDv4f3WRuriHKERccVpFU+8XMQUAbDzoiJCsmexxRExQeMwwCdamSKDA==",
"dev": true
},
"@types/json-schema": {
"version": "7.0.11",
"resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.11.tgz",
@ -3544,6 +3622,27 @@
"@types/react": "*"
}
},
"@types/react-router": {
"version": "5.1.20",
"resolved": "https://registry.npmjs.org/@types/react-router/-/react-router-5.1.20.tgz",
"integrity": "sha512-jGjmu/ZqS7FjSH6owMcD5qpq19+1RS9DeVRqfl1FeBMxTDQAGwlMWOcs52NDoXaNKyG3d1cYQFMs9rCrb88o9Q==",
"dev": true,
"requires": {
"@types/history": "^4.7.11",
"@types/react": "*"
}
},
"@types/react-router-dom": {
"version": "5.3.3",
"resolved": "https://registry.npmjs.org/@types/react-router-dom/-/react-router-dom-5.3.3.tgz",
"integrity": "sha512-kpqnYK4wcdm5UaWI3fLcELopqLrHgLqNsdpHauzlQktfkHL3npOSwtj1Uz9oKBAzs7lFtVkV8j83voAz2D8fhw==",
"dev": true,
"requires": {
"@types/history": "^4.7.11",
"@types/react": "*",
"@types/react-router": "*"
}
},
"@types/scheduler": {
"version": "0.16.3",
"resolved": "https://registry.npmjs.org/@types/scheduler/-/scheduler-0.16.3.tgz",
@ -4640,6 +4739,23 @@
"integrity": "sha512-wViHqhAd8OHeLS/IRMJjTSDHF3U9eWi62F/MledQGPdJGDhodXJ9PBLNGr6WWL7qlH12Mt3TyTpbS+hGXMjCzQ==",
"dev": true
},
"react-router": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/react-router/-/react-router-6.11.0.tgz",
"integrity": "sha512-hTm6KKNpj9SDG4syIWRjCU219O0RZY8RUPobCFt9p+PlF7nnkRgMoh2DieTKvw3F3Mw6zg565HGnSv8BuoY5oQ==",
"requires": {
"@remix-run/router": "1.6.0"
}
},
"react-router-dom": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/react-router-dom/-/react-router-dom-6.11.0.tgz",
"integrity": "sha512-Q3mK1c/CYoF++J6ZINz7EZzwlgSOZK/kc7lxIA7PhtWhKju4KfF1WHqlx0kVCIFJAWztuYVpXZeljEbds8z4Og==",
"requires": {
"@remix-run/router": "1.6.0",
"react-router": "6.11.0"
}
},
"resolve-from": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",

View File

@ -11,11 +11,13 @@
},
"dependencies": {
"react": "^18.2.0",
"react-dom": "^18.2.0"
"react-dom": "^18.2.0",
"react-router-dom": "^6.11.0"
},
"devDependencies": {
"@types/react": "^18.0.28",
"@types/react-dom": "^18.0.11",
"@types/react-router-dom": "^5.3.3",
"@typescript-eslint/eslint-plugin": "^5.57.1",
"@typescript-eslint/parser": "^5.57.1",
"@vitejs/plugin-react": "^4.0.0",

View File

@ -1,42 +0,0 @@
#root {
max-width: 1280px;
margin: 0 auto;
padding: 2rem;
text-align: center;
}
.logo {
height: 6em;
padding: 1.5em;
will-change: filter;
transition: filter 300ms;
}
.logo:hover {
filter: drop-shadow(0 0 2em #646cffaa);
}
.logo.react:hover {
filter: drop-shadow(0 0 2em #61dafbaa);
}
@keyframes logo-spin {
from {
transform: rotate(0deg);
}
to {
transform: rotate(360deg);
}
}
@media (prefers-reduced-motion: no-preference) {
a:nth-of-type(2) .logo {
animation: logo-spin infinite 20s linear;
}
}
.card {
padding: 2em;
}
.read-the-docs {
color: #888;
}

View File

@ -1,24 +0,0 @@
import { useState } from 'react'
import './App.css'
function App() {
return (
<>
<h1>Let's start!</h1>
<h2>What's your date of birth?</h2>
<button>Next</button>
<footer>
<p>By continuing, you agree to our
<a href='https://aura.wit.life/terms'>EULA</a>
and
<a href='https://aura.wit.life/privacy'>Privacy Notice</a>.
Have a question? Reach our support team
<a href='https://aura.wit.life/'>here</a>
</p>
<small>For entertaiment purposes only</small>
</footer>
</>
)
}
export default App

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,31 @@
import { Routes, Route, Navigate } from 'react-router-dom';
import BirthdayPage from '../BirthdayPage'
import BirthtimePage from '../BirthtimePage'
import CreateProfilePage from '../CreateProfilePage'
import NotFoundPage from '../NotFoundPage'
import Header from '../Header'
import routes from '../../routes'
import './styles.css'
function App() {
return (
<div className='container'>
<Header />
<main className='content'>
<section className='page'>
<Routes>
<Route path={routes.client.root()} element={
<Navigate to={routes.client.birthday()} />
} />
<Route path={routes.client.birthday()} element={<BirthdayPage />} />
<Route path={routes.client.birthtime()} element={<BirthtimePage />} />
<Route path={routes.client.createProfile()} element={<CreateProfilePage />} />
<Route path="*" element={<NotFoundPage />} />
</Routes>
</section>
</main>
</div>
)
}
export default App

View File

@ -0,0 +1,25 @@
.container {
display: flex;
flex-direction: column;
position: relative;
height: 100%;
margin: 0 auto;
max-width: 560px;
}
.content {
display: flex;
height: 100vh;
position: relative;
width: 100%;
}
.page {
display: flex;
position: relative;
flex-direction: column;
align-items: center;
flex: 1 1;
overflow: hidden;
padding: 15px 32px;
}

View File

@ -0,0 +1,15 @@
import './styles.css'
function BackButton({ className, ...props }: React.ButtonHTMLAttributes<HTMLDivElement>): JSX.Element {
const combinedClassNames = ['back-btn', className].filter(Boolean).join(' ')
return (
<div className={combinedClassNames} {...props}>
<svg width="25" height="25" viewBox="0 0 25 25" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M19.7676 12.3203H5.76758" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
<path d="M12.7676 19.3203L5.76758 12.3203L12.7676 5.32031" stroke="black" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round"></path>
</svg>
</div>
)
}
export default BackButton

View File

@ -0,0 +1,9 @@
.back-btn {
display: flex;
width: 30px;
height: 30px;
left: 28px;
cursor: pointer;
justify-content: center;
align-items: center;
}

View File

@ -0,0 +1,27 @@
import { useNavigate } from 'react-router-dom'
import routes from '../../routes'
import Policy from '../Policy'
import Purposes from '../Purposes'
import Title from '../Title'
import DateControl from '../DateControl'
import MainButton from '../MainButton'
import './styles.css'
function BirthdayPage(): JSX.Element {
const navigate = useNavigate();
const handleNext = () => navigate(routes.client.birthtime())
return (
<>
<Title variant='h3' className='mt-24'>Let's start!</Title>
<Title variant='h2'>What's your date of birth?</Title>
<DateControl />
<MainButton label='Next' onClick={handleNext} />
<footer className='footer'>
<Policy />
<Purposes />
</footer>
</>
)
}
export default BirthdayPage

View File

@ -0,0 +1,5 @@
.footer {
display: flex;
flex-direction: column;
align-items: center;
}

View File

@ -0,0 +1,21 @@
import { useNavigate } from "react-router-dom"
import Title from "../Title"
import MainButton from "../MainButton"
import TimeControl from "../TimeControl"
import routes from "../../routes"
import './styles.css'
function BirthtimePage(): JSX.Element {
const navigate = useNavigate();
const handleNext = () => navigate(routes.client.createProfile())
return (
<>
<Title variant="h2" className="mt-24">What time were you born?</Title>
<p className="description">We use NASA data to determine the exact position of the planets in the sky at the time of your birth to create wallpapers that are just right for you.</p>
<TimeControl />
<MainButton label='Next' onClick={handleNext} />
</>
)
}
export default BirthtimePage

View File

@ -0,0 +1,7 @@
.description {
font-size: 20px;
font-weight: 400;
line-height: 140%;
margin-bottom: 24px;
text-align: center;
}

View File

@ -0,0 +1,11 @@
import Title from "../Title"
function CreateProfilePage(): JSX.Element {
return (
<>
<Title variant="h2" className="mt-24">Creating your profile</Title>
</>
)
}
export default CreateProfilePage

View File

@ -0,0 +1,33 @@
import './styles.css'
function DateControl(): JSX.Element {
return (
<div className='date-control'>
<div className='date-control__container'>
<div className="date-control__field">
<h3 className='date-control__field-label'>Year</h3>
<label className="date-control__input">
<input type="number" placeholder=" " max="4" />
<p className="date-control__input-placeholder">YYYY</p>
</label>
</div>
<div className="date-control__field">
<h3 className='date-control__field-label'>Month</h3>
<label className="date-control__input">
<input type="text" placeholder=" " />
<p className="date-control__input-placeholder">MM</p>
</label>
</div>
<div className="date-control__field">
<h3 className='date-control__field-label'>Day</h3>
<label className="date-control__input">
<input type="text" placeholder=" " />
<p className="date-control__input-placeholder">DD</p>
</label>
</div>
</div>
</div>
)
}
export default DateControl

View File

@ -0,0 +1,99 @@
.date-control {
margin-bottom: 24px;
position: relative;
width: 100%;
}
.date-control__container {
grid-gap: 12px;
background-color: #fff;
display: grid;
gap: 12px;
grid-template-columns: repeat(3,1fr);
max-width: 400px;
position: relative;
width: 100%;
z-index: 3;
margin: 0 auto;
}
.date-control__field-label {
color: #6b7baa;
font-size: 12px;
line-height: 16px;
margin: 0 0 6px 6px;
}
.date-control__input {
display: block;
font-size: 16px;
width: 100%;
height: 48px;
position: relative;
margin-bottom: 0;
}
.date-control__input > input {
appearance: none;
border-radius: 8px;
color: #121620;
font-size: 16px;
height: 48px;
line-height: 18px;
max-width: 400px;
min-width: 96px;
outline: none;
padding: 12px 12px 5px;
transition: border-color .3s ease;
width: 100%;
background: #eff2fd;
border: 2px solid #dee5f9;
padding-top: 5px;
}
.date-control__input > input:focus {
border-color: #066fde;
transition-delay: .1s;
}
.date-control__input-placeholder {
color: #6b7baa;
font-size: 16px;
left: 12px;
overflow: hidden;
text-overflow: ellipsis;
transition: top .3s ease,color .3s ease,font-size .3s ease;
white-space: nowrap;
position: absolute;
top: 50%;
transform: translateY(-50%);
}
.date-control__input input:focus + .date-control__input-placeholder,
.date-control__input input:not(:placeholder-shown) + .date-control__input-placeholder {
display: none;
font-size: 12px;
top: 12px;
width: auto;
}
.date-control__field-select {
display: block;
font-size: 16px;
width: 100%;
position: relative;
margin-bottom: 0;
appearance: none;
border-radius: 8px;
color: #121620;
height: 48px;
line-height: 18px;
max-width: 400px;
min-width: 96px;
outline: none;
padding: 12px 12px 5px;
transition: border-color .3s ease;
background: #eff2fd;
border: 2px solid #dee5f9;
padding-top: 5px;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 MiB

View File

@ -0,0 +1,40 @@
import { useState, useEffect } from 'react'
import { useNavigate, useLocation } from 'react-router-dom'
import routes, { isNotEntrypoint } from '../../routes'
import BackButton from '../BackButton'
import iconUrl from './icon.png'
import './styles.css'
function Header(): JSX.Element {
const navigate = useNavigate()
const location = useLocation()
const [initialPath, setInitialPath] = useState<string | null>(null);
const [isNavigated, setIsNavigated] = useState<boolean>(false);
const showBackButton = isNotEntrypoint(location.pathname)
useEffect(() => {
if (!initialPath) {
setInitialPath(location.pathname)
}
if (initialPath && location.pathname !== initialPath && !isNavigated) {
setIsNavigated(true)
}
}, [location.pathname, initialPath, isNavigated])
const goBack = () => {
if (initialPath && isNotEntrypoint(initialPath) && !isNavigated) {
navigate(routes.client.root())
} else {
navigate(-1)
}
}
return (
<header className="header">
{ showBackButton ? <BackButton className="pa" onClick={goBack} /> : null }
<img src={iconUrl} alt="logo" width="40" height="40" />
</header>
)
}
export default Header

View File

@ -0,0 +1,10 @@
.header {
align-items: center;
display: flex;
justify-content: center;
background: #eff1fd;
height: 50px;
min-height: 50px;
position: relative;
width: 100%;
}

View File

@ -0,0 +1,13 @@
import './styles.css'
type ButtonProps = {
label: string;
} & React.ButtonHTMLAttributes<HTMLButtonElement>;
function MainButton({ className, label, ...props}: ButtonProps): JSX.Element {
const combinedClassNames = ['main-btn', className].filter(Boolean).join(' ')
return <button className={combinedClassNames} {...props}>{label}</button>
}
export default MainButton

View File

@ -0,0 +1,22 @@
.main-btn {
align-items: center;
background: #000;
border: none;
border-radius: 20px;
color: #fff;
cursor: pointer;
display: flex;
font-size: 18px;
font-weight: 500;
justify-content: center;
line-height: 20px;
max-width: 400px;
min-height: 60px;
min-width: 250px;
padding: 12px 16px;
width: 100%;
}
.main-btn:disabled {
opacity: 50%;
}

View File

@ -0,0 +1,10 @@
function NotFoundPage() {
return (
<>
<h1>Oops!</h1>
<p>Sorry, an unexpected error has occurred.</p>
</>
)
}
export default NotFoundPage

View File

@ -0,0 +1,39 @@
import { ReactNode } from 'react'
import './styles.css'
enum PolicyContentType {
Text = 'text',
Link = 'link',
}
type PolicyContent = {
type: PolicyContentType
content: string
href?: string
}
function Policy(): JSX.Element {
const text: PolicyContent[] = [
{ type: PolicyContentType.Text, content: 'By continuing, you agree to our ' },
{ type: PolicyContentType.Link, content: 'EULA', href: 'https://aura.wit.life/terms' },
{ type: PolicyContentType.Text, content: ' and ' },
{ type: PolicyContentType.Link, content: 'Privacy Notice', href: 'https://aura.wit.life/privacy' },
{ type: PolicyContentType.Text, content: '. Have a question? Reach our support team ' },
{ type: PolicyContentType.Link, content: 'here', href: 'https://aura.wit.life/' },
]
const toElement = (item: PolicyContent, idx: number) => {
switch (item.type) {
case 'text':
return item.content
case 'link':
return <a key={idx} href={item.href} target="_blank" rel="noreferrer nofollow">{item.content}</a>
default:
throw new Error(`Unknown type: ${item.type}`)
}
}
const content = text.map<ReactNode>(toElement)
return <div className="policy"><p>{ content }</p></div>
}
export default Policy

View File

@ -0,0 +1,20 @@
.policy {
display: flex;
flex-direction: column;
max-width: 400px;
width: 100%;
margin-top: 20px;
text-align: center;
}
.policy p,
.policy a {
color: #121620;
font-size: 12px;
font-weight: 400;
line-height: 18px;
}
.policy a {
text-decoration: underline;
}

View File

@ -0,0 +1,7 @@
import './styles.css'
function Purposes(): JSX.Element {
return <small className="purposes">For entertaiment purposes only</small>
}
export default Purposes

View File

@ -0,0 +1,6 @@
.purposes {
color: #8e8e93;
font-size: 12px;
line-height: 18px;
margin-top: 6px;
}

View File

@ -0,0 +1,33 @@
function TimeControl(): JSX.Element {
return (
<div className='date-control'>
<div className='date-control__container'>
<div className="date-control__field">
<select className="date-control__field-select">
{Array.from(Array(12).keys()).map((hour) => (
<option key={hour} value={hour + 1}>{hour + 1}</option>
))}
</select>
</div>
<div className="date-control__field">
<select className="date-control__field-select">
{Array.from(Array(60).keys()).map((minute) => {
const formattedMinute = String(minute).padStart(2, '0');
return (
<option key={minute} value={minute}>{formattedMinute}</option>
);
})}
</select>
</div>
<div className="date-control__field">
<select className="date-control__field-select">
<option value="AM">AM</option>
<option value="PM">PM</option>
</select>
</div>
</div>
</div>
)
}
export default TimeControl

View File

@ -0,0 +1,17 @@
import { PropsWithChildren, HTMLAttributes } from 'react'
import './styles.css'
type TitleProps = PropsWithChildren<{
variant?: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6'
} & HTMLAttributes<HTMLElement>>
function Title({ children, variant, className, ...props }: TitleProps): JSX.Element {
const combinedClassNames = ['title', className].filter(Boolean).join(' ')
const Tag = variant ?? 'h1'
return (
<Tag className={combinedClassNames} {...props}>{ children }</Tag>
)
}
export default Title

View File

@ -0,0 +1,6 @@
.title {
letter-spacing: .2px;
line-height: 150%;
margin-bottom: 24px;
text-align: center;
}

38
src/fonts.css Normal file
View File

@ -0,0 +1,38 @@
@font-face {
font-display: swap;
font-family: SF Pro Text;
font-style: normal;
font-weight: 400;
src: url(./assets/media/sf-pro-text-regular.woff) format("woff")
}
@font-face {
font-display: swap;
font-family: SF Pro Text;
font-style: normal;
font-weight: 500;
src: url(./assets/media/sf-pro-text-medium.woff) format("woff")
}
@font-face {
font-display: swap;
font-family: SF Pro Text;
font-style: normal;
font-weight: 600;
src: url(./assets/media/sf-pro-text-semibold.woff) format("woff")
}
@font-face {
font-display: swap;
font-family: SF Pro Text;
font-style: normal;
font-weight: 700;
src: url(./assets/media/sf-pro-text-bold.woff) format("woff")
}
@font-face {
font-display: swap;
font-family: "sf mono bold, sans-serif";
font-style: normal;
src: url(./assets/media/sf-mono-bold.otf) format("opentype")
}

View File

@ -1,69 +1,94 @@
:root {
font-family: Inter, system-ui, Avenir, Helvetica, Arial, sans-serif;
line-height: 1.5;
font-weight: 400;
color-scheme: light dark;
color: rgba(255, 255, 255, 0.87);
background-color: #242424;
font-synthesis: none;
text-rendering: optimizeLegibility;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
-webkit-text-size-adjust: 100%;
* {
box-sizing: border-box;
font-family: SF Pro Text, sans-serif;
}
a {
h2 {
font-size: 24px;
font-weight: 600;
}
h3 {
font-size: 20px;
font-weight: 500;
color: #646cff;
text-decoration: inherit;
}
a:hover {
color: #535bf2;
}
body {
margin: 0;
display: flex;
place-items: center;
min-width: 320px;
min-height: 100vh;
h4 {
font-weight: 400;
}
h1 {
font-size: 3.2em;
line-height: 1.1;
button,h4 {
font-size: 18px;
}
button {
border-radius: 8px;
border: 1px solid transparent;
padding: 0.6em 1.2em;
font-size: 1em;
font-weight: 500;
font-family: inherit;
background-color: #1a1a1a;
cursor: pointer;
transition: border-color 0.25s;
}
button:hover {
border-color: #646cff;
}
button:focus,
button:focus-visible {
outline: 4px auto -webkit-focus-ring-color;
line-height: 20px;
}
@media (prefers-color-scheme: light) {
:root {
color: #213547;
background-color: #ffffff;
}
a:hover {
color: #747bff;
}
button {
background-color: #f9f9f9;
}
a,abbr,acronym,address,applet,article,aside,audio,b,big,blockquote,body,canvas,caption,center,cite,code,dd,del,details,dfn,div,dl,dt,em,embed,fieldset,figcaption,figure,footer,form,h1,h2,h3,h4,h5,h6,header,hgroup,html,i,iframe,img,ins,kbd,label,legend,li,mark,menu,nav,object,ol,output,p,pre,q,ruby,s,samp,section,small,span,strike,strong,sub,summary,sup,table,tbody,td,tfoot,th,thead,time,tr,tt,u,ul,var,video {
border: 0;
margin: 0;
padding: 0;
vertical-align: initial;
}
article,aside,details,figcaption,figure,footer,header,hgroup,menu,nav,section {
display: block;
}
body,html {
height: 100%;
line-height: 1;
}
.height-initial {
height: auto;
}
ol,ul {
list-style: none;
}
a {
text-decoration: none;
}
div[class^=divider] {
font-family: SF Pro Text Regular,sans-serif
}
* {
scrollbar-color: #cde8f9 #fff;
scrollbar-width: auto;
}
::-webkit-scrollbar {
width: 13px;
}
::-webkit-scrollbar-track {
margin: 3px;
}
::-webkit-scrollbar-thumb {
background-color: #cde8f9;
border: 3px solid #fff;
border-radius: 10px;
height: 20px;
}
#root {
height: 100%;
}
a,button,div,input,select,textarea {
-webkit-tap-highlight-color: transparent;
}
.mt-24 {
margin-top: 24px;
}
.pa {
position: absolute;
}

15
src/init.tsx Normal file
View File

@ -0,0 +1,15 @@
import React from 'react'
import { BrowserRouter } from 'react-router-dom'
import App from './components/App'
const init = async () => {
return (
<React.StrictMode>
<BrowserRouter>
<App />
</BrowserRouter>
</React.StrictMode>
)
}
export default init

21
src/main.ts Normal file
View File

@ -0,0 +1,21 @@
import ReactDOM from 'react-dom/client'
import init from './init'
import './fonts.css'
import './index.css'
const getRootElement = (id: string): HTMLElement => {
const root = document.getElementById(id)
if (root) return root;
const element = document.createElement('div')
element.id = id
document.body.appendChild(element)
return element
}
const vdom = await init()
const rootElement = getRootElement('root')
ReactDOM.createRoot(rootElement).render(vdom)

View File

@ -1,10 +0,0 @@
import React from 'react'
import ReactDOM from 'react-dom/client'
import App from './App.tsx'
import './index.css'
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
<React.StrictMode>
<App />
</React.StrictMode>,
)

24
src/routes.ts Normal file
View File

@ -0,0 +1,24 @@
const host = '';
const prefix = 'api/v1';
const routes = {
client: {
root: () => [host, ''].join('/'),
email: () => [host, 'email'].join('/'),
birthday: () => [host, 'birthday'].join('/'),
birthtime: () => [host, 'birthtime'].join('/'),
subscription: () => [host, 'subscription'].join('/'),
createProfile: () => [host, 'profile', 'create'].join('/'),
},
server: {
locales: () => [host, prefix, 'locales.json'].join('/'),
translations: () => [host, prefix, 't.json'].join('/'),
userRegistration: () => [host, prefix, 'user', 'registration.json'].join('/'),
},
}
export const entrypoints = [routes.client.root(), routes.client.birthday()]
export const isEntrypoint = (path: string) => entrypoints.includes(path)
export const isNotEntrypoint = (path: string) => !isEntrypoint(path)
export default routes