@kaikybrofc/omnizap-system 2.1.8

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 (166) hide show
  1. package/.env.example +534 -0
  2. package/LICENSE +21 -0
  3. package/README.md +431 -0
  4. package/RELEASE-v2.1.2.md +83 -0
  5. package/app/config/adminIdentity.js +87 -0
  6. package/app/config/baileysConfig.js +693 -0
  7. package/app/config/groupUtils.js +388 -0
  8. package/app/connection/socketController.js +992 -0
  9. package/app/controllers/messageController.js +354 -0
  10. package/app/modules/adminModule/groupCommandHandlers.js +1294 -0
  11. package/app/modules/adminModule/groupEventHandlers.js +355 -0
  12. package/app/modules/aiModule/catCommand.js +1006 -0
  13. package/app/modules/broadcastModule/noticeCommand.js +416 -0
  14. package/app/modules/gameModule/diceCommand.js +67 -0
  15. package/app/modules/menuModule/common.js +311 -0
  16. package/app/modules/menuModule/menus.js +59 -0
  17. package/app/modules/playModule/playCommand.js +1615 -0
  18. package/app/modules/quoteModule/quoteCommand.js +851 -0
  19. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +786 -0
  20. package/app/modules/rpgPokemonModule/rpgBattleService.js +2082 -0
  21. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +760 -0
  22. package/app/modules/rpgPokemonModule/rpgEvolutionUtils.js +22 -0
  23. package/app/modules/rpgPokemonModule/rpgPokemonCommand.js +172 -0
  24. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +192 -0
  25. package/app/modules/rpgPokemonModule/rpgPokemonDomain.test.js +93 -0
  26. package/app/modules/rpgPokemonModule/rpgPokemonEvolution.test.js +46 -0
  27. package/app/modules/rpgPokemonModule/rpgPokemonMessages.js +746 -0
  28. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1859 -0
  29. package/app/modules/rpgPokemonModule/rpgPokemonService.js +6738 -0
  30. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +354 -0
  31. package/app/modules/statsModule/globalRankingCommand.js +65 -0
  32. package/app/modules/statsModule/noMessageCommand.js +288 -0
  33. package/app/modules/statsModule/rankingCommand.js +60 -0
  34. package/app/modules/statsModule/rankingCommon.js +889 -0
  35. package/app/modules/stickerModule/addStickerMetadata.js +239 -0
  36. package/app/modules/stickerModule/convertToWebp.js +390 -0
  37. package/app/modules/stickerModule/stickerCommand.js +454 -0
  38. package/app/modules/stickerModule/stickerConvertCommand.js +156 -0
  39. package/app/modules/stickerModule/stickerTextCommand.js +657 -0
  40. package/app/modules/stickerPackModule/autoPackCollectorRuntime.js +20 -0
  41. package/app/modules/stickerPackModule/autoPackCollectorService.js +284 -0
  42. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +466 -0
  43. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +88 -0
  44. package/app/modules/stickerPackModule/semanticThemeClusterService.js +571 -0
  45. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +449 -0
  46. package/app/modules/stickerPackModule/stickerAssetRepository.js +400 -0
  47. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +180 -0
  48. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +4078 -0
  49. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +598 -0
  50. package/app/modules/stickerPackModule/stickerClassificationService.js +588 -0
  51. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +102 -0
  52. package/app/modules/stickerPackModule/stickerPackCatalogHttp.js +7506 -0
  53. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +1095 -0
  54. package/app/modules/stickerPackModule/stickerPackEngagementRepository.js +108 -0
  55. package/app/modules/stickerPackModule/stickerPackErrors.js +30 -0
  56. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +110 -0
  57. package/app/modules/stickerPackModule/stickerPackItemRepository.js +440 -0
  58. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +337 -0
  59. package/app/modules/stickerPackModule/stickerPackMessageService.js +296 -0
  60. package/app/modules/stickerPackModule/stickerPackRepository.js +442 -0
  61. package/app/modules/stickerPackModule/stickerPackService.js +788 -0
  62. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +51 -0
  63. package/app/modules/stickerPackModule/stickerPackUtils.js +97 -0
  64. package/app/modules/stickerPackModule/stickerStorageService.js +507 -0
  65. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +233 -0
  66. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +205 -0
  67. package/app/modules/systemMetricsModule/pingCommand.js +421 -0
  68. package/app/modules/tiktokModule/tiktokCommand.js +798 -0
  69. package/app/modules/userModule/userCommand.js +1217 -0
  70. package/app/modules/waifuPicsModule/waifuPicsCommand.js +177 -0
  71. package/app/observability/metrics.js +734 -0
  72. package/app/services/captchaService.js +492 -0
  73. package/app/services/dbWriteQueue.js +572 -0
  74. package/app/services/groupMetadataService.js +279 -0
  75. package/app/services/lidMapService.js +663 -0
  76. package/app/services/messagePersistenceService.js +56 -0
  77. package/app/services/newsBroadcastService.js +351 -0
  78. package/app/services/pokeApiService.js +398 -0
  79. package/app/services/queueUtils.js +57 -0
  80. package/app/services/socketState.js +7 -0
  81. package/app/store/aiPromptStore.js +38 -0
  82. package/app/store/groupConfigStore.js +58 -0
  83. package/app/store/premiumUserStore.js +36 -0
  84. package/app/utils/antiLink/antiLinkModule.js +804 -0
  85. package/app/utils/http/getImageBufferModule.js +18 -0
  86. package/app/utils/json/jsonSanitizer.js +113 -0
  87. package/app/utils/json/jsonSanitizer.test.js +40 -0
  88. package/app/utils/logger/loggerModule.js +262 -0
  89. package/app/utils/systemMetrics/systemMetricsModule.js +91 -0
  90. package/database/index.js +2052 -0
  91. package/database/init.js +516 -0
  92. package/database/migrations/20260203_0001_sticker_packs.sql +54 -0
  93. package/database/migrations/20260210_0003_rpg_pokemon.sql +58 -0
  94. package/database/migrations/20260210_0004_rpg_shiny_biome.sql +9 -0
  95. package/database/migrations/20260210_0005_rpg_missions.sql +14 -0
  96. package/database/migrations/20260210_0006_rpg_world_pokedex_traits.sql +27 -0
  97. package/database/migrations/20260210_0007_rpg_raid_pvp.sql +56 -0
  98. package/database/migrations/20260210_0008_rpg_social_system.sql +195 -0
  99. package/database/migrations/20260211_0009_rpg_social_xp.sql +36 -0
  100. package/database/migrations/20260222_0010_remove_message_xp.sql +2 -0
  101. package/database/migrations/20260226_0011_sticker_asset_classification.sql +17 -0
  102. package/database/migrations/20260226_0012_sticker_pack_engagement.sql +16 -0
  103. package/database/migrations/20260226_0013_sticker_marketplace_intelligence.sql +19 -0
  104. package/database/migrations/20260226_0014_sticker_pack_publish_flow.sql +30 -0
  105. package/database/migrations/20260226_0014_sticker_worker_queues.sql +42 -0
  106. package/database/migrations/20260226_0015_sticker_auto_pack_curation_integrity.sql +18 -0
  107. package/database/migrations/20260226_0016_sticker_web_google_auth_persistence.sql +34 -0
  108. package/database/migrations/20260226_0017_sticker_web_admin_ban.sql +22 -0
  109. package/database/migrations/20260226_0018_sticker_web_admin_moderator.sql +18 -0
  110. package/database/migrations/20260227_0019_sticker_classification_v2_signals.sql +12 -0
  111. package/database/migrations/20260227_0020_semantic_theme_clusters.sql +35 -0
  112. package/docker-compose.yml +103 -0
  113. package/ecosystem.prod.config.cjs +35 -0
  114. package/eslint.config.js +61 -0
  115. package/index.js +437 -0
  116. package/ml/clip_classifier/Dockerfile +16 -0
  117. package/ml/clip_classifier/README.md +120 -0
  118. package/ml/clip_classifier/adaptive_scoring.py +40 -0
  119. package/ml/clip_classifier/classifier.py +654 -0
  120. package/ml/clip_classifier/embedding_store.py +481 -0
  121. package/ml/clip_classifier/env_loader.py +15 -0
  122. package/ml/clip_classifier/llm_label_expander.py +144 -0
  123. package/ml/clip_classifier/main.py +213 -0
  124. package/ml/clip_classifier/requirements.txt +10 -0
  125. package/ml/clip_classifier/similarity_engine.py +74 -0
  126. package/observability/alert-rules.yml +60 -0
  127. package/observability/grafana/dashboards/omnizap-mysql.json +136 -0
  128. package/observability/grafana/dashboards/omnizap-overview.json +170 -0
  129. package/observability/grafana/provisioning/dashboards/dashboards.yml +11 -0
  130. package/observability/grafana/provisioning/datasources/datasources.yml +15 -0
  131. package/observability/loki-config.yml +38 -0
  132. package/observability/mysql-exporter.cnf +5 -0
  133. package/observability/mysql-setup.sql +46 -0
  134. package/observability/prometheus.yml +32 -0
  135. package/observability/promtail-config.yml +84 -0
  136. package/package.json +109 -0
  137. package/public/api-docs/index.html +144 -0
  138. package/public/css/github-project-panel.css +297 -0
  139. package/public/css/stickers-admin.css +1272 -0
  140. package/public/css/styles.css +671 -0
  141. package/public/index.html +1311 -0
  142. package/public/js/apps/apiDocsApp.js +310 -0
  143. package/public/js/apps/createPackApp.js +2069 -0
  144. package/public/js/apps/homeApp.js +396 -0
  145. package/public/js/apps/stickersAdminApp.js +1744 -0
  146. package/public/js/apps/stickersApp.js +4830 -0
  147. package/public/js/catalog.js +1019 -0
  148. package/public/js/github-panel/components/CommitList.js +34 -0
  149. package/public/js/github-panel/components/ErrorState.js +16 -0
  150. package/public/js/github-panel/components/GithubProjectPanel.js +106 -0
  151. package/public/js/github-panel/components/ReleaseList.js +38 -0
  152. package/public/js/github-panel/components/SkeletonPanel.js +22 -0
  153. package/public/js/github-panel/components/StatCard.js +15 -0
  154. package/public/js/github-panel/index.js +15 -0
  155. package/public/js/github-panel/useGithubRepoData.js +154 -0
  156. package/public/js/github-panel/vendor/react.js +11 -0
  157. package/public/js/runtime/react-runtime.js +19 -0
  158. package/public/licenca/index.html +106 -0
  159. package/public/stickers/admin/index.html +23 -0
  160. package/public/stickers/create/index.html +47 -0
  161. package/public/stickers/index.html +48 -0
  162. package/public/termos-de-uso/index.html +125 -0
  163. package/scripts/cache-bust.mjs +107 -0
  164. package/scripts/deploy.sh +458 -0
  165. package/scripts/github-deploy-notify.mjs +174 -0
  166. package/scripts/release.sh +129 -0
