Prepare production deployment
This commit is contained in:
+1
-2
@@ -1,9 +1,8 @@
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<html lang="ru">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
||||
<meta name="text-scale" content="scale" />
|
||||
%sveltekit.head%
|
||||
</head>
|
||||
<body data-sveltekit-preload-data="hover">
|
||||
|
||||
+86
-37
@@ -1,11 +1,25 @@
|
||||
export type Post = {
|
||||
export type PostContentBlock =
|
||||
| {
|
||||
type: 'paragraph';
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: 'heading';
|
||||
text: string;
|
||||
}
|
||||
| {
|
||||
type: 'list';
|
||||
items: string[];
|
||||
};
|
||||
|
||||
export type Post = {
|
||||
id: number;
|
||||
slug: string;
|
||||
date: string;
|
||||
title: string;
|
||||
description: string;
|
||||
tags: string[];
|
||||
content: string;
|
||||
content: PostContentBlock[];
|
||||
};
|
||||
|
||||
export const posts: Post[] = [
|
||||
@@ -17,50 +31,85 @@ export const posts: Post[] = [
|
||||
description:
|
||||
'Как использовать уровни Stop Loss и Take Profit для грамотного управления капиталом при работе с прогнозами.',
|
||||
tags: ['RISK', 'TRADING', 'EDUCATION'],
|
||||
content: `
|
||||
<p>Управление рисками — фундамент любой торговой стратегии. Без него даже точный ML-прогноз не поможет сохранить капитал.</p>
|
||||
<h2>Stop Loss: где ваша позиция неправа</h2>
|
||||
<p>Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy AI уровень SL рассчитывается моделью на основе исторической волатильности инструмента.</p>
|
||||
<h2>Take Profit: когда забирать прибыль</h2>
|
||||
<p>Take Profit — целевой уровень закрытия позиции с прибылью. TP рассчитывается пропорционально прогнозному движению с учётом Risk:Reward ratio.</p>
|
||||
<h2>Risk:Reward Ratio</h2>
|
||||
<p>Оптимальное соотношение риска к доходности — не менее <strong>1:2</strong>. Это означает, что потенциальная прибыль должна минимум вдвое превышать риск.</p>
|
||||
<ul>
|
||||
<li>SL: не более 1% от капитала на сделку</li>
|
||||
<li>TP: 2% и выше от точки входа</li>
|
||||
<li>Одна сделка: не более 2% депозита</li>
|
||||
</ul>
|
||||
<h2>Почему это важно при работе с ML-прогнозами</h2>
|
||||
<p>ML-модель не даёт гарантий. Уверенность 80% означает, что 20% прогнозов будут неточными. Управление рисками — страховка на эти 20%.</p>
|
||||
`
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Управление рисками — фундамент любой торговой стратегии. Без него даже точный ML-прогноз не поможет сохранить капитал.'
|
||||
},
|
||||
{ type: 'heading', text: 'Stop Loss: где ваша позиция неправа' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Stop Loss — уровень, при достижении которого позиция закрывается автоматически. Это не признание ошибки, а часть стратегии. На графиках Flamy AI уровень SL рассчитывается моделью на основе исторической волатильности инструмента.'
|
||||
},
|
||||
{ type: 'heading', text: 'Take Profit: когда забирать прибыль' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Take Profit — целевой уровень закрытия позиции с прибылью. TP рассчитывается пропорционально прогнозному движению с учётом Risk:Reward ratio.'
|
||||
},
|
||||
{ type: 'heading', text: 'Risk:Reward Ratio' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Оптимальное соотношение риска к доходности — не менее 1:2. Это означает, что потенциальная прибыль должна минимум вдвое превышать риск.'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
items: [
|
||||
'SL: не более 1% от капитала на сделку',
|
||||
'TP: 2% и выше от точки входа',
|
||||
'Одна сделка: не более 2% депозита'
|
||||
]
|
||||
},
|
||||
{ type: 'heading', text: 'Почему это важно при работе с ML-прогнозами' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'ML-модель не даёт гарантий. Уверенность 80% означает, что 20% прогнозов будут неточными. Управление рисками — страховка на эти 20%.'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
slug: 'kak-chitat-grafiki-flamy-ai',
|
||||
date: '2026-05-17',
|
||||
title: 'Как читать графики Flamy AI',
|
||||
description:
|
||||
'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy AI.',
|
||||
description: 'Краткое объяснение свечей, прогнозной зоны, TP и SL на графиках Flamy AI.',
|
||||
tags: ['ML', 'FORECAST', 'CHARTS'],
|
||||
content: `
|
||||
<p>Графики Flamy AI содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.</p>
|
||||
<h2>Исторические свечи</h2>
|
||||
<p>Левая часть графика — реальные OHLC-свечи. Зелёные — бычьи (close > open), красные — медвежьи. Это основа для анализа модели.</p>
|
||||
<h2>Прогнозная зона</h2>
|
||||
<p>Правая часть — прогнозные свечи на горизонт <strong>19 периодов</strong> вперёд. Это не точные значения, а вероятностный коридор движения цены на ближайшие 95 минут.</p>
|
||||
<h2>Уровни Entry, TP и SL</h2>
|
||||
<p>Если модель публикует режим TRADE_PLAN, на графике появляются три горизонтальные линии:</p>
|
||||
<ul>
|
||||
<li><strong>Entry</strong> — рекомендуемый уровень входа в позицию</li>
|
||||
<li><strong>TP</strong> — цель Take Profit</li>
|
||||
<li><strong>SL</strong> — Stop Loss для ограничения убытков</li>
|
||||
</ul>
|
||||
<h2>Таймфрейм и горизонт</h2>
|
||||
<p>Все графики работают на таймфрейме <strong>5 минут</strong>. Горизонт прогноза — 19 свечей = 95 минут вперёд. Прогнозы обновляются каждый час автоматически.</p>
|
||||
`
|
||||
content: [
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Графики Flamy AI содержат три слоя: исторические свечи, прогнозную зону и уровни торгового плана. Разберём каждый из них.'
|
||||
},
|
||||
{ type: 'heading', text: 'Исторические свечи' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Левая часть графика — реальные OHLC-свечи. Зелёные — бычьи, красные — медвежьи. Это основа для анализа модели.'
|
||||
},
|
||||
{ type: 'heading', text: 'Прогнозная зона' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Правая часть — прогнозные свечи на горизонт 19 периодов вперёд. Это не точные значения, а вероятностный коридор движения цены на ближайшие 95 минут.'
|
||||
},
|
||||
{ type: 'heading', text: 'Уровни Entry, TP и SL' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Если модель публикует режим TRADE_PLAN, на графике появляются три горизонтальные линии.'
|
||||
},
|
||||
{
|
||||
type: 'list',
|
||||
items: [
|
||||
'Entry — рекомендуемый уровень входа в позицию',
|
||||
'TP — цель Take Profit',
|
||||
'SL — Stop Loss для ограничения убытков'
|
||||
]
|
||||
},
|
||||
{ type: 'heading', text: 'Таймфрейм и горизонт' },
|
||||
{
|
||||
type: 'paragraph',
|
||||
text: 'Все графики работают на таймфрейме 5 минут. Горизонт прогноза — 19 свечей = 95 минут вперёд. Прогнозы обновляются каждый час автоматически.'
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
||||
export function getPostBySlug(slug: string): Post | undefined {
|
||||
return posts.find((p) => p.slug === slug);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@
|
||||
type IChartApi,
|
||||
type ISeriesApi,
|
||||
type MouseEventParams,
|
||||
type UTCTimestamp,
|
||||
type UTCTimestamp
|
||||
} from 'lightweight-charts';
|
||||
import type { ChartData, CandleBar } from '$lib/stores/chartStore';
|
||||
|
||||
@@ -50,7 +50,7 @@
|
||||
high: 0,
|
||||
low: 0,
|
||||
close: 0,
|
||||
isUp: true,
|
||||
isUp: true
|
||||
});
|
||||
|
||||
function fmt(n: number): string {
|
||||
@@ -75,7 +75,7 @@
|
||||
lineWidth: 1,
|
||||
lineStyle: LineStyle.Solid,
|
||||
axisLabelVisible: true,
|
||||
title: 'Entry',
|
||||
title: 'Entry'
|
||||
}),
|
||||
candleSeries.createPriceLine({
|
||||
price: d.signal.tp,
|
||||
@@ -83,7 +83,7 @@
|
||||
lineWidth: 1,
|
||||
lineStyle: LineStyle.Dashed,
|
||||
axisLabelVisible: true,
|
||||
title: 'TP',
|
||||
title: 'TP'
|
||||
}),
|
||||
candleSeries.createPriceLine({
|
||||
price: d.signal.sl,
|
||||
@@ -91,8 +91,8 @@
|
||||
lineWidth: 1,
|
||||
lineStyle: LineStyle.Dashed,
|
||||
axisLabelVisible: true,
|
||||
title: 'SL',
|
||||
}),
|
||||
title: 'SL'
|
||||
})
|
||||
];
|
||||
|
||||
chart.timeScale().fitContent();
|
||||
@@ -107,11 +107,11 @@
|
||||
textColor: '#8a887f',
|
||||
fontFamily: "'JetBrains Mono', monospace",
|
||||
fontSize: 11,
|
||||
attributionLogo: false,
|
||||
attributionLogo: false
|
||||
},
|
||||
grid: {
|
||||
vertLines: { color: '#1a191e' },
|
||||
horzLines: { color: '#1a191e' },
|
||||
horzLines: { color: '#1a191e' }
|
||||
},
|
||||
crosshair: {
|
||||
mode: CrosshairMode.Normal,
|
||||
@@ -119,25 +119,25 @@
|
||||
color: '#3d3b42',
|
||||
labelBackgroundColor: '#1a191e',
|
||||
style: LineStyle.Dashed,
|
||||
width: 1,
|
||||
width: 1
|
||||
},
|
||||
horzLine: {
|
||||
color: '#3d3b42',
|
||||
labelBackgroundColor: '#1a191e',
|
||||
style: LineStyle.Dashed,
|
||||
width: 1,
|
||||
},
|
||||
width: 1
|
||||
}
|
||||
},
|
||||
rightPriceScale: {
|
||||
borderColor: '#1a191e',
|
||||
scaleMargins: { top: 0.06, bottom: 0.04 },
|
||||
scaleMargins: { top: 0.06, bottom: 0.04 }
|
||||
},
|
||||
timeScale: {
|
||||
borderColor: '#1a191e',
|
||||
timeVisible: true,
|
||||
secondsVisible: false,
|
||||
rightOffset: 24,
|
||||
},
|
||||
rightOffset: 24
|
||||
}
|
||||
});
|
||||
|
||||
candleSeries = chart.addSeries(CandlestickSeries, {
|
||||
@@ -146,7 +146,7 @@
|
||||
borderUpColor: '#26a69a',
|
||||
borderDownColor: '#ef5350',
|
||||
wickUpColor: '#26a69a',
|
||||
wickDownColor: '#ef5350',
|
||||
wickDownColor: '#ef5350'
|
||||
});
|
||||
|
||||
predSeries = chart.addSeries(LineSeries, {
|
||||
@@ -158,7 +158,7 @@
|
||||
crosshairMarkerBorderColor: '#fe4b07',
|
||||
crosshairMarkerBackgroundColor: '#09080a',
|
||||
priceLineVisible: false,
|
||||
lastValueVisible: true,
|
||||
lastValueVisible: true
|
||||
});
|
||||
|
||||
applyData(data);
|
||||
@@ -169,17 +169,22 @@
|
||||
return;
|
||||
}
|
||||
const c = param.seriesData.get(candleSeries) as CandleBar;
|
||||
if (!c) { tip = { ...tip, visible: false }; return; }
|
||||
if (!c) {
|
||||
tip = { ...tip, visible: false };
|
||||
return;
|
||||
}
|
||||
|
||||
const pred = predSeries && param.seriesData.has(predSeries)
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
? (param.seriesData.get(predSeries) as any)?.value
|
||||
: undefined;
|
||||
const pred =
|
||||
predSeries && param.seriesData.has(predSeries)
|
||||
? (param.seriesData.get(predSeries) as { value?: number })?.value
|
||||
: undefined;
|
||||
|
||||
const ts = param.time as number;
|
||||
const d = new Date(ts * 1000);
|
||||
const timeStr = d.toLocaleDateString('ru-RU', { day: '2-digit', month: 'short' })
|
||||
+ ' ' + d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
||||
const timeStr =
|
||||
d.toLocaleDateString('ru-RU', { day: '2-digit', month: 'short' }) +
|
||||
' ' +
|
||||
d.toLocaleTimeString('ru-RU', { hour: '2-digit', minute: '2-digit' });
|
||||
|
||||
tip = {
|
||||
visible: true,
|
||||
@@ -191,14 +196,14 @@
|
||||
low: c.low,
|
||||
close: c.close,
|
||||
isUp: c.close >= c.open,
|
||||
pred,
|
||||
pred
|
||||
};
|
||||
});
|
||||
|
||||
const ro = new ResizeObserver(() => {
|
||||
chart?.applyOptions({
|
||||
width: container.clientWidth,
|
||||
height: container.clientHeight,
|
||||
height: container.clientHeight
|
||||
});
|
||||
});
|
||||
ro.observe(container);
|
||||
@@ -229,7 +234,7 @@
|
||||
style="left: {left}px; top: {top}px"
|
||||
>
|
||||
<p class="mb-2 text-[0.5625rem] tracking-widest text-desc uppercase">{tip.time}</p>
|
||||
{#each [['O', tip.open], ['H', tip.high], ['L', tip.low], ['C', tip.close]] as [lbl, val]}
|
||||
{#each [['O', tip.open], ['H', tip.high], ['L', tip.low], ['C', tip.close]] as [lbl, val] (lbl)}
|
||||
<div class="flex justify-between gap-5">
|
||||
<span class="text-desc">{lbl}</span>
|
||||
<span class={tip.isUp ? 'text-emerald-400' : 'text-red-400'}>{fmt(val as number)}</span>
|
||||
@@ -237,7 +242,7 @@
|
||||
{/each}
|
||||
{#if tip.pred !== undefined}
|
||||
<div class="mt-1.5 flex justify-between gap-5 border-t border-white/8 pt-1.5">
|
||||
<span class="text-primary text-[0.5625rem] tracking-widest uppercase">ML</span>
|
||||
<span class="text-[0.5625rem] tracking-widest text-primary uppercase">ML</span>
|
||||
<span class="text-primary">{fmt(tip.pred)}</span>
|
||||
</div>
|
||||
{/if}
|
||||
|
||||
@@ -62,7 +62,7 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<div class="w-full max-w-170 z-20 mx-auto xl:mx-0" style="perspective: 1000px;">
|
||||
<div class="z-20 mx-auto w-full max-w-170 xl:mx-0" style="perspective: 1000px;">
|
||||
<div
|
||||
class="overflow-hidden rounded-2xl border border-white/10 bg-zinc-950 shadow-[0_40px_100px_rgba(0,0,0,0.7),0_0_0_1px_rgba(255,255,255,0.04)] will-change-transform"
|
||||
style="transform: perspective(1000px) rotateY(-6deg) rotateX(3deg); transform-style: preserve-3d;"
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
};
|
||||
</script>
|
||||
|
||||
<div class="hidden h-full w-full max-w-140 items-center justify-end lg:flex z-20">
|
||||
<div class="z-20 hidden h-full w-full max-w-140 items-center justify-end lg:flex">
|
||||
<div
|
||||
class="w-full overflow-hidden rounded-2xl border border-white/8 bg-zinc-950 shadow-[0_0_0_1px_rgba(255,255,255,0.03),0_40px_80px_rgba(0,0,0,0.5),0_0_80px_rgba(255,69,0,0.05)]"
|
||||
>
|
||||
@@ -81,9 +81,7 @@
|
||||
class="grid grid-cols-[1fr_56px_76px] gap-2.5 border-b border-white/6 bg-white/1 px-4 py-3.5"
|
||||
>
|
||||
<span class="font-mono text-[0.55rem] tracking-widest text-zinc-600">ИНСТРУМЕНТ</span>
|
||||
<span class="text-right font-mono text-[0.55rem] tracking-widest text-zinc-600"
|
||||
>КОНФИД.</span
|
||||
>
|
||||
<span class="text-right font-mono text-[0.55rem] tracking-widest text-zinc-600">КОНФИД.</span>
|
||||
<span class="pl-2 font-mono text-[0.55rem] tracking-widest text-zinc-600">ПРОГНОЗ</span>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -12,7 +12,6 @@
|
||||
|
||||
<footer class="mt-auto border-t border-white/6 bg-bg-e">
|
||||
<div class="mx-auto max-w-400 px-5 py-20 sm:px-10">
|
||||
|
||||
<div class="grid grid-cols-2 gap-10 sm:grid-cols-4 sm:justify-between">
|
||||
<div class="col-span-2 flex flex-col gap-4 lg:col-span-1">
|
||||
<Logo />
|
||||
@@ -23,7 +22,9 @@
|
||||
</div>
|
||||
|
||||
<div class="flex flex-col gap-1">
|
||||
<span class="font-mono text-base tracking-widest text-zinc-600 uppercase mb-1">Навигация</span>
|
||||
<span class="mb-1 font-mono text-base tracking-widest text-zinc-600 uppercase"
|
||||
>Навигация</span
|
||||
>
|
||||
{#each navLinks as link (link.id)}
|
||||
<a
|
||||
href={link.href}
|
||||
@@ -59,6 +60,5 @@
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
@@ -21,12 +21,12 @@
|
||||
opacity: 0,
|
||||
duration: 0.5,
|
||||
ease: 'power2.out',
|
||||
clearProps: 'transform,opacity',
|
||||
clearProps: 'transform,opacity'
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
<header bind:this={header} class="fixed right-0 left-0 border-b border-b-bg-h bg-bg-e z-9999">
|
||||
<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 />
|
||||
|
||||
@@ -60,7 +60,7 @@
|
||||
<span
|
||||
class={cn(
|
||||
'block h-0.5 w-6 bg-title transition-all duration-200',
|
||||
mobileOpen && 'opacity-0 scale-x-0'
|
||||
mobileOpen && 'scale-x-0 opacity-0'
|
||||
)}
|
||||
></span>
|
||||
<span
|
||||
|
||||
@@ -19,9 +19,9 @@
|
||||
{target}
|
||||
rel={target === '_blank' ? 'noopener noreferrer' : undefined}
|
||||
class={cn(
|
||||
'flex items-center justify-center gap-2 w-fit 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 === 'secondary' && 'bg-bg border hover:text-desc',
|
||||
variant === 'secondary' && 'border bg-bg hover:text-desc',
|
||||
className
|
||||
)}
|
||||
>
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
<script lang="ts">
|
||||
</script>
|
||||
|
||||
<a href="/" class="flex items-center gap-2.5 no-underline max-w-fit">
|
||||
<img src="images/icons/logo.svg" alt="Логотип" class="w-8 h-8" />
|
||||
<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" />
|
||||
<span class="font-display text-xl font-black tracking-tighter">
|
||||
FLAMY<span class="text-primary">TRADE</span>
|
||||
</span>
|
||||
</a>
|
||||
</a>
|
||||
|
||||
@@ -0,0 +1,16 @@
|
||||
import { env } from '$env/dynamic/public';
|
||||
|
||||
export const site = {
|
||||
name: env.PUBLIC_SITE_NAME || 'Flamy Trade',
|
||||
url: (env.PUBLIC_SITE_URL || 'https://trade.flamy.studio').replace(/\/$/, ''),
|
||||
description: env.PUBLIC_SITE_DESCRIPTION || 'Публичная витрина ML-прогнозов для крипторынка.'
|
||||
};
|
||||
|
||||
export function pageTitle(title?: string): string {
|
||||
return title ? `${title} | ${site.name}` : site.name;
|
||||
}
|
||||
|
||||
export function canonical(path = '/'): string {
|
||||
const normalizedPath = path.startsWith('/') ? path : `/${path}`;
|
||||
return `${site.url}${normalizedPath}`;
|
||||
}
|
||||
@@ -67,7 +67,7 @@ export function scrollFadeUp(el: HTMLElement, params: FadeUpParams = {}) {
|
||||
delay,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: el, start, once: true },
|
||||
scrollTrigger: { trigger: el, start, once: true }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -93,7 +93,7 @@ export function scrollRevealLeft(el: HTMLElement, params: RevealLeftParams = {})
|
||||
delay,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: el, start, once: true },
|
||||
scrollTrigger: { trigger: el, start, once: true }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -115,7 +115,7 @@ export function scrollStagger(el: HTMLElement, params: StaggerParams = {}) {
|
||||
duration = 0.65,
|
||||
stagger = 0.1,
|
||||
start = 'top 80%',
|
||||
selector = ':scope > *',
|
||||
selector = ':scope > *'
|
||||
} = params;
|
||||
|
||||
const targets = el.querySelectorAll<HTMLElement>(selector);
|
||||
@@ -133,7 +133,7 @@ export function scrollStagger(el: HTMLElement, params: StaggerParams = {}) {
|
||||
ease: 'power3.out',
|
||||
stagger: { each: stagger, ease: 'power1.inOut' },
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: el, start, once: true },
|
||||
scrollTrigger: { trigger: el, start, once: true }
|
||||
});
|
||||
});
|
||||
|
||||
@@ -160,7 +160,7 @@ export function scrollScaleIn(el: HTMLElement, params: { delay?: number; start?:
|
||||
delay,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: el, start, once: true },
|
||||
scrollTrigger: { trigger: el, start, once: true }
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -39,7 +39,7 @@ export const SYMBOLS = [
|
||||
{ id: 'XRPUSDT', label: 'XRP/USDT', short: 'XRP', base: 0.55, dec: 5 },
|
||||
{ id: 'ADAUSDT', label: 'ADA/USDT', short: 'ADA', base: 0.43, dec: 5 },
|
||||
{ id: 'DOGEUSDT', label: 'DOGE/USDT', short: 'DOGE', base: 0.131, dec: 5 },
|
||||
{ id: 'LINKUSDT', label: 'LINK/USDT', short: 'LINK', base: 18.5, dec: 3 },
|
||||
{ id: 'LINKUSDT', label: 'LINK/USDT', short: 'LINK', base: 18.5, dec: 3 }
|
||||
] as const;
|
||||
|
||||
export const TIMEFRAMES = [
|
||||
@@ -48,7 +48,7 @@ export const TIMEFRAMES = [
|
||||
{ id: '15m' as TimeframeId, label: '15м', sec: 900 },
|
||||
{ id: '1h' as TimeframeId, label: '1ч', sec: 3600 },
|
||||
{ id: '4h' as TimeframeId, label: '4ч', sec: 14400 },
|
||||
{ id: '1d' as TimeframeId, label: '1д', sec: 86400 },
|
||||
{ id: '1d' as TimeframeId, label: '1д', sec: 86400 }
|
||||
];
|
||||
|
||||
// Simple LCG PRNG — deterministic per seed so switching back gives the same chart
|
||||
@@ -91,7 +91,13 @@ export function generateChartData(symbolId: string, tfId: TimeframeId): ChartDat
|
||||
const low = Math.min(open, close) - bodyH * wk * 0.7 - rand() * vol * 0.2;
|
||||
|
||||
const round = (n: number) => parseFloat(n.toFixed(Math.min(sym.dec + 2, 8)));
|
||||
candles.push({ time: startTime + i * tf.sec, open: round(open), high: round(high), low: round(low), close: round(close) });
|
||||
candles.push({
|
||||
time: startTime + i * tf.sec,
|
||||
open: round(open),
|
||||
high: round(high),
|
||||
low: round(low),
|
||||
close: round(close)
|
||||
});
|
||||
price = close;
|
||||
}
|
||||
|
||||
@@ -107,15 +113,12 @@ export function generateChartData(symbolId: string, tfId: TimeframeId): ChartDat
|
||||
const predDir = rand() > 0.42 ? 1 : -1;
|
||||
const predStrength = (0.006 + rand() * 0.018) * sym.base;
|
||||
|
||||
const prediction: PredPoint[] = [
|
||||
{ time: candles[HISTORY - 1].time, value: currentPrice },
|
||||
];
|
||||
const prediction: PredPoint[] = [{ time: candles[HISTORY - 1].time, value: currentPrice }];
|
||||
|
||||
let pPrice = currentPrice;
|
||||
for (let i = 1; i <= HORIZON; i++) {
|
||||
const t = i / HORIZON;
|
||||
const noise = (rand() - 0.5) * vol * 0.6;
|
||||
pPrice = currentPrice + predDir * predStrength * t + noise;
|
||||
const pPrice = currentPrice + predDir * predStrength * t + noise;
|
||||
const round = (n: number) => parseFloat(n.toFixed(Math.min(sym.dec + 2, 8)));
|
||||
prediction.push({ time: now + i * tf.sec, value: round(Math.max(pPrice, currentPrice * 0.1)) });
|
||||
}
|
||||
@@ -127,8 +130,12 @@ export function generateChartData(symbolId: string, tfId: TimeframeId): ChartDat
|
||||
|
||||
const round = (n: number) => parseFloat(n.toFixed(Math.min(sym.dec + 2, 8)));
|
||||
const entry = round(currentPrice);
|
||||
const tp = round(direction === 'long' ? currentPrice + predRange * 1.3 : currentPrice - predRange * 1.3);
|
||||
const sl = round(direction === 'long' ? currentPrice - predRange * 0.65 : currentPrice + predRange * 0.65);
|
||||
const tp = round(
|
||||
direction === 'long' ? currentPrice + predRange * 1.3 : currentPrice - predRange * 1.3
|
||||
);
|
||||
const sl = round(
|
||||
direction === 'long' ? currentPrice - predRange * 0.65 : currentPrice + predRange * 0.65
|
||||
);
|
||||
|
||||
return {
|
||||
candles,
|
||||
@@ -136,6 +143,6 @@ export function generateChartData(symbolId: string, tfId: TimeframeId): ChartDat
|
||||
signal: { direction, confidence, entry, tp, sl },
|
||||
currentPrice,
|
||||
change24h,
|
||||
change24hPct,
|
||||
change24hPct
|
||||
};
|
||||
}
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
lg:h-[70vh] lg:flex-row lg:items-center lg:justify-center lg:gap-40 lg:py-0"
|
||||
>
|
||||
<div>
|
||||
<h2
|
||||
use:scrollRevealLeft
|
||||
class="font-display text-4xl font-bold sm:text-5xl lg:text-7xl"
|
||||
>Дашборд</h2>
|
||||
<h2 use:scrollRevealLeft class="font-display text-4xl font-bold sm:text-5xl lg:text-7xl">
|
||||
Дашборд
|
||||
</h2>
|
||||
|
||||
<p
|
||||
use:scrollFadeUp={{ delay: 0.1 }}
|
||||
|
||||
@@ -27,10 +27,7 @@
|
||||
{#if posts.length > 0}
|
||||
<div class="border-b border-white/6">
|
||||
<section class="px-5 py-24 sm:px-10 sm:py-32">
|
||||
<div
|
||||
use:scrollFadeUp={{ y: 20 }}
|
||||
class="mb-12 flex items-end justify-between"
|
||||
>
|
||||
<div use:scrollFadeUp={{ y: 20 }} class="mb-12 flex items-end justify-between">
|
||||
<h2 class="font-display text-4xl font-black tracking-tighter uppercase">БЛОГ</h2>
|
||||
<a
|
||||
href="/blog"
|
||||
@@ -44,7 +41,7 @@
|
||||
use:scrollStagger={{ selector: 'a', stagger: 0.1, y: 32 }}
|
||||
class="grid grid-cols-1 overflow-hidden rounded-xl border border-white/6 sm:grid-cols-[repeat(auto-fit,minmax(20rem,1fr))]"
|
||||
>
|
||||
{#each posts as post, i}
|
||||
{#each posts as post, i (post.slug)}
|
||||
<a
|
||||
href="/blog/{post.slug}"
|
||||
class="flex flex-col gap-5 p-10 no-underline transition-colors duration-150 hover:bg-white/2
|
||||
|
||||
@@ -12,35 +12,42 @@
|
||||
gsap.registerPlugin(ScrollTrigger);
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
const heading = section.querySelector<HTMLElement>('[data-cta-heading]');
|
||||
const btnWrap = section.querySelector<HTMLElement>('[data-cta-btn]');
|
||||
const heading = section.querySelector<HTMLElement>('[data-cta-heading]');
|
||||
const btnWrap = section.querySelector<HTMLElement>('[data-cta-btn]');
|
||||
const disclaimer = section.querySelector<HTMLElement>('[data-cta-note]');
|
||||
|
||||
if (heading) {
|
||||
gsap.set(heading, { opacity: 0, scale: 0.88, y: 32 });
|
||||
gsap.to(heading, {
|
||||
opacity: 1, scale: 1, y: 0,
|
||||
duration: 0.8, ease: 'power3.out',
|
||||
opacity: 1,
|
||||
scale: 1,
|
||||
y: 0,
|
||||
duration: 0.8,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true },
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true }
|
||||
});
|
||||
}
|
||||
if (btnWrap) {
|
||||
gsap.set(btnWrap, { opacity: 0, y: 20 });
|
||||
gsap.to(btnWrap, {
|
||||
opacity: 1, y: 0,
|
||||
duration: 0.6, delay: 0.2, ease: 'power3.out',
|
||||
opacity: 1,
|
||||
y: 0,
|
||||
duration: 0.6,
|
||||
delay: 0.2,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'transform,opacity',
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true },
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true }
|
||||
});
|
||||
}
|
||||
if (disclaimer) {
|
||||
gsap.set(disclaimer, { opacity: 0 });
|
||||
gsap.to(disclaimer, {
|
||||
opacity: 1,
|
||||
duration: 0.6, delay: 0.4,
|
||||
duration: 0.6,
|
||||
delay: 0.4,
|
||||
clearProps: 'opacity',
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true },
|
||||
scrollTrigger: { trigger: section, start: 'top 75%', once: true }
|
||||
});
|
||||
}
|
||||
}, section);
|
||||
|
||||
@@ -42,7 +42,9 @@
|
||||
<h2
|
||||
use:scrollRevealLeft
|
||||
class="mb-16 font-display text-4xl font-black tracking-tighter uppercase"
|
||||
>ВОЗМОЖНОСТИ</h2>
|
||||
>
|
||||
ВОЗМОЖНОСТИ
|
||||
</h2>
|
||||
|
||||
<div
|
||||
use:scrollStagger={{ selector: '[data-feature-row]', stagger: 0.07, y: 28 }}
|
||||
|
||||
@@ -11,26 +11,26 @@
|
||||
|
||||
const ctx = gsap.context(() => {
|
||||
const words = section.querySelectorAll<HTMLElement>('[data-hero-word]');
|
||||
const desc = section.querySelector<HTMLElement>('[data-hero-desc]');
|
||||
const cta = section.querySelector<HTMLElement>('[data-hero-cta]');
|
||||
const mock = section.querySelector<HTMLElement>('[data-hero-mockup]');
|
||||
const desc = section.querySelector<HTMLElement>('[data-hero-desc]');
|
||||
const cta = section.querySelector<HTMLElement>('[data-hero-cta]');
|
||||
const mock = section.querySelector<HTMLElement>('[data-hero-mockup]');
|
||||
|
||||
gsap.set(words, { opacity: 0, y: 44, skewX: -6 });
|
||||
gsap.set(desc, { opacity: 0, y: 24 });
|
||||
gsap.set(cta, { opacity: 0, y: 16 });
|
||||
gsap.set(mock, { opacity: 0, x: 48 });
|
||||
gsap.set(desc, { opacity: 0, y: 24 });
|
||||
gsap.set(cta, { opacity: 0, y: 16 });
|
||||
gsap.set(mock, { opacity: 0, x: 48 });
|
||||
|
||||
const tl = gsap.timeline({
|
||||
defaults: { ease: 'power3.out' },
|
||||
onComplete: () => gsap.set([words, desc, cta, mock], { clearProps: 'all' }),
|
||||
onComplete: () => gsap.set([words, desc, cta, mock], { clearProps: 'all' })
|
||||
});
|
||||
|
||||
words.forEach((w, i) =>
|
||||
tl.to(w, { opacity: 1, y: 0, skewX: 0, duration: 0.75 }, 0.05 + i * 0.1)
|
||||
);
|
||||
tl.to(desc, { opacity: 1, y: 0, duration: 0.65, ease: 'power2.out' }, 0.42);
|
||||
tl.to(cta, { opacity: 1, y: 0, duration: 0.55, ease: 'expo.out' }, 0.58);
|
||||
tl.to(mock, { opacity: 1, x: 0, duration: 0.9, ease: 'power2.out' }, 0.18);
|
||||
tl.to(cta, { opacity: 1, y: 0, duration: 0.55, ease: 'expo.out' }, 0.58);
|
||||
tl.to(mock, { opacity: 1, x: 0, duration: 0.9, ease: 'power2.out' }, 0.18);
|
||||
}, section);
|
||||
|
||||
return () => ctx.revert();
|
||||
@@ -47,12 +47,14 @@
|
||||
<h1 class="flex flex-col font-display leading-[0.95] font-black uppercase">
|
||||
<span data-hero-word class="text-[clamp(2.5rem,10vw,6.25rem)]">Рыночный</span>
|
||||
<span data-hero-word class="text-[clamp(3rem,13vw,8rem)]">прогноз</span>
|
||||
<span data-hero-word class="text-[clamp(1.5rem,6.5vw,4rem)] leading-none text-primary">от ML-модели</span>
|
||||
<span data-hero-word class="text-[clamp(1.5rem,6.5vw,4rem)] leading-none text-primary"
|
||||
>от ML-модели</span
|
||||
>
|
||||
</h1>
|
||||
|
||||
<p
|
||||
data-hero-desc
|
||||
class="mt-6 max-w-full text-base leading-[1.3] text-desc sm:mt-8 sm:text-xl lg:mt-10 xl:max-w-180 lg:text-2xl lg:leading-[1.1]"
|
||||
class="mt-6 max-w-full text-base leading-[1.3] text-desc sm:mt-8 sm:text-xl lg:mt-10 lg:text-2xl lg:leading-[1.1] xl:max-w-180"
|
||||
>
|
||||
Исторические свечи, прогнозная зона, уровни входа и выхода — всё на одном интерактивном
|
||||
графике. Горизонт 19 свечей, таймфрейм 5M.
|
||||
@@ -64,7 +66,7 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div data-hero-mockup class="w-full mx-auto xl:mx-0 z-20">
|
||||
<div data-hero-mockup class="z-20 mx-auto w-full xl:mx-0">
|
||||
<DashboardMockup />
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,12 +1,28 @@
|
||||
<script>
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
<script lang="ts">
|
||||
import { page } from '$app/state';
|
||||
import { pageTitle } from '$lib/config/site';
|
||||
</script>
|
||||
|
||||
<section class="flex h-screen items-center justify-center pt-16">
|
||||
<h1
|
||||
class="flex flex-col text-center font-display text-[110px] leading-[1.1] font-black uppercase"
|
||||
>
|
||||
Страница не найдена
|
||||
<span class="text-[64px] leading-none text-primary"> Возможно стр </span>
|
||||
</h1>
|
||||
<svelte:head>
|
||||
<title>{pageTitle('Страница не найдена')}</title>
|
||||
</svelte:head>
|
||||
|
||||
<section class="flex min-h-screen items-center justify-center px-5 pt-16 text-center">
|
||||
<div class="max-w-2xl">
|
||||
<p class="mb-4 font-mono text-xs tracking-widest text-primary uppercase">
|
||||
Ошибка {page.status}
|
||||
</p>
|
||||
<h1 class="font-display text-[clamp(2.75rem,9vw,6.5rem)] leading-none font-black uppercase">
|
||||
Страница не найдена
|
||||
</h1>
|
||||
<p class="mx-auto mt-5 max-w-lg text-base leading-relaxed text-desc sm:text-lg">
|
||||
Проверьте адрес или вернитесь к публичному дашборду Flamy Trade.
|
||||
</p>
|
||||
<a
|
||||
href="/"
|
||||
class="mt-8 inline-flex rounded-xl bg-primary px-5 py-3 font-display text-sm transition-colors hover:bg-primary-h"
|
||||
>
|
||||
На главную
|
||||
</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -2,19 +2,24 @@
|
||||
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';
|
||||
|
||||
let { children } = $props();
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin="anonymous" />
|
||||
<link
|
||||
href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,opsz,wght@0,9..40,100..1000;1,9..40,100..1000&family=JetBrains+Mono:ital,wght@0,100..800;1,100..800&family=Unbounded:wght@200..900&display=swap"
|
||||
rel="stylesheet"
|
||||
/>
|
||||
|
||||
<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>
|
||||
|
||||
+19
-9
@@ -1,20 +1,30 @@
|
||||
<script lang="ts">
|
||||
|
||||
import Hero from './(sections)/Hero.svelte';
|
||||
import Ticker from './(sections)/Ticker.svelte';
|
||||
import About from './(sections)/About.svelte';
|
||||
import Features from './(sections)/Features.svelte';
|
||||
import Stats from './(sections)/Stats.svelte';
|
||||
import BlogPreview from './(sections)/BlogPreview.svelte';
|
||||
import Cta from './(sections)/Cta.svelte';
|
||||
import Hero from './(sections)/Hero.svelte';
|
||||
import Ticker from './(sections)/Ticker.svelte';
|
||||
import About from './(sections)/About.svelte';
|
||||
import Features from './(sections)/Features.svelte';
|
||||
import Stats from './(sections)/Stats.svelte';
|
||||
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';
|
||||
</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('/')} />
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
<Hero />
|
||||
<Ticker />
|
||||
<About />
|
||||
<Features />
|
||||
<Stats />
|
||||
<BlogPreview posts={[]} />
|
||||
<BlogPreview {posts} />
|
||||
<Cta />
|
||||
</main>
|
||||
|
||||
@@ -1,9 +1,25 @@
|
||||
<script lang="ts">
|
||||
import Button from '$lib/components/ui/Button.svelte';
|
||||
import { about, params} from './_data';
|
||||
import { scrollFadeUp, scrollRevealLeft, scrollStagger } from '$lib/gsap/actions';
|
||||
import { about, params } from './_data';
|
||||
import { canonical, pageTitle } from '$lib/config/site';
|
||||
import { scrollFadeUp, scrollRevealLeft } from '$lib/gsap/actions';
|
||||
</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')} />
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
<div class="relative border-b border-b-bg-h bg-bg-e">
|
||||
<section
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import { posts } from '$lib/blog/posts';
|
||||
import { canonical, pageTitle } from '$lib/config/site';
|
||||
import { scrollRevealLeft, scrollFadeUp, scrollStagger } from '$lib/gsap/actions';
|
||||
|
||||
function fmtDate(d: string) {
|
||||
@@ -15,12 +16,27 @@
|
||||
}
|
||||
</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')} />
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
<div class="relative overflow-hidden border-b border-bg-h bg-bg-e">
|
||||
<section class="z-10 flex flex-col items-start justify-end px-5 py-16 sm:px-10 sm:py-20">
|
||||
<h1
|
||||
use:scrollRevealLeft={{ start: 'top 95%' }}
|
||||
class="font-display text-[clamp(2.5rem,10vw,8rem)] font-black uppercase leading-none"
|
||||
class="font-display text-[clamp(2.5rem,10vw,8rem)] leading-none font-black uppercase"
|
||||
>
|
||||
Блог
|
||||
</h1>
|
||||
@@ -39,7 +55,7 @@
|
||||
|
||||
<div
|
||||
use:scrollStagger={{ selector: 'a', stagger: 0.08, y: 24, start: 'top 88%' }}
|
||||
class="mx-auto max-w-400 px-5 sm:px-10 lg:px-0 mb-32"
|
||||
class="mx-auto mb-32 max-w-400 px-5 sm:px-10 lg:px-0"
|
||||
>
|
||||
{#if posts.length === 0}
|
||||
<div class="flex flex-col items-center gap-3 py-20 text-center">
|
||||
@@ -64,12 +80,11 @@
|
||||
<div
|
||||
class="absolute inset-0 bg-linear-to-br from-primary/10 via-transparent to-transparent"
|
||||
></div>
|
||||
|
||||
</div>
|
||||
|
||||
<div class="flex min-w-0 flex-1 flex-col gap-2">
|
||||
<h2
|
||||
class="font-display text-[clamp(1.125rem,2.5vw,1.625rem)] font-black leading-[1.15] tracking-tight text-title transition-colors duration-150 group-hover:text-primary"
|
||||
class="font-display text-[clamp(1.125rem,2.5vw,1.625rem)] leading-[1.15] font-black tracking-tight text-title transition-colors duration-150 group-hover:text-primary"
|
||||
>
|
||||
{post.title}
|
||||
</h2>
|
||||
@@ -80,7 +95,7 @@
|
||||
<div class="mt-1 flex flex-wrap gap-1.5">
|
||||
{#each post.tags.slice(0, 3) as tag, i (i)}
|
||||
<span
|
||||
class="rounded-sm border border-white/6 bg-white/3 px-1.75 py-0.5 font-mono text-xs uppercase tracking-widest text-desc/60"
|
||||
class="rounded-sm border border-white/6 bg-white/3 px-1.75 py-0.5 font-mono text-xs tracking-widest text-desc/60 uppercase"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
@@ -96,7 +111,7 @@
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="shrink-0 pt-1 text-desc/30 transition-[color,translate] duration-700 group-hover:translate-x-2 group-hover:text-primary mr-6"
|
||||
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">
|
||||
<path
|
||||
@@ -112,4 +127,4 @@
|
||||
{/each}
|
||||
{/if}
|
||||
</div>
|
||||
</main>
|
||||
</main>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<script lang="ts">
|
||||
import type { PageData } from './$types';
|
||||
import { canonical, pageTitle } from '$lib/config/site';
|
||||
|
||||
let { data }: { data: PageData } = $props();
|
||||
const { post } = $derived(data);
|
||||
@@ -17,11 +18,24 @@
|
||||
}
|
||||
</script>
|
||||
|
||||
<svelte:head>
|
||||
<title>{pageTitle(post.title)}</title>
|
||||
<meta name="description" content={post.description} />
|
||||
<link rel="canonical" href={canonical(`/blog/${post.slug}`)} />
|
||||
<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="article:published_time" content={post.date} />
|
||||
<meta name="twitter:title" content={pageTitle(post.title)} />
|
||||
<meta name="twitter:description" content={post.description} />
|
||||
</svelte:head>
|
||||
|
||||
<main>
|
||||
<div class="mx-auto max-w-3xl px-5 pb-24 pt-16 sm:px-10 sm:pb-32 sm:pt-20 lg:px-0 mt-10">
|
||||
<div class="mx-auto mt-10 max-w-3xl px-5 pt-16 pb-24 sm:px-10 sm:pt-20 sm:pb-32 lg:px-0">
|
||||
<a
|
||||
href="/blog"
|
||||
class="back-link mb-6 inline-flex items-center gap-1.5 font-mono text-[0.625rem] uppercase tracking-[0.08em] 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">
|
||||
<path
|
||||
@@ -40,7 +54,7 @@
|
||||
<div class="mb-5 flex flex-wrap gap-1.5">
|
||||
{#each post.tags as tag (tag)}
|
||||
<span
|
||||
class="rounded-sm border border-primary/25 bg-primary/10 px-1.75 py-0.5 font-mono text-xs uppercase tracking-widest text-primary"
|
||||
class="rounded-sm border border-primary/25 bg-primary/10 px-1.75 py-0.5 font-mono text-xs tracking-widest text-primary uppercase"
|
||||
>
|
||||
{tag}
|
||||
</span>
|
||||
@@ -49,7 +63,7 @@
|
||||
{/if}
|
||||
|
||||
<h1
|
||||
class="mb-5 font-display text-[clamp(1.875rem,5vw,2.75rem)] font-black leading-[1.08] tracking-[-0.035em] text-title"
|
||||
class="mb-5 font-display text-[clamp(1.875rem,5vw,2.75rem)] leading-[1.08] font-black tracking-[-0.035em] text-title"
|
||||
>
|
||||
{post.title}
|
||||
</h1>
|
||||
@@ -58,19 +72,34 @@
|
||||
{post.description}
|
||||
</p>
|
||||
|
||||
<time datetime={post.date} class="font-mono text-[0.625rem] uppercase tracking-[0.08em] text-desc/50">
|
||||
<time
|
||||
datetime={post.date}
|
||||
class="font-mono text-[0.625rem] tracking-[0.08em] text-desc/50 uppercase"
|
||||
>
|
||||
{fmtDate(post.date)}
|
||||
</time>
|
||||
</header>
|
||||
|
||||
<article class="prose-article">
|
||||
{@html post.content}
|
||||
{#each post.content as block, i (`${block.type}-${i}`)}
|
||||
{#if block.type === 'heading'}
|
||||
<h2>{block.text}</h2>
|
||||
{:else if block.type === 'paragraph'}
|
||||
<p>{block.text}</p>
|
||||
{:else}
|
||||
<ul>
|
||||
{#each block.items as item (item)}
|
||||
<li>{item}</li>
|
||||
{/each}
|
||||
</ul>
|
||||
{/if}
|
||||
{/each}
|
||||
</article>
|
||||
|
||||
<div class="mt-16 flex items-center justify-between gap-3 border-t border-bg-h pt-7">
|
||||
<a
|
||||
href="/blog"
|
||||
class="back-link inline-flex items-center gap-1.5 font-mono text-[0.625rem] uppercase tracking-[0.08em] 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">
|
||||
<path
|
||||
@@ -106,7 +135,7 @@
|
||||
}
|
||||
|
||||
.prose-article :global(h2) {
|
||||
font-family: var(--font-display),sans-serif;
|
||||
font-family: var(--font-display), sans-serif;
|
||||
font-size: 1.25rem;
|
||||
font-weight: 800;
|
||||
color: var(--color-title);
|
||||
@@ -158,7 +187,7 @@
|
||||
color: var(--color-title);
|
||||
}
|
||||
.prose-article :global(code) {
|
||||
font-family: var(--font-mono),sans-serif;
|
||||
font-family: var(--font-mono), sans-serif;
|
||||
font-size: 0.875em;
|
||||
color: var(--color-primary);
|
||||
background: color-mix(in srgb, var(--color-primary) 10%, transparent);
|
||||
@@ -197,4 +226,4 @@
|
||||
border: 1px solid var(--color-bg-h);
|
||||
margin: 1.75em 0;
|
||||
}
|
||||
</style>
|
||||
</style>
|
||||
|
||||
@@ -6,4 +6,4 @@ export const load: PageLoad = ({ params }) => {
|
||||
const post = getPostBySlug(params.slug);
|
||||
if (!post) throw error(404, 'Статья не найдена');
|
||||
return { post };
|
||||
};
|
||||
};
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
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 { cn } from '$lib/utils';
|
||||
|
||||
let activeSymbol = $state('BTCUSDT');
|
||||
@@ -24,19 +25,38 @@
|
||||
|
||||
if (!window.matchMedia('(prefers-reduced-motion: reduce)').matches) {
|
||||
const ctx = gsap.context(() => {
|
||||
gsap.from(titleEl, { opacity: 0, y: 20, duration: 0.6, ease: 'power3.out', clearProps: 'all' });
|
||||
gsap.from(panelEl, { opacity: 0, y: 32, duration: 0.7, delay: 0.12, ease: 'power3.out', clearProps: 'all' });
|
||||
gsap.from(titleEl, {
|
||||
opacity: 0,
|
||||
y: 20,
|
||||
duration: 0.6,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'all'
|
||||
});
|
||||
gsap.from(panelEl, {
|
||||
opacity: 0,
|
||||
y: 32,
|
||||
duration: 0.7,
|
||||
delay: 0.12,
|
||||
ease: 'power3.out',
|
||||
clearProps: 'all'
|
||||
});
|
||||
});
|
||||
return () => { ctx.revert(); window.removeEventListener('resize', update); };
|
||||
return () => {
|
||||
ctx.revert();
|
||||
window.removeEventListener('resize', update);
|
||||
};
|
||||
}
|
||||
|
||||
return () => window.removeEventListener('resize', update);
|
||||
});
|
||||
|
||||
function fmtPrice(n: number): string {
|
||||
if (n >= 10000) return n.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 });
|
||||
if (n >= 100) return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
if (n >= 1) return n.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
|
||||
if (n >= 10000)
|
||||
return n.toLocaleString('en-US', { minimumFractionDigits: 1, maximumFractionDigits: 1 });
|
||||
if (n >= 100)
|
||||
return n.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
|
||||
if (n >= 1)
|
||||
return n.toLocaleString('en-US', { minimumFractionDigits: 4, maximumFractionDigits: 4 });
|
||||
return n.toLocaleString('en-US', { minimumFractionDigits: 6, maximumFractionDigits: 6 });
|
||||
}
|
||||
|
||||
@@ -45,10 +65,27 @@
|
||||
}
|
||||
</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')} />
|
||||
</svelte:head>
|
||||
|
||||
<main class="min-h-screen bg-bg">
|
||||
<div class="mx-auto max-w-400 px-5 pt-22 pb-16 sm:px-10 sm:pt-26 lg:pt-24">
|
||||
<div bind:this={titleEl} class="mb-5 mx-auto w-fit">
|
||||
<h1 class="font-display text-center text-2xl font-black uppercase leading-none tracking-tight mb-2 sm:text-4xl ">
|
||||
<div bind:this={titleEl} class="mx-auto mb-5 w-fit">
|
||||
<h1
|
||||
class="mb-2 text-center font-display text-2xl leading-none font-black tracking-tight uppercase sm:text-4xl"
|
||||
>
|
||||
Дашборд
|
||||
</h1>
|
||||
<p class="mt-1.5 font-mono text-[0.625rem] tracking-widest text-desc uppercase sm:text-xs">
|
||||
@@ -56,10 +93,16 @@
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div bind:this={panelEl} class="overflow-hidden rounded-2xl border border-bg-h bg-bg-c shadow-[0_40px_80px_rgba(0,0,0,0.6)]">
|
||||
<div class="flex flex-col border-b border-bg-h sm:flex-row sm:items-stretch sm:justify-between">
|
||||
|
||||
<div class="flex overflow-hidden border-b border-bg-h sm:border-b-0 [&::-webkit-scrollbar]:hidden">
|
||||
<div
|
||||
bind:this={panelEl}
|
||||
class="overflow-hidden rounded-2xl border border-bg-h bg-bg-c shadow-[0_40px_80px_rgba(0,0,0,0.6)]"
|
||||
>
|
||||
<div
|
||||
class="flex flex-col border-b border-bg-h sm:flex-row sm:items-stretch sm:justify-between"
|
||||
>
|
||||
<div
|
||||
class="flex overflow-hidden border-b border-bg-h sm:border-b-0 [&::-webkit-scrollbar]:hidden"
|
||||
>
|
||||
{#each SYMBOLS as s (s.id)}
|
||||
<button
|
||||
class={cn(
|
||||
@@ -90,44 +133,59 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-3 border-b border-bg-h px-4 py-3 sm:px-5">
|
||||
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-x-5 gap-y-3 border-b border-bg-h px-4 py-3 sm:px-5"
|
||||
>
|
||||
<div class="flex items-baseline gap-2.5">
|
||||
<span class="font-display text-[0.625rem] font-black tracking-tight text-desc uppercase hidden sm:inline">
|
||||
<span
|
||||
class="hidden font-display text-[0.625rem] font-black tracking-tight text-desc uppercase sm:inline"
|
||||
>
|
||||
{sym.label}
|
||||
</span>
|
||||
<span class="font-mono text-lg font-medium text-title sm:text-2xl">
|
||||
{fmtPrice(data.currentPrice)}
|
||||
</span>
|
||||
<span class={cn(
|
||||
'font-mono text-xs font-medium sm:text-sm',
|
||||
data.change24hPct >= 0 ? 'text-emerald-400' : 'text-red-400'
|
||||
)}>
|
||||
<span
|
||||
class={cn(
|
||||
'font-mono text-xs font-medium sm:text-sm',
|
||||
data.change24hPct >= 0 ? 'text-emerald-400' : 'text-red-400'
|
||||
)}
|
||||
>
|
||||
{fmtPct(data.change24hPct)}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class={cn(
|
||||
'flex items-center gap-1.5 rounded-lg px-2.5 py-1.5',
|
||||
data.signal.direction === 'long' ? 'bg-emerald-400/10' : 'bg-red-400/10'
|
||||
)}>
|
||||
<span class={cn(
|
||||
'font-display text-[0.6875rem] font-black uppercase tracking-tight',
|
||||
data.signal.direction === 'long' ? 'text-emerald-400' : 'text-red-400'
|
||||
)}>
|
||||
<div
|
||||
class={cn(
|
||||
'flex items-center gap-1.5 rounded-lg px-2.5 py-1.5',
|
||||
data.signal.direction === 'long' ? 'bg-emerald-400/10' : 'bg-red-400/10'
|
||||
)}
|
||||
>
|
||||
<span
|
||||
class={cn(
|
||||
'font-display text-[0.6875rem] font-black tracking-tight uppercase',
|
||||
data.signal.direction === 'long' ? 'text-emerald-400' : 'text-red-400'
|
||||
)}
|
||||
>
|
||||
{data.signal.direction === 'long' ? '▲ LONG' : '▼ SHORT'}
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-2">
|
||||
<span class="font-mono text-[0.5625rem] tracking-widest text-desc uppercase hidden sm:inline">
|
||||
<span
|
||||
class="hidden font-mono text-[0.5625rem] tracking-widest text-desc uppercase sm:inline"
|
||||
>
|
||||
Уверенность
|
||||
</span>
|
||||
<div class="h-1.5 w-16 overflow-hidden rounded-full bg-bg-h sm:w-20">
|
||||
<div
|
||||
class={cn(
|
||||
'h-full rounded-full transition-all duration-700',
|
||||
data.signal.confidence >= 70 ? 'bg-emerald-400' : data.signal.confidence >= 55 ? 'bg-amber-400' : 'bg-red-400'
|
||||
data.signal.confidence >= 70
|
||||
? 'bg-emerald-400'
|
||||
: data.signal.confidence >= 55
|
||||
? 'bg-amber-400'
|
||||
: 'bg-red-400'
|
||||
)}
|
||||
style="width: {data.signal.confidence}%"
|
||||
></div>
|
||||
@@ -136,22 +194,22 @@
|
||||
</div>
|
||||
|
||||
<div class="ml-auto flex gap-4 sm:gap-6">
|
||||
{#each [
|
||||
{ label: 'Entry', val: data.signal.entry, cls: 'text-primary' },
|
||||
{ label: 'TP', val: data.signal.tp, cls: 'text-emerald-400' },
|
||||
{ label: 'SL', val: data.signal.sl, cls: 'text-red-400' },
|
||||
] as lvl (lvl.label)}
|
||||
{#each [{ label: 'Entry', val: data.signal.entry, cls: 'text-primary' }, { label: 'TP', val: data.signal.tp, cls: 'text-emerald-400' }, { label: 'SL', val: data.signal.sl, cls: 'text-red-400' }] as lvl (lvl.label)}
|
||||
<div class="flex flex-col gap-0.5 text-right">
|
||||
<span class="font-mono text-[0.5rem] tracking-widest text-desc uppercase">{lvl.label}</span>
|
||||
<span class="font-mono text-[0.5rem] tracking-widest text-desc uppercase"
|
||||
>{lvl.label}</span
|
||||
>
|
||||
<span class="font-mono text-xs {lvl.cls}">{fmtPrice(lvl.val)}</span>
|
||||
</div>
|
||||
{/each}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<Chart data={data} decimals={sym.dec} height={chartHeight} />
|
||||
<Chart {data} decimals={sym.dec} height={chartHeight} />
|
||||
|
||||
<div class="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-bg-h px-4 py-3 sm:px-5">
|
||||
<div
|
||||
class="flex flex-wrap items-center gap-x-5 gap-y-2 border-t border-bg-h px-4 py-3 sm:px-5"
|
||||
>
|
||||
<div class="flex items-center gap-1.5">
|
||||
<div class="flex gap-0.5">
|
||||
<span class="h-3.5 w-1.5 rounded-[2px] bg-emerald-400/80"></span>
|
||||
@@ -162,9 +220,19 @@
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
<svg width="22" height="4" aria-hidden="true">
|
||||
<line x1="0" y1="2" x2="22" y2="2" stroke="#fe4b07" stroke-width="2" stroke-dasharray="4 3" />
|
||||
<line
|
||||
x1="0"
|
||||
y1="2"
|
||||
x2="22"
|
||||
y2="2"
|
||||
stroke="#fe4b07"
|
||||
stroke-width="2"
|
||||
stroke-dasharray="4 3"
|
||||
/>
|
||||
</svg>
|
||||
<span class="font-mono text-[0.5625rem] tracking-widest text-desc uppercase">ML-прогноз</span>
|
||||
<span class="font-mono text-[0.5625rem] tracking-widest text-desc uppercase"
|
||||
>ML-прогноз</span
|
||||
>
|
||||
</div>
|
||||
|
||||
<div class="flex items-center gap-1.5">
|
||||
@@ -190,7 +258,7 @@
|
||||
</div>
|
||||
|
||||
<span
|
||||
class="pointer-events-none fixed top-0 left-1/2 -translate-x-1/2 h-80 w-full max-w-3xl rounded-full bg-primary opacity-[0.07] blur-[160px]"
|
||||
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>
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
import { json } from '@sveltejs/kit';
|
||||
|
||||
export const GET = () => {
|
||||
return json({
|
||||
status: 'ok',
|
||||
service: 'flamy-trade'
|
||||
});
|
||||
};
|
||||
+25
-1
@@ -1,5 +1,29 @@
|
||||
@import 'tailwindcss';
|
||||
|
||||
@font-face {
|
||||
font-family: 'DM Sans';
|
||||
src: url('/fonts/DMSans.ttf') format('truetype');
|
||||
font-weight: 100 1000;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('/fonts/JetBrainsMono.ttf') format('truetype');
|
||||
font-weight: 100 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'Unbounded';
|
||||
src: url('/fonts/Unbounded.ttf') format('truetype');
|
||||
font-weight: 200 900;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@theme {
|
||||
--font-display: 'Unbounded', sans-serif;
|
||||
--font-sans: 'DM Sans', sans-serif;
|
||||
@@ -42,7 +66,7 @@
|
||||
}
|
||||
|
||||
section {
|
||||
@apply relative mx-auto max-w-400 lg:min-h-175 lg:max-h-300 px-10;
|
||||
@apply relative mx-auto max-w-400 px-10 lg:max-h-300 lg:min-h-175;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import { posts } from '$lib/blog/posts';
|
||||
import { canonical } from '$lib/config/site';
|
||||
|
||||
const staticPages = ['/', '/about', '/blog', '/dashboard'];
|
||||
|
||||
export const GET = () => {
|
||||
const urls = [
|
||||
...staticPages.map((path) => ({ loc: canonical(path), lastmod: undefined })),
|
||||
...posts.map((post) => ({ loc: canonical(`/blog/${post.slug}`), lastmod: post.date }))
|
||||
];
|
||||
|
||||
const body = `<?xml version="1.0" encoding="UTF-8"?>
|
||||
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
|
||||
${urls
|
||||
.map(
|
||||
(url) => ` <url>
|
||||
<loc>${url.loc}</loc>${url.lastmod ? `\n <lastmod>${url.lastmod}</lastmod>` : ''}
|
||||
</url>`
|
||||
)
|
||||
.join('\n')}
|
||||
</urlset>
|
||||
`;
|
||||
|
||||
return new Response(body, {
|
||||
headers: {
|
||||
'content-type': 'application/xml; charset=utf-8',
|
||||
'cache-control': 'public, max-age=3600'
|
||||
}
|
||||
});
|
||||
};
|
||||
Reference in New Issue
Block a user