neuroalpha 0.0.0-stage → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (46) hide show
  1. package/README.md +164 -3
  2. package/bin/cli.js +69 -0
  3. package/config/strategy.json +33 -0
  4. package/docs//320/230/320/235/320/241/320/242/320/240/320/243/320/232/320/246/320/230/320/257.md +156 -0
  5. package/package.json +43 -6
  6. package/public/app.js +2410 -0
  7. package/public/index.html +337 -0
  8. package/public/landing.html +870 -0
  9. package/public/legal.html +90 -0
  10. package/public/styles.css +343 -0
  11. package/server/api/routes.js +333 -0
  12. package/server/auth/index.js +340 -0
  13. package/server/billing/yookassa.js +132 -0
  14. package/server/brokers/index.js +645 -0
  15. package/server/config.js +44 -0
  16. package/server/data/moex.js +165 -0
  17. package/server/db.js +420 -0
  18. package/server/hub.js +14 -0
  19. package/server/index.js +92 -0
  20. package/server/llm/client.js +187 -0
  21. package/server/llm/providers.js +355 -0
  22. package/server/model/dataset.js +352 -0
  23. package/server/model/forecast.js +121 -0
  24. package/server/model/gpu-worker.html +11 -0
  25. package/server/model/gpu-worker.js +60 -0
  26. package/server/model/linalg.js +142 -0
  27. package/server/model/live.js +486 -0
  28. package/server/model/metrics.js +134 -0
  29. package/server/model/models.js +459 -0
  30. package/server/model/profiles.js +166 -0
  31. package/server/model/research.js +880 -0
  32. package/server/model/tf-node.js +26 -0
  33. package/server/model/transformer-core.js +330 -0
  34. package/server/model/transformer-gpu.js +221 -0
  35. package/server/model/transformer-pool.js +200 -0
  36. package/server/model/transformer-worker.js +28 -0
  37. package/server/model/transformer.js +57 -0
  38. package/server/model/vocab.js +28 -0
  39. package/server/news/index.js +309 -0
  40. package/server/notify/index.js +168 -0
  41. package/server/params-meta.js +63 -0
  42. package/server/settings.js +79 -0
  43. package/server/strategist/analytics.js +150 -0
  44. package/server/strategist/index.js +484 -0
  45. package/server/strategist/tools.js +302 -0
  46. package/server/util.js +9 -0
