Prepare product layout and reusable Flutter theme
Relocate the existing Astro website and preserve its assets and hosting configuration. Add the consumer/backend scaffold and themed Flutter package with consumer tests. Related to #1.
This commit is contained in:
1 parent
ef65a40213
commit
d9318053e8
46 files changed
+703
-1
No files matched your search
@@ -0,0 +1,296 @@
|
||||
---
|
||||
import Layout from '../../layouts/Layout.astro';
|
||||
import Footer from '../../components/Footer.astro';
|
||||
import AccountDialog from '../../components/AccountDialog.astro';
|
||||
import { appLinks } from '../../lib/app-links';
|
||||
import { copy, languages, type Lang } from '../../i18n/copy';
|
||||
export function getStaticPaths() {
|
||||
return languages.map((lang) => ({ params: { lang } }));
|
||||
}
|
||||
const lang = Astro.params.lang as Lang;
|
||||
const t = copy[lang];
|
||||
---
|
||||
|
||||
<Layout lang={lang}>
|
||||
<div
|
||||
class="hero-shell"
|
||||
data-register-url={appLinks.register}
|
||||
data-login-url={appLinks.login}
|
||||
>
|
||||
<header class="site-header wrap">
|
||||
<a class="wordmark" href={`/${lang}/`} aria-label="SXCHNGE"
|
||||
><span class="brand-icon" aria-hidden="true">↗</span> sxchnge<span
|
||||
class="brand-dot">®</span
|
||||
></a
|
||||
>
|
||||
<nav
|
||||
class="desktop-nav"
|
||||
aria-label={lang === 'en' ? 'Main navigation' : 'Основная навигация'}
|
||||
>
|
||||
{
|
||||
t.nav.map((label, i) => (
|
||||
<a href={['#how', '#why', '#faq'][i]}>{label}</a>
|
||||
))
|
||||
}
|
||||
</nav>
|
||||
<div class="header-actions">
|
||||
<div
|
||||
class="language-switch"
|
||||
aria-label={lang === 'en' ? 'Language' : 'Язык'}
|
||||
>
|
||||
<a
|
||||
href="/en/"
|
||||
lang="en"
|
||||
aria-current={lang === 'en' ? 'page' : undefined}>EN</a
|
||||
><span>/</span><a
|
||||
href="/ru/"
|
||||
lang="ru"
|
||||
aria-current={lang === 'ru' ? 'page' : undefined}>RU</a>
|
||||
</div><button class="login-button" data-auth="login"
|
||||
>{t.login}<span aria-hidden="true">↗</span></button>
|
||||
</div>
|
||||
</header>
|
||||
<main id="main">
|
||||
<noscript
|
||||
><p class="no-script">
|
||||
{
|
||||
lang === 'en'
|
||||
? 'Enable JavaScript to use the demo calculator and account preview. All information and policies remain available below.'
|
||||
: 'Включите JavaScript для демо-калькулятора и просмотра регистрации. Вся информация и документы доступны ниже.'
|
||||
}
|
||||
</p></noscript
|
||||
>
|
||||
<section class="hero wrap" aria-labelledby="hero-title">
|
||||
<div class="hero-copy">
|
||||
<p class="eyebrow"><span class="status-dot"></span>{t.eyebrow}</p><h1
|
||||
id="hero-title"
|
||||
>
|
||||
{t.headline[0]}<br /><span>{t.headline[1]}</span><span
|
||||
class="headline-star"
|
||||
aria-hidden="true">✳</span>
|
||||
</h1><p class="hero-intro">{t.intro}</p><div class="hero-actions">
|
||||
<button class="button button-dark" data-auth="register"
|
||||
>{t.register}<span aria-hidden="true">↗</span></button
|
||||
><a class="text-link" href="#how"
|
||||
>{t.subcta}<span aria-hidden="true">↓</span></a>
|
||||
</div><div class="hero-signoff">
|
||||
<span class="mini-line" aria-hidden="true"></span>{t.heroNote}
|
||||
</div>
|
||||
</div>
|
||||
<div class="exchange-stage">
|
||||
<div class="orbit" aria-hidden="true"></div><div
|
||||
class="stage-caption"
|
||||
aria-hidden="true"
|
||||
>
|
||||
MAKE YOUR MOVE ↗
|
||||
</div><section class="exchange-card" aria-labelledby="exchange-title">
|
||||
<div class="exchange-heading">
|
||||
<h2 id="exchange-title">{t.preview}</h2><span class="demo-badge"
|
||||
>{t.demo}</span>
|
||||
</div><div class="amount-box">
|
||||
<label for="send-amount">{t.send}</label><div class="amount-row">
|
||||
<input
|
||||
id="send-amount"
|
||||
type="text"
|
||||
inputmode="decimal"
|
||||
value="1"
|
||||
autocomplete="off"
|
||||
aria-describedby="amount-error quote-disclaimer"
|
||||
/><div class="currency-picker">
|
||||
<span class="coin coin-btc" id="coin-icon" aria-hidden="true"
|
||||
>₿</span
|
||||
><select id="crypto" aria-label={t.crypto}
|
||||
><option value="BTC">BTC</option><option value="ETH"
|
||||
>ETH</option
|
||||
><option value="USDT">USDT</option></select>
|
||||
</div>
|
||||
</div>
|
||||
</div><div class="direction-divider" aria-hidden="true">
|
||||
<span>↓</span>
|
||||
</div><div class="amount-box receive-box">
|
||||
<label for="receive-amount">{t.receive}</label><div
|
||||
class="amount-row"
|
||||
>
|
||||
<output
|
||||
id="receive-amount"
|
||||
aria-label={t.quoteLabel}
|
||||
aria-live="polite">56,925.00</output
|
||||
><div class="currency-picker">
|
||||
<span class="coin coin-fiat" id="fiat-icon" aria-hidden="true"
|
||||
>€</span
|
||||
><select id="fiat" aria-label={t.fiat}
|
||||
><option value="EUR">EUR</option><option value="USD"
|
||||
>USD</option
|
||||
></select>
|
||||
</div>
|
||||
</div>
|
||||
</div><p id="amount-error" class="amount-error" hidden>
|
||||
{t.invalid}
|
||||
</p><dl class="quote-details">
|
||||
<div>
|
||||
<dt>{t.sampleRate}</dt><dd id="quote-rate">
|
||||
1 BTC ≈ 57,500 EUR
|
||||
</dd>
|
||||
</div><div>
|
||||
<dt>{t.fee}</dt><dd id="quote-fee">575.00 EUR</dd>
|
||||
</div><div><dt>{t.payout}</dt><dd>{t.bank}</dd></div>
|
||||
</dl><button
|
||||
class="button button-mint exchange-submit"
|
||||
data-auth="register"
|
||||
>{t.exchange}<span aria-hidden="true">↗</span></button
|
||||
><p class="quote-disclaimer" id="quote-disclaimer">
|
||||
{t.disclaimer}
|
||||
</p>
|
||||
</section><div class="stage-bottom" aria-hidden="true">
|
||||
<span>₿</span><span>→</span><span>€</span><div class="stage-line">
|
||||
</div><span>01 — ∞</span>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<div class="ticker" aria-hidden="true">
|
||||
<div>
|
||||
{
|
||||
[...t.strip, ...t.strip].map((s) => (
|
||||
<span>
|
||||
{s}
|
||||
<b>✳</b>
|
||||
</span>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<section class="section how-section" id="how" aria-labelledby="how-title">
|
||||
<div class="wrap">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">{t.howLabel}</p><h2 id="how-title">
|
||||
{t.howTitle}
|
||||
</h2>
|
||||
</div><p>{t.howIntro}</p>
|
||||
</div><div class="steps">
|
||||
{
|
||||
t.steps.map(([title, body], i) => (
|
||||
<article class="step">
|
||||
<>
|
||||
<div class="step-top">
|
||||
<>
|
||||
<span class="step-number">0{i + 1}</span>
|
||||
<span class="step-arrow" aria-hidden="true">
|
||||
{i === 2 ? '↗' : '→'}
|
||||
</span>
|
||||
</>
|
||||
</div>
|
||||
<h3>{title}</h3>
|
||||
<p>{body}</p>
|
||||
</>
|
||||
</article>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section why-section" id="why" aria-labelledby="why-title">
|
||||
<div class="wrap">
|
||||
<div class="section-heading">
|
||||
<div>
|
||||
<p class="eyebrow">{t.whyLabel}</p><h2 id="why-title">
|
||||
{t.whyTitle}
|
||||
</h2>
|
||||
</div><p>{t.whyIntro}</p>
|
||||
</div><div class="feature-grid">
|
||||
<article class="feature-card clarity-card">
|
||||
<div class="feature-top">
|
||||
<span class="feature-symbol" aria-hidden="true">↗</span><span
|
||||
class="feature-index">/ 01</span>
|
||||
</div><div class="clarity-visual" aria-hidden="true">
|
||||
<div>
|
||||
<span>BTC</span><span class="visual-line"></span><span
|
||||
>EUR</span>
|
||||
</div><div class="clarity-flow">
|
||||
<span>₿</span><span>⟶</span><span>€</span>
|
||||
</div><div class="visual-ruler">
|
||||
<i></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i
|
||||
></i><i></i><i></i><i></i><i></i><i></i><i></i><i></i><i
|
||||
></i><i></i><i></i><i></i>
|
||||
</div>
|
||||
</div><h3>{t.features[0][0]}</h3><p>{t.features[0][1]}</p>
|
||||
</article><article class="feature-card choice-card">
|
||||
<div class="feature-top">
|
||||
<span class="feature-symbol" aria-hidden="true">↔</span><span
|
||||
class="feature-index">/ 02</span>
|
||||
</div><div class="currency-visual" aria-hidden="true">
|
||||
<span>₿</span><span>Ξ</span><span>₮</span><span>€</span><span
|
||||
>$</span>
|
||||
</div><span class="pair-note">{t.pairNote}</span><h3>
|
||||
{t.features[1][0]}
|
||||
</h3><p>{t.features[1][1]}</p>
|
||||
</article><article class="verification-card">
|
||||
<div class="verification-mark" aria-hidden="true">
|
||||
<svg viewBox="0 0 64 64" fill="none"
|
||||
><path
|
||||
d="M32 6 52 14v17c0 13-20 25-20 25S12 44 12 31V14L32 6Z"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"></path><path
|
||||
d="m23 30 6 6 13-14"
|
||||
stroke="currentColor"
|
||||
stroke-width="2"></path></svg>
|
||||
</div><div class="verification-copy">
|
||||
<p class="eyebrow">{t.verification}</p><h3>
|
||||
{t.features[2][0]}
|
||||
</h3><p>{t.features[2][1]}</p>
|
||||
</div><div class="verification-links">
|
||||
<a href={`/${lang}/policies/kyc/`}
|
||||
>{t.kyc}<span aria-hidden="true">↗</span></a
|
||||
><a href={`/${lang}/policies/aml/`}
|
||||
>{t.aml}<span aria-hidden="true">↗</span></a>
|
||||
</div>
|
||||
</article>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="section faq-section" id="faq" aria-labelledby="faq-title">
|
||||
<div class="wrap faq-grid">
|
||||
<div>
|
||||
<p class="eyebrow">{t.faqLabel}</p><h2 id="faq-title">
|
||||
{t.faqTitle}
|
||||
</h2><span class="faq-asterisk" aria-hidden="true">✳</span>
|
||||
</div><div class="faq-list">
|
||||
{
|
||||
t.faqs.map(([q, a]) => (
|
||||
<details name="faq">
|
||||
<>
|
||||
<summary>
|
||||
{q}
|
||||
<span aria-hidden="true">+</span>
|
||||
</summary>
|
||||
<p>{a}</p>
|
||||
</>
|
||||
</details>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="closing-section" aria-labelledby="closing-title">
|
||||
<div class="wrap closing-content">
|
||||
<p class="eyebrow">{t.closingLabel}</p><h2 id="closing-title">
|
||||
{t.closing}<span aria-hidden="true">↗</span>
|
||||
</h2><div class="closing-actions">
|
||||
<button class="button button-dark" data-auth="register"
|
||||
>{t.register}<span aria-hidden="true">↗</span></button
|
||||
><button class="closing-login" data-auth="login"
|
||||
>{t.login} ↗</button>
|
||||
</div><p class="closing-note">{t.closingNote}</p><span
|
||||
class="closing-star"
|
||||
aria-hidden="true">✳</span>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<Footer lang={lang} />
|
||||
<AccountDialog lang={lang} />
|
||||
</div>
|
||||
<script>
|
||||
import '../../scripts/home';
|
||||
</script>
|
||||
</Layout>
|
||||
@@ -0,0 +1,52 @@
|
||||
---
|
||||
import Layout from '../../../layouts/Layout.astro';
|
||||
import Footer from '../../../components/Footer.astro';
|
||||
import { copy, languages, type Lang } from '../../../i18n/copy';
|
||||
const policySlugs = ['terms', 'privacy', 'aml', 'kyc'];
|
||||
export function getStaticPaths() {
|
||||
return languages.flatMap((lang) =>
|
||||
['terms', 'privacy', 'aml', 'kyc'].map((policy) => ({
|
||||
params: { lang, policy },
|
||||
})),
|
||||
);
|
||||
}
|
||||
const lang = Astro.params.lang as Lang;
|
||||
const policy = Astro.params.policy!;
|
||||
const t = copy[lang];
|
||||
const index = policySlugs.indexOf(policy);
|
||||
---
|
||||
|
||||
<Layout
|
||||
lang={lang}
|
||||
title={`${t.legalNames[index]} — SXCHNGE`}
|
||||
path={`policies/${policy}/`}
|
||||
>
|
||||
<header class="site-header wrap">
|
||||
<a class="wordmark" href={`/${lang}/`}
|
||||
><span class="brand-icon" aria-hidden="true">↗</span> sxchnge</a
|
||||
><div class="language-switch">
|
||||
<a
|
||||
href={`/en/policies/${policy}/`}
|
||||
lang="en"
|
||||
aria-current={lang === 'en' ? 'page' : undefined}>EN</a
|
||||
><span>/</span><a
|
||||
href={`/ru/policies/${policy}/`}
|
||||
lang="ru"
|
||||
aria-current={lang === 'ru' ? 'page' : undefined}>RU</a>
|
||||
</div>
|
||||
</header>
|
||||
<main class="policy-content wrap" id="main">
|
||||
<a class="text-link" href={`/${lang}/`}>← {t.policyBack}</a><p
|
||||
class="eyebrow policy-label"
|
||||
>
|
||||
{t.policyDraft}
|
||||
</p><h1>{t.legalNames[index]}</h1><p class="policy-intro">
|
||||
{t.policyIntro}
|
||||
</p><section class="policy-placeholder">
|
||||
<h2>{t.policyNeeded}</h2><ul>
|
||||
{t.policyItems[index].map((item) => <li>{item}</li>)}
|
||||
</ul>
|
||||
</section><p class="policy-company">{t.companyFields[0]} · {t.support}</p>
|
||||
</main>
|
||||
<Footer lang={lang} />
|
||||
</Layout>
|
||||
@@ -0,0 +1,3 @@
|
||||
---
|
||||
return Astro.redirect('/en/');
|
||||
---
|
||||
Reference in new issue
Block a user