317 lines
11 KiB
Markdown
317 lines
11 KiB
Markdown
# Flamy Trade — Frontend
|
||
|
||
Публичный лендинг и дашборд ML-прогнозов для крипторынка. Построен на **SvelteKit 5** (Runes API), **Tailwind v4**, **TypeScript** и **lightweight-charts v5**.
|
||
|
||
---
|
||
|
||
## Стек
|
||
|
||
| Слой | Технология |
|
||
|---|---|
|
||
| Фреймворк | SvelteKit 5 (runes, SSR) |
|
||
| Язык | TypeScript (strict) |
|
||
| Стили | Tailwind CSS v4 (Vite plugin, `@theme`) |
|
||
| Графики | lightweight-charts v5 (TradingView) |
|
||
| Сервер | `@sveltejs/adapter-node` (Node.js) |
|
||
| Пакетный менеджер | pnpm |
|
||
|
||
---
|
||
|
||
## Быстрый старт
|
||
|
||
**Требования:** Node.js ≥ 20, pnpm ≥ 9
|
||
|
||
```bash
|
||
# 1. Установить зависимости
|
||
pnpm install
|
||
|
||
# 2. Запустить dev-сервер
|
||
pnpm dev
|
||
|
||
# 3. Открыть в браузере
|
||
# http://localhost:5173
|
||
```
|
||
|
||
Остальные команды:
|
||
|
||
```bash
|
||
pnpm build # Сборка для продакшена (папка build/)
|
||
pnpm preview # Превью продакшен-сборки
|
||
pnpm check # Svelte + TypeScript проверка типов
|
||
pnpm lint # ESLint + Prettier проверка
|
||
pnpm format # Автоформатирование
|
||
```
|
||
|
||
---
|
||
|
||
## Структура проекта
|
||
|
||
```
|
||
src/
|
||
├── lib/
|
||
│ ├── components/
|
||
│ │ ├── Chart.svelte # lightweight-charts обёртка
|
||
│ │ ├── home/
|
||
│ │ │ ├── DashboardMockup.svelte # 3D-макет на главной
|
||
│ │ │ └── DashboardStatus.svelte
|
||
│ │ ├── layout/
|
||
│ │ │ ├── Header.svelte # Фиксированная шапка + мобильное меню
|
||
│ │ │ └── Footer.svelte
|
||
│ │ └── ui/
|
||
│ │ ├── Button.svelte # Универсальная кнопка/ссылка
|
||
│ │ └── Logo.svelte
|
||
│ ├── blog/
|
||
│ │ └── posts.ts # Статьи блога (статические данные)
|
||
│ ├── stores/
|
||
│ │ └── chartStore.ts # Типы + генератор фейковых данных
|
||
│ ├── utils.ts # cn() — утилита для className
|
||
│ └── index.ts
|
||
│
|
||
├── routes/
|
||
│ ├── +layout.svelte # Корневой layout: Header, Footer, шрифты
|
||
│ ├── +page.svelte # Главная страница (секции)
|
||
│ ├── +error.svelte # Страница ошибки
|
||
│ ├── (sections)/ # Секции главной страницы
|
||
│ │ ├── Hero.svelte
|
||
│ │ ├── Ticker.svelte
|
||
│ │ ├── About.svelte
|
||
│ │ ├── Features.svelte
|
||
│ │ ├── Stats.svelte
|
||
│ │ ├── BlogPreview.svelte
|
||
│ │ └── Cta.svelte
|
||
│ ├── about/
|
||
│ │ ├── +page.svelte
|
||
│ │ └── _data.ts
|
||
│ ├── blog/
|
||
│ │ ├── +page.svelte # Список статей
|
||
│ │ └── [slug]/
|
||
│ │ ├── +page.ts # Загрузка статьи по slug
|
||
│ │ └── +page.svelte # Статья
|
||
│ └── dashboard/
|
||
│ └── +page.svelte # Интерактивный дашборд с графиком
|
||
│
|
||
└── routes/layout.css # Tailwind @theme + глобальные стили
|
||
```
|
||
|
||
---
|
||
|
||
## Дизайн-система
|
||
|
||
### Цвета (`layout.css`)
|
||
|
||
```css
|
||
--color-primary: #fe4b07 /* Акцент — оранжевый */
|
||
--color-primary-h:#c83e06 /* Hover-состояние */
|
||
--color-bg: #09080a /* Фон страницы */
|
||
--color-bg-e: #0f0e10 /* Elevated (шапка, карточки) */
|
||
--color-bg-c: #141318 /* Card background */
|
||
--color-bg-h: #1a191e /* Hover / бордеры */
|
||
--color-title: #f0ede6 /* Основной текст */
|
||
--color-desc: #8a887f /* Вторичный текст */
|
||
```
|
||
|
||
В Tailwind используются как `bg-primary`, `text-desc`, `border-bg-h` и т.д.
|
||
|
||
### Шрифты
|
||
|
||
| Переменная | Семейство | Применение |
|
||
|---|---|---|
|
||
| `font-display` | Unbounded | Заголовки, кнопки, тикеры |
|
||
| `font-sans` | DM Sans | Основной текст |
|
||
| `font-mono` | JetBrains Mono | Цены, метки, метаданные |
|
||
|
||
Подключаются через Google Fonts в `+layout.svelte`.
|
||
|
||
### Компонент `Button.svelte`
|
||
|
||
```svelte
|
||
<!-- Первичная (ссылка) -->
|
||
<Button href="/dashboard">Открыть дашборд</Button>
|
||
|
||
<!-- Вторичная (кнопка) -->
|
||
<Button variant="secondary" onclick={handler}>Действие</Button>
|
||
```
|
||
|
||
Props: `href?`, `target?`, `variant?: 'primary' | 'secondary'`, `class?`
|
||
|
||
---
|
||
|
||
## Дашборд и Charts
|
||
|
||
### Архитектура данных
|
||
|
||
```
|
||
chartStore.ts → generateChartData(symbolId, timeframeId)
|
||
└── возвращает ChartData
|
||
├── candles: CandleBar[] // 160 свечей
|
||
├── prediction: PredPoint[] // 19 прогнозных точек
|
||
├── signal: Signal // direction, confidence, entry/tp/sl
|
||
├── currentPrice: number
|
||
└── change24h(Pct): number
|
||
```
|
||
|
||
`generateChartData` — **заглушка с детерминированными фейковыми данными**. При подключении реального бекенда её нужно заменить на API-запрос, сохранив возвращаемые типы:
|
||
|
||
```typescript
|
||
// chartStore.ts — заменить эту функцию на реальный запрос:
|
||
export async function fetchChartData(symbolId: string, tfId: TimeframeId): Promise<ChartData> {
|
||
const res = await fetch(`/api/chart/${symbolId}?tf=${tfId}`);
|
||
return res.json();
|
||
}
|
||
```
|
||
|
||
### Компонент `Chart.svelte`
|
||
|
||
```svelte
|
||
<Chart data={chartData} decimals={2} height={520} />
|
||
```
|
||
|
||
| Prop | Тип | Описание |
|
||
|---|---|---|
|
||
| `data` | `ChartData` | Свечи + прогноз + сигнал |
|
||
| `decimals` | `number` | Кол-во знаков после запятой для цены |
|
||
| `height` | `number` | Высота canvas в пикселях (default: 520) |
|
||
|
||
Что рендерится:
|
||
- **CandlestickSeries** — исторические зелёные/красные свечи
|
||
- **LineSeries** — пунктирная оранжевая линия ML-прогноза (19 свечей вперёд)
|
||
- **PriceLine** × 3 — горизонтальные линии Entry / TP / SL с подписями на шкале
|
||
- **OHLC тултип** — кастомный HTML-оверлей при наведении (показывает O/H/L/C и ML-значение)
|
||
- **ResizeObserver** — автоматически подстраивает ширину и высоту при изменении окна
|
||
|
||
### Таймфреймы и символы
|
||
|
||
Определены в `chartStore.ts`:
|
||
|
||
```typescript
|
||
SYMBOLS // 8 монет: BTC ETH SOL BNB XRP ADA DOGE LINK
|
||
TIMEFRAMES // 6 таймфреймов: 1m 5m 15m 1h 4h 1d
|
||
```
|
||
|
||
---
|
||
|
||
## Контракт API (для бекенда)
|
||
|
||
Дашборд ожидает от бекенда следующую форму ответа:
|
||
|
||
```typescript
|
||
type ChartData = {
|
||
candles: Array<{
|
||
time: number; // Unix timestamp (UTC seconds)
|
||
open: number;
|
||
high: number;
|
||
low: number;
|
||
close: number;
|
||
}>;
|
||
prediction: Array<{
|
||
time: number; // Unix timestamp будущих свечей
|
||
value: number; // Прогнозная цена (mid/close)
|
||
}>;
|
||
signal: {
|
||
direction: 'long' | 'short';
|
||
confidence: number; // 0–100
|
||
entry: number;
|
||
tp: number;
|
||
sl: number;
|
||
};
|
||
currentPrice: number;
|
||
change24h: number;
|
||
change24hPct: number;
|
||
};
|
||
```
|
||
|
||
Эндпоинт (предполагаемый):
|
||
```
|
||
GET /api/chart/:symbol?tf=5m
|
||
```
|
||
|
||
---
|
||
|
||
## Блог
|
||
|
||
Статьи хранятся в `src/lib/blog/posts.ts` как статический массив `Post[]`.
|
||
|
||
```typescript
|
||
type Post = {
|
||
id: number;
|
||
slug: string; // URL: /blog/:slug
|
||
date: string; // ISO 8601
|
||
title: string;
|
||
description: string;
|
||
tags: string[];
|
||
content: string; // HTML-строка
|
||
};
|
||
```
|
||
|
||
Для подключения CMS — заменить `posts` на API-запрос в `+page.ts` / `+page.server.ts`.
|
||
|
||
---
|
||
|
||
## Конфигурация
|
||
|
||
### SvelteKit (`svelte.config.js`)
|
||
|
||
- Адаптер: `adapter-node` (деплой как Node.js-сервер)
|
||
- Runes: принудительно включены для всех файлов вне `node_modules`
|
||
|
||
### Tailwind v4 (`layout.css`)
|
||
|
||
Tailwind подключается через Vite-плагин (`@tailwindcss/vite`), конфиг CSS-первый — все кастомные токены в блоке `@theme` в `src/routes/layout.css`.
|
||
|
||
**Добавить новый токен:**
|
||
```css
|
||
/* layout.css */
|
||
@theme {
|
||
--color-accent: #your-color;
|
||
}
|
||
```
|
||
После этого доступен как `bg-accent`, `text-accent` и т.д.
|
||
|
||
---
|
||
|
||
## Деплой
|
||
|
||
Сборка производит Node.js-сервер:
|
||
|
||
```bash
|
||
pnpm build
|
||
node build/index.js
|
||
```
|
||
|
||
Переменные окружения:
|
||
```
|
||
PORT=3000 # порт (default: 3000)
|
||
HOST=0.0.0.0 # хост
|
||
ORIGIN=https://... # обязательно при deploy за проксей
|
||
```
|
||
|
||
Docker (минимальный `Dockerfile`):
|
||
```dockerfile
|
||
FROM node:22-alpine
|
||
WORKDIR /app
|
||
COPY build/ ./build/
|
||
COPY package.json .
|
||
RUN npm install --omit=dev --ignore-scripts
|
||
CMD ["node", "build/index.js"]
|
||
```
|
||
|
||
---
|
||
|
||
## Соглашения по коду
|
||
|
||
- **Svelte 5 Runes** везде: `$state`, `$derived`, `$effect`, `$props` — никакого legacy API
|
||
- **Нет `export let`** — только деструктуризация `$props()`
|
||
- **Tailwind-first** — inline-стили только там, где Tailwind не справляется (динамические значения, chart dimensions)
|
||
- **cn()** из `$lib/utils` для условного объединения классов
|
||
- **Нет комментариев** если смысл очевиден из имён; комментарий — только для неочевидного инварианта
|
||
- **SSR-safe** — всё что требует `window`/`document` — только в `onMount`
|
||
- **Cleanup** — каждый `onMount` возвращает функцию очистки (removeEventListener, disconnect, remove)
|
||
|
||
---
|
||
|
||
## Известные ограничения
|
||
|
||
- Данные в дашборде — **фейковые** (детерминированный генератор). Требует подключения реального API бекенда через замену `generateChartData` в `chartStore.ts`
|
||
- Прогнозная линия — условная визуализация; реальная модель возвращает `prediction[]` из бекенда
|
||
- Блог — статические данные в коде; для production рекомендуется CMS или headless API
|