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
+4 -4
View File
@@ -39,7 +39,7 @@ export const posts: Post[] = [
{ type: 'heading', text: 'Stop Loss: где ваша позиция неправа' },
{
type: 'paragraph',
text: 'Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy AI уровень SL рассчитывается моделью на основе исторической волатильности инструмента.'
text: 'Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy Trade уровень SL рассчитывается моделью на основе исторической волатильности инструмента.'
},
{ type: 'heading', text: 'Take Profit: когда забирать прибыль' },
{
@@ -70,13 +70,13 @@ export const posts: Post[] = [
id: 2,
slug: 'kak-chitat-grafiki-flamy-ai',
date: '2026-05-17',
title: 'Как читать графики Flamy AI',
description: 'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy AI.',
title: 'Как читать графики Flamy Trade',
description: 'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy Trade.',
tags: ['ML', 'FORECAST', 'CHARTS'],
content: [
{
type: 'paragraph',
text: 'Графики Flamy AI содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.'
text: 'Графики Flamy Trade содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.'
},
{ type: 'heading', text: 'Исторические свечи' },
{
+2 -2
View File
@@ -7,6 +7,7 @@
CrosshairMode,
LineStyle,
type IChartApi,
type IPriceLine,
type ISeriesApi,
type MouseEventParams,
type UTCTimestamp
@@ -25,8 +26,7 @@
let chart: IChartApi | undefined;
let candleSeries: ISeriesApi<'Candlestick'> | undefined;
let predSeries: ISeriesApi<'Line'> | undefined;
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let priceLines: any[] = [];
let priceLines: IPriceLine[] = [];
type Tooltip = {
visible: boolean;
+40 -4
View File
@@ -1,4 +1,6 @@
<script lang="ts">
import { page } from '$app/state';
import { tick } from 'svelte';
import { onMount } from 'svelte';
import { gsap } from 'gsap';
import { cn } from '$lib/utils';
@@ -13,6 +15,9 @@
let mobileOpen = $state(false);
let header = $state<HTMLElement>(null!);
let menuButton = $state<HTMLButtonElement>(null!);
const mobileMenuId = 'mobile-navigation';
onMount(() => {
if (window.matchMedia('(prefers-reduced-motion: reduce)').matches) return;
@@ -24,8 +29,33 @@
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>
<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">
<div class="mx-auto flex max-w-400 items-center justify-between px-5 py-5 sm:px-10 lg:px-5">
<Logo />
@@ -37,6 +67,7 @@
<a
href={link.href}
class={cn('transition-colors duration-300', link.isButton ? 'nav-btn' : 'nav-link')}
aria-current={isActive(link.href) ? 'page' : undefined}
>
{link.text}
</a>
@@ -46,10 +77,13 @@
</nav>
<button
bind:this={menuButton}
type="button"
class="flex flex-col justify-center gap-1.5 p-1 lg:hidden"
onclick={() => (mobileOpen = !mobileOpen)}
onclick={toggleMobileMenu}
aria-label={mobileOpen ? 'Закрыть меню' : 'Открыть меню'}
aria-expanded={mobileOpen}
aria-controls={mobileMenuId}
>
<span
class={cn(
@@ -74,7 +108,7 @@
<!-- Мобильное выпадающее меню -->
{#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">
{#each menuLinks as link (link.id)}
<li>
@@ -82,7 +116,8 @@
<a
href={link.href}
class="nav-btn mt-3 block text-center"
onclick={() => (mobileOpen = false)}
onclick={() => closeMobileMenu()}
aria-current={isActive(link.href) ? 'page' : undefined}
>
{link.text}
</a>
@@ -90,7 +125,8 @@
<a
href={link.href}
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}
</a>
+34 -18
View File
@@ -5,29 +5,45 @@
type Props = {
href?: string;
target?: string;
type?: 'button' | 'submit' | 'reset';
variant?: 'primary' | 'secondary';
class?: string;
children: Snippet;
};
let { href, target = '_self', variant = 'primary', class: className, children }: Props = $props();
let {
href,
target = '_self',
type = 'button',
variant = 'primary',
class: className,
children
}: Props = $props();
const classes = $derived(
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',
variant === 'primary' && 'bg-primary hover:bg-primary-h',
variant === 'secondary' && 'border bg-bg hover:text-desc',
className
)
);
</script>
<svelte:element
this={href ? 'a' : 'button'}
{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',
variant === 'primary' && 'bg-primary hover:bg-primary-h',
variant === 'secondary' && 'border bg-bg hover:text-desc',
className
)}
>
{@render children()}
{#if href}
<a {href} {target} rel={target === '_blank' ? 'noopener noreferrer' : undefined} class={classes}>
{@render children()}
{#if variant === 'primary'}
<img src="images/icons/arrow-right.svg" alt="Перейти" class="h-5 w-5" />
{/if}
</svelte:element>
{#if variant === 'primary'}
<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}
+1 -1
View File
@@ -2,7 +2,7 @@
</script>
<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">
FLAMY<span class="text-primary">TRADE</span>
</span>
+22
View File
@@ -6,6 +6,12 @@ export const site = {
description: env.PUBLIC_SITE_DESCRIPTION || 'Публичная витрина ML-прогнозов для крипторынка.'
};
export type PageMeta = {
title: string;
description: string;
url: string;
};
export function pageTitle(title?: string): string {
return title ? `${title} | ${site.name}` : site.name;
}
@@ -14,3 +20,19 @@ export function canonical(path = '/'): string {
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
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 />
</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 top-55 left-0 z-0 hidden h-px w-full bg-bg-h xl:block"></span>
</section>
+18 -3
View File
@@ -1,10 +1,25 @@
<script lang="ts">
import { page } from '$app/state';
import { pageTitle } from '$lib/config/site';
const title = $derived(
page.status === 404
? 'Страница не найдена'
: page.status >= 500
? 'Внутренняя ошибка'
: 'Произошла ошибка'
);
const description = $derived(
page.status === 404
? 'Проверьте адрес или вернитесь к публичному дашборду Flamy Trade.'
: 'Запрос не удалось обработать. Попробуйте обновить страницу позже.'
);
</script>
<svelte:head>
<title>{pageTitle('Страница не найдена')}</title>
<title>{pageTitle(title)}</title>
<meta name="robots" content="noindex" />
</svelte:head>
<section class="flex min-h-screen items-center justify-center px-5 pt-16 text-center">
@@ -13,10 +28,10 @@
Ошибка {page.status}
</p>
<h1 class="font-display text-[clamp(2.75rem,9vw,6.5rem)] leading-none font-black uppercase">
Страница не найдена
{title}
</h1>
<p class="mx-auto mt-5 max-w-lg text-base leading-relaxed text-desc sm:text-lg">
Проверьте адрес или вернитесь к публичному дашборду Flamy Trade.
{description}
</p>
<a
href="/"
+1 -10
View File
@@ -2,24 +2,15 @@
import './layout.css';
import Header from '$lib/components/layout/Header.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();
</script>
<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" />
<meta property="og:type" content="website" />
<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:title" content={pageTitle()} />
<meta name="twitter:description" content={site.description} />
</svelte:head>
<div class="noise"></div>
+12 -7
View File
@@ -7,16 +7,21 @@
import BlogPreview from './(sections)/BlogPreview.svelte';
import Cta from './(sections)/Cta.svelte';
import { posts } from '$lib/blog/posts';
import { canonical, pageTitle, site } from '$lib/config/site';
import { pageMeta } from '$lib/config/site';
const meta = pageMeta();
</script>
<svelte:head>
<title>{pageTitle()}</title>
<meta name="description" content={site.description} />
<link rel="canonical" href={canonical('/')} />
<meta property="og:title" content={pageTitle()} />
<meta property="og:description" content={site.description} />
<meta property="og:url" content={canonical('/')} />
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="canonical" href={meta.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={meta.title} />
<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>
<main>
+20 -21
View File
@@ -1,23 +1,27 @@
<script lang="ts">
<script lang="ts">
import Button from '$lib/components/ui/Button.svelte';
import { about, params } from './_data';
import { canonical, pageTitle } from '$lib/config/site';
import { pageMeta } from '$lib/config/site';
import { scrollFadeUp, scrollRevealLeft } from '$lib/gsap/actions';
import { about, params } from './_data';
const meta = pageMeta({
title: 'О проекте',
description:
'Flamy Trade показывает исследовательские результаты ML-модели для анализа крипторынка.',
path: '/about'
});
</script>
<svelte:head>
<title>{pageTitle('О проекте')}</title>
<meta
name="description"
content="Flamy Trade показывает демонстрационные результаты ML-модели для анализа крипторынка."
/>
<link rel="canonical" href={canonical('/about')} />
<meta property="og:title" content={pageTitle('О проекте')} />
<meta
property="og:description"
content="Flamy Trade показывает демонстрационные результаты ML-модели для анализа крипторынка."
/>
<meta property="og:url" content={canonical('/about')} />
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="canonical" href={meta.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={meta.title} />
<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>
<main>
@@ -39,10 +43,6 @@
Публичная витрина результатов ML-модели, обученной на исторических рыночных данных.
</p>
</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>
<section class="grid max-h-full! grid-cols-1 lg:grid-cols-[1fr_420px]">
@@ -74,7 +74,6 @@
</div>
</div>
{: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="flex h-[50vh] w-full items-center justify-center rounded-2xl
@@ -108,7 +107,7 @@
<p
class="mb-2 flex items-center gap-2 font-display text-xs font-bold tracking-widest text-primary uppercase"
>
Дисклеймер
Дисклеймер
</p>
<p class="text-sm leading-normal text-desc">
Материалы являются результатом работы исследовательской ML-модели и используются в
+1 -1
View File
@@ -19,7 +19,7 @@ export const about: About[] = [
desc: 'Назначение',
title: 'Исследовательская платформа',
text: [
'Flamy AI — публичная витрина результатов ML-модели, обученной на исторических рыночных данных. Показывает прогнозы по крипто-инструментам: направление, уровни входа, Take Profit и Stop Loss.',
'Flamy Trade — публичная витрина результатов ML-модели, обученной на исторических рыночных данных. Показывает прогнозы по крипто-инструментам: направление, уровни входа, Take Profit и Stop Loss.',
'Платформа создана для изучения поведения ML-модели в рыночных условиях — не для торговли реальными средствами.'
]
},
+20 -20
View File
@@ -1,7 +1,14 @@
<script lang="ts">
<script lang="ts">
import { posts } from '$lib/blog/posts';
import { canonical, pageTitle } from '$lib/config/site';
import { scrollRevealLeft, scrollFadeUp, scrollStagger } from '$lib/gsap/actions';
import { pageMeta } from '$lib/config/site';
import { scrollFadeUp, scrollRevealLeft, scrollStagger } from '$lib/gsap/actions';
const meta = pageMeta({
title: 'Блог',
description:
'Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами.',
path: '/blog'
});
function fmtDate(d: string) {
try {
@@ -17,18 +24,15 @@
</script>
<svelte:head>
<title>{pageTitle('Блог')}</title>
<meta
name="description"
content="Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами."
/>
<link rel="canonical" href={canonical('/blog')} />
<meta property="og:title" content={pageTitle('Блог')} />
<meta
property="og:description"
content="Гайды Flamy Trade по чтению графиков, управлению рисками и работе с ML-прогнозами."
/>
<meta property="og:url" content={canonical('/blog')} />
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="canonical" href={meta.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={meta.title} />
<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>
<main>
@@ -47,10 +51,6 @@
Гайды по чтению графиков, управлению рисками и работе с ML-прогнозами.
</p>
</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
@@ -113,7 +113,7 @@
<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"
>
<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
d="M4 10 H16 M12 6 L16 10 L12 14"
stroke="currentColor"
+23 -16
View File
@@ -1,9 +1,16 @@
<script lang="ts">
<script lang="ts">
import type { PageData } from './$types';
import { canonical, pageTitle } from '$lib/config/site';
import { pageMeta } from '$lib/config/site';
let { data }: { data: PageData } = $props();
const { post } = $derived(data);
const meta = $derived(
pageMeta({
title: post.title,
description: post.description,
path: `/blog/${post.slug}`
})
);
function fmtDate(d: string) {
try {
@@ -19,16 +26,16 @@
</script>
<svelte:head>
<title>{pageTitle(post.title)}</title>
<meta name="description" content={post.description} />
<link rel="canonical" href={canonical(`/blog/${post.slug}`)} />
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="canonical" href={meta.url} />
<meta property="og:type" content="article" />
<meta property="og:title" content={pageTitle(post.title)} />
<meta property="og:description" content={post.description} />
<meta property="og:url" content={canonical(`/blog/${post.slug}`)} />
<meta property="og:title" content={meta.title} />
<meta property="og:description" content={meta.description} />
<meta property="og:url" content={meta.url} />
<meta property="article:published_time" content={post.date} />
<meta name="twitter:title" content={pageTitle(post.title)} />
<meta name="twitter:description" content={post.description} />
<meta name="twitter:title" content={meta.title} />
<meta name="twitter:description" content={meta.description} />
</svelte:head>
<main>
@@ -37,7 +44,7 @@
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"
>
<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
d="M11 7 H3 M6 4 L3 7 L6 10"
stroke="currentColor"
@@ -63,7 +70,7 @@
{/if}
<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}
</h1>
@@ -101,7 +108,7 @@
href="/blog"
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
d="M11 7 H3 M6 4 L3 7 L6 10"
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"
>
Открыть дашборд
<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>
</div>
</div>
@@ -139,7 +146,7 @@
font-size: 1.25rem;
font-weight: 800;
color: var(--color-title);
letter-spacing: -0.025em;
letter-spacing: 0;
line-height: 1.2;
margin-top: 2.25em;
margin-bottom: 0.75em;
@@ -149,7 +156,7 @@
font-size: 1.0625rem;
font-weight: 700;
color: var(--color-title);
letter-spacing: -0.02em;
letter-spacing: 0;
margin-top: 1.75em;
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 type { PageLoad } from './$types';
+37 -23
View File
@@ -1,17 +1,30 @@
<script lang="ts">
import { onMount } from 'svelte';
import { gsap } from 'gsap';
import { generateChartData, SYMBOLS, TIMEFRAMES, type TimeframeId } from '$lib/stores/chartStore';
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';
const meta = pageMeta({
title: 'Дашборд',
description:
'Публичный исследовательский дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL.',
path: '/dashboard'
});
let activeSymbol = $state('BTCUSDT');
let activeTimeframe = $state<TimeframeId>('5m');
let chartHeight = $state(520);
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 panelEl = $state<HTMLElement>(null!);
@@ -66,18 +79,15 @@
</script>
<svelte:head>
<title>{pageTitle('Дашборд')}</title>
<meta
name="description"
content="Демонстрационный дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL."
/>
<link rel="canonical" href={canonical('/dashboard')} />
<meta property="og:title" content={pageTitle('Дашборд')} />
<meta
property="og:description"
content="Демонстрационный дашборд Flamy Trade с графиком, ML-прогнозом и уровнями Entry, TP и SL."
/>
<meta property="og:url" content={canonical('/dashboard')} />
<title>{meta.title}</title>
<meta name="description" content={meta.description} />
<link rel="canonical" href={meta.url} />
<meta property="og:type" content="website" />
<meta property="og:title" content={meta.title} />
<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>
<main class="min-h-screen bg-bg">
@@ -89,7 +99,7 @@
Дашборд
</h1>
<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>
</div>
@@ -112,6 +122,7 @@
: 'border-transparent text-desc hover:text-title'
)}
onclick={() => (activeSymbol = s.id)}
aria-pressed={activeSymbol === s.id}
>
{s.short}
</button>
@@ -126,6 +137,7 @@
activeTimeframe === tf.id ? 'bg-bg-h text-title' : 'text-desc hover:text-title'
)}
onclick={() => (activeTimeframe = tf.id)}
aria-pressed={activeTimeframe === tf.id}
>
{tf.label}
</button>
@@ -177,7 +189,14 @@
>
Уверенность
</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
class={cn(
'h-full rounded-full transition-all duration-700',
@@ -251,14 +270,9 @@
</div>
<span class="ml-auto font-mono text-[0.5rem] tracking-widest text-desc/60 uppercase">
не является инвестиционной рекомендацией
исследовательские данные · не является инвестиционной рекомендацией
</span>
</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>
+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'
}
});
};