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,90 @@
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
+ <style>
8
+ :root { --bg: #0b0e14; --panel: #131822; --line: #242c3d; --text: #eef2f6; --muted: #94a3b8; --accent: #3b82f6; }
9
+ * { box-sizing: border-box; }
10
+ body { margin: 0; background: var(--bg); color: var(--text); font: 15.5px/1.65 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; }
11
+ a { color: var(--accent); text-decoration: none; }
12
+ .wrap { max-width: 900px; margin: 0 auto; padding: 36px 24px 70px; }
13
+ .top { display: flex; justify-content: space-between; align-items: center; margin-bottom: 28px; padding-bottom: 16px; border-bottom: 1px solid var(--line); }
14
+ .brand { font-weight: 700; font-size: 19px; color: var(--text); }
15
+ .doc { background: var(--panel); border: 1px solid var(--line); border-radius: 12px; padding: 28px 32px; margin-bottom: 24px; }
16
+ h1 { font-size: 28px; margin: 0 0 18px; }
17
+ h2 { font-size: 21px; margin: 0 0 12px; color: #fff; }
18
+ h3 { font-size: 16px; margin: 18px 0 6px; color: #cbd5e1; }
19
+ p, li { color: var(--muted); margin: 8px 0; }
20
+ ul { padding-left: 20px; }
21
+ </style>
22
+ </head>
23
+ <body>
24
+ <div class="wrap">
25
+ <div class="top">
26
+ <a class="brand" href="/landing">◈ НейроАльфа</a>
27
+ <div style="display:flex;gap:16px;font-size:14px">
28
+ <a href="#offer">Публичная оферта</a>
29
+ <a href="#privacy">Политика 152-ФЗ</a>
30
+ <a href="#risks">Декларация о рисках</a>
31
+ <a href="/landing">← Назад на витрину</a>
32
+ </div>
33
+ </div>
34
+
35
+ <h1>Правовые документы сервиса «НейроАльфа»</h1>
36
+
37
+ <section class="doc" id="offer">
38
+ <h2>1. Публичная оферта (Договор об оказании информационных услуг доступа к SaaS-платформе)</h2>
39
+ <p>Настоящий документ является официальным предложением (публичной офертой в соответствии со ст. 435 и ч. 2 ст. 437 Гражданского кодекса РФ) Администрации сервиса «НейроАльфа» (далее — Исполнитель) любому дееспособному физическому или юридическому лицу (далее — Пользователь) заключить договор на предоставление доступа к вычислительному программному комплексу «НейроАльфа» через сеть Интернет (модель SaaS).</p>
40
+
41
+ <h3>1.1. Предмет договора и правовой статус сервиса</h3>
42
+ <ul>
43
+ <li>Исполнитель предоставляет Пользователю дистанционный доступ к функциям аналитического программного обеспечения «НейроАльфа» в соответствии с выбранным Пользователем тарифным планом («Старт», «Инвестор», «Профи»).</li>
44
+ <li>Сервис «НейроАльфа» является исключительно программно-техническим средством (калькулятором статистических закономерностей и интерфейсом автоматизации отправки поручений по параметрам, заданным самим Пользователем).</li>
45
+ <li><b>Исполнитель не осуществляет деятельность по инвестиционному консультированию</b> (в смысле ст. 6.1 Федерального закона № 39-ФЗ «О рынке ценных бумаг»), не составляет индивидуальных инвестиционных профилей и не даёт индивидуальных инвестиционных рекомендаций.</li>
46
+ <li><b>Исполнитель не осуществляет доверительное управление</b> или брокерскую деятельность, не принимает денежные средства или ценные бумаги Пользователей на свои счета. Все торговые операции совершаются Пользователем самостоятельно на его личном счёте у лицензированного брокера.</li>
47
+ </ul>
48
+
49
+ <h3>1.2. Порядок оплаты и предоставления доступа</h3>
50
+ <ul>
51
+ <li>Оплата тарифных планов производится в рублях РФ безналичным способом (банковские карты МИР, Visa, Mastercard, Система быстрых платежей).</li>
52
+ <li>Доступ к оплаченному тарифному плану активируется автоматически в момент получения подтверждения об успешной оплате (`succeeded`) от платёжной системы на срок оплаченного периода (30, 90 или 365 календарных дней).</li>
53
+ <li>Услуга предоставления доступа к SaaS-платформе считается оказанной надлежащим образом с момента открытия технической возможности использования оплаченного тарифа в личном кабинете Пользователя.</li>
54
+ </ul>
55
+
56
+ <h3>1.3. Ограничение ответственности</h3>
57
+ <ul>
58
+ <li>Программное обеспечение предоставляется по принципу «как есть» (as is). Исполнитель не гарантирует достижения какой-либо положительной доходности при использовании аналитических расчётов программы.</li>
59
+ <li>Пользователь самостоятельно задаёт параметры стратегии, лимиты суммы, пороги дневного стопа и несёт полную имущественную ответственность за результаты сделок, совершаемых через подключённые им API-токены брокеров.</li>
60
+ </ul>
61
+ </section>
62
+
63
+ <section class="doc" id="privacy">
64
+ <h2>2. Политика обработки персональных данных (в соответствии с ФЗ № 152-ФЗ)</h2>
65
+ <h3>2.1. Какие данные обрабатываются</h3>
66
+ <ul>
67
+ <li>Адрес электронной почты (Email) и указанное Пользователем имя/псевдоним при регистрации и формировании кассового чека (54-ФЗ);</li>
68
+ <li>Идентификатор чата (chat_id) в мессенджерах Telegram и MAX при добровольной привязке уведомлений Пользователем;</li>
69
+ <li>Пользовательские API-токены брокеров — хранятся исключительно в зашифрованном виде по алгоритму <b>AES-256-GCM</b> и используются только для выполнения запросов по поручению Пользователя.</li>
70
+ </ul>
71
+ <h3>2.2. Цели обработки и защита</h3>
72
+ <ul>
73
+ <li>Предоставление доступа к личному кабинету, отправка уведомлений о рассчитанных заявках и исполнение требований законодательства РФ о применении контрольно-кассовой техники.</li>
74
+ <li>Персональные данные хранятся на территории Российской Федерации и не передаются третьим лицам, за исключением уполномоченного платёжного оператора в объёме, необходимом для проведения платежа и формирования кассового чека.</li>
75
+ <li>Пользователь вправе в любой момент удалить свои API-токены в разделе «Настройки → Брокерские счета» и запросить удаление учётной записи.</li>
76
+ </ul>
77
+ </section>
78
+
79
+ <section class="doc" id="risks">
80
+ <h2>3. Декларация о рисках</h2>
81
+ <p>Совершение операций с ценными бумагами и производными финансовыми инструментами на бирже сопряжено с высоким уровнем финансового риска и может привести к частичной или полной потере инвестированного капитала.</p>
82
+ <ul>
83
+ <li>Результаты пошаговых проверок на исторических данных и результаты на виртуальных счетах рассчитываются на прошлых котировках и <b>не гарантируют</b> получения аналогичных результатов в будущем.</li>
84
+ <li>При работе с реальным брокерским счётом возможны технические задержки связи, расхождения между расчётным и фактическим проскальзыванием в биржевом стакане, отказы API брокера и резкие ценовые разрывы (гэпы).</li>
85
+ <li>Используя сервис «НейроАльфа», Пользователь подтверждает, что осознаёт указанные риски и принимает все инвестиционные решения самостоятельно.</li>
86
+ </ul>
87
+ </section>
88
+ </div>
89
+ </body>
90
+ </html>
@@ -0,0 +1,343 @@
1
+ :root {
2
+ --bg: #0d1016;
3
+ --panel: #13171f;
4
+ --line: #2a313e;
5
+ --text: #e6edf3;
6
+ --muted: #98a2b3;
7
+ --accent: #4f8cff;
8
+ --green: #3fb950;
9
+ --red: #f85149;
10
+ --yellow: #d29922;
11
+ --violet: #a371f7;
12
+ --best-bg: #13241a;
13
+ --flash-bg: #2a1f45;
14
+ --chat-w: 38%;
15
+ }
16
+ body.light {
17
+ --bg: #f6f8fa;
18
+ --panel: #ffffff;
19
+ --line: #d0d7de;
20
+ --text: #1f2328;
21
+ --muted: #57606a;
22
+ --accent: #2457d6;
23
+ --green: #1a7f37;
24
+ --red: #cf222e;
25
+ --yellow: #9a6700;
26
+ --violet: #8250df;
27
+ --best-bg: #dcfce7;
28
+ --flash-bg: #ede9fe;
29
+ }
30
+ * { box-sizing: border-box; }
31
+ html, body { height: 100%; }
32
+ body { margin: 0; background: var(--bg); color: var(--text); font: 14px/1.5 system-ui, -apple-system, "Segoe UI", Roboto, sans-serif; overflow: hidden; }
33
+ .muted { color: var(--muted); }
34
+ .small { font-size: 12px; }
35
+ .grow { flex: 1; }
36
+ .pos { color: var(--green); }
37
+ .neg { color: var(--red); }
38
+ h4 { margin: 18px 0 6px; font-size: 13px; color: var(--muted); font-weight: 500; }
39
+
40
+ header { height: 46px; display: flex; justify-content: space-between; align-items: center; padding: 0 16px; border-bottom: 1px solid var(--line); }
41
+ .brand { display: flex; gap: 8px; align-items: baseline; }
42
+ .logo { color: var(--accent); font-size: 18px; }
43
+ .hright { display: flex; gap: 10px; align-items: center; }
44
+ .dot { width: 9px; height: 9px; border-radius: 50%; background: var(--muted); display: inline-block; cursor: help; }
45
+ .dot.ok { background: var(--green); }
46
+ .dot.warn { background: var(--yellow); }
47
+ .dot.bad { background: var(--red); }
48
+
49
+ input, select, textarea { background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 6px; padding: 6px 8px; font: inherit; }
50
+ textarea { resize: none; width: 100%; }
51
+ button { background: var(--panel); color: var(--text); border: 1px solid var(--line); border-radius: 6px; padding: 6px 12px; cursor: pointer; font: inherit; font-weight: 500; }
52
+ button:hover { border-color: var(--accent); }
53
+ button:disabled { opacity: .55; cursor: default; }
54
+ button.primary { background: var(--accent); border-color: var(--accent); color: #fff; }
55
+ button.icon { padding: 2px 8px; color: var(--text); background: transparent; }
56
+ button.icon-lg { width: 40px; height: 40px; padding: 0; font-size: 15px; flex: none; }
57
+
58
+ main { display: grid; grid-template-columns: var(--chat-w) 5px 1fr; height: calc(100vh - 46px); }
59
+ #splitter { cursor: col-resize; background: var(--line); transition: background .15s; }
60
+ #splitter:hover, #splitter.drag { background: var(--accent); }
61
+ body.dragging { cursor: col-resize; user-select: none; }
62
+ body.dragging iframe, body.dragging canvas { pointer-events: none; }
63
+
64
+ ::-webkit-scrollbar { width: 10px; height: 10px; }
65
+ ::-webkit-scrollbar-thumb { background: #2a3140; border-radius: 5px; border: 2px solid var(--bg); }
66
+ ::-webkit-scrollbar-thumb:hover { background: #3a4356; }
67
+ ::-webkit-scrollbar-track { background: transparent; }
68
+ .feed, .tab, .think-body, .modal .md { scrollbar-width: thin; scrollbar-color: #2a3140 transparent; }
69
+
70
+ /* ---------- чат ---------- */
71
+ .chat { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
72
+ .chat-head { display: flex; align-items: center; gap: 6px; padding: 8px 12px; border-bottom: 1px solid var(--line); }
73
+ .chat-head select { font-size: 12.5px; padding: 3px 6px; }
74
+ .chat-head label { font-size: 12px; color: var(--muted); }
75
+ .chat-head label input { width: 46px; padding: 2px 4px; }
76
+ .feed-wrap { flex: 1; min-height: 0; position: relative; display: flex; flex-direction: column; }
77
+ .feed { flex: 1; overflow-y: auto; padding: 14px; display: flex; flex-direction: column; gap: 10px; min-height: 0; }
78
+ .feed-down { position: absolute; right: 16px; bottom: 10px; font-size: 12px; padding: 2px 12px; border-radius: 999px; background: var(--panel); box-shadow: 0 4px 14px #0008; }
79
+ .msg { max-width: 92%; }
80
+ .msg.user { align-self: flex-end; background: #1d3359; border-radius: 12px 12px 3px 12px; padding: 7px 12px; white-space: pre-wrap; }
81
+ .msg.ai { align-self: stretch; }
82
+ .md-c > :first-child { margin-top: 0; }
83
+ .md-c > :last-child { margin-bottom: 0; }
84
+ .md-c p { margin: 6px 0; }
85
+ .md-c ul, .md-c ol { margin: 6px 0; padding-left: 20px; }
86
+ .md-c h1, .md-c h2, .md-c h3 { font-size: 14.5px; margin: 10px 0 4px; }
87
+ .md-c table { margin: 6px 0; font-size: 12.5px; }
88
+ .md-c code { background: var(--panel); padding: 0 4px; border-radius: 4px; }
89
+ details.think { font-size: 12px; margin-bottom: 4px; }
90
+ details.think summary { cursor: pointer; color: #b392f0; list-style: none; }
91
+ details.think summary::-webkit-details-marker { display: none; }
92
+ .think-body { white-space: pre-wrap; color: #9c95b5; border-left: 2px solid var(--violet); padding: 4px 10px; margin-top: 4px; max-height: 200px; overflow-y: auto; }
93
+ .typing::after { content: '▍'; animation: blink 1s steps(1) infinite; color: var(--accent); }
94
+ @keyframes blink { 50% { opacity: 0; } }
95
+ .act { margin-top: 6px; border: 1px solid var(--line); border-radius: 8px; padding: 6px 10px; font-size: 12.5px; }
96
+ .act.pending { border-color: var(--yellow); }
97
+ .act .h { display: flex; justify-content: space-between; gap: 8px; }
98
+ .act .p { white-space: pre-wrap; color: var(--muted); }
99
+ .act .btns { display: flex; gap: 6px; margin-top: 6px; }
100
+ .act .btns button { padding: 3px 10px; font-size: 12.5px; }
101
+ .st { font-size: 11px; color: var(--muted); white-space: nowrap; }
102
+ .st.pending { color: var(--yellow); }
103
+ .st.running { color: var(--accent); }
104
+ .st.done { color: var(--green); }
105
+ .st.error, .st.rejected { color: var(--red); }
106
+ .link { color: var(--accent); cursor: pointer; }
107
+ .ev { font-size: 12px; color: var(--muted); padding: 0 4px; }
108
+ .ev.ok::before { content: '● '; color: var(--green); }
109
+ .ev.bad::before { content: '● '; color: var(--red); }
110
+ .ev.info::before { content: '● '; color: var(--muted); }
111
+ .live { font-size: 12px; color: var(--muted); border-left: 2px solid var(--accent); padding: 2px 10px; }
112
+ .live .bar-s { height: 3px; background: var(--line); border-radius: 2px; margin-top: 4px; overflow: hidden; }
113
+ .live .bar-s div { height: 100%; background: var(--accent); width: 0; transition: width .2s; }
114
+ .composer { padding: 10px 14px 10px; border-top: 1px solid var(--line); }
115
+ .suggest { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 8px; }
116
+ .suggest:empty { display: none; }
117
+ .suggest button { font-size: 12px; padding: 3px 10px; border-radius: 999px; color: var(--muted); }
118
+ .prompt-box { background: var(--panel); border: 1.5px solid var(--line); border-radius: 20px; padding: 12px 14px 10px; display: flex; flex-direction: column; gap: 6px; transition: border-color .15s, box-shadow .15s; }
119
+ .prompt-box:focus-within { border-color: var(--violet); box-shadow: 0 0 0 2px #a371f722; }
120
+ .prompt-box textarea { background: transparent; border: 0; padding: 0 2px; outline: none; font-size: 14px; line-height: 1.45; min-height: 36px; max-height: 150px; overflow-y: auto; }
121
+ .prompt-bar { display: flex; justify-content: space-between; align-items: center; gap: 8px; flex-wrap: wrap; margin-top: 2px; }
122
+ .prompt-left, .prompt-right { display: flex; align-items: center; gap: 6px; min-width: 0; }
123
+ .prompt-right { margin-left: auto; }
124
+ .ghost-select { background: transparent; border: 1px solid transparent; border-radius: 8px; padding: 4px 8px; font-size: 12.5px; font-weight: 400; color: var(--text); cursor: pointer; display: inline-flex; align-items: center; gap: 5px; max-width: 240px; transition: background .12s; }
125
+ .ghost-select:hover { background: #ffffff0d; border-color: transparent; }
126
+ body.light .ghost-select:hover { background: #eaeef2; }
127
+ .ghost-select .g-ico { display: inline-flex; align-items: center; color: var(--muted); flex: none; }
128
+ .ghost-select .caret-svg { color: var(--muted); flex: none; margin-left: 1px; }
129
+ .mode-picker, .model-picker { position: relative; display: inline-flex; min-width: 0; }
130
+ .model-btn #chat-model-lbl { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
131
+ .pop-menu { position: absolute; bottom: calc(100% + 8px); background: var(--panel); border: 1px solid var(--line); border-radius: 10px; box-shadow: 0 10px 30px rgba(0, 0, 0, 0.45); z-index: 60; display: flex; flex-direction: column; overflow: hidden; }
132
+ .pop-menu[hidden] { display: none; }
133
+ body.light .pop-menu { box-shadow: 0 10px 28px rgba(15, 23, 42, 0.16); }
134
+ .mode-menu { left: 0; width: 245px; padding: 4px; }
135
+ .model-menu { right: 0; width: 290px; }
136
+ .model-search-wrap { display: flex; align-items: center; gap: 6px; padding: 7px 10px; border-bottom: 1px solid var(--line); font-size: 12px; }
137
+ .model-search-wrap input { border: 0; background: transparent; padding: 2px 0; width: 100%; outline: none; font-size: 12.5px; color: var(--text); }
138
+ .model-menu-list { max-height: 240px; overflow-y: auto; padding: 4px; scrollbar-width: thin; }
139
+ .model-group-lbl { font-size: 11px; font-weight: 600; color: var(--muted); padding: 6px 8px 2px; text-transform: uppercase; letter-spacing: 0.3px; }
140
+ .model-opt { width: 100%; border: 0; background: transparent; color: var(--text); text-align: left; padding: 6px 8px; border-radius: 6px; font-size: 12.5px; font-weight: 400; display: flex; align-items: center; justify-content: space-between; gap: 8px; }
141
+ .model-opt:hover:not(:disabled), .model-opt.active { background: #ffffff10; border-color: transparent; }
142
+ body.light .model-opt:hover:not(:disabled), body.light .model-opt.active { background: #eaeef2; }
143
+ .model-opt .m-name { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
144
+ .model-opt .m-badge { font-size: 10.5px; color: var(--muted); border: 1px solid var(--line); border-radius: 4px; padding: 0 5px; flex: none; }
145
+ .model-opt .m-check { color: var(--accent); font-weight: 700; flex: none; }
146
+ .model-manage-btn { width: 100%; border: 0; border-top: 1px solid var(--line); border-radius: 0; background: transparent; color: var(--text); padding: 9px 12px; font-size: 12.5px; font-weight: 500; display: flex; align-items: center; gap: 8px; text-align: left; }
147
+ .model-manage-btn:hover { background: #ffffff0c; color: var(--accent); border-color: var(--line); }
148
+ body.light .model-manage-btn:hover { background: #f0f3f6; }
149
+ .pbtn { width: 34px; height: 34px; border-radius: 999px; padding: 0; display: inline-flex; align-items: center; justify-content: center; font-size: 14px; background: var(--bg); border: 1px solid var(--line); color: var(--text); transition: background .15s, border-color .15s, color .15s, transform .15s; flex: none; }
150
+ .pbtn[hidden], .pbtn svg[hidden] { display: none; }
151
+ .pbtn.action-btn { background: #ffffff0c; border-color: transparent; color: var(--text); }
152
+ body.light .pbtn.action-btn { background: #eef1f5; color: #374151; }
153
+ .pbtn.action-btn:hover:not(:disabled) { border-color: var(--violet); color: var(--violet); }
154
+ .pbtn.action-btn.send-mode { background: var(--violet); border-color: var(--violet); color: #fff; }
155
+ body.light .pbtn.action-btn.send-mode { background: #9333ea; border-color: #9333ea; color: #fff; }
156
+ .pbtn.stop { border-color: var(--red); color: var(--red); }
157
+ .pbtn.rec { background: var(--red) !important; border-color: var(--red) !important; color: #fff !important; animation: pulse 1.2s infinite; }
158
+ @keyframes pulse { 0%, 100% { transform: scale(1); } 50% { transform: scale(1.08); } }
159
+ .prompt-hint { padding: 5px 6px 0; font-size: 11.5px; opacity: 0.78; }
160
+ #chat-status { font-size: 12px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
161
+
162
+ /* ---------- правая часть ---------- */
163
+ .right { display: flex; flex-direction: column; min-width: 0; min-height: 0; }
164
+ .tabs { display: flex; gap: 2px; padding: 0 12px; border-bottom: 1px solid var(--line); }
165
+ .tabs button { border: 0; border-bottom: 2px solid transparent; border-radius: 0; color: var(--muted); padding: 10px 12px; display: inline-flex; align-items: center; gap: 6px; }
166
+ .tabs button.active { color: var(--text); border-bottom-color: var(--accent); }
167
+ .tstat { display: inline-flex; align-items: center; gap: 4px; }
168
+ .tstat:empty { display: none; }
169
+ .tbadge { font-size: 11px; line-height: 1.3; padding: 1px 6px; border-radius: 999px; background: var(--panel); border: 1px solid var(--line); color: var(--muted); font-weight: 500; font-variant-numeric: tabular-nums; display: inline-flex; align-items: center; gap: 3px; }
170
+ .tbadge.ok { border-color: #3fb95066; color: var(--green); background: #3fb95014; }
171
+ .tbadge.run { border-color: #4f8cff88; color: var(--accent); background: #4f8cff18; }
172
+ body.light .tbadge { background: #eef1f5; }
173
+ body.light .tbadge.ok { background: #dcfce7; border-color: #86efac; }
174
+ body.light .tbadge.run { background: #dbeafe; border-color: #93c5fd; }
175
+ .tspin { width: 11px; height: 11px; border: 1.8px solid currentColor; border-right-color: transparent; border-radius: 50%; display: inline-block; animation: tspin .75s linear infinite; }
176
+ @keyframes tspin { to { transform: rotate(360deg); } }
177
+ .tab { flex: 1; overflow-y: auto; padding: 16px 20px 30px; min-height: 0; }
178
+ .tab[hidden] { display: none; }
179
+ .toolbar { display: flex; gap: 8px; align-items: center; }
180
+ .toolbar select { flex: 1; min-width: 0; }
181
+ .progress { position: relative; height: 22px; background: var(--panel); border-radius: 6px; overflow: hidden; margin-top: 10px; }
182
+ .progress .bar { position: absolute; inset: 0 auto 0 0; width: 0; background: #24467f; transition: width .2s; }
183
+ .progress span { position: relative; font-size: 12px; padding: 0 8px; line-height: 22px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; display: block; }
184
+ .empty { margin-top: 30px; color: var(--muted); line-height: 1.7; max-width: 640px; }
185
+ .empty b { color: var(--text); }
186
+ .verdict { margin: 16px 0 12px; font-size: 15px; line-height: 1.5; }
187
+ .kpis { display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px; background: var(--line); border: 1px solid var(--line); border-radius: 8px; overflow: hidden; }
188
+ .kpi { background: var(--bg); padding: 10px 14px; cursor: help; }
189
+ .kpi .v { font-size: 22px; font-weight: 600; }
190
+ .kpi .l { font-size: 12px; color: var(--muted); }
191
+ .seg { display: inline-flex; border: 1px solid var(--line); border-radius: 6px; overflow: hidden; margin: 14px 0 6px; }
192
+ .seg button { border: 0; border-radius: 0; padding: 3px 10px; font-size: 12px; color: var(--muted); }
193
+ .seg button.active { background: var(--panel); color: var(--text); }
194
+ .chart { height: 260px; }
195
+ .chart.small { height: 160px; }
196
+ .legend { display: flex; gap: 14px; flex-wrap: wrap; font-size: 12px; color: var(--muted); margin-top: 4px; }
197
+ .legend i { display: inline-block; width: 12px; height: 3px; vertical-align: middle; margin-right: 5px; }
198
+ details#periods-box #pmode { margin-top: 0; }
199
+ #periods table { table-layout: fixed; }
200
+ #periods th, #periods td { padding: 4px 2px; text-align: center; font-size: 12px; }
201
+ #periods td.num { text-align: center; }
202
+ #periods th.y, #periods td.y { text-align: left; width: 74px; padding-left: 4px; color: var(--muted); }
203
+ #periods td.cell { cursor: help; }
204
+ #periods td.oos { border-bottom: 2px dotted var(--muted); }
205
+ #periods td.tot { font-weight: 600; border-left: 1px solid var(--line); }
206
+ #periods .note { margin: 8px 0 0; }
207
+ details#more, details#trades-box, details#periods-box { margin-top: 18px; border-top: 1px solid var(--line); padding-top: 10px; }
208
+ details > summary { cursor: pointer; color: var(--muted); }
209
+ details[open] > summary { color: var(--text); margin-bottom: 8px; }
210
+ .block { margin-bottom: 26px; }
211
+
212
+ /* новости (только прогноз «сейчас» и живая проверка) */
213
+ .news-item { padding: 3px 0; font-size: 13px; }
214
+ .news-item a { color: var(--text); text-decoration: none; border-bottom: 1px dotted var(--line); }
215
+ .news-item a:hover { color: var(--accent); }
216
+ .bhead { display: flex; justify-content: space-between; align-items: center; gap: 10px; margin-bottom: 10px; }
217
+ #map-slider { width: 260px; }
218
+ #map-canvas { width: 100%; height: calc(100vh - 200px); min-height: 380px; display: block; cursor: pointer; }
219
+
220
+ table { width: 100%; border-collapse: collapse; font-size: 12.5px; }
221
+ th, td { text-align: left; padding: 5px 8px; border-bottom: 1px solid var(--line); vertical-align: top; }
222
+ th { color: var(--muted); font-weight: 500; }
223
+ td.num, th.num { text-align: right; font-variant-numeric: tabular-nums; }
224
+ tr.best td { background: var(--best-bg); }
225
+ .tag { font-size: 11px; padding: 0 6px; border-radius: 4px; border: 1px solid var(--line); color: var(--muted); }
226
+ .tag.ok { border-color: var(--green); color: var(--green); }
227
+ .tag.no { border-color: var(--red); color: var(--red); }
228
+ .tag.mid { border-color: var(--yellow); color: var(--yellow); }
229
+ .stab-box { background: var(--panel); border: 1px solid var(--line); border-radius: 8px; padding: 10px 14px; margin: 8px 0 10px; font-size: 13px; }
230
+ .stab-head { display: flex; flex-wrap: wrap; gap: 8px; align-items: center; }
231
+ .stab-chips { display: flex; flex-wrap: wrap; gap: 6px; margin-top: 8px; }
232
+ .stab-chip { background: var(--bg); border: 1px solid var(--line); border-radius: 6px; padding: 2px 8px; font-size: 12px; }
233
+ .live-card { background: var(--bg); border: 1px solid var(--line); border-radius: 8px; padding: 12px 14px; margin-top: 10px; }
234
+ .live-card.stopped { opacity: .75; }
235
+ .live-head { display: flex; justify-content: space-between; align-items: flex-start; gap: 12px; margin-bottom: 10px; }
236
+ .stab-box[hidden], .run-bar[hidden], .run-bar button[hidden] { display: none; }
237
+ .run-bar { display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 8px; margin: 8px 0 4px; padding-bottom: 10px; border-bottom: 1px solid var(--line); }
238
+ .run-bar button { background: var(--panel); color: var(--text); border: 1px solid var(--line); padding: 5px 11px; font-size: 12.5px; font-weight: 500; }
239
+ .run-bar button:hover { border-color: var(--accent); color: var(--accent); }
240
+ .run-bar button:disabled { opacity: .85; color: var(--accent); border-color: var(--accent); }
241
+ .run-bar-left { display: flex; align-items: center; flex-wrap: wrap; gap: 6px; }
242
+ .act-chips { display: flex; flex-wrap: wrap; gap: 5px; margin: 6px 0; }
243
+ body.light .msg.user { background: #dbe8ff; color: #1f2328; }
244
+ body.light details.think summary { color: var(--violet); }
245
+ body.light .think-body { color: #4b5563; }
246
+ body.light .scard { background: var(--panel); }
247
+ body.light .snav button.active { background: #eaeef2; color: var(--text); }
248
+ body.light .snav button:hover { background: #f0f3f6; }
249
+ body.light .progress .bar { background: #b6d0ff; }
250
+ body.light .feed-down, body.light #toast div { box-shadow: 0 6px 20px rgba(15, 23, 42, 0.14); }
251
+ body.light ::-webkit-scrollbar-thumb { background: #c1c8d1; border-color: var(--bg); }
252
+ body.light ::-webkit-scrollbar-thumb:hover { background: #9aa4b2; }
253
+ body.light .feed, body.light .tab, body.light .think-body, body.light .modal .md { scrollbar-color: #c1c8d1 transparent; }
254
+ body.light input[type=checkbox].switch { background: #d0d7de; border-color: #afb8c1; }
255
+ body.light input[type=checkbox].switch::after { background: #fff; }
256
+ body.light input[type=checkbox].switch:checked { background: #1a7f37; border-color: #1a7f37; }
257
+ .headbar { display: grid; grid-template-columns: 1fr 140px 56px; gap: 8px; align-items: center; font-size: 12.5px; padding: 1px 0; }
258
+ .headbar .b { height: 6px; background: var(--panel); border-radius: 3px; position: relative; }
259
+ .headbar .b div { position: absolute; top: 0; bottom: 0; border-radius: 3px; }
260
+
261
+ .settings-wrap { display: grid; grid-template-columns: 220px minmax(0, 740px); gap: 36px; align-items: start; }
262
+ .snav { display: flex; flex-direction: column; gap: 3px; position: sticky; top: 0; }
263
+ .snav #s-search { width: 100%; margin-bottom: 10px; background: transparent; border-color: var(--line); padding: 6px 10px; font-size: 13px; }
264
+ .snav button { text-align: left; border: 0; padding: 7px 12px; border-radius: 6px; color: var(--muted); font-size: 13.5px; }
265
+ .snav button:hover { background: #ffffff08; color: var(--text); }
266
+ .snav button.active { background: #262b36; color: var(--text); font-weight: 500; }
267
+ .snav .ssep { height: 1px; background: var(--line); margin: 8px 4px; }
268
+ .snav .sreset { color: var(--muted); font-size: 12.5px; }
269
+ .spane { min-width: 0; }
270
+ .ssec { margin-bottom: 24px; }
271
+ .stitle { font-size: 14px; font-weight: 600; margin: 2px 0 12px; color: var(--text); }
272
+ .stitle.bar { display: flex; justify-content: space-between; align-items: center; gap: 12px; flex-wrap: wrap; }
273
+ .scard { background: #141820; border: 1px solid var(--line); border-radius: 10px; padding: 0 20px; }
274
+ .scard.spadded { padding: 16px 20px; }
275
+ .srow { display: grid; grid-template-columns: 1fr auto; gap: 24px; align-items: center; padding: 15px 0; border-bottom: 1px solid var(--line); }
276
+ .srow:last-child { border-bottom: 0; }
277
+ .srow[hidden] { display: none; }
278
+ .srow .slbl { font-weight: 600; font-size: 13.5px; color: var(--text); }
279
+ .srow .hint { font-size: 12.5px; color: var(--muted); margin-top: 3px; line-height: 1.45; }
280
+ .srow .mark { border: 0; padding: 0 0 0 8px; font-size: 11px; font-weight: 400; color: var(--yellow); text-decoration: underline dotted; }
281
+ .srow select.sctrl { background: transparent; border: 1px solid transparent; padding: 5px 8px; font-weight: 500; text-align: right; cursor: pointer; }
282
+ .srow select.sctrl:hover, .srow select.sctrl:focus { background: var(--bg); border-color: var(--line); }
283
+ .srow input[type=number].sctrl { width: 110px; text-align: right; background: var(--bg); }
284
+ input[type=checkbox].switch { appearance: none; width: 38px; height: 22px; background: #2b313d; border: 1px solid #3a4252; border-radius: 999px; position: relative; cursor: pointer; transition: background .15s, border-color .15s; flex: none; padding: 0; }
285
+ input[type=checkbox].switch::after { content: ""; position: absolute; width: 16px; height: 16px; border-radius: 50%; background: #d0d7de; top: 2px; left: 2px; transition: transform .15s, background .15s; }
286
+ input[type=checkbox].switch:checked { background: #3fb9a0; border-color: #3fb9a0; }
287
+ input[type=checkbox].switch:checked::after { transform: translateX(16px); background: #fff; }
288
+ .chat-head #chat-model { max-width: 190px; min-width: 0; text-overflow: ellipsis; }
289
+ #llm-box details { margin-top: 12px; }
290
+ .llm-prov { border-bottom: 1px solid var(--line); padding: 8px 0; }
291
+ .llm-prov .llm-head { display: flex; align-items: center; gap: 6px; flex-wrap: wrap; }
292
+ .llm-prov .llm-head button { padding: 2px 9px; font-size: 12px; }
293
+ .llm-prov .llm-meta { font-size: 12px; color: var(--muted); word-break: break-all; }
294
+ .llm-prov .llm-meta .nokey { color: var(--yellow); }
295
+ .llm-models { margin-top: 4px; font-size: 12.5px; }
296
+ .llm-models .m { display: flex; align-items: center; gap: 8px; padding: 1px 0; }
297
+ .llm-models .m.on b { color: var(--accent); }
298
+ .llm-models .m label { color: var(--muted); font-size: 12px; display: inline-flex; align-items: center; gap: 4px; }
299
+ .llm-remote { display: flex; flex-wrap: wrap; gap: 4px; margin-top: 6px; }
300
+ .llm-remote button { padding: 1px 8px; font-size: 12px; border-radius: 999px; color: var(--muted); }
301
+ .llm-remote button:disabled { opacity: .5; }
302
+ .row-gap { display: flex; gap: 8px; }
303
+ form#llm-add, form#broker-form { display: grid; gap: 10px; }
304
+ form#llm-add label, form#broker-form label { display: grid; grid-template-columns: 1fr 1.4fr; gap: 10px; align-items: center; }
305
+ form#llm-add label[hidden], form#broker-form label[hidden] { display: none; }
306
+ form#llm-add label.check, form#broker-form label.check { grid-template-columns: 1fr auto; }
307
+ form#llm-add input[type=checkbox] { width: 18px; height: 18px; }
308
+ .flash { animation: flash 2.5s ease-out; }
309
+ @keyframes flash { 0% { background: var(--flash-bg); } 100% { background: transparent; } }
310
+
311
+ #toast { position: fixed; right: 16px; bottom: 16px; display: flex; flex-direction: column; gap: 8px; z-index: 50; }
312
+ #toast div { background: var(--panel); border: 1px solid var(--line); padding: 8px 12px; border-radius: 8px; max-width: 420px; font-size: 13px; box-shadow: 0 6px 20px #0008; }
313
+ #toast div.error { border-color: var(--red); }
314
+ .modal { position: fixed; inset: 0; background: #000a; z-index: 100; display: flex; justify-content: center; align-items: flex-start; padding: 30px 16px; }
315
+ .modal[hidden] { display: none; }
316
+ .modal-box { background: var(--panel); border: 1px solid var(--line); border-radius: 10px; width: min(860px, 100%); max-height: calc(100vh - 60px); display: flex; flex-direction: column; }
317
+ .modal-head { display: flex; justify-content: space-between; align-items: center; padding: 10px 18px; border-bottom: 1px solid var(--line); }
318
+ .md { padding: 4px 26px 26px; overflow: auto; line-height: 1.6; }
319
+ .md h1 { font-size: 21px; }
320
+ .md h2 { font-size: 17px; margin-top: 24px; }
321
+ .md h3 { font-size: 14.5px; }
322
+ .md table { margin: 8px 0; }
323
+ .md code { background: var(--bg); padding: 1px 5px; border-radius: 4px; }
324
+ .md pre { overflow: auto; background: var(--bg); padding: 10px; border-radius: 6px; }
325
+ .md blockquote { margin: 10px 0; padding: 4px 14px; border-left: 3px solid var(--accent); color: var(--muted); }
326
+ .md hr { border: 0; border-top: 1px solid var(--line); margin: 20px 0; }
327
+
328
+ /* Сравнение исследований */
329
+ .cmp-b { margin-top: 10px; padding: 10px 14px; background: var(--panel); border: 1px solid var(--line); border-radius: 8px; font-size: 13px; }
330
+ .cmp-b b { color: var(--text); }
331
+ .cmp-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 12px; margin-top: 8px; }
332
+ table.cmp { font-size: 12.5px; }
333
+ table.cmp th { padding: 4px 8px; font-size: 12px; }
334
+ table.cmp td { padding: 3px 8px; }
335
+
336
+ @media (max-width: 900px) {
337
+ body { overflow: auto; }
338
+ main { grid-template-columns: 1fr; height: auto; }
339
+ #splitter { display: none; }
340
+ .chat { height: 70vh; border-bottom: 1px solid var(--line); }
341
+ .kpis { grid-template-columns: repeat(2, 1fr); }
342
+ .cmp-grid { grid-template-columns: 1fr; }
343
+ }