@@ -0,0 +1,396 @@
1
+ import { React, createRoot, useEffect } from '../runtime/react-runtime.js';
2
+
3
+ const h = React.createElement;
4
+ const shortNum = (value) =>
5
+ new Intl.NumberFormat('pt-BR', {
6
+ notation: Number(value) >= 1000 ? 'compact' : 'standard',
7
+ maximumFractionDigits: Number(value) >= 1000 ? 1 : 0,
8
+ }).format(Math.max(0, Number(value) || 0));
9
+
10
+ const animateCountUp = (element, value, formatter = shortNum, durationMs = 850) => {
11
+ if (!element) return;
12
+ const target = Math.max(0, Number(value) || 0);
13
+ if (!Number.isFinite(target)) {
14
+ element.textContent = formatter(0);
15
+ return;
16
+ }
17
+
18
+ if (typeof requestAnimationFrame !== 'function' || typeof performance === 'undefined') {
19
+ element.dataset.value = String(target);
20
+ element.textContent = formatter(target);
21
+ return;
22
+ }
23
+
24
+ const previous = Number(element.dataset.value || 0);
25
+ const start = Number.isFinite(previous) ? previous : 0;
26
+ const delta = target - start;
27
+ const startTime = performance.now();
28
+ const easeOut = (t) => 1 - Math.pow(1 - t, 3);
29
+
30
+ const tick = (now) => {
31
+ const progress = Math.min(1, (now - startTime) / durationMs);
32
+ const eased = easeOut(progress);
33
+ const currentValue = start + delta * eased;
34
+ element.textContent = formatter(currentValue);
35
+ if (progress < 1) requestAnimationFrame(tick);
36
+ };
37
+
38
+ element.dataset.value = String(target);
39
+ requestAnimationFrame(tick);
40
+ };
41
+
42
+ function HomeEffects() {
43
+ useEffect(() => {
44
+ const proofPacks = document.getElementById('proof-packs');
45
+ const proofStickers = document.getElementById('proof-stickers');
46
+ const proofDownloads = document.getElementById('proof-downloads');
47
+ const proofUsers = document.getElementById('proof-users');
48
+ const proofGroups = document.getElementById('proof-groups');
49
+ const proofSystem = document.getElementById('proof-system');
50
+ const previewStatus = document.getElementById('hero-preview-status');
51
+ const previewGrid = document.getElementById('hero-pack-preview');
52
+ if (
53
+ !proofPacks
54
+ || !proofStickers
55
+ || !proofDownloads
56
+ || !proofUsers
57
+ || !proofGroups
58
+ || !proofSystem
59
+ || !previewStatus
60
+ || !previewGrid
61
+ ) {
62
+ return;
63
+ }
64
+
65
+ const fallbackThumb = 'https://iili.io/FC3FABe.jpg';
66
+ const isAutoPack = (pack) =>
67
+ Number(pack?.is_auto_pack || pack?.auto_pack || 0) === 1 || /\[auto\]/i.test(String(pack?.name || ''));
68
+
69
+ const renderPreviewSkeleton = (count = 6) => {
70
+ previewGrid.innerHTML = Array.from({ length: count })
71
+ .map(
72
+ () =>
73
+ '<article class="market-pack is-loading">' +
74
+ '<div class="market-pack-skeleton-thumb"></div>' +
75
+ '<div class="market-pack-skeleton-body">' +
76
+ '<span class="market-pack-skeleton-line"></span>' +
77
+ '<span class="market-pack-skeleton-line short"></span>' +
78
+ '</div>' +
79
+ '</article>',
80
+ )
81
+ .join('');
82
+ };
83
+
84
+ const renderPreview = (packs) => {
85
+ previewGrid.innerHTML = '';
86
+ if (!Array.isArray(packs) || !packs.length) {
87
+ previewStatus.textContent = 'Sem packs em destaque no momento.';
88
+ return;
89
+ }
90
+
91
+ previewStatus.textContent = `${packs.length} packs sugeridos agora`;
92
+ packs.slice(0, 6).forEach((pack, index) => {
93
+ const card = document.createElement('a');
94
+ card.className = 'market-pack reveal';
95
+ card.href = pack.web_url || `/stickers/${encodeURIComponent(pack.pack_key || '')}`;
96
+ card.innerHTML =
97
+ `<img class="market-pack-thumb" loading="lazy" src="${pack.cover_url || fallbackThumb}" alt="${String(
98
+ pack.name || 'Pack',
99
+ ).replace(/"/g, '&quot;')}">` +
100
+ (isAutoPack(pack) ? '<span class="market-pack-tag">auto</span>' : '') +
101
+ '<div class="market-pack-body">' +
102
+ `<p class="market-pack-name">${pack.name || 'Pack sem nome'}</p>` +
103
+ `<p class="market-pack-meta">${shortNum(pack.sticker_count || 0)} stickers · ${shortNum(
104
+ pack?.engagement?.open_count || 0,
105
+ )} aberturas</p>` +
106
+ '</div>';
107
+ card.style.transitionDelay = `${index * 40}ms`;
108
+ previewGrid.appendChild(card);
109
+ requestAnimationFrame(() => card.classList.add('in-view'));
110
+ });
111
+ };
112
+
113
+ const loadMarketplaceData = async () => {
114
+ try {
115
+ const [statsResponse, intentsResponse] = await Promise.all([
116
+ fetch('/api/sticker-packs/stats'),
117
+ fetch('/api/sticker-packs/intents?limit=12'),
118
+ ]);
119
+
120
+ const statsPayload = statsResponse.ok ? await statsResponse.json() : null;
121
+ const intentsPayload = intentsResponse.ok ? await intentsResponse.json() : null;
122
+ const stats = statsPayload?.data || {};
123
+ const intents = intentsPayload?.data || {};
124
+ const trending = Array.isArray(intents?.em_alta) ? intents.em_alta : [];
125
+
126
+ animateCountUp(proofPacks, stats.packs_total || 0);
127
+ animateCountUp(proofStickers, stats.stickers_total || 0);
128
+ animateCountUp(proofDownloads, stats.downloads_total || 0);
129
+ renderPreview(trending);
130
+ } catch {
131
+ proofPacks.textContent = 'n/d';
132
+ proofStickers.textContent = 'n/d';
133
+ proofDownloads.textContent = 'n/d';
134
+ proofUsers.textContent = 'n/d';
135
+ proofGroups.textContent = 'n/d';
136
+ proofSystem.textContent = 'n/d';
137
+ previewGrid.innerHTML = '';
138
+ previewStatus.textContent = 'Não foi possível carregar o preview agora.';
139
+ }
140
+ };
141
+
142
+ renderPreviewSkeleton(6);
143
+ loadMarketplaceData();
144
+ }, []);
145
+
146
+ useEffect(() => {
147
+ const summaryEl = document.getElementById('rank-summary');
148
+ const listEl = document.getElementById('rank-list');
149
+ if (!summaryEl || !listEl) return;
150
+
151
+ const formatDate = (value) => {
152
+ const time = Date.parse(String(value || ''));
153
+ if (!Number.isFinite(time)) return 'n/d';
154
+ return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(time));
155
+ };
156
+
157
+ const renderFallback = (message) => {
158
+ summaryEl.textContent = message || 'Ranking indisponível no momento.';
159
+ listEl.innerHTML = '<li class="rank-item"><span class="rank-name">Sem dados no momento</span><span class="rank-value">--</span></li>';
160
+ };
161
+
162
+ const renderRanking = (data) => {
163
+ const rows = Array.isArray(data?.rows) ? data.rows : [];
164
+ const topType = data?.top_type ? `${data.top_type} (${data.top_type_count || 0})` : 'N/D';
165
+ summaryEl.textContent =
166
+ `Top recentes: ${Number(data?.top_share_percent || 0).toFixed(
167
+ 2,
168
+ )}% das ${Number(data?.total_messages || 0)} mensagens · Tipo mais usado: ${topType} · Janela: ${Number(
169
+ data?.sample_limit || 0,
170
+ )} msgs · Atualizado: ${formatDate(
171
+ data?.updated_at,
172
+ )}`;
173
+
174
+ if (!rows.length) {
175
+ renderFallback('Ainda não há mensagens suficientes para o ranking global.');
176
+ return;
177
+ }
178
+
179
+ listEl.innerHTML = '';
180
+ rows.forEach((row) => {
181
+ const item = document.createElement('li');
182
+ item.className = 'rank-item';
183
+ const userWrap = document.createElement('span');
184
+ userWrap.className = 'rank-user';
185
+ const avatar = document.createElement('img');
186
+ avatar.className = 'rank-avatar';
187
+ avatar.alt = row.display_name || 'Usuário';
188
+ avatar.loading = 'lazy';
189
+ avatar.src = row.avatar_url || 'https://iili.io/FC3FABe.jpg';
190
+ const name = document.createElement('span');
191
+ name.className = 'rank-name';
192
+ name.textContent = `${row.position}. ${row.display_name || 'Desconhecido'}`;
193
+ userWrap.append(avatar, name);
194
+ const value = document.createElement('span');
195
+ value.className = 'rank-value';
196
+ value.textContent = `${Number(row.total_messages || 0)} msg · ${Number(row.percent_of_total || 0).toFixed(2)}%`;
197
+ item.append(userWrap, value);
198
+ listEl.appendChild(item);
199
+ });
200
+ };
201
+
202
+ const loadRanking = () =>
203
+ fetch('/api/sticker-packs/global-ranking-summary')
204
+ .then((response) => {
205
+ if (!response.ok) throw new Error('Falha ao carregar ranking');
206
+ return response.json();
207
+ })
208
+ .then((payload) => {
209
+ renderRanking(payload?.data || {});
210
+ })
211
+ .catch(() => {
212
+ renderFallback('Ranking indisponível no momento.');
213
+ });
214
+
215
+ loadRanking();
216
+ const intervalId = setInterval(loadRanking, 10 * 60 * 1000);
217
+ return () => clearInterval(intervalId);
218
+ }, []);
219
+
220
+ useEffect(() => {
221
+ const toggle = document.getElementById('nav-toggle');
222
+ const nav = document.getElementById('main-nav');
223
+ if (!toggle || !nav) return;
224
+
225
+ const handleClick = () => {
226
+ const isOpen = nav.classList.toggle('open');
227
+ toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
228
+ };
229
+
230
+ toggle.addEventListener('click', handleClick);
231
+ return () => toggle.removeEventListener('click', handleClick);
232
+ }, []);
233
+
234
+ useEffect(() => {
235
+ const wppButton = document.getElementById('wpp-float');
236
+ if (!wppButton) return;
237
+
238
+ const command = 'iniciar';
239
+ const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
240
+ const buildUrl = (phone) => `https://wa.me/${phone}?text=${encodeURIComponent(command)}`;
241
+ const applyLink = (phone) => {
242
+ const digits = normalizeDigits(phone);
243
+ if (!digits) return false;
244
+ wppButton.href = buildUrl(digits);
245
+ wppButton.hidden = false;
246
+ return true;
247
+ };
248
+
249
+ fetch('/api/sticker-packs?visibility=public&limit=1')
250
+ .then((response) => {
251
+ if (!response.ok) throw new Error('Falha ao buscar bot');
252
+ return response.json();
253
+ })
254
+ .then((payload) => {
255
+ const firstPack = Array.isArray(payload?.data) ? payload.data[0] : null;
256
+ const phone = firstPack?.whatsapp?.phone || '';
257
+ applyLink(phone);
258
+ })
259
+ .catch(() => {
260
+ wppButton.hidden = true;
261
+ });
262
+ }, []);
263
+
264
+ useEffect(() => {
265
+ const cpuEl = document.getElementById('metric-host-cpu');
266
+ const memEl = document.getElementById('metric-host-memory');
267
+ const uptimeEl = document.getElementById('metric-process-uptime');
268
+ const obsEl = document.getElementById('metric-observability');
269
+ const proofUsers = document.getElementById('proof-users');
270
+ const proofGroups = document.getElementById('proof-groups');
271
+ const proofSystem = document.getElementById('proof-system');
272
+ if (!cpuEl || !memEl || !uptimeEl || !obsEl) return;
273
+
274
+ const normalizeStatus = (value) => {
275
+ const normalized = String(value || '')
276
+ .trim()
277
+ .toLowerCase();
278
+ if (!normalized) return 'degraded';
279
+ if (['online', 'healthy', 'ok'].includes(normalized)) return 'online';
280
+ if (['offline', 'down', 'disconnected'].includes(normalized)) return 'offline';
281
+ if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'connecting';
282
+ return 'degraded';
283
+ };
284
+
285
+ const formatSystemStatusLabel = (status) => {
286
+ if (status === 'online') return 'online';
287
+ if (status === 'offline') return 'offline';
288
+ if (status === 'connecting') return 'conectando';
289
+ return 'instável';
290
+ };
291
+
292
+ const setFallback = () => {
293
+ cpuEl.textContent = 'CPU host: n/d';
294
+ memEl.textContent = 'RAM host: n/d';
295
+ uptimeEl.textContent = 'Uptime processo: n/d';
296
+ obsEl.textContent = 'Observabilidade: API em /api/sticker-packs';
297
+ if (proofUsers) proofUsers.textContent = 'n/d';
298
+ if (proofGroups) proofGroups.textContent = 'n/d';
299
+ if (proofSystem) {
300
+ proofSystem.textContent = 'n/d';
301
+ const card = proofSystem.closest('.proof-card');
302
+ if (card) card.dataset.status = 'degraded';
303
+ }
304
+ };
305
+
306
+ const fmt = (value) => (Number.isFinite(value) ? value.toFixed(2) : 'n/d');
307
+
308
+ fetch('/api/sticker-packs/system-summary')
309
+ .then((response) => {
310
+ if (!response.ok) throw new Error('Falha ao carregar métricas');
311
+ return response.json();
312
+ })
313
+ .then((payload) => {
314
+ const data = payload && payload.data ? payload.data : {};
315
+ const host = data.host || {};
316
+ const process = data.process || {};
317
+ const observability = data.observability || {};
318
+ const platform = data.platform || {};
319
+ const bot = data.bot || {};
320
+ const systemStatus = normalizeStatus(data.system_status || bot.connection_status);
321
+
322
+ cpuEl.textContent = 'CPU host: ' + fmt(Number(host.cpu_percent)) + '%';
323
+ memEl.textContent =
324
+ 'RAM host: ' +
325
+ String(host.memory_used || 'n/d') +
326
+ ' / ' +
327
+ String(host.memory_total || 'n/d') +
328
+ ' (' +
329
+ fmt(Number(host.memory_percent)) +
330
+ '%)';
331
+ uptimeEl.textContent = 'Uptime processo: ' + String(process.uptime || 'n/d');
332
+
333
+ const lag = Number(observability.lag_p99_ms);
334
+ const dbTotal = observability.db_total;
335
+ const dbSlow = observability.db_slow;
336
+ obsEl.textContent =
337
+ 'Lag p99: ' +
338
+ (Number.isFinite(lag) ? lag.toFixed(2) + 'ms' : 'n/d') +
339
+ ' | DB slow: ' +
340
+ (Number.isFinite(Number(dbSlow)) && Number.isFinite(Number(dbTotal)) ? String(dbSlow) + '/' + String(dbTotal) : 'n/d');
341
+
342
+ if (proofUsers) animateCountUp(proofUsers, platform.total_users || 0);
343
+ if (proofGroups) animateCountUp(proofGroups, platform.total_groups || 0);
344
+ if (proofSystem) {
345
+ proofSystem.textContent = formatSystemStatusLabel(systemStatus);
346
+ const card = proofSystem.closest('.proof-card');
347
+ if (card) card.dataset.status = systemStatus;
348
+ }
349
+ })
350
+ .catch(() => {
351
+ setFallback();
352
+ });
353
+ }, []);
354
+
355
+ useEffect(() => {
356
+ const revealTargets = Array.from(
357
+ document.querySelectorAll(
358
+ '.hero, .market-preview, .section-title, .grid .card, .api, .rank-panel, .final-cta, .hero-stats .chip, .hero-proof .proof-card',
359
+ ),
360
+ );
361
+ if (!revealTargets.length) return undefined;
362
+
363
+ revealTargets.forEach((element) => element.classList.add('reveal'));
364
+ if (typeof IntersectionObserver !== 'function') {
365
+ revealTargets.forEach((element) => element.classList.add('in-view'));
366
+ return undefined;
367
+ }
368
+
369
+ const observer = new IntersectionObserver(
370
+ (entries) => {
371
+ entries.forEach((entry) => {
372
+ if (entry.isIntersecting) {
373
+ entry.target.classList.add('in-view');
374
+ observer.unobserve(entry.target);
375
+ }
376
+ });
377
+ },
378
+ {
379
+ root: null,
380
+ threshold: 0.14,
381
+ rootMargin: '0px 0px -8% 0px',
382
+ },
383
+ );
384
+
385
+ revealTargets.forEach((element) => observer.observe(element));
386
+ return () => observer.disconnect();
387
+ }, []);
388
+
389
+ return null;
390
+ }
391
+
392
+ const rootEl = document.getElementById('home-react-root');
393
+ if (rootEl) {
394
+ const root = createRoot(rootEl);
395
+ root.render(h(HomeEffects));
396
+ }