@@ -0,0 +1,337 @@
1
+ <!doctype html>
2
+ <html lang="ru">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>Рынок как язык · Мосбиржа</title>
7
+ <link rel="stylesheet" href="styles.css">
8
+ <script src="https://unpkg.com/lightweight-charts@4.2.3/dist/lightweight-charts.standalone.production.js"></script>
9
+ <script src="https://unpkg.com/marked@12.0.2/marked.min.js"></script>
10
+ </head>
11
+ <body>
12
+ <header>
13
+ <div class="brand"><span class="logo">◈</span><b>НейроАльфа</b><span class="muted">· Рынок как язык · Мосбиржа</span></div>
14
+ <div class="hright">
15
+ <a href="/landing" class="icon small" style="text-decoration:none;padding:3px 9px;border:1px solid var(--line);border-radius:6px" title="Публичная витрина и тарифы">Витрина</a>
16
+ <button id="btn-user" type="button" class="icon small" style="padding:3px 9px" title="Профиль, подписка и боты Telegram / MAX">Профиль</button>
17
+ <span id="st" class="dot" title="Подключение…"></span>
18
+ <button id="btn-theme" class="icon" title="Светлая / тёмная тема">☀</button>
19
+ <button id="btn-help" class="icon" title="Справка">?</button>
20
+ </div>
21
+ </header>
22
+
23
+ <main id="main">
24
+ <section class="chat">
25
+ <div class="chat-head">
26
+ <b>Стратег</b>
27
+ <span id="chat-status" class="muted small"></span>
28
+ <span class="grow"></span>
29
+ <button id="chat-new" class="icon small" title="Начать новый разговор (журнал гипотез сохранится)">+ Новый диалог</button>
30
+ </div>
31
+ <div class="feed-wrap">
32
+ <div id="feed" class="feed"></div>
33
+ <button id="feed-down" class="feed-down" hidden title="К новым сообщениям"></button>
34
+ </div>
35
+ <div class="composer">
36
+ <div id="suggest" class="suggest"></div>
37
+ <div class="prompt-box" id="prompt-box">
38
+ <textarea id="chat-input" rows="2" placeholder="Ответьте агенту…"></textarea>
39
+ <div class="prompt-bar">
40
+ <div class="prompt-left">
41
+ <div class="mode-picker" id="mode-picker">
42
+ <button type="button" id="chat-mode-btn" class="ghost-select" title="Режим работы Стратега">
43
+ <span id="chat-mode-ico" class="g-ico"></span>
44
+ <span id="chat-mode-lbl">Авто</span>
45
+ <svg class="caret-svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.5L6 7.5L9 4.5"/></svg>
46
+ </button>
47
+ <div id="mode-menu" class="pop-menu mode-menu" hidden>
48
+ <button type="button" class="model-opt" data-mode-val="autopilot">
49
+ <span class="m-name">⚡ Авто</span>
50
+ <span class="muted small">сам ставит эксперименты</span>
51
+ </button>
52
+ <button type="button" class="model-opt" data-mode-val="advisor">
53
+ <span class="m-name">✋ С согласия</span>
54
+ <span class="muted small">спрашивает перед запуском</span>
55
+ </button>
56
+ <button type="button" class="model-opt" data-mode-val="discuss">
57
+ <span class="m-name">💬 Обсуждение</span>
58
+ <span class="muted small">только анализ и беседа</span>
59
+ </button>
60
+ </div>
61
+ </div>
62
+ </div>
63
+ <div class="prompt-right">
64
+ <div class="model-picker" id="model-picker">
65
+ <button type="button" id="chat-model-btn" class="ghost-select model-btn" title="Модель ИИ (настраивается в «Настройки → Модели ИИ»)">
66
+ <svg class="g-ico model-cube" width="14" height="14" viewBox="0 0 16 16" fill="none"><path d="M8 1.5L14 5V11L8 14.5L2 11V5L8 1.5Z" stroke="var(--accent)" stroke-width="1.4" stroke-linejoin="round"/><path d="M2 5L8 8.5L14 5M8 8.5V14.5" stroke="var(--accent)" stroke-width="1.2" opacity="0.7"/></svg>
67
+ <span id="chat-model-lbl">…</span>
68
+ <svg class="caret-svg" width="12" height="12" viewBox="0 0 12 12" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"><path d="M3 4.5L6 7.5L9 4.5"/></svg>
69
+ </button>
70
+ <div id="model-menu" class="pop-menu model-menu" hidden>
71
+ <div class="model-search-wrap">
72
+ <span class="muted">🔍</span>
73
+ <input id="model-search" type="search" placeholder="Поиск моделей" autocomplete="off">
74
+ </div>
75
+ <div id="model-menu-list" class="model-menu-list"></div>
76
+ <button type="button" id="model-manage-btn" class="model-manage-btn">
77
+ <svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round"><line x1="2" y1="5" x2="14" y2="5"/><circle cx="5.5" cy="5" r="2" fill="var(--panel)"/><line x1="2" y1="11" x2="14" y2="11"/><circle cx="10.5" cy="11" r="2" fill="var(--panel)"/></svg>
78
+ <span>Управление моделями</span>
79
+ </button>
80
+ </div>
81
+ </div>
82
+ <button id="chat-action" type="button" class="pbtn action-btn" title="Голосовой ввод (русский язык)">
83
+ <svg id="ico-mic" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="9" y="2" width="6" height="11" rx="3"/><path d="M5 10a7 7 0 0 0 14 0"/><line x1="12" y1="17" x2="12" y2="21"/><line x1="8" y1="21" x2="16" y2="21"/></svg>
84
+ <svg id="ico-send" hidden width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.2" stroke-linecap="round" stroke-linejoin="round"><line x1="12" y1="19" x2="12" y2="5"/><polyline points="5 12 12 5 19 12"/></svg>
85
+ <svg id="ico-stop" hidden width="14" height="14" viewBox="0 0 24 24" fill="currentColor"><rect x="5" y="5" width="14" height="14" rx="2.5"/></svg>
86
+ </button>
87
+ <button id="chat-stop" type="button" class="pbtn stop" hidden title="Остановить ответ">■</button>
88
+ </div>
89
+ </div>
90
+ </div>
91
+ <div class="prompt-hint muted small"><b>Enter</b> — отправить &nbsp; <b>Shift+Enter</b> — новая строка</div>
92
+ </div>
93
+ </section>
94
+
95
+ <div id="splitter" title="Потяните, чтобы изменить ширину. Двойной щелчок — по умолчанию"></div>
96
+
97
+ <section class="right">
98
+ <nav class="tabs" id="tabs">
99
+ <button data-tab="overview" class="active"><span>Эксперименты</span><span id="tstat-overview" class="tstat"></span></button>
100
+ <button data-tab="now"><span>Торговля</span><span id="tstat-now" class="tstat"></span></button>
101
+ <button data-tab="map"><span>Карта и журнал</span><span id="tstat-map" class="tstat"></span></button>
102
+ <button data-tab="settings"><span>Настройки</span><span id="tstat-settings" class="tstat"></span></button>
103
+ </nav>
104
+
105
+ <div class="tab" id="tab-overview">
106
+ <div class="toolbar">
107
+ <select id="run-select" title="Прошлые эксперименты"></select>
108
+ <button id="btn-model" class="primary">Исследовать</button>
109
+ <button id="btn-cancel" hidden>Отменить</button>
110
+ </div>
111
+ <div id="run-bar" class="run-bar" hidden>
112
+ <div class="run-bar-left">
113
+ <button id="btn-rename-run" type="button" title="Дать эксперименту понятное имя">✎ Назвать</button>
114
+ <button id="btn-apply" type="button" hidden title="Скопировать настройки этого эксперимента в текущие настройки">✓ Применить эти настройки</button>
115
+ <button id="btn-stability" type="button" title="Проверить стратегию на 5 сдвигах периода (−8…+8 дней)">Проверить устойчивость</button>
116
+ <button id="btn-to-live" type="button" title="Запустить виртуальный счёт с замороженными настройками этого эксперимента">▶ В торговлю</button>
117
+ <button id="btn-showcase" type="button" hidden title="Показывать этот эксперимент на публичной витрине лендинга">☆ На витрину</button>
118
+ </div>
119
+ <div class="row-gap">
120
+ <button id="btn-ask-run" type="button" title="Попросить Стратега разобрать этот эксперимент">💬 Разобрать</button>
121
+ <button id="btn-cmp-run" type="button" title="Попросить Стратега сравнить этот эксперимент с другим">⇄ Сравнить</button>
122
+ <button id="btn-del" type="button" title="Удалить этот эксперимент из истории">Удалить</button>
123
+ </div>
124
+ </div>
125
+ <div id="progress" class="progress" hidden><div class="bar"></div><span></span></div>
126
+ <div id="overview-empty" class="empty"></div>
127
+ <div id="overview" hidden>
128
+ <div id="verdict" class="verdict"></div>
129
+ <div class="kpis" id="kpis"></div>
130
+ <div class="muted small" id="sides" title="Вклад покупок и продаж в итог после комиссий, в % от стартовой суммы" hidden></div>
131
+ <div id="stability" class="stab-box" hidden></div>
132
+ <div id="eq-chart" class="chart"></div>
133
+ <div class="legend" id="legend"></div>
134
+ <details id="periods-box" hidden>
135
+ <summary>Доходность по месяцам и годам</summary>
136
+ <div class="seg" id="pmode" title="Что показывать в ячейках">
137
+ <button data-k="model" class="active">Стратегия</button><button data-k="market">Рынок</button><button data-k="diff">Разница</button>
138
+ </div>
139
+ <div id="periods"></div>
140
+ </details>
141
+ <details id="more">
142
+ <summary>Сравнение стратегий и на что смотрит стратегия</summary>
143
+ <div id="cmp"></div>
144
+ <h4>Совпадение прогноза с фактом по неделям</h4>
145
+ <div id="ic-chart" class="chart small"></div>
146
+ <h4>Головы внимания</h4>
147
+ <div id="heads"></div>
148
+ </details>
149
+ <details id="trades-box">
150
+ <summary id="trades-sum">Сделки</summary>
151
+ <div id="trades"></div>
152
+ </details>
153
+ </div>
154
+ </div>
155
+
156
+ <div class="tab" id="tab-now" hidden>
157
+ <div class="settings-wrap">
158
+ <aside class="snav" id="now-nav">
159
+ <button type="button" data-nsec="forecast" class="active">Прогноз сейчас</button>
160
+ <button type="button" data-nsec="virtual" id="nnav-virtual">Виртуальные счета</button>
161
+ <button type="button" data-nsec="brokers" id="nnav-brokers">Брокерские счета</button>
162
+ <div class="ssep"></div>
163
+ <button type="button" data-nsec="news" id="nnav-news">Новости рынка</button>
164
+ </aside>
165
+ <div class="spane">
166
+ <section class="ssec" data-nsec="forecast">
167
+ <div class="stitle bar"><span id="fc-title">Прогноз на ближайшие часы</span><div class="row-gap"><button id="btn-ask-forecast" type="button" title="Попросить Стратега разобрать текущий прогноз">💬 Спросить Стратега</button><button id="btn-forecast" class="primary">Обновить</button></div></div>
168
+ <div class="scard spadded">
169
+ <div id="forecast" class="muted">Стратегия обучится на последних месяцах и скажет, какие акции, по её мнению, будут сильнее или слабее рынка в ближайшие часы и под чьим влиянием.</div>
170
+ </div>
171
+ </section>
172
+ <section class="ssec" data-nsec="virtual" hidden>
173
+ <div class="stitle bar"><span>Виртуальные счета</span><button id="btn-live" class="primary">+ Новый виртуальный счёт</button></div>
174
+ <div class="scard spadded">
175
+ <div id="live" class="muted">Стратегия торгует виртуальными деньгами на текущих ценах: раз в несколько часов прогноз и пересмотр портфеля. Настройки счёта замораживаются при запуске.</div>
176
+ </div>
177
+ </section>
178
+ <section class="ssec" data-nsec="brokers" id="brokers-trade-box" hidden>
179
+ <div class="stitle bar"><span>Брокерские счета (Т-Банк, Финам, Алор, БКС)</span><button id="btn-brokers-cfg" type="button">Настроить счета</button></div>
180
+ <div class="scard spadded">
181
+ <div id="brokers-trade" class="muted">Подключите брокерский счёт в «Настройки → Брокерские счета». Счёт может работать в режиме «Только чтение», «Подтверждение заявок» или «Автомат» с жёсткими предохранителями.</div>
182
+ </div>
183
+ </section>
184
+ <section class="ssec" data-nsec="news" id="news-box" hidden>
185
+ <div class="stitle">Новости и раскрытия компаний</div>
186
+ <div class="scard spadded">
187
+ <div id="news-list" class="muted">Свежих размеченных новостей пока нет.</div>
188
+ </div>
189
+ </section>
190
+ </div>
191
+ </div>
192
+ </div>
193
+
194
+ <div class="tab" id="tab-map" hidden>
195
+ <div class="settings-wrap">
196
+ <aside class="snav" id="map-nav">
197
+ <button type="button" data-msec="behav" class="active">Карта по поведению</button>
198
+ <button type="button" data-msec="text">Карта по описанию</button>
199
+ <div class="ssep"></div>
200
+ <button type="button" data-msec="journal" id="mnav-journal">Журнал гипотез</button>
201
+ </aside>
202
+ <div class="spane">
203
+ <section class="ssec" id="map-sec">
204
+ <div class="stitle bar"><span id="map-title-lbl">Карта рынка · по совместному поведению</span><div class="row-gap" style="align-items:center"><span id="map-date" class="muted small"></span><input id="map-slider" type="range" min="0" max="0" value="0" style="width:160px"></div></div>
205
+ <div class="scard spadded">
206
+ <canvas id="map-canvas"></canvas>
207
+ <div id="map-info" class="muted small" style="margin-top:8px"></div>
208
+ <div class="row-gap" id="map-text-box" hidden style="margin-top:10px"><button id="btn-profiles">Составить описания компаний (ИИ)</button><span id="profiles-msg" class="muted small"></span></div>
209
+ </div>
210
+ </section>
211
+ <section class="ssec" id="journal-box" hidden>
212
+ <div class="stitle">Журнал проверенных гипотез</div>
213
+ <div class="scard spadded">
214
+ <div id="journal"></div>
215
+ </div>
216
+ </section>
217
+ </div>
218
+ </div>
219
+ </div>
220
+
221
+ <div class="tab" id="tab-settings" hidden>
222
+ <div class="settings-wrap">
223
+ <aside class="snav">
224
+ <input id="s-search" type="search" placeholder="Поиск">
225
+ <button type="button" data-sec="g0" class="active">Общие</button>
226
+ <button type="button" data-sec="g1">Для карты</button>
227
+ <button type="button" data-sec="g2">Для Трансформера</button>
228
+ <button type="button" data-sec="g3">Дополнительно</button>
229
+ <div class="ssep"></div>
230
+ <button type="button" data-sec="cloud" id="snav-cloud">Профиль, тариф и боты</button>
231
+ <button type="button" data-sec="brokers" id="snav-brokers">Брокерские счета</button>
232
+ <button type="button" data-sec="llm">Модели ИИ</button>
233
+ <button type="button" data-sec="hist" id="snav-hist">История изменений</button>
234
+ <div class="ssep"></div>
235
+ <button id="btn-reset" type="button" class="sreset">Вернуть по умолчанию</button>
236
+ </aside>
237
+ <div class="spane">
238
+ <form id="params"></form>
239
+ <section class="ssec" data-sec="cloud" id="cloud-box" hidden>
240
+ <div class="stitle">Профиль, подписка и мессенджеры (Telegram / MAX)</div>
241
+ <div class="scard spadded" id="cloud-pane"></div>
242
+ </section>
243
+ <section class="ssec" data-sec="brokers" id="brokers-box" hidden>
244
+ <div class="stitle">Брокерские счета (Т-Банк, Финам, Алор, БКС)</div>
245
+ <div class="scard spadded">
246
+ <p class="muted small" style="margin-top:0">Токены хранятся только в локальной базе и в интерфейс не передаются. Предохранители счёта (лимит суммы, дневной стоп, защита краёв сессии) недоступны для изменения ИИ-Стратегом.</p>
247
+ <div id="brokers-list"></div>
248
+ <details id="broker-add-box" style="margin-top:12px">
249
+ <summary id="broker-add-sum">Добавить брокерский счёт</summary>
250
+ <form id="broker-form" autocomplete="off" style="margin-top:10px;display:grid;gap:10px">
251
+ <input type="hidden" name="id">
252
+ <label><span>Название счёта</span><input name="name" placeholder="Например, Т-Банк основной"></label>
253
+ <label><span>Брокер</span>
254
+ <select name="broker">
255
+ <option value="tbank">Т-Банк (T-Invest API)</option>
256
+ <option value="finam">Финам (Trade API)</option>
257
+ <option value="alor">Алор (OpenAPI)</option>
258
+ <option value="bcs">БКС Мир инвестиций</option>
259
+ </select>
260
+ </label>
261
+ <label><span>Номер счёта / портфеля</span><input name="accountId" placeholder="ID счёта или портфеля"></label>
262
+ <label><span id="broker-token-lbl">Токен API</span><input name="token" type="password" autocomplete="new-password" placeholder="Токен доступа к API брокера"></label>
263
+ <label><span>Режим работы счёта</span>
264
+ <select name="mode">
265
+ <option value="readonly">Только чтение (портфель и сверка)</option>
266
+ <option value="confirm" selected>Подтверждение заявок (рекомендуется)</option>
267
+ <option value="auto">Автомат (лимитные заявки)</option>
268
+ </select>
269
+ </label>
270
+ <label><span>Номер эксперимента для настроек (пусто — текущие)</span><input name="baseRunId" type="number" min="1" placeholder="например, 234"></label>
271
+ <label><span>Лимит суммы на стратегию, ₽ (предохранитель)</span><input name="maxCapitalRub" type="number" min="10000" step="10000" value="100000"></label>
272
+ <label><span>Дневной стоп по просадке, % (предохранитель)</span><input name="maxDailyDrawdownPct" type="number" min="0.5" max="50" step="0.5" value="3"></label>
273
+ <label><span>Защитный отступ лимитной заявки, %</span><input name="limitOffsetPct" type="number" min="0" max="2" step="0.05" value="0.1"></label>
274
+ <label class="check"><span>Формировать заявки автоматически по закрытию свечи (с отправкой в Telegram / MAX)</span><input name="autoSchedule" type="checkbox" class="switch" checked></label>
275
+ <label class="check"><span>Не торговать в первые и последние 10 минут сессии</span><input name="skipEdgeMinutes" type="checkbox" class="switch" checked></label>
276
+ <label class="check"><span>Песочница брокера (тестовый контур)</span><input name="sandbox" type="checkbox" class="switch"></label>
277
+ <div class="row-gap">
278
+ <button class="primary" type="submit" id="broker-save-btn">Сохранить счёт</button>
279
+ <button type="button" id="broker-cancel-btn" hidden>Отмена</button>
280
+ </div>
281
+ </form>
282
+ </details>
283
+ </div>
284
+ </section>
285
+ <section class="ssec" data-sec="llm" id="llm-box" hidden>
286
+ <div class="stitle" id="llm-sum">Модели ИИ для Стратега</div>
287
+ <div class="scard spadded">
288
+ <p class="muted small" style="margin-top:0">Выберите модель в шапке чата. Ключи хранятся только в базе на этом компьютере и в интерфейсе не показываются.</p>
289
+ <div id="llm-list"></div>
290
+ <details id="llm-add-box">
291
+ <summary id="llm-add-sum">Добавить подключение</summary>
292
+ <form id="llm-add" autocomplete="off">
293
+ <input type="hidden" name="pid">
294
+ <label><span>Название</span><input name="name" required placeholder="Например, Мой сервер"></label>
295
+ <label><span>Протокол</span>
296
+ <select name="protocol">
297
+ <option value="openai">OpenAI-совместимый</option>
298
+ <option value="gigachat">GigaChat</option>
299
+ <option value="ollama">Ollama (на этом компьютере)</option>
300
+ <option value="anthropic">Anthropic (только беседа)</option>
301
+ </select>
302
+ </label>
303
+ <label><span>Адрес</span><input name="baseUrl" placeholder="https://…/v1/chat/completions"></label>
304
+ <label data-for="key"><span id="llm-key-label">Ключ</span><input name="apiKey" type="password" autocomplete="new-password"></label>
305
+ <label data-for="scope" hidden><span>Область доступа (scope)</span><input name="scope" placeholder="GIGACHAT_API_PERS"></label>
306
+ <label data-for="cert" hidden><span>Сертификат НУЦ Минцифры (путь к .pem, для GigaChat)</span><input name="caFile" placeholder="путь к russian_trusted_root_ca.pem"></label>
307
+ <label data-for="model"><span>Модель (идентификатор на сервере)</span><input name="model" placeholder="например, qwen/Qwen3-8B"></label>
308
+ <label class="check" data-for="tools"><span>Модель умеет пользоваться инструментами</span><input name="tools" type="checkbox" checked></label>
309
+ <label class="check" data-for="clearkey" hidden><span>Убрать сохранённый ключ</span><input name="clearKey" type="checkbox"></label>
310
+ <div class="row-gap">
311
+ <button class="primary" type="submit" id="llm-add-btn">Добавить</button>
312
+ <button type="button" id="llm-cancel" hidden>Отмена</button>
313
+ </div>
314
+ </form>
315
+ </details>
316
+ </div>
317
+ </section>
318
+ <section class="ssec" data-sec="hist" id="settings-history" hidden>
319
+ <div class="stitle">История изменений</div>
320
+ <div class="scard spadded" id="history"></div>
321
+ </section>
322
+ </div>
323
+ </div>
324
+ </div>
325
+ </section>
326
+ </main>
327
+
328
+ <div id="help" class="modal" hidden>
329
+ <div class="modal-box">
330
+ <div class="modal-head"><b>Справка</b><button id="help-close" class="icon">✕</button></div>
331
+ <div id="help-body" class="md">Загрузка…</div>
332
+ </div>
333
+ </div>
334
+ <div id="toast"></div>
335
+ <script src="app.js" type="module"></script>
336
+ </body>
337
+ </html>