chore: prepare production deployment

This commit is contained in:
2026-07-20 05:24:12 +05:00
parent 2e3550a4dc
commit 77ed2d8423
31 changed files with 681 additions and 496 deletions
+12 -1
View File
@@ -1,13 +1,24 @@
# Production identity
PUBLIC_SITE_URL=https://trade.flamy.studio PUBLIC_SITE_URL=https://trade.flamy.studio
PUBLIC_SITE_NAME=Flamy Trade PUBLIC_SITE_NAME=Flamy Trade
PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка. PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка.
PUBLIC_DASHBOARD_DATA_MODE=research-static
# SvelteKit adapter-node runtime
HOST=0.0.0.0 HOST=0.0.0.0
PORT=3000 PORT=3000
ORIGIN=https://trade.flamy.studio ORIGIN=https://trade.flamy.studio
BODY_SIZE_LIMIT=1M BODY_SIZE_LIMIT=1M
SHUTDOWN_TIMEOUT=15 SHUTDOWN_TIMEOUT=15
APP_STOP_GRACE_PERIOD=20s
# Docker image provenance
APP_IMAGE_REPOSITORY=registry.example.com/flamy-trade
APP_IMAGE_TAG=local
BUILD_DATE=
VCS_REF=
# Reverse proxy publishing
APP_HOST_IP=10.20.0.20 APP_HOST_IP=10.20.0.20
APP_PUBLISHED_PORT=18082 APP_PUBLISHED_PORT=18082
APP_IMAGE_TAG=local NGINX_IMAGE=nginxinc/nginx-unprivileged:1.30.4-alpine-perl@sha256:36fa38fb8f34faba45e7e1857644d6561f2faf45e7ce6b15e97d1a2a1682a0cd
+2
View File
@@ -1,6 +1,7 @@
node_modules node_modules
.npm-cache .npm-cache
.corepack .corepack
.pnpm-store
# Output # Output
.output .output
@@ -27,3 +28,4 @@ vite.config.ts.timestamp-*
#IDE & AI #IDE & AI
.claude .claude
.idea .idea
.vscode
+30 -49
View File
@@ -1,6 +1,6 @@
# Flamy Trade # Flamy Trade
Публичный frontend для `trade.flamy.studio`: лендинг, блог и демонстрационный дашборд ML-прогнозов по крипторынку. Публичный frontend для `trade.flamy.studio`: лендинг, блог и исследовательский дашборд ML-прогнозов по крипторынку.
Проект построен на SvelteKit, Svelte 5 Runes, Tailwind CSS v4, TypeScript и `lightweight-charts`. Проект построен на SvelteKit, Svelte 5 Runes, Tailwind CSS v4, TypeScript и `lightweight-charts`.
@@ -38,94 +38,75 @@ pnpm preview # preview production build
pnpm audit # аудит зависимостей pnpm audit # аудит зависимостей
``` ```
`pnpm outdated` может показывать `@types/node` 26.x и TypeScript 7.x. Это не ошибка текущего стека: проект закреплён на Node 24 LTS, а `@sveltejs/kit` и `typescript-eslint` на текущих версиях требуют TypeScript `<6.1`. `prepare` намеренно выполняет `svelte-kit sync` без подавления ошибок. Если синхронизация SvelteKit падает, установка зависимостей тоже должна завершаться ошибкой.
## Конфигурация ## Конфигурация
Основные изменяемые значения вынесены в `.env.example`. `.env.example` описывает production-настройки. Для сервера скопируйте его в `.env` и заполните значения без локальных заглушек.
Публичные значения сайта: Основные переменные:
```bash ```bash
PUBLIC_SITE_URL=https://trade.flamy.studio PUBLIC_SITE_URL=https://trade.flamy.studio
PUBLIC_SITE_NAME=Flamy Trade PUBLIC_SITE_NAME=Flamy Trade
PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка. PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка.
``` PUBLIC_DASHBOARD_DATA_MODE=research-static
Runtime-переменные SvelteKit adapter-node:
```bash
HOST=0.0.0.0 HOST=0.0.0.0
PORT=3000 PORT=3000
ORIGIN=https://trade.flamy.studio ORIGIN=https://trade.flamy.studio
BODY_SIZE_LIMIT=1M BODY_SIZE_LIMIT=1M
SHUTDOWN_TIMEOUT=15 SHUTDOWN_TIMEOUT=15
``` APP_STOP_GRACE_PERIOD=20s
Docker Compose: APP_IMAGE_REPOSITORY=registry.example.com/flamy-trade
APP_IMAGE_TAG=<git-sha>
BUILD_DATE=<utc-iso-date>
VCS_REF=<git-sha>
```bash
APP_HOST_IP=10.20.0.20 APP_HOST_IP=10.20.0.20
APP_PUBLISHED_PORT=18082 APP_PUBLISHED_PORT=18082
APP_IMAGE_TAG=<git-sha> NGINX_IMAGE=nginxinc/nginx-unprivileged:1.30.4-alpine-perl@sha256:36fa38fb8f34faba45e7e1857644d6561f2faf45e7ce6b15e97d1a2a1682a0cd
``` ```
`PUBLIC_DASHBOARD_DATA_MODE=research-static` означает, что дашборд показывает исследовательские синтетические данные. Это production-поведение текущего публичного этапа, а не интеграция с реальным торговым API.
## Структура ## Структура
```text ```text
src/lib/blog/posts.ts # статические статьи блога src/lib/blog/posts.ts # типизированные статьи блога
src/lib/config/site.ts # единая конфигурация сайта и canonical URL src/lib/config/site.ts # единая конфигурация сайта, canonical и metadata
src/lib/stores/chartStore.ts # типы графика и демонстрационный provider src/lib/data/dashboard.ts # provider данных дашборда
src/lib/components/Chart.svelte # lightweight-charts компонент src/lib/stores/chartStore.ts # типы графика и research-static генератор
src/lib/components/Chart.svelte # компонент lightweight-charts
src/routes/healthz/+server.ts # health endpoint src/routes/healthz/+server.ts # health endpoint
src/routes/sitemap.xml/+server.ts # sitemap src/routes/robots.txt/+server.ts # runtime robots.txt
docker/nginx/default.conf # project nginx src/routes/sitemap.xml/+server.ts # runtime sitemap
docker/nginx/default.conf # nginx server config
docker/nginx/includes # общие nginx include-файлы
docs/deployment.md # runbook развёртывания docs/deployment.md # runbook развёртывания
``` ```
## Данные дашборда
Текущий provider в `chartStore.ts` генерирует демонстрационные данные. Он не является реальным торговым API и не меняет контракт будущего backend.
Ожидаемая форма `ChartData`:
```typescript
type ChartData = {
candles: Array<{ time: number; open: number; high: number; low: number; close: number }>;
prediction: Array<{ time: number; value: number }>;
signal: {
direction: 'long' | 'short';
confidence: number;
entry: number;
tp: number;
sl: number;
};
currentPrice: number;
change24h: number;
change24hPct: number;
};
```
## Блог
Статьи хранятся как типизированные блоки, а не как HTML-строки. Это позволяет рендерить контент без `{@html}` и без отдельной санитаризации.
## Production ## Production
Production-сборка рассчитана на схему: Production-схема:
```text ```text
Internet -> Caddy -> 10.20.0.20:18082 -> nginx -> app:3000 Internet -> Caddy -> 10.20.0.20:18082 -> nginx -> app:3000
``` ```
Публикуется только nginx. Node-приложение доступно только во внутренней Docker-сети. Nginx запускается через unprivileged image с `cap_drop: ALL`, `read_only` и `no-new-privileges`.
Подробный порядок развёртывания, Caddy block, smoke-check и rollback описаны в [docs/deployment.md](docs/deployment.md). Подробный порядок развёртывания, Caddy block, smoke-check и rollback описаны в [docs/deployment.md](docs/deployment.md).
## Зависимости
Renovate закрепляет версии и digest Docker-образов, а также ждёт 3 дня перед обновлением свежих релизов. TypeScript 6.x является осознанным compatibility hold до обновления peer constraints в SvelteKit/typescript-eslint.
## Что не входит в текущий этап ## Что не входит в текущий этап
Пока не подключаются:
- ML-runtime; - ML-runtime;
- реальный API; - реальный торговый API;
- S3; - S3;
- база данных; - база данных;
- Redis; - Redis;
+7 -2
View File
@@ -1,3 +1,5 @@
name: flamy_trade
services: services:
app: app:
build: build:
@@ -6,10 +8,12 @@ services:
args: args:
BUILD_DATE: ${BUILD_DATE:-} BUILD_DATE: ${BUILD_DATE:-}
VCS_REF: ${VCS_REF:-} VCS_REF: ${VCS_REF:-}
image: flamy-trade:${APP_IMAGE_TAG:-local} image: ${APP_IMAGE_REPOSITORY:-flamy-trade}:${APP_IMAGE_TAG:-local}
restart: unless-stopped restart: unless-stopped
init: true init: true
read_only: true read_only: true
stop_signal: SIGTERM
stop_grace_period: ${APP_STOP_GRACE_PERIOD:-20s}
environment: environment:
NODE_ENV: production NODE_ENV: production
HOST: 0.0.0.0 HOST: 0.0.0.0
@@ -52,7 +56,7 @@ services:
max-file: '3' max-file: '3'
nginx: nginx:
image: nginx:1.30.4-alpine@sha256:97d490c12ba55b4946b01546d1c3ed324e8d41ab1c9fcb2a616aa470620e5b46 image: ${NGINX_IMAGE:-nginxinc/nginx-unprivileged:1.30.4-alpine-perl@sha256:36fa38fb8f34faba45e7e1857644d6561f2faf45e7ce6b15e97d1a2a1682a0cd}
restart: unless-stopped restart: unless-stopped
init: true init: true
read_only: true read_only: true
@@ -68,6 +72,7 @@ services:
app_protocol: http app_protocol: http
volumes: volumes:
- ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./docker/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro
- ./docker/nginx/includes:/etc/nginx/includes:ro
networks: networks:
- backend - backend
cpus: 0.10 cpus: 0.10
+13 -22
View File
@@ -8,44 +8,35 @@ server {
server_name _; server_name _;
client_max_body_size 1m; client_max_body_size 1m;
add_header_inherit merge;
add_header X-Content-Type-Options "nosniff" always; add_header X-Content-Type-Options "nosniff" always;
add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always;
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Frame-Options "SAMEORIGIN" always;
add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always; add_header Permissions-Policy "camera=(), microphone=(), geolocation=(), payment=()" always;
add_header Content-Security-Policy "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; frame-ancestors 'self'; base-uri 'self'; form-action 'self'" always;
location /_app/immutable/ { location = /healthz {
access_log off;
proxy_pass http://app:3000; proxy_pass http://app:3000;
proxy_http_version 1.1; include /etc/nginx/includes/proxy-headers.conf;
proxy_set_header Host $host; add_header Cache-Control "no-cache" always;
proxy_set_header X-Real-IP $remote_addr; }
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host; location ^~ /_app/immutable/ {
proxy_set_header X-Forwarded-Proto $forwarded_proto; proxy_pass http://app:3000;
include /etc/nginx/includes/proxy-headers.conf;
add_header Cache-Control "public, max-age=31536000, immutable" always; add_header Cache-Control "public, max-age=31536000, immutable" always;
} }
location ~* \.(?:css|js|mjs|svg|png|jpg|jpeg|gif|webp|ico|ttf|woff2?)$ { location ~* \.(?:css|js|mjs|svg|png|jpg|jpeg|gif|webp|ico|ttf|woff2?)$ {
proxy_pass http://app:3000; proxy_pass http://app:3000;
proxy_http_version 1.1; include /etc/nginx/includes/proxy-headers.conf;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
add_header Cache-Control "public, max-age=86400" always; add_header Cache-Control "public, max-age=86400" always;
} }
location / { location / {
proxy_pass http://app:3000; proxy_pass http://app:3000;
proxy_http_version 1.1; include /etc/nginx/includes/proxy-headers.conf;
proxy_set_header Host $host; add_header Cache-Control "no-cache" always;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
proxy_set_header Connection "";
add_header Cache-Control "no-store" always;
} }
} }
+7
View File
@@ -0,0 +1,7 @@
proxy_http_version 1.1;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Proto $forwarded_proto;
proxy_set_header Connection "";
+82 -21
View File
@@ -1,6 +1,6 @@
# Развёртывание Flamy Trade # Развёртывание Flamy Trade
Документ описывает production-развёртывание сайта `trade.flamy.studio` без ML-runtime и без будущего API. Эти части подключаются отдельно. Документ описывает production-развёртывание сайта `trade.flamy.studio` без ML-runtime и будущего API. Эти части подключаются отдельным этапом.
## Схема ## Схема
@@ -9,11 +9,11 @@ Internet
-> Router :80/:443 -> Router :80/:443
-> edge-proxy VM / Caddy -> edge-proxy VM / Caddy
-> 10.20.0.20:18082 -> 10.20.0.20:18082
-> flamy_trade_nginx -> flamy_trade-nginx-1
-> flamy_trade_app:3000 -> flamy_trade-app-1:3000
``` ```
На VM публикуется только nginx. Node-приложение доступно только во внутренней Docker-сети. Compose project name зафиксирован как `flamy_trade`. `container_name` не задаётся, чтобы не ломать стандартное управление Compose.
## Переменные ## Переменные
@@ -23,16 +23,23 @@ Internet
PUBLIC_SITE_URL=https://trade.flamy.studio PUBLIC_SITE_URL=https://trade.flamy.studio
PUBLIC_SITE_NAME=Flamy Trade PUBLIC_SITE_NAME=Flamy Trade
PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка. PUBLIC_SITE_DESCRIPTION=Публичная витрина ML-прогнозов для крипторынка.
PUBLIC_DASHBOARD_DATA_MODE=research-static
HOST=0.0.0.0 HOST=0.0.0.0
PORT=3000 PORT=3000
ORIGIN=https://trade.flamy.studio ORIGIN=https://trade.flamy.studio
BODY_SIZE_LIMIT=1M BODY_SIZE_LIMIT=1M
SHUTDOWN_TIMEOUT=15 SHUTDOWN_TIMEOUT=15
APP_STOP_GRACE_PERIOD=20s
APP_IMAGE_REPOSITORY=registry.example.com/flamy-trade
APP_IMAGE_TAG=<git-sha>
BUILD_DATE=<utc-iso-date>
VCS_REF=<git-sha>
APP_HOST_IP=10.20.0.20 APP_HOST_IP=10.20.0.20
APP_PUBLISHED_PORT=18082 APP_PUBLISHED_PORT=18082
APP_IMAGE_TAG=<git-sha> NGINX_IMAGE=nginxinc/nginx-unprivileged:1.30.4-alpine-perl@sha256:36fa38fb8f34faba45e7e1857644d6561f2faf45e7ce6b15e97d1a2a1682a0cd
``` ```
Перед фиксацией порта проверьте, что `18082` свободен: Перед фиксацией порта проверьте, что `18082` свободен:
@@ -42,7 +49,7 @@ sudo ss -lntup | grep ':18082'
docker ps --format 'table {{.Names}}\t{{.Ports}}' docker ps --format 'table {{.Names}}\t{{.Ports}}'
``` ```
## Сборка ## Проверки перед сборкой
```bash ```bash
pnpm install --frozen-lockfile pnpm install --frozen-lockfile
@@ -51,14 +58,22 @@ pnpm lint
pnpm check pnpm check
pnpm build pnpm build
pnpm audit pnpm audit
docker compose config
``` ```
`pnpm outdated` должен проверяться вручную. На текущем стеке допустимы только осознанные удержания: `@types/node` на ветке 24 под Node 24 LTS и TypeScript 6.x до обновления peer constraints в SvelteKit/typescript-eslint. ## Сборка
Docker: Перед сборкой image задайте provenance:
```bash
export BUILD_DATE="$(date -u +%Y-%m-%dT%H:%M:%SZ)"
export VCS_REF="$(git rev-parse HEAD)"
export APP_IMAGE_TAG="$VCS_REF"
```
Сборка и запуск:
```bash ```bash
docker compose config
docker compose build --pull docker compose build --pull
docker compose up -d --wait docker compose up -d --wait
docker compose ps docker compose ps
@@ -76,6 +91,8 @@ trade.flamy.studio {
reverse_proxy 10.20.0.20:18082 { reverse_proxy 10.20.0.20:18082 {
header_up Host {host} header_up Host {host}
header_up X-Real-IP {remote_host} header_up X-Real-IP {remote_host}
header_up X-Forwarded-For {remote_host}
header_up X-Forwarded-Proto {scheme}
} }
} }
``` ```
@@ -90,19 +107,22 @@ sudo systemctl reload caddy
## Smoke-check ## Smoke-check
С docker-runtime: С Docker-runtime:
```bash ```bash
curl -fsS http://10.20.0.20:18082/healthz curl -fsS http://10.20.0.20:18082/healthz
curl -I http://10.20.0.20:18082/ curl -fsSI http://10.20.0.20:18082/
curl -fsSI http://10.20.0.20:18082/robots.txt
curl -fsSI http://10.20.0.20:18082/sitemap.xml
``` ```
Снаружи: Снаружи:
```bash ```bash
curl -I http://trade.flamy.studio curl -fsSI https://trade.flamy.studio/
curl -I https://trade.flamy.studio
curl -fsS https://trade.flamy.studio/healthz curl -fsS https://trade.flamy.studio/healthz
curl -fsS https://trade.flamy.studio/robots.txt
curl -fsS https://trade.flamy.studio/sitemap.xml
``` ```
Маршруты: Маршруты:
@@ -120,15 +140,56 @@ curl -fsS https://trade.flamy.studio/healthz
/nesuschestvuyuschiy-url 404 /nesuschestvuyuschiy-url 404
``` ```
## Rollback Проверка headers:
1. Каждый image получает тег git SHA через `APP_IMAGE_TAG`.
2. Предыдущий image не удаляется до успешного smoke-check.
3. Для отката поменяйте `APP_IMAGE_TAG` на предыдущий SHA.
4. Выполните:
```bash ```bash
docker compose up -d --no-build curl -fsSI https://trade.flamy.studio/
curl -fsSI https://trade.flamy.studio/favicon.svg
curl -fsSI https://trade.flamy.studio/_app/immutable/<real-file>.js
``` ```
Если проблема в маршрутизации, восстановите backup Caddyfile и выполните `sudo systemctl reload caddy`. Ожидаемо:
```text
X-Content-Type-Options: nosniff
Referrer-Policy: strict-origin-when-cross-origin
X-Frame-Options: SAMEORIGIN
Permissions-Policy: camera=(), microphone=(), geolocation=(), payment=()
```
`Content-Security-Policy` задаётся SvelteKit и ожидается на HTML-ответах приложения.
Для `/_app/immutable/*` ожидается:
```text
Cache-Control: public, max-age=31536000, immutable
```
Для HTML:
```text
Cache-Control: no-cache
```
## Rollback
Надёжный rollback должен опираться на immutable image tag:
```text
${APP_IMAGE_REPOSITORY}:<git-sha>
```
На production-сервере меняется только `APP_IMAGE_TAG`, затем выполняется:
```bash
docker compose pull app
docker compose up -d --no-build --wait
```
Если проблема в маршрутизации, восстановите backup Caddyfile и выполните:
```bash
sudo systemctl reload caddy
```
Предыдущий image не удаляется до успешного smoke-check нового релиза.
+3 -3
View File
@@ -12,7 +12,7 @@
"dev": "vite dev", "dev": "vite dev",
"build": "vite build", "build": "vite build",
"preview": "vite preview", "preview": "vite preview",
"prepare": "svelte-kit sync || echo ''", "prepare": "svelte-kit sync",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json", "check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch", "check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
"format:check": "prettier --check .", "format:check": "prettier --check .",
@@ -28,7 +28,7 @@
"@types/node": "24.12.4", "@types/node": "24.12.4",
"eslint": "10.7.0", "eslint": "10.7.0",
"eslint-config-prettier": "10.1.8", "eslint-config-prettier": "10.1.8",
"eslint-plugin-svelte": "3.21.0", "eslint-plugin-svelte": "3.20.0",
"globals": "17.7.0", "globals": "17.7.0",
"prettier": "3.9.5", "prettier": "3.9.5",
"prettier-plugin-svelte": "4.1.1", "prettier-plugin-svelte": "4.1.1",
@@ -41,7 +41,7 @@
"vite": "8.1.5" "vite": "8.1.5"
}, },
"dependencies": { "dependencies": {
"@sveltejs/kit": "2.70.1", "@sveltejs/kit": "2.69.3",
"clsx": "2.1.1", "clsx": "2.1.1",
"gsap": "3.15.0", "gsap": "3.15.0",
"lightweight-charts": "5.2.0", "lightweight-charts": "5.2.0",
+214 -243
View File
@@ -12,8 +12,8 @@ importers:
.: .:
dependencies: dependencies:
'@sveltejs/kit': '@sveltejs/kit':
specifier: 2.70.1 specifier: 2.69.3
version: 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)) version: 2.69.3(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))
clsx: clsx:
specifier: 2.1.1 specifier: 2.1.1
version: 2.1.1 version: 2.1.1
@@ -35,7 +35,7 @@ importers:
version: 10.0.1(eslint@10.7.0(jiti@2.7.0)) version: 10.0.1(eslint@10.7.0(jiti@2.7.0))
'@sveltejs/adapter-node': '@sveltejs/adapter-node':
specifier: 5.5.7 specifier: 5.5.7
version: 5.5.7(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))) version: 5.5.7(@sveltejs/kit@2.69.3(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))
'@sveltejs/vite-plugin-svelte': '@sveltejs/vite-plugin-svelte':
specifier: 7.2.0 specifier: 7.2.0
version: 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)) version: 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))
@@ -52,8 +52,8 @@ importers:
specifier: 10.1.8 specifier: 10.1.8
version: 10.1.8(eslint@10.7.0(jiti@2.7.0)) version: 10.1.8(eslint@10.7.0(jiti@2.7.0))
eslint-plugin-svelte: eslint-plugin-svelte:
specifier: 3.21.0 specifier: 3.20.0
version: 3.21.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0)) version: 3.20.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0))
globals: globals:
specifier: 17.7.0 specifier: 17.7.0
version: 17.7.0 version: 17.7.0
@@ -71,7 +71,7 @@ importers:
version: 5.56.6(@typescript-eslint/types@8.64.0) version: 5.56.6(@typescript-eslint/types@8.64.0)
svelte-check: svelte-check:
specifier: 4.7.3 specifier: 4.7.3
version: 4.7.3(picomatch@4.0.4)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3) version: 4.7.3(picomatch@4.0.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)
tailwindcss: tailwindcss:
specifier: 4.3.3 specifier: 4.3.3
version: 4.3.3 version: 4.3.3
@@ -290,8 +290,8 @@ packages:
'@rolldown/pluginutils@1.0.1': '@rolldown/pluginutils@1.0.1':
resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==} resolution: {integrity: sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==}
'@rollup/plugin-commonjs@29.0.2': '@rollup/plugin-commonjs@29.0.3':
resolution: {integrity: sha512-S/ggWH1LU7jTyi9DxZOKyxpVd4hF/OZ0JrEbeLjXk/DFXwRny0tjD2c992zOUYQobLrVkRVMDdmHP16HKP7GRg==} resolution: {integrity: sha512-ZaOxZceP7SOUW7Lqw5IRVweSQYWaeIPnXIGLiB690EBA3FGJTO40EEr2L5yZplJWsgTCogILRSpcAe7+U0Otdg==}
engines: {node: '>=16.0.0 || 14 >= 14.17'} engines: {node: '>=16.0.0 || 14 >= 14.17'}
peerDependencies: peerDependencies:
rollup: ^2.68.0||^3.0.0||^4.0.0 rollup: ^2.68.0||^3.0.0||^4.0.0
@@ -326,8 +326,8 @@ packages:
rollup: rollup:
optional: true optional: true
'@rollup/pluginutils@5.3.0': '@rollup/pluginutils@5.4.0':
resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} resolution: {integrity: sha512-MfPp06CjRLfXQ3wY0R8vJDYBy/MvVcc9OulEfR0B8Iv9ko+GCNaRZ+EpJYFl27LhKsZK0o420sYCRHCjfCgeUg==}
engines: {node: '>=14.0.0'} engines: {node: '>=14.0.0'}
peerDependencies: peerDependencies:
rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0
@@ -335,149 +335,149 @@ packages:
rollup: rollup:
optional: true optional: true
'@rollup/rollup-android-arm-eabi@4.60.4': '@rollup/rollup-android-arm-eabi@4.62.2':
resolution: {integrity: sha512-F5QXMSiFebS9hKZj02XhWLLnRpJ3B3AROP0tWbFBSj+6kCbg5m9j5JoHKd4mmSVy5mS/IMQloYgYxCuJC0fxEQ==} resolution: {integrity: sha512-6o7ZLZK+BeenkZCFNDXqpbjw9bD6nuWonvS/lwQJp7NoVVxm6p3qE7qQ5jGuBjiFsgvqjD8mZAU5oWxTmbOeOg==}
cpu: [arm] cpu: [arm]
os: [android] os: [android]
'@rollup/rollup-android-arm64@4.60.4': '@rollup/rollup-android-arm64@4.62.2':
resolution: {integrity: sha512-GxxTKApUpzRhof7poWvCJHRF51C67u1R7D6DiluBE8wKU1u5GWE8t+v81JvJYtbawoBFX1hLv5Ei4eVjkWokaw==} resolution: {integrity: sha512-BaH7BllCACHoH1LguOU56UItGfUWjujlO65kS9LAodViaN4bwIKd7oeW/ZHJ/4ljr/7MIiENnNy3HJ0zXv8Zkw==}
cpu: [arm64] cpu: [arm64]
os: [android] os: [android]
'@rollup/rollup-darwin-arm64@4.60.4': '@rollup/rollup-darwin-arm64@4.62.2':
resolution: {integrity: sha512-tua0TaJxMOB1R0V0RS1jFZ/RpURFDJIOR2A6jWwQeawuFyS4gBW+rntLRaQd0EQ4bd6Vp44Z2rXW+YYDBsj6IA==} resolution: {integrity: sha512-v39RCCvj4He82I9sFmk+M1VZ0PLM9sfsLVikjfx2hYBNALhrrOR2D3JjQA6AhlaSOgcR+RzrKY7e1+bT6SUO/A==}
cpu: [arm64] cpu: [arm64]
os: [darwin] os: [darwin]
'@rollup/rollup-darwin-x64@4.60.4': '@rollup/rollup-darwin-x64@4.62.2':
resolution: {integrity: sha512-CSKq7MsP+5PFIcydhAiR1K0UhEI1A2jWXVKHPCBZ151yOutENwvnPocgVHkivu2kviURtCEB6zUQw0vs8RrhMg==} resolution: {integrity: sha512-yl0y2vq3S3lHeuXhEdss6TWfKW8vkujImO12tn4ZkG/4oghr09LvdYm2RElVjokTQiUvDUGXLGsYeLqUMCKpGA==}
cpu: [x64] cpu: [x64]
os: [darwin] os: [darwin]
'@rollup/rollup-freebsd-arm64@4.60.4': '@rollup/rollup-freebsd-arm64@4.62.2':
resolution: {integrity: sha512-+O8OkVdyvXMtJEciu2wS/pzm1IxntEEQx3z5TAVy4l32G0etZn+RsA48ARRrFm6Ri8fvqPQfgrvNxSjKAbnd3g==} resolution: {integrity: sha512-tT4pvt4qXD+vEoezupCWi+a1F0vvDiksiHc+PxRlYTOH1I6/X4id9jPxTP+Fg+545euaFT1jJVs4CEdHZAU1vw==}
cpu: [arm64] cpu: [arm64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-freebsd-x64@4.60.4': '@rollup/rollup-freebsd-x64@4.62.2':
resolution: {integrity: sha512-Iw3oMskH3AfNuhU0MSN7vNbdi4me/NiYo2azqPz/Le16zHSa+3RRmliCMWWQmh4lcndccU40xcJuTYJZxNo/lw==} resolution: {integrity: sha512-6nU5F2wCW+qvCBhTn1pdIU3bzsIoF7EUwsCDRxilWGprQR6yd508YnH9+OKFCwpfS8pjZqDUmnCAr7exax0XCg==}
cpu: [x64] cpu: [x64]
os: [freebsd] os: [freebsd]
'@rollup/rollup-linux-arm-gnueabihf@4.60.4': '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
resolution: {integrity: sha512-EIPRXTVQpHyF8WOo219AD2yEltPehLTcTMz2fn6JsatLYSzQf00hj3rulF+yauOlF9/FtM2WpkT/hJh/KJFGhA==} resolution: {integrity: sha512-n1GJHPOvpIfhi3TmrCeh6S6URt9BFCt0KQE3qvexyGCTAKpR4Lg+eWvNZEqu7epxwus/8ElT3hacYEucm49SZg==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-arm-musleabihf@4.60.4': '@rollup/rollup-linux-arm-musleabihf@4.62.2':
resolution: {integrity: sha512-J3Yh9PzzF1Ovah2At+lHiGQdsYgArxBbXv/zHfSyaiFQEqvNv7DcW98pCrmdjCZBrqBiKrKKe2V+aaSGWuBe/w==} resolution: {integrity: sha512-JqgflS8wEB+UXV/vS1RpRbifGBeN4D5lz8D8oOFbFZw4vedvdOgCFAjfBmIMdW3yL10XpQQ0Ambepw6MXrhOnA==}
cpu: [arm] cpu: [arm]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-arm64-gnu@4.60.4': '@rollup/rollup-linux-arm64-gnu@4.62.2':
resolution: {integrity: sha512-BFDEZMYfUvLn37ONE1yMBojPxnMlTFsdyNoqncT0qFq1mAfllL+ATMMJd8TeuVMiX84s1KbcxcZbXInmcO2mRg==} resolution: {integrity: sha512-wnFJkogWvN4jm/hQRF2UBaeUmk20j5+DmHvoyWii2b8HJDyvz1MF2OU/6ynXt2KR63rbZLWkFpoytpdc/yBuSA==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-arm64-musl@4.60.4': '@rollup/rollup-linux-arm64-musl@4.62.2':
resolution: {integrity: sha512-pc9EYOSlOgdQ2uPl1o9PF6/kLSgaUosia7gOuS8mB69IxJvlclko1MECXysjs5ryez1/5zjYqx3+xYU0TU6R1A==} resolution: {integrity: sha512-HVu2bp0zhvJ8xHEV9+UUs7S90VadmBSY3LcIMvozbPo4AuMGDWlz3ymHLHZPX4hR67TKTt8Qp5PJ5RBg/i+RMQ==}
cpu: [arm64] cpu: [arm64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-loong64-gnu@4.60.4': '@rollup/rollup-linux-loong64-gnu@4.62.2':
resolution: {integrity: sha512-NxnomyxYerDh5n4iLrNa+sH+Z+U4BMEE46V2PgQ/hoB909i8gV1M5wPojWg9fk1jWpO3IQnOs20K4wyZuFLEFQ==} resolution: {integrity: sha512-mQqqAV8QaoSgr9I2fKDLY2BAVvmKjWoGiu/cSYQonsLvtqwEn1E4QYfnCOcp5zoEqNhsDYin1s6jx/VJmrxlZg==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-loong64-musl@4.60.4': '@rollup/rollup-linux-loong64-musl@4.62.2':
resolution: {integrity: sha512-nbJnQ8a3z1mtmrwImCYhc6BGpThAyYVRQxw9uKSKG4wR6aAYno9sVjJ0zaZcW9BPJX1GbrDPf+SvdWjgTuDmnw==} resolution: {integrity: sha512-IxKLoxCQ2IWi6bT2akyDUBGsOImDKB+sPp4EsTmwFQ/fMwpCKm8uLSSgP/Kx/QYUgKis6SEZ5/Nlhup0DIA0PQ==}
cpu: [loong64] cpu: [loong64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-ppc64-gnu@4.60.4': '@rollup/rollup-linux-ppc64-gnu@4.62.2':
resolution: {integrity: sha512-2EU6acNrQLd8tYvo/LXW535wupT3m6fo7HKo6lr7ktQoItxTyOL1ZCR/GfGCuXl2vR+zmfI6eRXkSemafv+iVg==} resolution: {integrity: sha512-Mk5ha2RQSgyFfmYYLkBpPnUk8D8FriBxesO1u9O75X0mHgXL1UQcH5Itl2lurWL2tj0RxV9b9tJgipac0hRY9A==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-ppc64-musl@4.60.4': '@rollup/rollup-linux-ppc64-musl@4.62.2':
resolution: {integrity: sha512-WeBtoMuaMxiiIrO2IYP3xs6GMWkJP2C0EoT8beTLkUPmzV1i/UcOSVw1d5r9KBODtHKilG5yFxsGRnBbK3wJ4A==} resolution: {integrity: sha512-CjvEnqJL/0/TQ3TXX3OPIJ/kmBellrWd4heXUmHeJlTnmwjKpSJzoehLaL6Xk0ZnMHBu9dZuFADNOrtjF4v+2w==}
cpu: [ppc64] cpu: [ppc64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-riscv64-gnu@4.60.4': '@rollup/rollup-linux-riscv64-gnu@4.62.2':
resolution: {integrity: sha512-FJHFfqpKUI3A10WrWKiFbBZ7yVbGT4q4B5o1qKFFojqpaYoh9LrQgqWCmmcxQzVSXYtyB5bzkXrYzlHTs21MYA==} resolution: {integrity: sha512-1SiZbzwdkaDURsew/tSOrooKiYy7EQGT6m8ufavAi9NEyQb/6VuIxFXAL1fqa4iZe3g4NbNk4P7J32z2tw5Mgg==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-riscv64-musl@4.60.4': '@rollup/rollup-linux-riscv64-musl@4.62.2':
resolution: {integrity: sha512-mcEl6CUT5IAUmQf1m9FYSmVqCJlpQ8r8eyftFUHG8i9OhY7BkBXSUdnLH5DOf0wCOjcP9v/QO93zpmF1SptCCw==} resolution: {integrity: sha512-nQts12zJ3NQRoE6uYljOH89v7szzLDvG2JD/vsX+vGXU8w/At1GowTZ5/7qeFQ8m7L55rpR8Okugnuo5bgjy2Q==}
cpu: [riscv64] cpu: [riscv64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-linux-s390x-gnu@4.60.4': '@rollup/rollup-linux-s390x-gnu@4.62.2':
resolution: {integrity: sha512-ynt3JxVd2w2buzoKDWIyiV1pJW93xlQic1THVLXilz429oijRpSHivZAgp65KBu+cMcgf1eVVjdnTLvPxgCuoQ==} resolution: {integrity: sha512-E9/ll019jhPIJgpzfZoIkBGhcz+kKNgVWYRY0zr9srBdPPFVpvOKW8VaJKUbeK+eZXyQF9ltME+Kk6affeaPgg==}
cpu: [s390x] cpu: [s390x]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-x64-gnu@4.60.4': '@rollup/rollup-linux-x64-gnu@4.62.2':
resolution: {integrity: sha512-Boiz5+MsaROEWDf+GGEwF8VMHGhlUoQMtIPjOgA5fv4osupqTVnJteQNKJwUcnUog2G55jYXH7KZFFiJe0TEzQ==} resolution: {integrity: sha512-5BqxR/pshjey51iliyzTD5Xi3EN0aLmQ2lZ3lvefVV9c82BvrLo2/6OT55iifpWBufs6kdwWbuOKS841DrmK9A==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [glibc] libc: [glibc]
'@rollup/rollup-linux-x64-musl@4.60.4': '@rollup/rollup-linux-x64-musl@4.62.2':
resolution: {integrity: sha512-+qfSY27qIrFfI/Hom04KYFw3GKZSGU4lXus51wsb5EuySfFlWRwjkKWoE9emgRw/ukoT4Udsj4W/+xxG8VbPKg==} resolution: {integrity: sha512-uNN83XxQrRAh/w0/pmAfibcwyb6YWt4gP+dpnQKPVJshAloQ785ii8CT8ZCIxkGg9opVsvAlGhFitSm6D1Jjpg==}
cpu: [x64] cpu: [x64]
os: [linux] os: [linux]
libc: [musl] libc: [musl]
'@rollup/rollup-openbsd-x64@4.60.4': '@rollup/rollup-openbsd-x64@4.62.2':
resolution: {integrity: sha512-VpTfOPHgVXEBeeR8hZ2O0F3aSso+JDWqTWmTmzcQKted54IAdUVbxE+j/MVxUsKa8L20HJhv3vUezVPoquqWjA==} resolution: {integrity: sha512-srjEIxSH3LRnJN6THczDHWQplqEMFiAJrTab0msUryh9kwNpkICf3Ea6q6MN/2cZwRFUNx5w+h6Hpi4QuHS6Zg==}
cpu: [x64] cpu: [x64]
os: [openbsd] os: [openbsd]
'@rollup/rollup-openharmony-arm64@4.60.4': '@rollup/rollup-openharmony-arm64@4.62.2':
resolution: {integrity: sha512-IPOsh5aRYuLv/nkU51X10Bf75Bsf6+gZdx1X+QP5QM6lIJFHHqbHLG0uJn/hWthzo13UAc2umiUorqZy3axoZg==} resolution: {integrity: sha512-8hOJnxgbyObnCm5AlRA3A931xX19xq80RjVTKgJOvEKWqJruP/Uf12IbAOaDjjEXYRewwHLfmF0YRIdK3OwKWA==}
cpu: [arm64] cpu: [arm64]
os: [openharmony] os: [openharmony]
'@rollup/rollup-win32-arm64-msvc@4.60.4': '@rollup/rollup-win32-arm64-msvc@4.62.2':
resolution: {integrity: sha512-4QzE9E81OohJ/HKzHhsqU+zcYYojVOXlFMs1DdyMT6qXl/niOH7AVElmmEdUNHHS/oRkc++d5k6Vy85zFs0DEw==} resolution: {integrity: sha512-mmF4AY1i0hG/bLWUctUq59gtmgaSIRa3cu/A3JFRp/sCNEme2bgDEiDS22P9FbnJB8NJNF4jPJiSP5RHQpUTDg==}
cpu: [arm64] cpu: [arm64]
os: [win32] os: [win32]
'@rollup/rollup-win32-ia32-msvc@4.60.4': '@rollup/rollup-win32-ia32-msvc@4.62.2':
resolution: {integrity: sha512-zTPgT1YuHHcd+Tmx7h8aml0FWFVelV5N54oHow9SLj+GfoDy/huQ+UV396N/C7KpMDMiPspRktzM1/0r1usYEA==} resolution: {integrity: sha512-DZgkknc6jhHrk46V25vbAM0zZkyP0nSDkJB8/dRkLTxv470dOmWDqGoEJl/9A0dFfS7yE3REOwNDxpHwSLSt0Q==}
cpu: [ia32] cpu: [ia32]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-gnu@4.60.4': '@rollup/rollup-win32-x64-gnu@4.62.2':
resolution: {integrity: sha512-DRS4G7mi9lJxqEDezIkKCaUIKCrLUUDCUaCsTPCi/rtqaC6D/jjwslMQyiDU50Ka0JKpeXeRBFBAXwArY52vBw==} resolution: {integrity: sha512-T6xr6ucWSFto+VGajA8YH26LdpHRuP4YLHEKAtCWvJDOlnmWcDZVCI2Jmjr+IFHDlt2zRaTAKE4tfjTaWLgJBg==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@rollup/rollup-win32-x64-msvc@4.60.4': '@rollup/rollup-win32-x64-msvc@4.62.2':
resolution: {integrity: sha512-QVTUovf40zgTqlFVrKA1uXMVvU2QWEFWfAH8Wdc48IxLvrJMQVMBRjuQyUpzZCDkakImib9eVazbWlC6ksWtJw==} resolution: {integrity: sha512-BfzEnDJOt9T8M989/lA37EcJgat01wLRnoi5dQf3QzOH7jzpqTAzdDbVfRljVr5r+jzKqpbHeyOfAaXxAd0PAA==}
cpu: [x64] cpu: [x64]
os: [win32] os: [win32]
'@standard-schema/spec@1.1.0': '@standard-schema/spec@1.1.0':
resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==}
'@sveltejs/acorn-typescript@1.0.10': '@sveltejs/acorn-typescript@1.0.11':
resolution: {integrity: sha512-4WfKk68eTih+MiJD4fSbxN7E8kVBmTMPWHUPYjvl2N0rMs53YLTT8/YjKU5Dtnz5LqDjl7LEw4U7lXR2W3J5WA==} resolution: {integrity: sha512-LFuZUkjJ9iF7JZye/aG5XM0SFcQ5VyL0oVX4WJ9dc0Va3R3s0OauX1BESVCb+YN/ol8TAfqGDDAQsTG627Y5kw==}
peerDependencies: peerDependencies:
acorn: ^8.9.0 acorn: ^8.9.0
@@ -486,8 +486,8 @@ packages:
peerDependencies: peerDependencies:
'@sveltejs/kit': ^2.4.0 '@sveltejs/kit': ^2.4.0
'@sveltejs/kit@2.70.1': '@sveltejs/kit@2.69.3':
resolution: {integrity: sha512-nY9SPHGOZro3doud9vZXDBwl9tCZIouuJztjgSHs6PAIrv9M/z5O7eOhPV5xU7CgVHA976Jwu3BA1hIFvXztkA==} resolution: {integrity: sha512-cphwqMRcE19/9VkrIPr5qZhQ0SptSSDfDzRUpYHu9OJDFGuYBFyJzK+KQA27wB4YG32O/yF2QjBkDmTyo0vtCw==}
engines: {node: '>=18.13'} engines: {node: '>=18.13'}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
@@ -616,9 +616,6 @@ packages:
'@types/esrecurse@4.3.1': '@types/esrecurse@4.3.1':
resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==}
'@types/estree@1.0.8':
resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==}
'@types/estree@1.0.9': '@types/estree@1.0.9':
resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==} resolution: {integrity: sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==}
@@ -698,8 +695,8 @@ packages:
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
acorn@8.16.0: acorn@8.17.0:
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} resolution: {integrity: sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==}
engines: {node: '>=0.4.0'} engines: {node: '>=0.4.0'}
hasBin: true hasBin: true
@@ -718,8 +715,8 @@ packages:
resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==}
engines: {node: 18 || 20 || >=22} engines: {node: 18 || 20 || >=22}
brace-expansion@5.0.6: brace-expansion@5.0.7:
resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} resolution: {integrity: sha512-7oFy703dxfY3/NLxC1fh2SUCQ0H9rmAY+5EpDVfXjUTTs+HEwR2nYaqLv+GWcTsumwxPfiz6CzCNkwXwBUwqCA==}
engines: {node: 18 || 20 || >=22} engines: {node: 18 || 20 || >=22}
chokidar@4.0.3: chokidar@4.0.3:
@@ -787,8 +784,8 @@ packages:
peerDependencies: peerDependencies:
eslint: '>=7.0.0' eslint: '>=7.0.0'
eslint-plugin-svelte@3.21.0: eslint-plugin-svelte@3.20.0:
resolution: {integrity: sha512-2dkuAk77FzQn5Ad5bJvcS6gU9iRCjywzQv6wVBR7RNseZzDsxfaIu+yzah/hTBB2znDd8Hl4oOISdhQuNyjTdQ==} resolution: {integrity: sha512-AElKLVt7Hjy4d7ljwhrhw9hux60DCxCNkmK8cY/aAXvjs8tpR7PvU4DlyI/SA1PaJww1gh0wPGo2pbyURuEwxQ==}
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
peerDependencies: peerDependencies:
eslint: ^8.57.1 || ^9.0.0 || ^10.0.0 eslint: ^8.57.1 || ^9.0.0 || ^10.0.0
@@ -927,16 +924,16 @@ packages:
gsap@3.15.0: gsap@3.15.0:
resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==} resolution: {integrity: sha512-dMW4CWBTUK1AEEDeZc1g4xpPGIrSf9fJF960qbTZmN/QwZIWY5wgliS6JWl9/25fpTGJrMRtSjGtOmPnfjZB+A==}
hasown@2.0.3: hasown@2.0.4:
resolution: {integrity: sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==} resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
engines: {node: '>= 0.4'} engines: {node: '>= 0.4'}
ignore@5.3.2: ignore@5.3.2:
resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
ignore@7.0.5: ignore@7.0.6:
resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==}
engines: {node: '>= 4'} engines: {node: '>= 4'}
imurmurhash@0.1.4: imurmurhash@0.1.4:
@@ -1100,16 +1097,17 @@ packages:
ms@2.1.3: ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==} resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
nanoid@3.3.12: nanoid@3.3.16:
resolution: {integrity: sha512-ZB9RH/39qpq5Vu6Y+NmUaFhQR6pp+M2Xt76XBnEwDaGcVAqhlvxrl3B2bKS5D3NH3QR76v3aSrKaF/Kiy7lEtQ==} resolution: {integrity: sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==}
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
hasBin: true hasBin: true
natural-compare@1.4.0: natural-compare@1.4.0:
resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==} resolution: {integrity: sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==}
obug@2.1.1: obug@2.1.4:
resolution: {integrity: sha512-uTqF9MuPraAQ+IsnPf366RG4cP9RtUi7MLO1N3KEc+wb0a6yKpeL0lmk2IB1jY5KHPAlTc6T/JRdC/YqxHNwkQ==} resolution: {integrity: sha512-4a+OsYv9UktOJKE+l1A4OufDgdRF9PifWj+tJnHURo/P+WOxpG4GzUFL9qCalmWauao6ogiG+QvnCovwPoyAWA==}
engines: {node: '>=12.20.0'}
optionator@0.9.4: optionator@0.9.4:
resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==}
@@ -1137,10 +1135,6 @@ packages:
picocolors@1.1.1: picocolors@1.1.1:
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
picomatch@4.0.4:
resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==}
engines: {node: '>=12'}
picomatch@4.0.5: picomatch@4.0.5:
resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==}
engines: {node: '>=12'} engines: {node: '>=12'}
@@ -1169,14 +1163,10 @@ packages:
peerDependencies: peerDependencies:
postcss: ^8.4.29 postcss: ^8.4.29
postcss-selector-parser@7.1.1: postcss-selector-parser@7.1.4:
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==} resolution: {integrity: sha512-HeP7D2wyhkR+XaK6v4W8oRF62Dsz4flyuczALJp61GckGm42u1saSSJ/0auvcBqxs3jMRFEcPK34At/0JBKdOg==}
engines: {node: '>=4'} engines: {node: '>=4'}
postcss@8.5.15:
resolution: {integrity: sha512-FfR8sjd4em2T6fb3I2MwAJU7HWVMr9zba+enmQeeWFfCbm+UOC/0X4DS8XtpUTMwWMGbjKYP7xjfNekzyGmB3A==}
engines: {node: ^10 || ^12 || >=14}
postcss@8.5.19: postcss@8.5.19:
resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==} resolution: {integrity: sha512-Mz8SaolMd8nB+G13WkORcxQKHZ/NE4xXevtkJHVuG+guo9/wYKlIMTKAqGdEmYOXR2ijPjTYNHssizdaVSUNdQ==}
engines: {node: ^10 || ^12 || >=14} engines: {node: ^10 || ^12 || >=14}
@@ -1270,8 +1260,8 @@ packages:
engines: {node: ^20.19.0 || >=22.12.0} engines: {node: ^20.19.0 || >=22.12.0}
hasBin: true hasBin: true
rollup@4.60.4: rollup@4.62.2:
resolution: {integrity: sha512-WHeFSbZYsPu3+bLoNRUuAO+wavNlocOPf3wSHTP7hcFKVnJeWsYlCDbr3mTS14FCizf9ccIxXA8sGL8zKeQN3g==} resolution: {integrity: sha512-RFnrW4lhXA3s3eqHDZvN654g8OTjzRfqpIRJYczCGB6HzphckVAi/Qh4tbPUbRuDi7s1Llv8g/NspLkttY3gTA==}
engines: {node: '>=18.0.0', npm: '>=8.0.0'} engines: {node: '>=18.0.0', npm: '>=8.0.0'}
hasBin: true hasBin: true
@@ -1279,13 +1269,13 @@ packages:
resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==}
engines: {node: '>=6'} engines: {node: '>=6'}
semver@7.8.0: semver@7.8.5:
resolution: {integrity: sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==} resolution: {integrity: sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==}
engines: {node: '>=10'} engines: {node: '>=10'}
hasBin: true hasBin: true
set-cookie-parser@3.1.0: set-cookie-parser@3.1.2:
resolution: {integrity: sha512-kjnC1DXBHcxaOaOXBHBeRtltsDG2nUiUni+jP92M9gYdW12rsmx92UsfpH7o5tDRs7I1ZZPSQJQGv3UaRfCiuw==} resolution: {integrity: sha512-5/r/lTwbJ3zQ+qwdUFZYeRNqda7P5HD8zQKqlSjdGt1/S0cjLAphHusj4Y58ahDtWn/g32xrIS58/ikOvwl0Lw==}
shebang-command@2.0.0: shebang-command@2.0.0:
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
@@ -1338,10 +1328,6 @@ packages:
resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==} resolution: {integrity: sha512-uxc/zpqFg6x7C8vOE7lh6Lbda8eEL9zmVm/PLeTPBRhh1xCgdWaQ+J1CUieGpIfm2HdtsUpRv+HshiasBMcc6A==}
engines: {node: '>=6'} engines: {node: '>=6'}
tinyglobby@0.2.16:
resolution: {integrity: sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==}
engines: {node: '>=12.0.0'}
tinyglobby@0.2.17: tinyglobby@0.2.17:
resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==} resolution: {integrity: sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==}
engines: {node: '>=12.0.0'} engines: {node: '>=12.0.0'}
@@ -1610,153 +1596,153 @@ snapshots:
'@rolldown/pluginutils@1.0.1': {} '@rolldown/pluginutils@1.0.1': {}
'@rollup/plugin-commonjs@29.0.2(rollup@4.60.4)': '@rollup/plugin-commonjs@29.0.3(rollup@4.62.2)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.4) '@rollup/pluginutils': 5.4.0(rollup@4.62.2)
commondir: 1.0.1 commondir: 1.0.1
estree-walker: 2.0.2 estree-walker: 2.0.2
fdir: 6.5.0(picomatch@4.0.4) fdir: 6.5.0(picomatch@4.0.5)
is-reference: 1.2.1 is-reference: 1.2.1
magic-string: 0.30.21 magic-string: 0.30.21
picomatch: 4.0.4 picomatch: 4.0.5
optionalDependencies: optionalDependencies:
rollup: 4.60.4 rollup: 4.62.2
'@rollup/plugin-json@6.1.0(rollup@4.60.4)': '@rollup/plugin-json@6.1.0(rollup@4.62.2)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.4) '@rollup/pluginutils': 5.4.0(rollup@4.62.2)
optionalDependencies: optionalDependencies:
rollup: 4.60.4 rollup: 4.62.2
'@rollup/plugin-node-resolve@16.0.3(rollup@4.60.4)': '@rollup/plugin-node-resolve@16.0.3(rollup@4.62.2)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.4) '@rollup/pluginutils': 5.4.0(rollup@4.62.2)
'@types/resolve': 1.20.2 '@types/resolve': 1.20.2
deepmerge: 4.3.1 deepmerge: 4.3.1
is-module: 1.0.0 is-module: 1.0.0
resolve: 1.22.12 resolve: 1.22.12
optionalDependencies: optionalDependencies:
rollup: 4.60.4 rollup: 4.62.2
'@rollup/plugin-replace@6.0.3(rollup@4.60.4)': '@rollup/plugin-replace@6.0.3(rollup@4.62.2)':
dependencies: dependencies:
'@rollup/pluginutils': 5.3.0(rollup@4.60.4) '@rollup/pluginutils': 5.4.0(rollup@4.62.2)
magic-string: 0.30.21 magic-string: 0.30.21
optionalDependencies: optionalDependencies:
rollup: 4.60.4 rollup: 4.62.2
'@rollup/pluginutils@5.3.0(rollup@4.60.4)': '@rollup/pluginutils@5.4.0(rollup@4.62.2)':
dependencies: dependencies:
'@types/estree': 1.0.9 '@types/estree': 1.0.9
estree-walker: 2.0.2 estree-walker: 2.0.2
picomatch: 4.0.4 picomatch: 4.0.5
optionalDependencies: optionalDependencies:
rollup: 4.60.4 rollup: 4.62.2
'@rollup/rollup-android-arm-eabi@4.60.4': '@rollup/rollup-android-arm-eabi@4.62.2':
optional: true optional: true
'@rollup/rollup-android-arm64@4.60.4': '@rollup/rollup-android-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-darwin-arm64@4.60.4': '@rollup/rollup-darwin-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-darwin-x64@4.60.4': '@rollup/rollup-darwin-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-freebsd-arm64@4.60.4': '@rollup/rollup-freebsd-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-freebsd-x64@4.60.4': '@rollup/rollup-freebsd-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm-gnueabihf@4.60.4': '@rollup/rollup-linux-arm-gnueabihf@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm-musleabihf@4.60.4': '@rollup/rollup-linux-arm-musleabihf@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm64-gnu@4.60.4': '@rollup/rollup-linux-arm64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-arm64-musl@4.60.4': '@rollup/rollup-linux-arm64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-loong64-gnu@4.60.4': '@rollup/rollup-linux-loong64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-loong64-musl@4.60.4': '@rollup/rollup-linux-loong64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-ppc64-gnu@4.60.4': '@rollup/rollup-linux-ppc64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-ppc64-musl@4.60.4': '@rollup/rollup-linux-ppc64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-riscv64-gnu@4.60.4': '@rollup/rollup-linux-riscv64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-riscv64-musl@4.60.4': '@rollup/rollup-linux-riscv64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-s390x-gnu@4.60.4': '@rollup/rollup-linux-s390x-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-x64-gnu@4.60.4': '@rollup/rollup-linux-x64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-linux-x64-musl@4.60.4': '@rollup/rollup-linux-x64-musl@4.62.2':
optional: true optional: true
'@rollup/rollup-openbsd-x64@4.60.4': '@rollup/rollup-openbsd-x64@4.62.2':
optional: true optional: true
'@rollup/rollup-openharmony-arm64@4.60.4': '@rollup/rollup-openharmony-arm64@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-arm64-msvc@4.60.4': '@rollup/rollup-win32-arm64-msvc@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-ia32-msvc@4.60.4': '@rollup/rollup-win32-ia32-msvc@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-x64-gnu@4.60.4': '@rollup/rollup-win32-x64-gnu@4.62.2':
optional: true optional: true
'@rollup/rollup-win32-x64-msvc@4.60.4': '@rollup/rollup-win32-x64-msvc@4.62.2':
optional: true optional: true
'@standard-schema/spec@1.1.0': {} '@standard-schema/spec@1.1.0': {}
'@sveltejs/acorn-typescript@1.0.10(acorn@8.16.0)': '@sveltejs/acorn-typescript@1.0.11(acorn@8.17.0)':
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.17.0
'@sveltejs/adapter-node@5.5.7(@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))': '@sveltejs/adapter-node@5.5.7(@sveltejs/kit@2.69.3(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))':
dependencies: dependencies:
'@rollup/plugin-commonjs': 29.0.2(rollup@4.60.4) '@rollup/plugin-commonjs': 29.0.3(rollup@4.62.2)
'@rollup/plugin-json': 6.1.0(rollup@4.60.4) '@rollup/plugin-json': 6.1.0(rollup@4.62.2)
'@rollup/plugin-node-resolve': 16.0.3(rollup@4.60.4) '@rollup/plugin-node-resolve': 16.0.3(rollup@4.62.2)
'@rollup/plugin-replace': 6.0.3(rollup@4.60.4) '@rollup/plugin-replace': 6.0.3(rollup@4.62.2)
'@sveltejs/kit': 2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)) '@sveltejs/kit': 2.69.3(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))
rollup: 4.60.4 rollup: 4.62.2
'@sveltejs/kit@2.70.1(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))': '@sveltejs/kit@2.69.3(@sveltejs/vite-plugin-svelte@7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)))(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3)(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))':
dependencies: dependencies:
'@standard-schema/spec': 1.1.0 '@standard-schema/spec': 1.1.0
'@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0)
'@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)) '@sveltejs/vite-plugin-svelte': 7.2.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))
'@types/cookie': 0.6.0 '@types/cookie': 0.6.0
acorn: 8.16.0 acorn: 8.17.0
cookie: 0.7.2 cookie: 0.7.2
devalue: 5.8.1 devalue: 5.8.1
esm-env: 1.2.2 esm-env: 1.2.2
kleur: 4.1.5 kleur: 4.1.5
magic-string: 0.30.21 magic-string: 0.30.21
mrmime: 2.0.1 mrmime: 2.0.1
set-cookie-parser: 3.1.0 set-cookie-parser: 3.1.2
sirv: 3.0.2 sirv: 3.0.2
svelte: 5.56.6(@typescript-eslint/types@8.64.0) svelte: 5.56.6(@typescript-eslint/types@8.64.0)
vite: 8.1.5(@types/node@24.12.4)(jiti@2.7.0) vite: 8.1.5(@types/node@24.12.4)(jiti@2.7.0)
@@ -1769,7 +1755,7 @@ snapshots:
dependencies: dependencies:
deepmerge: 4.3.1 deepmerge: 4.3.1
magic-string: 0.30.21 magic-string: 0.30.21
obug: 2.1.1 obug: 2.1.4
svelte: 5.56.6(@typescript-eslint/types@8.64.0) svelte: 5.56.6(@typescript-eslint/types@8.64.0)
vite: 8.1.5(@types/node@24.12.4)(jiti@2.7.0) vite: 8.1.5(@types/node@24.12.4)(jiti@2.7.0)
vitefu: 1.1.3(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0)) vitefu: 1.1.3(vite@8.1.5(@types/node@24.12.4)(jiti@2.7.0))
@@ -1851,8 +1837,6 @@ snapshots:
'@types/esrecurse@4.3.1': {} '@types/esrecurse@4.3.1': {}
'@types/estree@1.0.8': {}
'@types/estree@1.0.9': {} '@types/estree@1.0.9': {}
'@types/json-schema@7.0.15': {} '@types/json-schema@7.0.15': {}
@@ -1874,7 +1858,7 @@ snapshots:
'@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3) '@typescript-eslint/utils': 8.64.0(eslint@10.7.0(jiti@2.7.0))(typescript@6.0.3)
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.64.0
eslint: 10.7.0(jiti@2.7.0) eslint: 10.7.0(jiti@2.7.0)
ignore: 7.0.5 ignore: 7.0.6
natural-compare: 1.4.0 natural-compare: 1.4.0
ts-api-utils: 2.5.0(typescript@6.0.3) ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3 typescript: 6.0.3
@@ -1933,8 +1917,8 @@ snapshots:
'@typescript-eslint/visitor-keys': 8.64.0 '@typescript-eslint/visitor-keys': 8.64.0
debug: 4.4.3 debug: 4.4.3
minimatch: 10.2.5 minimatch: 10.2.5
semver: 7.8.0 semver: 7.8.5
tinyglobby: 0.2.16 tinyglobby: 0.2.17
ts-api-utils: 2.5.0(typescript@6.0.3) ts-api-utils: 2.5.0(typescript@6.0.3)
typescript: 6.0.3 typescript: 6.0.3
transitivePeerDependencies: transitivePeerDependencies:
@@ -1956,11 +1940,11 @@ snapshots:
'@typescript-eslint/types': 8.64.0 '@typescript-eslint/types': 8.64.0
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.1
acorn-jsx@5.3.2(acorn@8.16.0): acorn-jsx@5.3.2(acorn@8.17.0):
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.17.0
acorn@8.16.0: {} acorn@8.17.0: {}
ajv@6.15.0: ajv@6.15.0:
dependencies: dependencies:
@@ -1975,7 +1959,7 @@ snapshots:
balanced-match@4.0.4: {} balanced-match@4.0.4: {}
brace-expansion@5.0.6: brace-expansion@5.0.7:
dependencies: dependencies:
balanced-match: 4.0.4 balanced-match: 4.0.4
@@ -2022,7 +2006,7 @@ snapshots:
dependencies: dependencies:
eslint: 10.7.0(jiti@2.7.0) eslint: 10.7.0(jiti@2.7.0)
eslint-plugin-svelte@3.21.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0)): eslint-plugin-svelte@3.20.0(eslint@10.7.0(jiti@2.7.0))(svelte@5.56.6(@typescript-eslint/types@8.64.0)):
dependencies: dependencies:
'@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0)) '@eslint-community/eslint-utils': 4.9.1(eslint@10.7.0(jiti@2.7.0))
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
@@ -2030,10 +2014,10 @@ snapshots:
esutils: 2.0.3 esutils: 2.0.3
globals: 16.5.0 globals: 16.5.0
known-css-properties: 0.37.0 known-css-properties: 0.37.0
postcss: 8.5.15 postcss: 8.5.19
postcss-load-config: 3.1.4(postcss@8.5.15) postcss-load-config: 3.1.4(postcss@8.5.19)
postcss-safe-parser: 7.0.1(postcss@8.5.15) postcss-safe-parser: 7.0.1(postcss@8.5.19)
semver: 7.8.0 semver: 7.8.5
svelte-eslint-parser: 1.8.0(svelte@5.56.6(@typescript-eslint/types@8.64.0)) svelte-eslint-parser: 1.8.0(svelte@5.56.6(@typescript-eslint/types@8.64.0))
optionalDependencies: optionalDependencies:
svelte: 5.56.6(@typescript-eslint/types@8.64.0) svelte: 5.56.6(@typescript-eslint/types@8.64.0)
@@ -2099,14 +2083,14 @@ snapshots:
espree@10.4.0: espree@10.4.0:
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.17.0
acorn-jsx: 5.3.2(acorn@8.16.0) acorn-jsx: 5.3.2(acorn@8.17.0)
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
espree@11.2.0: espree@11.2.0:
dependencies: dependencies:
acorn: 8.16.0 acorn: 8.17.0
acorn-jsx: 5.3.2(acorn@8.16.0) acorn-jsx: 5.3.2(acorn@8.17.0)
eslint-visitor-keys: 5.0.1 eslint-visitor-keys: 5.0.1
esquery@1.7.0: esquery@1.7.0:
@@ -2137,9 +2121,9 @@ snapshots:
fast-levenshtein@2.0.6: {} fast-levenshtein@2.0.6: {}
fdir@6.5.0(picomatch@4.0.4): fdir@6.5.0(picomatch@4.0.5):
optionalDependencies: optionalDependencies:
picomatch: 4.0.4 picomatch: 4.0.5
file-entry-cache@8.0.0: file-entry-cache@8.0.0:
dependencies: dependencies:
@@ -2174,19 +2158,19 @@ snapshots:
gsap@3.15.0: {} gsap@3.15.0: {}
hasown@2.0.3: hasown@2.0.4:
dependencies: dependencies:
function-bind: 1.1.2 function-bind: 1.1.2
ignore@5.3.2: {} ignore@5.3.2: {}
ignore@7.0.5: {} ignore@7.0.6: {}
imurmurhash@0.1.4: {} imurmurhash@0.1.4: {}
is-core-module@2.16.2: is-core-module@2.16.2:
dependencies: dependencies:
hasown: 2.0.3 hasown: 2.0.4
is-extglob@2.1.1: {} is-extglob@2.1.1: {}
@@ -2294,7 +2278,7 @@ snapshots:
minimatch@10.2.5: minimatch@10.2.5:
dependencies: dependencies:
brace-expansion: 5.0.6 brace-expansion: 5.0.7
mri@1.2.0: {} mri@1.2.0: {}
@@ -2302,11 +2286,11 @@ snapshots:
ms@2.1.3: {} ms@2.1.3: {}
nanoid@3.3.12: {} nanoid@3.3.16: {}
natural-compare@1.4.0: {} natural-compare@1.4.0: {}
obug@2.1.1: {} obug@2.1.4: {}
optionator@0.9.4: optionator@0.9.4:
dependencies: dependencies:
@@ -2333,39 +2317,31 @@ snapshots:
picocolors@1.1.1: {} picocolors@1.1.1: {}
picomatch@4.0.4: {}
picomatch@4.0.5: {} picomatch@4.0.5: {}
postcss-load-config@3.1.4(postcss@8.5.15): postcss-load-config@3.1.4(postcss@8.5.19):
dependencies: dependencies:
lilconfig: 2.1.0 lilconfig: 2.1.0
yaml: 1.10.3 yaml: 1.10.3
optionalDependencies: optionalDependencies:
postcss: 8.5.15 postcss: 8.5.19
postcss-safe-parser@7.0.1(postcss@8.5.15): postcss-safe-parser@7.0.1(postcss@8.5.19):
dependencies: dependencies:
postcss: 8.5.15 postcss: 8.5.19
postcss-scss@4.0.9(postcss@8.5.15): postcss-scss@4.0.9(postcss@8.5.19):
dependencies: dependencies:
postcss: 8.5.15 postcss: 8.5.19
postcss-selector-parser@7.1.1: postcss-selector-parser@7.1.4:
dependencies: dependencies:
cssesc: 3.0.0 cssesc: 3.0.0
util-deprecate: 1.0.2 util-deprecate: 1.0.2
postcss@8.5.15:
dependencies:
nanoid: 3.3.12
picocolors: 1.1.1
source-map-js: 1.2.1
postcss@8.5.19: postcss@8.5.19:
dependencies: dependencies:
nanoid: 3.3.12 nanoid: 3.3.16
picocolors: 1.1.1 picocolors: 1.1.1
source-map-js: 1.2.1 source-map-js: 1.2.1
@@ -2416,44 +2392,44 @@ snapshots:
'@rolldown/binding-win32-arm64-msvc': 1.1.5 '@rolldown/binding-win32-arm64-msvc': 1.1.5
'@rolldown/binding-win32-x64-msvc': 1.1.5 '@rolldown/binding-win32-x64-msvc': 1.1.5
rollup@4.60.4: rollup@4.62.2:
dependencies: dependencies:
'@types/estree': 1.0.8 '@types/estree': 1.0.9
optionalDependencies: optionalDependencies:
'@rollup/rollup-android-arm-eabi': 4.60.4 '@rollup/rollup-android-arm-eabi': 4.62.2
'@rollup/rollup-android-arm64': 4.60.4 '@rollup/rollup-android-arm64': 4.62.2
'@rollup/rollup-darwin-arm64': 4.60.4 '@rollup/rollup-darwin-arm64': 4.62.2
'@rollup/rollup-darwin-x64': 4.60.4 '@rollup/rollup-darwin-x64': 4.62.2
'@rollup/rollup-freebsd-arm64': 4.60.4 '@rollup/rollup-freebsd-arm64': 4.62.2
'@rollup/rollup-freebsd-x64': 4.60.4 '@rollup/rollup-freebsd-x64': 4.62.2
'@rollup/rollup-linux-arm-gnueabihf': 4.60.4 '@rollup/rollup-linux-arm-gnueabihf': 4.62.2
'@rollup/rollup-linux-arm-musleabihf': 4.60.4 '@rollup/rollup-linux-arm-musleabihf': 4.62.2
'@rollup/rollup-linux-arm64-gnu': 4.60.4 '@rollup/rollup-linux-arm64-gnu': 4.62.2
'@rollup/rollup-linux-arm64-musl': 4.60.4 '@rollup/rollup-linux-arm64-musl': 4.62.2
'@rollup/rollup-linux-loong64-gnu': 4.60.4 '@rollup/rollup-linux-loong64-gnu': 4.62.2
'@rollup/rollup-linux-loong64-musl': 4.60.4 '@rollup/rollup-linux-loong64-musl': 4.62.2
'@rollup/rollup-linux-ppc64-gnu': 4.60.4 '@rollup/rollup-linux-ppc64-gnu': 4.62.2
'@rollup/rollup-linux-ppc64-musl': 4.60.4 '@rollup/rollup-linux-ppc64-musl': 4.62.2
'@rollup/rollup-linux-riscv64-gnu': 4.60.4 '@rollup/rollup-linux-riscv64-gnu': 4.62.2
'@rollup/rollup-linux-riscv64-musl': 4.60.4 '@rollup/rollup-linux-riscv64-musl': 4.62.2
'@rollup/rollup-linux-s390x-gnu': 4.60.4 '@rollup/rollup-linux-s390x-gnu': 4.62.2
'@rollup/rollup-linux-x64-gnu': 4.60.4 '@rollup/rollup-linux-x64-gnu': 4.62.2
'@rollup/rollup-linux-x64-musl': 4.60.4 '@rollup/rollup-linux-x64-musl': 4.62.2
'@rollup/rollup-openbsd-x64': 4.60.4 '@rollup/rollup-openbsd-x64': 4.62.2
'@rollup/rollup-openharmony-arm64': 4.60.4 '@rollup/rollup-openharmony-arm64': 4.62.2
'@rollup/rollup-win32-arm64-msvc': 4.60.4 '@rollup/rollup-win32-arm64-msvc': 4.62.2
'@rollup/rollup-win32-ia32-msvc': 4.60.4 '@rollup/rollup-win32-ia32-msvc': 4.62.2
'@rollup/rollup-win32-x64-gnu': 4.60.4 '@rollup/rollup-win32-x64-gnu': 4.62.2
'@rollup/rollup-win32-x64-msvc': 4.60.4 '@rollup/rollup-win32-x64-msvc': 4.62.2
fsevents: 2.3.3 fsevents: 2.3.3
sade@1.8.1: sade@1.8.1:
dependencies: dependencies:
mri: 1.2.0 mri: 1.2.0
semver@7.8.0: {} semver@7.8.5: {}
set-cookie-parser@3.1.0: {} set-cookie-parser@3.1.2: {}
shebang-command@2.0.0: shebang-command@2.0.0:
dependencies: dependencies:
@@ -2471,12 +2447,12 @@ snapshots:
supports-preserve-symlinks-flag@1.0.0: {} supports-preserve-symlinks-flag@1.0.0: {}
svelte-check@4.7.3(picomatch@4.0.4)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3): svelte-check@4.7.3(picomatch@4.0.5)(svelte@5.56.6(@typescript-eslint/types@8.64.0))(typescript@6.0.3):
dependencies: dependencies:
'@jridgewell/trace-mapping': 0.3.31 '@jridgewell/trace-mapping': 0.3.31
'@sveltejs/load-config': 0.2.0 '@sveltejs/load-config': 0.2.0
chokidar: 4.0.3 chokidar: 4.0.3
fdir: 6.5.0(picomatch@4.0.4) fdir: 6.5.0(picomatch@4.0.5)
picocolors: 1.1.1 picocolors: 1.1.1
sade: 1.8.1 sade: 1.8.1
svelte: 5.56.6(@typescript-eslint/types@8.64.0) svelte: 5.56.6(@typescript-eslint/types@8.64.0)
@@ -2489,10 +2465,10 @@ snapshots:
eslint-scope: 8.4.0 eslint-scope: 8.4.0
eslint-visitor-keys: 4.2.1 eslint-visitor-keys: 4.2.1
espree: 10.4.0 espree: 10.4.0
postcss: 8.5.15 postcss: 8.5.19
postcss-scss: 4.0.9(postcss@8.5.15) postcss-scss: 4.0.9(postcss@8.5.19)
postcss-selector-parser: 7.1.1 postcss-selector-parser: 7.1.4
semver: 7.8.0 semver: 7.8.5
optionalDependencies: optionalDependencies:
svelte: 5.56.6(@typescript-eslint/types@8.64.0) svelte: 5.56.6(@typescript-eslint/types@8.64.0)
@@ -2500,10 +2476,10 @@ snapshots:
dependencies: dependencies:
'@jridgewell/remapping': 2.3.5 '@jridgewell/remapping': 2.3.5
'@jridgewell/sourcemap-codec': 1.5.5 '@jridgewell/sourcemap-codec': 1.5.5
'@sveltejs/acorn-typescript': 1.0.10(acorn@8.16.0) '@sveltejs/acorn-typescript': 1.0.11(acorn@8.17.0)
'@types/estree': 1.0.9 '@types/estree': 1.0.9
'@types/trusted-types': 2.0.7 '@types/trusted-types': 2.0.7
acorn: 8.16.0 acorn: 8.17.0
aria-query: 5.3.1 aria-query: 5.3.1
axobject-query: 4.1.0 axobject-query: 4.1.0
clsx: 2.1.1 clsx: 2.1.1
@@ -2523,15 +2499,10 @@ snapshots:
tapable@2.3.3: {} tapable@2.3.3: {}
tinyglobby@0.2.16:
dependencies:
fdir: 6.5.0(picomatch@4.0.4)
picomatch: 4.0.4
tinyglobby@0.2.17: tinyglobby@0.2.17:
dependencies: dependencies:
fdir: 6.5.0(picomatch@4.0.4) fdir: 6.5.0(picomatch@4.0.5)
picomatch: 4.0.4 picomatch: 4.0.5
totalist@3.0.1: {} totalist@3.0.1: {}
-4
View File
@@ -1,10 +1,6 @@
packages: packages:
- . - .
minimumReleaseAgeExclude:
- '@sveltejs/kit@2.70.1'
- eslint-plugin-svelte@3.21.0
overrides: overrides:
cookie: 0.7.2 cookie: 0.7.2
+1
View File
@@ -3,6 +3,7 @@
"extends": ["config:recommended"], "extends": ["config:recommended"],
"dependencyDashboard": true, "dependencyDashboard": true,
"labels": ["dependencies"], "labels": ["dependencies"],
"minimumReleaseAge": "3 days",
"packageRules": [ "packageRules": [
{ {
"matchManagers": ["npm"], "matchManagers": ["npm"],
+4 -4
View File
@@ -39,7 +39,7 @@ export const posts: Post[] = [
{ type: 'heading', text: 'Stop Loss: где ваша позиция неправа' }, { type: 'heading', text: 'Stop Loss: где ваша позиция неправа' },
{ {
type: 'paragraph', type: 'paragraph',
text: 'Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy AI уровень SL рассчитывается моделью на основе исторической волатильности инструмента.' text: 'Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy Trade уровень SL рассчитывается моделью на основе исторической волатильности инструмента.'
}, },
{ type: 'heading', text: 'Take Profit: когда забирать прибыль' }, { type: 'heading', text: 'Take Profit: когда забирать прибыль' },
{ {
@@ -70,13 +70,13 @@ export const posts: Post[] = [
id: 2, id: 2,
slug: 'kak-chitat-grafiki-flamy-ai', slug: 'kak-chitat-grafiki-flamy-ai',
date: '2026-05-17', date: '2026-05-17',
title: 'Как читать графики Flamy AI', title: 'Как читать графики Flamy Trade',
description: 'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy AI.', description: 'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy Trade.',
tags: ['ML', 'FORECAST', 'CHARTS'], tags: ['ML', 'FORECAST', 'CHARTS'],
content: [ content: [
{ {
type: 'paragraph', type: 'paragraph',
text: 'Графики Flamy AI содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.' text: 'Графики Flamy Trade содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.'
}, },
{ type: 'heading', text: 'Исторические свечи' }, { type: 'heading', text: 'Исторические свечи' },
{ {
+2 -2
View File
@@ -7,6 +7,7 @@
CrosshairMode, CrosshairMode,
LineStyle, LineStyle,
type IChartApi, type IChartApi,
type IPriceLine,
type ISeriesApi, type ISeriesApi,
type MouseEventParams, type MouseEventParams,
type UTCTimestamp type UTCTimestamp
@@ -25,8 +26,7 @@
let chart: IChartApi | undefined; let chart: IChartApi | undefined;
let candleSeries: ISeriesApi<'Candlestick'> | undefined; let candleSeries: ISeriesApi<'Candlestick'> | undefined;
let predSeries: ISeriesApi<'Line'> | undefined; let predSeries: ISeriesApi<'Line'> | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any let priceLines: IPriceLine[] = [];
let priceLines: any[] = [];
type Tooltip = { type Tooltip = {
visible: boolean; visible: boolean;
+40 -4
View File
@@ -1,4 +1,6 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state';
import { tick } from 'svelte';
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { gsap } from 'gsap'; import { gsap } from 'gsap';
import { cn } from '$lib/utils'; import { cn } from '$lib/utils';
@@ -13,6 +15,9 @@
let mobileOpen = $state(false); let mobileOpen = $state(false);
let header = $state<HTMLElement>(null!); let header = $state<HTMLElement>(null!);
let menuButton = $state<HTMLButtonElement>(null!);
const mobileMenuId = 'mobile-navigation';
onMount(() => { onMount(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return; if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
@@ -24,8 +29,33 @@
clearProps: 'transform,opacity' clearProps: 'transform,opacity'
}); });
}); });
function isActive(href: string) {
const pathname = page.url.pathname;
return href === '/' ? pathname === href : pathname === href || pathname.startsWith(`${href}/`);
}
async function toggleMobileMenu() {
mobileOpen = !mobileOpen;
if (mobileOpen) {
await tick();
header.querySelector<HTMLAnchorElement>(`#${mobileMenuId} a`)?.focus();
}
}
function closeMobileMenu({ restoreFocus = false } = {}) {
mobileOpen = false;
if (restoreFocus) menuButton?.focus();
}
function handleWindowKeydown(event: KeyboardEvent) {
if (event.key !== 'Escape' || !mobileOpen) return;
closeMobileMenu({ restoreFocus: true });
}
</script> </script>
<svelte:window onkeydown={handleWindowKeydown} />
<header bind:this={header} class="fixed right-0 left-0 z-9999 border-b border-b-bg-h bg-bg-e"> <header bind:this={header} class="fixed right-0 left-0 z-9999 border-b border-b-bg-h bg-bg-e">
<div class="mx-auto flex max-w-400 items-center justify-between px-5 py-5 sm:px-10 lg:px-5"> <div class="mx-auto flex max-w-400 items-center justify-between px-5 py-5 sm:px-10 lg:px-5">
<Logo /> <Logo />
@@ -37,6 +67,7 @@
<a <a
href={link.href} href={link.href}
class={cn('transition-colors duration-300', link.isButton ? 'nav-btn' : 'nav-link')} class={cn('transition-colors duration-300', link.isButton ? 'nav-btn' : 'nav-link')}
aria-current={isActive(link.href) ? 'page' : undefined}
> >
{link.text} {link.text}
</a> </a>
@@ -46,10 +77,13 @@
</nav> </nav>
<button <button
bind:this={menuButton}
type="button"
class="flex flex-col justify-center gap-1.5 p-1 lg:hidden" class="flex flex-col justify-center gap-1.5 p-1 lg:hidden"
onclick={() => (mobileOpen = !mobileOpen)} onclick={toggleMobileMenu}
aria-label={mobileOpen ? 'Закрыть меню' : 'Открыть меню'} aria-label={mobileOpen ? 'Закрыть меню' : 'Открыть меню'}
aria-expanded={mobileOpen} aria-expanded={mobileOpen}
aria-controls={mobileMenuId}
> >
<span <span
class={cn( class={cn(
@@ -74,7 +108,7 @@
<!-- Мобильное выпадающее меню --> <!-- Мобильное выпадающее меню -->
{#if mobileOpen} {#if mobileOpen}
<nav class="border-t border-white/6 px-5 pb-4 sm:px-10 lg:hidden"> <nav id={mobileMenuId} class="border-t border-white/6 px-5 pb-4 sm:px-10 lg:hidden">
<ul class="flex flex-col"> <ul class="flex flex-col">
{#each menuLinks as link (link.id)} {#each menuLinks as link (link.id)}
<li> <li>
@@ -82,7 +116,8 @@
<a <a
href={link.href} href={link.href}
class="nav-btn mt-3 block text-center" class="nav-btn mt-3 block text-center"
onclick={() => (mobileOpen = false)} onclick={() => closeMobileMenu()}
aria-current={isActive(link.href) ? 'page' : undefined}
> >
{link.text} {link.text}
</a> </a>
@@ -90,7 +125,8 @@
<a <a
href={link.href} href={link.href}
class="nav-link block border-b border-white/6 py-3.5 last:border-0" class="nav-link block border-b border-white/6 py-3.5 last:border-0"
onclick={() => (mobileOpen = false)} onclick={() => closeMobileMenu()}
aria-current={isActive(link.href) ? 'page' : undefined}
> >
{link.text} {link.text}
</a> </a>
+28 -12
View File
@@ -5,29 +5,45 @@
type Props = { type Props = {
href?: string; href?: string;
target?: string; target?: string;
type?: 'button' | 'submit' | 'reset';
variant?: 'primary' | 'secondary'; variant?: 'primary' | 'secondary';
class?: string; class?: string;
children: Snippet; children: Snippet;
}; };
let { href, target = '_self', variant = 'primary', class: className, children }: Props = $props(); let {
</script> href,
target = '_self',
type = 'button',
variant = 'primary',
class: className,
children
}: Props = $props();
<svelte:element const classes = $derived(
this={href ? 'a' : 'button'} cn(
{href}
{target}
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
class={cn(
'flex w-fit items-center justify-center gap-2 rounded-xl px-6 py-3 font-display text-lg font-medium transition-colors duration-300', 'flex w-fit items-center justify-center gap-2 rounded-xl px-6 py-3 font-display text-lg font-medium transition-colors duration-300',
variant === 'primary' && 'bg-primary hover:bg-primary-h', variant === 'primary' && 'bg-primary hover:bg-primary-h',
variant === 'secondary' && 'border bg-bg hover:text-desc', variant === 'secondary' && 'border bg-bg hover:text-desc',
className className
)} )
> );
</script>
{#if href}
<a {href} {target} rel={target === '_blank' ? 'noopener noreferrer' : undefined} class={classes}>
{@render children()} {@render children()}
{#if variant === 'primary'} {#if variant === 'primary'}
<img src="images/icons/arrow-right.svg" alt="Перейти" class="h-5 w-5" /> <img src="/images/icons/arrow-right.svg" alt="" aria-hidden="true" class="h-5 w-5" />
{/if}
</a>
{:else}
<button {type} class={classes}>
{@render children()}
{#if variant === 'primary'}
<img src="/images/icons/arrow-right.svg" alt="" aria-hidden="true" class="h-5 w-5" />
{/if}
</button>
{/if} {/if}
</svelte:element>
+1 -1
View File
@@ -2,7 +2,7 @@
</script> </script>
<a href="/" class="flex max-w-fit items-center gap-2.5 no-underline"> <a href="/" class="flex max-w-fit items-center gap-2.5 no-underline">
<img src="images/icons/logo.svg" alt="Логотип" class="h-8 w-8" /> <img src="/images/icons/logo.svg" alt="" aria-hidden="true" class="h-8 w-8" />
<span class="font-display text-xl font-black tracking-tighter"> <span class="font-display text-xl font-black tracking-tighter">
FLAMY<span class="text-primary">TRADE</span> FLAMY<span class="text-primary">TRADE</span>
</span> </span>
+22
View File
@@ -6,6 +6,12 @@ export const site = {
description: env.PUBLIC_SITE_DESCRIPTION || 'Публичная витрина ML-прогнозов для крипторынка.' description: env.PUBLIC_SITE_DESCRIPTION || 'Публичная витрина ML-прогнозов для крипторынка.'
}; };
export type PageMeta = {
title: string;
description: string;
url: string;
};
export function pageTitle(title?: string): string { export function pageTitle(title?: string): string {
return title ? `${title} | ${site.name}` : site.name; return title ? `${title} | ${site.name}` : site.name;
} }
@@ -14,3 +20,19 @@ export function canonical(path = '/'): string {
const normalizedPath = path.startsWith('/') ? path : `/${path}`; const normalizedPath = path.startsWith('/') ? path : `/${path}`;
return `${site.url}${normalizedPath}`; return `${site.url}${normalizedPath}`;
} }
export function pageMeta({
title,
description = site.description,
path = '/'
}: {
title?: string;
description?: string;
path?: string;
} = {}): PageMeta {
return {
title: pageTitle(title),
description,
url: canonical(path)
};
}
+29
View File
@@ -0,0 +1,29 @@
import { env } from '$env/dynamic/public';
import {
generateChartData,
SYMBOLS,
TIMEFRAMES,
type ChartData,
type TimeframeId
} from '$lib/stores/chartStore';
const supportedModes = ['research-static'] as const;
export type DashboardDataMode = (typeof supportedModes)[number];
export const dashboardDataMode: DashboardDataMode = supportedModes.includes(
env.PUBLIC_DASHBOARD_DATA_MODE as DashboardDataMode
)
? (env.PUBLIC_DASHBOARD_DATA_MODE as DashboardDataMode)
: 'research-static';
export const dashboardDataNotice =
dashboardDataMode === 'research-static'
? 'Исследовательские синтетические данные'
: 'Исследовательские данные';
export function getDashboardChartData(symbolId: string, timeframeId: TimeframeId): ChartData {
return generateChartData(symbolId, timeframeId);
}
export { SYMBOLS, TIMEFRAMES };
-4
View File
@@ -70,10 +70,6 @@
<DashboardMockup /> <DashboardMockup />
</div> </div>
<!-- Визуальные элементы -->
<span
class="absolute -top-60 left-0 z-0 h-80 w-full rotate-3 rounded-full bg-primary opacity-30 blur-[200px]"
></span>
<span class="absolute bottom-35 left-0 z-0 hidden h-px w-full bg-bg-h xl:block"></span> <span class="absolute bottom-35 left-0 z-0 hidden h-px w-full bg-bg-h xl:block"></span>
<span class="absolute top-55 left-0 z-0 hidden h-px w-full bg-bg-h xl:block"></span> <span class="absolute top-55 left-0 z-0 hidden h-px w-full bg-bg-h xl:block"></span>
</section> </section>
+18 -3
View File
@@ -1,10 +1,25 @@
<script lang="ts"> <script lang="ts">
import { page } from '$app/state'; import { page } from '$app/state';
import { pageTitle } from '$lib/config/site'; import { pageTitle } from '$lib/config/site';
const title = $derived(
page.status === 404
? 'Страница не найдена'
: page.status >= 500
? 'Внутренняя ошибка'
: 'Произошла ошибка'
);
const description = $derived(
page.status === 404
? 'Проверьте адрес или вернитесь к публичному дашборду Flamy Trade.'
: 'Запрос не удалось обработать. Попробуйте обновить страницу позже.'
);
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle('Страница не найдена')}</title> <title>{pageTitle(title)}</title>
<meta name="robots" content="noindex" />
</svelte:head> </svelte:head>
<section class="flex min-h-screen items-center justify-center px-5 pt-16 text-center"> <section class="flex min-h-screen items-center justify-center px-5 pt-16 text-center">
@@ -13,10 +28,10 @@
Ошибка {page.status} Ошибка {page.status}
</p> </p>
<h1 class="font-display text-[clamp(2.75rem,9vw,6.5rem)] leading-none font-black uppercase"> <h1 class="font-display text-[clamp(2.75rem,9vw,6.5rem)] leading-none font-black uppercase">
Страница не найдена {title}
</h1> </h1>
<p class="mx-auto mt-5 max-w-lg text-base leading-relaxed text-desc sm:text-lg"> <p class="mx-auto mt-5 max-w-lg text-base leading-relaxed text-desc sm:text-lg">
Проверьте адрес или вернитесь к публичному дашборду Flamy Trade. {description}
</p> </p>
<a <a
href="/" href="/"
+1 -10
View File
@@ -2,24 +2,15 @@
import './layout.css'; import './layout.css';
import Header from '$lib/components/layout/Header.svelte'; import Header from '$lib/components/layout/Header.svelte';
import Footer from '$lib/components/layout/Footer.svelte'; import Footer from '$lib/components/layout/Footer.svelte';
import { canonical, pageTitle, site } from '$lib/config/site'; import { site } from '$lib/config/site';
let { children } = $props(); let { children } = $props();
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle()}</title>
<meta name="description" content={site.description} />
<link rel="canonical" href={canonical('/')} />
<link rel="icon" href="/favicon.svg" type="image/svg+xml" /> <link rel="icon" href="/favicon.svg" type="image/svg+xml" />
<meta property="og:type" content="website" />
<meta property="og:site_name" content={site.name} /> <meta property="og:site_name" content={site.name} />
<meta property="og:title" content={pageTitle()} />
<meta property="og:description" content={site.description} />
<meta property="og:url" content={canonical('/')} />
<meta name="twitter:card" content="summary" /> <meta name="twitter:card" content="summary" />
<meta name="twitter:title" content={pageTitle()} />
<meta name="twitter:description" content={site.description} />
</svelte:head> </svelte:head>
<div class="noise"></div> <div class="noise"></div>
+12 -7
View File
@@ -7,16 +7,21 @@
import BlogPreview from './(sections)/BlogPreview.svelte'; import BlogPreview from './(sections)/BlogPreview.svelte';
import Cta from './(sections)/Cta.svelte'; import Cta from './(sections)/Cta.svelte';
import { posts } from '$lib/blog/posts'; import { posts } from '$lib/blog/posts';
import { canonical, pageTitle, site } from '$lib/config/site'; import { pageMeta } from '$lib/config/site';
const meta = pageMeta();
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle()}</title> <title>{meta.title}</title>
<meta name="description" content={site.description} /> <meta name="description" content={meta.description} />
<link rel="canonical" href={canonical('/')} /> <link rel="canonical" href={meta.url} />
<meta property="og:title" content={pageTitle()} /> <meta property="og:type" content="website" />
<meta property="og:description" content={site.description} /> <meta property="og:title" content={meta.title} />
<meta property="og:url" content={canonical('/')} /> <meta property="og:description" content={meta.description} />
<meta property="og:url" content={meta.url} />
<meta name="twitter:title" content={meta.title} />
<meta name="twitter:description" content={meta.description} />
</svelte:head> </svelte:head>
<main> <main>
+20 -21
View File
@@ -1,23 +1,27 @@
<script lang="ts"> <script lang="ts">
import Button from '$lib/components/ui/Button.svelte'; import Button from '$lib/components/ui/Button.svelte';
import { about, params } from './_data'; import { pageMeta } from '$lib/config/site';
import { canonical, pageTitle } from '$lib/config/site';
import { scrollFadeUp, scrollRevealLeft } from '$lib/gsap/actions'; import { scrollFadeUp, scrollRevealLeft } from '$lib/gsap/actions';
import { about, params } from './_data';
const meta = pageMeta({
title: 'О проекте',
description:
'Flamy Trade показывает исследовательские результаты ML-модели для анализа крипторынка.',
path: '/about'
});
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle('О проекте')}</title> <title>{meta.title}</title>
<meta <meta name="description" content={meta.description} />
name="description" <link rel="canonical" href={meta.url} />
content="Flamy Trade показывает демонстрационные результаты ML-модели для анализа крипторынка." <meta property="og:type" content="website" />
/> <meta property="og:title" content={meta.title} />
<link rel="canonical" href={canonical('/about')} /> <meta property="og:description" content={meta.description} />
<meta property="og:title" content={pageTitle('О проекте')} /> <meta property="og:url" content={meta.url} />
<meta <meta name="twitter:title" content={meta.title} />
property="og:description" <meta name="twitter:description" content={meta.description} />
content="Flamy Trade показывает демонстрационные результаты ML-модели для анализа крипторынка."
/>
<meta property="og:url" content={canonical('/about')} />
</svelte:head> </svelte:head>
<main> <main>
@@ -39,10 +43,6 @@
Публичная витрина результатов ML-модели, обученной на исторических рыночных данных. Публичная витрина результатов ML-модели, обученной на исторических рыночных данных.
</p> </p>
</section> </section>
<span
class="absolute -top-20 -right-20 z-0 h-80 w-full rotate-3 rounded-full bg-primary opacity-30 blur-[200px]"
></span>
</div> </div>
<section class="grid max-h-full! grid-cols-1 lg:grid-cols-[1fr_420px]"> <section class="grid max-h-full! grid-cols-1 lg:grid-cols-[1fr_420px]">
@@ -74,7 +74,6 @@
</div> </div>
</div> </div>
{:else} {:else}
<!-- padding на родителе — не margin/w-full на самом блоке -->
<div class="border-b border-b-bg-h px-5 py-12 sm:px-10 sm:py-16"> <div class="border-b border-b-bg-h px-5 py-12 sm:px-10 sm:py-16">
<div <div
class="flex h-[50vh] w-full items-center justify-center rounded-2xl class="flex h-[50vh] w-full items-center justify-center rounded-2xl
@@ -108,7 +107,7 @@
<p <p
class="mb-2 flex items-center gap-2 font-display text-xs font-bold tracking-widest text-primary uppercase" class="mb-2 flex items-center gap-2 font-display text-xs font-bold tracking-widest text-primary uppercase"
> >
Дисклеймер Дисклеймер
</p> </p>
<p class="text-sm leading-normal text-desc"> <p class="text-sm leading-normal text-desc">
Материалы являются результатом работы исследовательской ML-модели и используются в Материалы являются результатом работы исследовательской ML-модели и используются в
+1 -1
View File
@@ -19,7 +19,7 @@ export const about: About[] = [
desc: 'Назначение', desc: 'Назначение',
title: 'Исследовательская платформа', title: 'Исследовательская платформа',
text: [ text: [
'Flamy AI — публичная витрина результатов ML-модели, обученной на исторических рыночных данных. Показывает прогнозы по крипто-инструментам: направление, уровни входа, Take Profit и Stop Loss.', 'Flamy Trade — публичная витрина результатов ML-модели, обученной на исторических рыночных данных. Показывает прогнозы по крипто-инструментам: направление, уровни входа, Take Profit и Stop Loss.',
'Платформа создана для изучения поведения ML-модели в рыночных условиях — не для торговли реальными средствами.' 'Платформа создана для изучения поведения ML-модели в рыночных условиях — не для торговли реальными средствами.'
] ]
}, },
+20 -20
View File
@@ -1,7 +1,14 @@
<script lang="ts"> <script lang="ts">
import { posts } from '$lib/blog/posts'; import { posts } from '$lib/blog/posts';
import { canonical, pageTitle } from '$lib/config/site'; import { pageMeta } from '$lib/config/site';
import { scrollRevealLeft, scrollFadeUp, scrollStagger } from '$lib/gsap/actions'; import { scrollFadeUp, scrollRevealLeft, scrollStagger } from '$lib/gsap/actions';
const meta = pageMeta({
title: 'Блог',
description:
'Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами.',
path: '/blog'
});
function fmtDate(d: string) { function fmtDate(d: string) {
try { try {
@@ -17,18 +24,15 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle('Блог')}</title> <title>{meta.title}</title>
<meta <meta name="description" content={meta.description} />
name="description" <link rel="canonical" href={meta.url} />
content="Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами." <meta property="og:type" content="website" />
/> <meta property="og:title" content={meta.title} />
<link rel="canonical" href={canonical('/blog')} /> <meta property="og:description" content={meta.description} />
<meta property="og:title" content={pageTitle('Блог')} /> <meta property="og:url" content={meta.url} />
<meta <meta name="twitter:title" content={meta.title} />
property="og:description" <meta name="twitter:description" content={meta.description} />
content="Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами."
/>
<meta property="og:url" content={canonical('/blog')} />
</svelte:head> </svelte:head>
<main> <main>
@@ -47,10 +51,6 @@
Гайды по чтению графиков, управлению рисками и работе с ML-прогнозами. Гайды по чтению графиков, управлению рисками и работе с ML-прогнозами.
</p> </p>
</section> </section>
<span
class="absolute -right-20 -bottom-40 z-0 h-80 w-full -rotate-3 rounded-full bg-primary opacity-30 blur-[200px]"
></span>
</div> </div>
<div <div
@@ -113,7 +113,7 @@
<div <div
class="mr-6 shrink-0 pt-1 text-desc/30 transition-[color,translate] duration-700 group-hover:translate-x-2 group-hover:text-primary" class="mr-6 shrink-0 pt-1 text-desc/30 transition-[color,translate] duration-700 group-hover:translate-x-2 group-hover:text-primary"
> >
<svg width="20" height="20" viewBox="0 0 20 20" fill="none"> <svg width="20" height="20" viewBox="0 0 20 20" fill="none" aria-hidden="true">
<path <path
d="M4 10 H16 M12 6 L16 10 L12 14" d="M4 10 H16 M12 6 L16 10 L12 14"
stroke="currentColor" stroke="currentColor"
+23 -16
View File
@@ -1,9 +1,16 @@
<script lang="ts"> <script lang="ts">
import type { PageData } from './$types'; import type { PageData } from './$types';
import { canonical, pageTitle } from '$lib/config/site'; import { pageMeta } from '$lib/config/site';
let { data }: { data: PageData } = $props(); let { data }: { data: PageData } = $props();
const { post } = $derived(data); const { post } = $derived(data);
const meta = $derived(
pageMeta({
title: post.title,
description: post.description,
path: `/blog/${post.slug}`
})
);
function fmtDate(d: string) { function fmtDate(d: string) {
try { try {
@@ -19,16 +26,16 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle(post.title)}</title> <title>{meta.title}</title>
<meta name="description" content={post.description} /> <meta name="description" content={meta.description} />
<link rel="canonical" href={canonical(`/blog/${post.slug}`)} /> <link rel="canonical" href={meta.url} />
<meta property="og:type" content="article" /> <meta property="og:type" content="article" />
<meta property="og:title" content={pageTitle(post.title)} /> <meta property="og:title" content={meta.title} />
<meta property="og:description" content={post.description} /> <meta property="og:description" content={meta.description} />
<meta property="og:url" content={canonical(`/blog/${post.slug}`)} /> <meta property="og:url" content={meta.url} />
<meta property="article:published_time" content={post.date} /> <meta property="article:published_time" content={post.date} />
<meta name="twitter:title" content={pageTitle(post.title)} /> <meta name="twitter:title" content={meta.title} />
<meta name="twitter:description" content={post.description} /> <meta name="twitter:description" content={meta.description} />
</svelte:head> </svelte:head>
<main> <main>
@@ -37,7 +44,7 @@
href="/blog" href="/blog"
class="back-link mb-6 inline-flex items-center gap-1.5 font-mono text-[0.625rem] tracking-[0.08em] uppercase transition-colors" class="back-link mb-6 inline-flex items-center gap-1.5 font-mono text-[0.625rem] tracking-[0.08em] uppercase transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path <path
d="M11 7 H3 M6 4 L3 7 L6 10" d="M11 7 H3 M6 4 L3 7 L6 10"
stroke="currentColor" stroke="currentColor"
@@ -63,7 +70,7 @@
{/if} {/if}
<h1 <h1
class="mb-5 font-display text-[clamp(1.875rem,5vw,2.75rem)] leading-[1.08] font-black tracking-[-0.035em] text-title" class="mb-5 font-display text-[clamp(1.875rem,5vw,2.75rem)] leading-[1.08] font-black tracking-tight text-title"
> >
{post.title} {post.title}
</h1> </h1>
@@ -101,7 +108,7 @@
href="/blog" href="/blog"
class="back-link inline-flex items-center gap-1.5 font-mono text-[0.625rem] tracking-[0.08em] uppercase transition-colors" class="back-link inline-flex items-center gap-1.5 font-mono text-[0.625rem] tracking-[0.08em] uppercase transition-colors"
> >
<svg width="14" height="14" viewBox="0 0 14 14" fill="none"> <svg width="14" height="14" viewBox="0 0 14 14" fill="none" aria-hidden="true">
<path <path
d="M11 7 H3 M6 4 L3 7 L6 10" d="M11 7 H3 M6 4 L3 7 L6 10"
stroke="currentColor" stroke="currentColor"
@@ -118,7 +125,7 @@
class="inline-flex items-center gap-2 rounded-xl bg-primary px-5 py-3 font-display text-sm font-normal transition-colors hover:bg-primary-h" class="inline-flex items-center gap-2 rounded-xl bg-primary px-5 py-3 font-display text-sm font-normal transition-colors hover:bg-primary-h"
> >
Открыть дашборд Открыть дашборд
<img src="/images/icons/arrow-right.svg" alt="" class="h-4 w-4" /> <img src="/images/icons/arrow-right.svg" alt="" aria-hidden="true" class="h-4 w-4" />
</a> </a>
</div> </div>
</div> </div>
@@ -139,7 +146,7 @@
font-size: 1.25rem; font-size: 1.25rem;
font-weight: 800; font-weight: 800;
color: var(--color-title); color: var(--color-title);
letter-spacing: -0.025em; letter-spacing: 0;
line-height: 1.2; line-height: 1.2;
margin-top: 2.25em; margin-top: 2.25em;
margin-bottom: 0.75em; margin-bottom: 0.75em;
@@ -149,7 +156,7 @@
font-size: 1.0625rem; font-size: 1.0625rem;
font-weight: 700; font-weight: 700;
color: var(--color-title); color: var(--color-title);
letter-spacing: -0.02em; letter-spacing: 0;
margin-top: 1.75em; margin-top: 1.75em;
margin-bottom: 0.5em; margin-bottom: 0.5em;
} }
+1 -1
View File
@@ -1,4 +1,4 @@
import { error } from '@sveltejs/kit'; import { error } from '@sveltejs/kit';
import { getPostBySlug } from '$lib/blog/posts'; import { getPostBySlug } from '$lib/blog/posts';
import type { PageLoad } from './$types'; import type { PageLoad } from './$types';
+37 -23
View File
@@ -1,17 +1,30 @@
<script lang="ts"> <script lang="ts">
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import { gsap } from 'gsap'; import { gsap } from 'gsap';
import { generateChartData, SYMBOLS, TIMEFRAMES, type TimeframeId } from '$lib/stores/chartStore';
import Chart from '$lib/components/Chart.svelte'; import Chart from '$lib/components/Chart.svelte';
import { canonical, pageTitle } from '$lib/config/site'; import {
dashboardDataNotice,
getDashboardChartData,
SYMBOLS,
TIMEFRAMES
} from '$lib/data/dashboard';
import type { TimeframeId } from '$lib/stores/chartStore';
import { pageMeta } from '$lib/config/site';
import { cn } from '$lib/utils'; import { cn } from '$lib/utils';
const meta = pageMeta({
title: 'Дашборд',
description:
'Публичный исследовательский дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL.',
path: '/dashboard'
});
let activeSymbol = $state('BTCUSDT'); let activeSymbol = $state('BTCUSDT');
let activeTimeframe = $state<TimeframeId>('5m'); let activeTimeframe = $state<TimeframeId>('5m');
let chartHeight = $state(520); let chartHeight = $state(520);
const sym = $derived(SYMBOLS.find((s) => s.id === activeSymbol) ?? SYMBOLS[0]); const sym = $derived(SYMBOLS.find((s) => s.id === activeSymbol) ?? SYMBOLS[0]);
const data = $derived(generateChartData(activeSymbol, activeTimeframe)); const data = $derived(getDashboardChartData(activeSymbol, activeTimeframe));
let titleEl = $state<HTMLElement>(null!); let titleEl = $state<HTMLElement>(null!);
let panelEl = $state<HTMLElement>(null!); let panelEl = $state<HTMLElement>(null!);
@@ -66,18 +79,15 @@
</script> </script>
<svelte:head> <svelte:head>
<title>{pageTitle('Дашборд')}</title> <title>{meta.title}</title>
<meta <meta name="description" content={meta.description} />
name="description" <link rel="canonical" href={meta.url} />
content="Демонстрационный дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL." <meta property="og:type" content="website" />
/> <meta property="og:title" content={meta.title} />
<link rel="canonical" href={canonical('/dashboard')} /> <meta property="og:description" content={meta.description} />
<meta property="og:title" content={pageTitle('Дашборд')} /> <meta property="og:url" content={meta.url} />
<meta <meta name="twitter:title" content={meta.title} />
property="og:description" <meta name="twitter:description" content={meta.description} />
content="Демонстрационный дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL."
/>
<meta property="og:url" content={canonical('/dashboard')} />
</svelte:head> </svelte:head>
<main class="min-h-screen bg-bg"> <main class="min-h-screen bg-bg">
@@ -89,7 +99,7 @@
Дашборд Дашборд
</h1> </h1>
<p class="mt-1.5 font-mono text-[0.625rem] tracking-widest text-desc uppercase sm:text-xs"> <p class="mt-1.5 font-mono text-[0.625rem] tracking-widest text-desc uppercase sm:text-xs">
ML-прогноз · Горизонт 19 свечей · Таймфрейм 5M {dashboardDataNotice} · Горизонт 19 свечей · Таймфрейм {activeTimeframe.toUpperCase()}
</p> </p>
</div> </div>
@@ -112,6 +122,7 @@
: 'border-transparent text-desc hover:text-title' : 'border-transparent text-desc hover:text-title'
)} )}
onclick={() => (activeSymbol = s.id)} onclick={() => (activeSymbol = s.id)}
aria-pressed={activeSymbol === s.id}
> >
{s.short} {s.short}
</button> </button>
@@ -126,6 +137,7 @@
activeTimeframe === tf.id ? 'bg-bg-h text-title' : 'text-desc hover:text-title' activeTimeframe === tf.id ? 'bg-bg-h text-title' : 'text-desc hover:text-title'
)} )}
onclick={() => (activeTimeframe = tf.id)} onclick={() => (activeTimeframe = tf.id)}
aria-pressed={activeTimeframe === tf.id}
> >
{tf.label} {tf.label}
</button> </button>
@@ -177,7 +189,14 @@
> >
Уверенность Уверенность
</span> </span>
<div class="h-1.5 w-16 overflow-hidden rounded-full bg-bg-h sm:w-20"> <div
class="h-1.5 w-16 overflow-hidden rounded-full bg-bg-h sm:w-20"
role="progressbar"
aria-label="Уверенность прогноза"
aria-valuemin="0"
aria-valuemax="100"
aria-valuenow={data.signal.confidence}
>
<div <div
class={cn( class={cn(
'h-full rounded-full transition-all duration-700', 'h-full rounded-full transition-all duration-700',
@@ -251,14 +270,9 @@
</div> </div>
<span class="ml-auto font-mono text-[0.5rem] tracking-widest text-desc/60 uppercase"> <span class="ml-auto font-mono text-[0.5rem] tracking-widest text-desc/60 uppercase">
не является инвестиционной рекомендацией исследовательские данные · не является инвестиционной рекомендацией
</span> </span>
</div> </div>
</div> </div>
</div> </div>
<span
class="pointer-events-none fixed top-0 left-1/2 h-80 w-full max-w-3xl -translate-x-1/2 rounded-full bg-primary opacity-[0.07] blur-[160px]"
aria-hidden="true"
></span>
</main> </main>
+16
View File
@@ -0,0 +1,16 @@
import { canonical } from '$lib/config/site';
export const GET = () => {
const body = `User-agent: *
Allow: /
Sitemap: ${canonical('/sitemap.xml')}
`;
return new Response(body, {
headers: {
'content-type': 'text/plain; charset=utf-8',
'cache-control': 'public, max-age=3600'
}
});
};
-4
View File
@@ -1,4 +0,0 @@
User-agent: *
Allow: /
Sitemap: https://trade.flamy.studio/sitemap.xml
+18 -1
View File
@@ -6,7 +6,24 @@ const config = {
// Force runes mode for the project, except for libraries. Can be removed in svelte 6. // Force runes mode for the project, except for libraries. Can be removed in svelte 6.
runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true) runes: ({ filename }) => (filename.split(/[/\\]/).includes('node_modules') ? undefined : true)
}, },
kit: { adapter: adapter() } kit: {
adapter: adapter(),
csp: {
mode: 'auto',
directives: {
'default-src': ['self'],
'script-src': ['self'],
'style-src': ['self', 'unsafe-inline'],
'img-src': ['self', 'data:'],
'font-src': ['self'],
'connect-src': ['self'],
'object-src': ['none'],
'base-uri': ['self'],
'frame-ancestors': ['self'],
'form-action': ['self']
}
}
}
}; };
export default config; export default config;