chore: prepare production deployment
This commit is contained in:
@@ -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: 'Исторические свечи' },
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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)
|
||||
};
|
||||
}
|
||||
|
||||
@@ -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 };
|
||||
Reference in New Issue
Block a user