Files
2026-05-28 21:03:35 +05:00

11 KiB
Raw Permalink Blame History

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

# 1. Установить зависимости
pnpm install

# 2. Запустить dev-сервер
pnpm dev

# 3. Открыть в браузере
# http://localhost:5173

Остальные команды:

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)

--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

<!-- Первичная (ссылка) -->
<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-запрос, сохранив возвращаемые типы:

// 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

<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:

SYMBOLS  // 8 монет: BTC ETH SOL BNB XRP ADA DOGE LINK
TIMEFRAMES  // 6 таймфреймов: 1m 5m 15m 1h 4h 1d

Контракт API (для бекенда)

Дашборд ожидает от бекенда следующую форму ответа:

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;  // 0100
    entry: number;
    tp: number;
    sl: number;
  };
  currentPrice: number;
  change24h: number;
  change24hPct: number;
};

Эндпоинт (предполагаемый):

GET /api/chart/:symbol?tf=5m

Блог

Статьи хранятся в src/lib/blog/posts.ts как статический массив Post[].

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.

Добавить новый токен:

/* layout.css */
@theme {
  --color-accent: #your-color;
}

После этого доступен как bg-accent, text-accent и т.д.


Деплой

Сборка производит Node.js-сервер:

pnpm build
node build/index.js

Переменные окружения:

PORT=3000          # порт (default: 3000)
HOST=0.0.0.0       # хост
ORIGIN=https://...  # обязательно при deploy за проксей

Docker (минимальный 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