@kaikybrofc/omnizap-system 2.2.8 → 2.2.10
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.
- package/README.md +1 -1
- package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +194 -0
- package/docs/seo/satellite-page-template.md +89 -0
- package/docs/seo/satellite-pages-phase1.json +486 -0
- package/package.json +3 -1
- package/public/api-docs/index.html +78 -22
- package/public/bot-whatsapp-para-grupo/index.html +276 -0
- package/public/bot-whatsapp-sem-programar/index.html +276 -0
- package/public/comandos/index.html +413 -0
- package/public/como-automatizar-avisos-no-whatsapp/index.html +276 -0
- package/public/como-criar-comandos-whatsapp/index.html +276 -0
- package/public/como-evitar-spam-no-whatsapp/index.html +276 -0
- package/public/como-moderar-grupo-whatsapp/index.html +276 -0
- package/public/como-organizar-comunidade-whatsapp/index.html +276 -0
- package/public/css/github-project-panel.css +8 -8
- package/public/css/stickers-admin.css +31 -31
- package/public/css/styles.css +17 -16
- package/public/index.html +701 -1181
- package/public/js/apps/apiDocsApp.js +39 -6
- package/public/js/apps/homeApp.js +157 -410
- package/public/js/apps/stickersApp.js +42 -0
- package/public/licenca/index.html +9 -9
- package/public/login/index.html +26 -22
- package/public/melhor-bot-whatsapp-para-grupos/index.html +276 -0
- package/public/sitemap.xml +45 -0
- package/public/stickers/create/index.html +7 -6
- package/public/stickers/index.html +72 -5
- package/public/termos-de-uso/index.html +10 -10
- package/public/user/index.html +25 -21
- package/scripts/generate-seo-satellite-pages.mjs +434 -0
- package/server/controllers/stickerCatalogController.js +341 -700
- package/kaikybrofc-omnizap-system-2.2.7.tgz +0 -0
- package/kaikybrofc-omnizap-system-2.2.8.tgz +0 -0
|
@@ -6,45 +6,48 @@ const shortNum = (value) =>
|
|
|
6
6
|
maximumFractionDigits: Number(value) >= 1000 ? 1 : 0,
|
|
7
7
|
}).format(Math.max(0, Number(value) || 0));
|
|
8
8
|
|
|
9
|
-
const animateCountUp = (element, value,
|
|
9
|
+
const animateCountUp = (element, value, durationMs = 780) => {
|
|
10
10
|
if (!element) return;
|
|
11
|
+
|
|
11
12
|
const target = Math.max(0, Number(value) || 0);
|
|
12
13
|
if (!Number.isFinite(target)) {
|
|
13
|
-
element.textContent =
|
|
14
|
+
element.textContent = shortNum(0);
|
|
14
15
|
return;
|
|
15
16
|
}
|
|
16
17
|
|
|
17
18
|
if (typeof requestAnimationFrame !== 'function' || typeof performance === 'undefined') {
|
|
19
|
+
element.textContent = shortNum(target);
|
|
18
20
|
element.dataset.value = String(target);
|
|
19
|
-
element.textContent = formatter(target);
|
|
20
21
|
return;
|
|
21
22
|
}
|
|
22
23
|
|
|
23
24
|
const previous = Number(element.dataset.value || 0);
|
|
24
25
|
const start = Number.isFinite(previous) ? previous : 0;
|
|
25
26
|
const delta = target - start;
|
|
26
|
-
const
|
|
27
|
-
const easeOut = (t) => 1 - Math.pow(1 - t, 3);
|
|
27
|
+
const startAt = performance.now();
|
|
28
28
|
|
|
29
29
|
const tick = (now) => {
|
|
30
|
-
const progress = Math.min(1, (now -
|
|
31
|
-
const eased =
|
|
32
|
-
const
|
|
33
|
-
element.textContent =
|
|
34
|
-
if (progress < 1)
|
|
30
|
+
const progress = Math.min(1, (now - startAt) / durationMs);
|
|
31
|
+
const eased = 1 - Math.pow(1 - progress, 3);
|
|
32
|
+
const current = start + delta * eased;
|
|
33
|
+
element.textContent = shortNum(current);
|
|
34
|
+
if (progress < 1) {
|
|
35
|
+
requestAnimationFrame(tick);
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
element.dataset.value = String(target);
|
|
35
39
|
};
|
|
36
40
|
|
|
37
|
-
element.dataset.value = String(target);
|
|
38
41
|
requestAnimationFrame(tick);
|
|
39
42
|
};
|
|
40
43
|
|
|
41
|
-
const runAfterLoadIdle = (callback, { delayMs = 0, timeoutMs =
|
|
44
|
+
const runAfterLoadIdle = (callback, { delayMs = 0, timeoutMs = 1800 } = {}) => {
|
|
42
45
|
let cancelled = false;
|
|
43
|
-
let loadListener = null;
|
|
44
46
|
let timeoutId = null;
|
|
45
47
|
let idleId = null;
|
|
48
|
+
let loadHandler = null;
|
|
46
49
|
|
|
47
|
-
const
|
|
50
|
+
const run = () => {
|
|
48
51
|
if (cancelled) return;
|
|
49
52
|
callback();
|
|
50
53
|
};
|
|
@@ -52,313 +55,120 @@ const runAfterLoadIdle = (callback, { delayMs = 0, timeoutMs = 2200 } = {}) => {
|
|
|
52
55
|
const schedule = () => {
|
|
53
56
|
if (cancelled) return;
|
|
54
57
|
|
|
55
|
-
const
|
|
58
|
+
const invoke = () => {
|
|
56
59
|
if (cancelled) return;
|
|
57
60
|
if (delayMs > 0) {
|
|
58
|
-
timeoutId = window.setTimeout(
|
|
61
|
+
timeoutId = window.setTimeout(run, delayMs);
|
|
59
62
|
return;
|
|
60
63
|
}
|
|
61
|
-
|
|
64
|
+
run();
|
|
62
65
|
};
|
|
63
66
|
|
|
64
67
|
if (typeof window.requestIdleCallback === 'function') {
|
|
65
|
-
idleId = window.requestIdleCallback(
|
|
68
|
+
idleId = window.requestIdleCallback(invoke, { timeout: timeoutMs });
|
|
66
69
|
return;
|
|
67
70
|
}
|
|
68
71
|
|
|
69
|
-
timeoutId = window.setTimeout(
|
|
72
|
+
timeoutId = window.setTimeout(invoke, Math.min(240, delayMs || 120));
|
|
70
73
|
};
|
|
71
74
|
|
|
72
75
|
if (document.readyState === 'complete') {
|
|
73
76
|
schedule();
|
|
74
77
|
} else {
|
|
75
|
-
|
|
76
|
-
window.addEventListener('load',
|
|
78
|
+
loadHandler = () => schedule();
|
|
79
|
+
window.addEventListener('load', loadHandler, { once: true });
|
|
77
80
|
}
|
|
78
81
|
|
|
79
82
|
return () => {
|
|
80
83
|
cancelled = true;
|
|
81
|
-
if (
|
|
82
|
-
window.removeEventListener('load', loadListener);
|
|
83
|
-
}
|
|
84
|
-
if (timeoutId !== null) {
|
|
85
|
-
window.clearTimeout(timeoutId);
|
|
86
|
-
}
|
|
84
|
+
if (timeoutId !== null) window.clearTimeout(timeoutId);
|
|
87
85
|
if (idleId !== null && typeof window.cancelIdleCallback === 'function') {
|
|
88
86
|
window.cancelIdleCallback(idleId);
|
|
89
87
|
}
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
const initMarketplacePreview = () => {
|
|
94
|
-
const proofPacks = document.getElementById('proof-packs');
|
|
95
|
-
const proofStickers = document.getElementById('proof-stickers');
|
|
96
|
-
const proofDownloads = document.getElementById('proof-downloads');
|
|
97
|
-
const proofUsers = document.getElementById('proof-users');
|
|
98
|
-
const proofGroups = document.getElementById('proof-groups');
|
|
99
|
-
const proofSystem = document.getElementById('proof-system');
|
|
100
|
-
const previewStatus = document.getElementById('hero-preview-status');
|
|
101
|
-
const previewGrid = document.getElementById('hero-pack-preview');
|
|
102
|
-
|
|
103
|
-
if (
|
|
104
|
-
!proofPacks
|
|
105
|
-
|| !proofStickers
|
|
106
|
-
|| !proofDownloads
|
|
107
|
-
|| !proofUsers
|
|
108
|
-
|| !proofGroups
|
|
109
|
-
|| !proofSystem
|
|
110
|
-
|| !previewStatus
|
|
111
|
-
|| !previewGrid
|
|
112
|
-
) {
|
|
113
|
-
return null;
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
const isAutoPack = (pack) =>
|
|
117
|
-
Number(pack?.is_auto_pack || pack?.auto_pack || 0) === 1 || /\[auto\]/i.test(String(pack?.name || ''));
|
|
118
|
-
|
|
119
|
-
const renderPreviewSkeleton = (count = 6) => {
|
|
120
|
-
previewGrid.innerHTML = Array.from({ length: count })
|
|
121
|
-
.map(
|
|
122
|
-
() =>
|
|
123
|
-
'<article class="market-pack is-loading">' +
|
|
124
|
-
'<div class="market-pack-skeleton-thumb"></div>' +
|
|
125
|
-
'<div class="market-pack-skeleton-body">' +
|
|
126
|
-
'<span class="market-pack-skeleton-line"></span>' +
|
|
127
|
-
'<span class="market-pack-skeleton-line short"></span>' +
|
|
128
|
-
'</div>' +
|
|
129
|
-
'</article>',
|
|
130
|
-
)
|
|
131
|
-
.join('');
|
|
132
|
-
};
|
|
133
|
-
|
|
134
|
-
const renderPreview = (packs) => {
|
|
135
|
-
previewGrid.innerHTML = '';
|
|
136
|
-
if (!Array.isArray(packs) || !packs.length) {
|
|
137
|
-
previewStatus.textContent = 'Sem packs em destaque no momento.';
|
|
138
|
-
return;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
previewStatus.textContent = `${packs.length} packs sugeridos agora`;
|
|
142
|
-
packs.slice(0, 6).forEach((pack, index) => {
|
|
143
|
-
const card = document.createElement('a');
|
|
144
|
-
card.className = 'market-pack reveal';
|
|
145
|
-
card.href = pack.web_url || `/stickers/${encodeURIComponent(pack.pack_key || '')}`;
|
|
146
|
-
card.innerHTML =
|
|
147
|
-
`<img class="market-pack-thumb" loading="lazy" decoding="async" fetchpriority="low" width="320" height="320" src="${pack.cover_url || FALLBACK_THUMB_URL}" alt="${String(
|
|
148
|
-
pack.name || 'Pack',
|
|
149
|
-
).replace(/"/g, '"')}">` +
|
|
150
|
-
(isAutoPack(pack) ? '<span class="market-pack-tag">auto</span>' : '') +
|
|
151
|
-
'<div class="market-pack-body">' +
|
|
152
|
-
`<p class="market-pack-name">${pack.name || 'Pack sem nome'}</p>` +
|
|
153
|
-
`<p class="market-pack-meta">${shortNum(pack.sticker_count || 0)} stickers · ${shortNum(
|
|
154
|
-
pack?.engagement?.open_count || 0,
|
|
155
|
-
)} aberturas</p>` +
|
|
156
|
-
'</div>';
|
|
157
|
-
card.style.transitionDelay = `${index * 40}ms`;
|
|
158
|
-
previewGrid.appendChild(card);
|
|
159
|
-
requestAnimationFrame(() => card.classList.add('in-view'));
|
|
160
|
-
});
|
|
161
|
-
};
|
|
162
|
-
|
|
163
|
-
const loadMarketplaceData = async () => {
|
|
164
|
-
try {
|
|
165
|
-
const [statsResponse, intentsResponse] = await Promise.all([
|
|
166
|
-
fetch('/api/sticker-packs/stats'),
|
|
167
|
-
fetch('/api/sticker-packs/intents?limit=12'),
|
|
168
|
-
]);
|
|
169
|
-
|
|
170
|
-
const statsPayload = statsResponse.ok ? await statsResponse.json() : null;
|
|
171
|
-
const intentsPayload = intentsResponse.ok ? await intentsResponse.json() : null;
|
|
172
|
-
const stats = statsPayload?.data || {};
|
|
173
|
-
const intents = intentsPayload?.data || {};
|
|
174
|
-
const trending = Array.isArray(intents?.em_alta) ? intents.em_alta : [];
|
|
175
|
-
|
|
176
|
-
animateCountUp(proofPacks, stats.packs_total || 0);
|
|
177
|
-
animateCountUp(proofStickers, stats.stickers_total || 0);
|
|
178
|
-
animateCountUp(proofDownloads, stats.downloads_total || 0);
|
|
179
|
-
renderPreview(trending);
|
|
180
|
-
} catch {
|
|
181
|
-
proofPacks.textContent = 'n/d';
|
|
182
|
-
proofStickers.textContent = 'n/d';
|
|
183
|
-
proofDownloads.textContent = 'n/d';
|
|
184
|
-
proofUsers.textContent = 'n/d';
|
|
185
|
-
proofGroups.textContent = 'n/d';
|
|
186
|
-
proofSystem.textContent = 'n/d';
|
|
187
|
-
previewGrid.innerHTML = '';
|
|
188
|
-
previewStatus.textContent = 'Não foi possível carregar o preview agora.';
|
|
88
|
+
if (loadHandler) {
|
|
89
|
+
window.removeEventListener('load', loadHandler);
|
|
189
90
|
}
|
|
190
91
|
};
|
|
191
|
-
|
|
192
|
-
renderPreviewSkeleton(6);
|
|
193
|
-
return runAfterLoadIdle(() => {
|
|
194
|
-
void loadMarketplaceData();
|
|
195
|
-
}, { timeoutMs: 1200 });
|
|
196
92
|
};
|
|
197
93
|
|
|
198
|
-
const
|
|
199
|
-
const
|
|
200
|
-
const
|
|
201
|
-
if (!
|
|
202
|
-
|
|
203
|
-
const formatDate = (value) => {
|
|
204
|
-
const time = Date.parse(String(value || ''));
|
|
205
|
-
if (!Number.isFinite(time)) return 'n/d';
|
|
206
|
-
return new Intl.DateTimeFormat('pt-BR', { dateStyle: 'short', timeStyle: 'short' }).format(new Date(time));
|
|
207
|
-
};
|
|
94
|
+
const initNavToggle = () => {
|
|
95
|
+
const toggle = document.getElementById('nav-toggle');
|
|
96
|
+
const nav = document.getElementById('main-nav');
|
|
97
|
+
if (!toggle || !nav) return null;
|
|
208
98
|
|
|
209
|
-
const
|
|
210
|
-
|
|
211
|
-
|
|
99
|
+
const closeMenu = () => {
|
|
100
|
+
nav.classList.remove('open');
|
|
101
|
+
toggle.setAttribute('aria-expanded', 'false');
|
|
212
102
|
};
|
|
213
103
|
|
|
214
|
-
const
|
|
215
|
-
const
|
|
216
|
-
|
|
217
|
-
summaryEl.textContent =
|
|
218
|
-
`Top recentes: ${Number(data?.top_share_percent || 0).toFixed(
|
|
219
|
-
2,
|
|
220
|
-
)}% das ${Number(data?.total_messages || 0)} mensagens · Tipo mais usado: ${topType} · Janela: ${Number(
|
|
221
|
-
data?.sample_limit || 0,
|
|
222
|
-
)} msgs · Atualizado: ${formatDate(
|
|
223
|
-
data?.updated_at,
|
|
224
|
-
)}`;
|
|
225
|
-
|
|
226
|
-
if (!rows.length) {
|
|
227
|
-
renderFallback('Ainda não há mensagens suficientes para o ranking global.');
|
|
228
|
-
return;
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
listEl.innerHTML = '';
|
|
232
|
-
rows.forEach((row) => {
|
|
233
|
-
const item = document.createElement('li');
|
|
234
|
-
item.className = 'rank-item';
|
|
235
|
-
const userWrap = document.createElement('span');
|
|
236
|
-
userWrap.className = 'rank-user';
|
|
237
|
-
const avatar = document.createElement('img');
|
|
238
|
-
avatar.className = 'rank-avatar';
|
|
239
|
-
avatar.alt = row.display_name || 'Usuário';
|
|
240
|
-
avatar.loading = 'lazy';
|
|
241
|
-
avatar.decoding = 'async';
|
|
242
|
-
avatar.setAttribute('fetchpriority', 'low');
|
|
243
|
-
avatar.width = 34;
|
|
244
|
-
avatar.height = 34;
|
|
245
|
-
avatar.src = row.avatar_url || FALLBACK_THUMB_URL;
|
|
246
|
-
avatar.onerror = () => {
|
|
247
|
-
avatar.src = FALLBACK_THUMB_URL;
|
|
248
|
-
};
|
|
249
|
-
const name = document.createElement('span');
|
|
250
|
-
name.className = 'rank-name';
|
|
251
|
-
name.textContent = `${row.position}. ${row.display_name || 'Desconhecido'}`;
|
|
252
|
-
userWrap.append(avatar, name);
|
|
253
|
-
const value = document.createElement('span');
|
|
254
|
-
value.className = 'rank-value';
|
|
255
|
-
value.textContent = `${Number(row.total_messages || 0)} msg · ${Number(row.percent_of_total || 0).toFixed(2)}%`;
|
|
256
|
-
item.append(userWrap, value);
|
|
257
|
-
listEl.appendChild(item);
|
|
258
|
-
});
|
|
104
|
+
const onClick = () => {
|
|
105
|
+
const isOpen = nav.classList.toggle('open');
|
|
106
|
+
toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
|
|
259
107
|
};
|
|
260
108
|
|
|
261
|
-
const
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
if (!response.ok) throw new Error('Falha ao carregar ranking');
|
|
265
|
-
return response.json();
|
|
266
|
-
})
|
|
267
|
-
.then((payload) => {
|
|
268
|
-
renderRanking(payload?.data || {});
|
|
269
|
-
})
|
|
270
|
-
.catch(() => {
|
|
271
|
-
renderFallback('Ranking indisponível no momento.');
|
|
272
|
-
});
|
|
109
|
+
const onNavClick = (event) => {
|
|
110
|
+
const target = event.target;
|
|
111
|
+
if (!(target instanceof Element)) return;
|
|
273
112
|
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
void loadRanking();
|
|
277
|
-
intervalId = window.setInterval(() => {
|
|
278
|
-
void loadRanking();
|
|
279
|
-
}, 10 * 60 * 1000);
|
|
280
|
-
}, { delayMs: 450, timeoutMs: 2200 });
|
|
113
|
+
const link = target.closest('a[href]');
|
|
114
|
+
if (!link) return;
|
|
281
115
|
|
|
282
|
-
|
|
283
|
-
cancelScheduledLoad();
|
|
284
|
-
if (intervalId !== null) {
|
|
285
|
-
window.clearInterval(intervalId);
|
|
286
|
-
}
|
|
116
|
+
closeMenu();
|
|
287
117
|
};
|
|
288
|
-
};
|
|
289
118
|
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
const nav = document.getElementById('main-nav');
|
|
293
|
-
if (!toggle || !nav) return null;
|
|
119
|
+
toggle.addEventListener('click', onClick);
|
|
120
|
+
nav.addEventListener('click', onNavClick);
|
|
294
121
|
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
122
|
+
return () => {
|
|
123
|
+
toggle.removeEventListener('click', onClick);
|
|
124
|
+
nav.removeEventListener('click', onNavClick);
|
|
298
125
|
};
|
|
299
|
-
|
|
300
|
-
toggle.addEventListener('click', handleClick);
|
|
301
|
-
return () => toggle.removeEventListener('click', handleClick);
|
|
302
126
|
};
|
|
303
127
|
|
|
304
128
|
const initAuthSession = () => {
|
|
305
129
|
const authLink = document.getElementById('nav-auth-link');
|
|
306
|
-
const schedulerLink = document.getElementById('nav-scheduler-link');
|
|
307
130
|
const heroLoginCta = document.getElementById('hero-login-cta');
|
|
131
|
+
const finalLoginCta = document.getElementById('final-login-cta');
|
|
308
132
|
if (!authLink) return null;
|
|
309
133
|
|
|
310
134
|
const clearChildren = (node) => {
|
|
311
|
-
while (node.firstChild)
|
|
312
|
-
node.removeChild(node.firstChild);
|
|
313
|
-
}
|
|
135
|
+
while (node.firstChild) node.removeChild(node.firstChild);
|
|
314
136
|
};
|
|
315
137
|
|
|
316
|
-
const
|
|
317
|
-
document.body.classList.remove('home-authenticated');
|
|
138
|
+
const setLoginState = () => {
|
|
318
139
|
authLink.classList.remove('nav-user-chip');
|
|
319
140
|
authLink.href = '/login/';
|
|
320
141
|
authLink.removeAttribute('title');
|
|
321
142
|
authLink.removeAttribute('aria-label');
|
|
322
143
|
clearChildren(authLink);
|
|
323
144
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
145
|
+
const icon = document.createElement('i');
|
|
146
|
+
icon.className = 'fa-solid fa-right-to-bracket';
|
|
147
|
+
icon.setAttribute('aria-hidden', 'true');
|
|
148
|
+
|
|
149
|
+
authLink.append(icon, document.createTextNode('Entrar'));
|
|
328
150
|
|
|
329
151
|
if (heroLoginCta) {
|
|
330
152
|
heroLoginCta.hidden = false;
|
|
331
153
|
heroLoginCta.removeAttribute('aria-hidden');
|
|
332
154
|
}
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
authLink.append(icon, document.createTextNode('Login'));
|
|
155
|
+
if (finalLoginCta) {
|
|
156
|
+
finalLoginCta.hidden = false;
|
|
157
|
+
finalLoginCta.removeAttribute('aria-hidden');
|
|
158
|
+
}
|
|
338
159
|
};
|
|
339
160
|
|
|
340
|
-
const
|
|
161
|
+
const setLoggedState = (sessionData) => {
|
|
341
162
|
const profile = sessionData?.user || {};
|
|
342
163
|
const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
|
|
343
164
|
const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
|
|
344
165
|
|
|
345
|
-
document.body.classList.add('home-authenticated');
|
|
346
166
|
authLink.classList.add('nav-user-chip');
|
|
347
167
|
authLink.href = '/user/';
|
|
348
168
|
authLink.title = `${resolvedName} (sessão ativa)`;
|
|
349
169
|
authLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
|
|
350
170
|
clearChildren(authLink);
|
|
351
171
|
|
|
352
|
-
if (schedulerLink) {
|
|
353
|
-
schedulerLink.hidden = true;
|
|
354
|
-
schedulerLink.setAttribute('aria-hidden', 'true');
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
if (heroLoginCta) {
|
|
358
|
-
heroLoginCta.hidden = true;
|
|
359
|
-
heroLoginCta.setAttribute('aria-hidden', 'true');
|
|
360
|
-
}
|
|
361
|
-
|
|
362
172
|
const avatarBubble = document.createElement('span');
|
|
363
173
|
avatarBubble.className = 'nav-user-avatar-bubble';
|
|
364
174
|
|
|
@@ -368,7 +178,6 @@ const initAuthSession = () => {
|
|
|
368
178
|
photo.alt = `Foto de ${resolvedName}`;
|
|
369
179
|
photo.loading = 'lazy';
|
|
370
180
|
photo.decoding = 'async';
|
|
371
|
-
photo.setAttribute('fetchpriority', 'low');
|
|
372
181
|
photo.width = 34;
|
|
373
182
|
photo.height = 34;
|
|
374
183
|
photo.onerror = () => {
|
|
@@ -389,6 +198,15 @@ const initAuthSession = () => {
|
|
|
389
198
|
|
|
390
199
|
nameBubble.append(icon, name);
|
|
391
200
|
authLink.append(avatarBubble, nameBubble);
|
|
201
|
+
|
|
202
|
+
if (heroLoginCta) {
|
|
203
|
+
heroLoginCta.hidden = true;
|
|
204
|
+
heroLoginCta.setAttribute('aria-hidden', 'true');
|
|
205
|
+
}
|
|
206
|
+
if (finalLoginCta) {
|
|
207
|
+
finalLoginCta.hidden = true;
|
|
208
|
+
finalLoginCta.setAttribute('aria-hidden', 'true');
|
|
209
|
+
}
|
|
392
210
|
};
|
|
393
211
|
|
|
394
212
|
return runAfterLoadIdle(() => {
|
|
@@ -400,187 +218,119 @@ const initAuthSession = () => {
|
|
|
400
218
|
.then((payload) => {
|
|
401
219
|
const sessionData = payload?.data || {};
|
|
402
220
|
if (!sessionData?.authenticated || !sessionData?.user?.sub) {
|
|
403
|
-
|
|
221
|
+
setLoginState();
|
|
404
222
|
return;
|
|
405
223
|
}
|
|
406
|
-
|
|
224
|
+
setLoggedState(sessionData);
|
|
407
225
|
})
|
|
408
226
|
.catch(() => {
|
|
409
|
-
|
|
227
|
+
setLoginState();
|
|
410
228
|
});
|
|
411
|
-
}, { delayMs:
|
|
229
|
+
}, { delayMs: 160, timeoutMs: 1200 });
|
|
412
230
|
};
|
|
413
231
|
|
|
414
|
-
const
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
232
|
+
const resolveSupportUrl = async () => {
|
|
233
|
+
try {
|
|
234
|
+
const response = await fetch('/api/sticker-packs/support');
|
|
235
|
+
if (!response.ok) throw new Error(`HTTP ${response.status}`);
|
|
236
|
+
const payload = await response.json();
|
|
237
|
+
const url = String(payload?.data?.url || '').trim();
|
|
238
|
+
return url || '';
|
|
239
|
+
} catch {
|
|
240
|
+
return '';
|
|
241
|
+
}
|
|
242
|
+
};
|
|
243
|
+
|
|
244
|
+
const initAddBotCtas = () => {
|
|
245
|
+
const ctas = Array.from(document.querySelectorAll('[data-add-bot-cta]'));
|
|
246
|
+
const floatButton = document.getElementById('wpp-float');
|
|
247
|
+
if (!ctas.length && !floatButton) return null;
|
|
248
|
+
|
|
249
|
+
const applyLink = (url) => {
|
|
250
|
+
const safeUrl = String(url || '').trim();
|
|
251
|
+
if (!safeUrl) return false;
|
|
252
|
+
|
|
253
|
+
ctas.forEach((element) => {
|
|
254
|
+
element.href = safeUrl;
|
|
255
|
+
element.target = '_blank';
|
|
256
|
+
element.rel = 'noreferrer noopener';
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
if (floatButton) {
|
|
260
|
+
floatButton.href = safeUrl;
|
|
261
|
+
floatButton.hidden = false;
|
|
262
|
+
}
|
|
426
263
|
return true;
|
|
427
264
|
};
|
|
428
265
|
|
|
429
266
|
return runAfterLoadIdle(() => {
|
|
430
|
-
|
|
431
|
-
.then((
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
const firstPack = Array.isArray(payload?.data) ? payload.data[0] : null;
|
|
437
|
-
const phone = firstPack?.whatsapp?.phone || '';
|
|
438
|
-
applyLink(phone);
|
|
267
|
+
resolveSupportUrl()
|
|
268
|
+
.then((url) => {
|
|
269
|
+
const applied = applyLink(url);
|
|
270
|
+
if (!applied && floatButton) {
|
|
271
|
+
floatButton.hidden = true;
|
|
272
|
+
}
|
|
439
273
|
})
|
|
440
274
|
.catch(() => {
|
|
441
|
-
|
|
275
|
+
if (floatButton) floatButton.hidden = true;
|
|
442
276
|
});
|
|
443
|
-
}, { delayMs:
|
|
277
|
+
}, { delayMs: 120, timeoutMs: 1400 });
|
|
444
278
|
};
|
|
445
279
|
|
|
446
|
-
const
|
|
447
|
-
const
|
|
448
|
-
const
|
|
449
|
-
const
|
|
450
|
-
const
|
|
451
|
-
const proofUsers = document.getElementById('proof-users');
|
|
452
|
-
const proofGroups = document.getElementById('proof-groups');
|
|
453
|
-
const proofSystem = document.getElementById('proof-system');
|
|
454
|
-
if (!cpuEl || !memEl || !uptimeEl || !obsEl) return null;
|
|
280
|
+
const initSocialProof = () => {
|
|
281
|
+
const packsEl = document.getElementById('proof-packs');
|
|
282
|
+
const stickersEl = document.getElementById('proof-stickers');
|
|
283
|
+
const groupsEl = document.getElementById('proof-groups');
|
|
284
|
+
const statusEl = document.getElementById('proof-status');
|
|
455
285
|
|
|
456
|
-
|
|
457
|
-
const normalized = String(value || '')
|
|
458
|
-
.trim()
|
|
459
|
-
.toLowerCase();
|
|
460
|
-
if (!normalized) return 'degraded';
|
|
461
|
-
if (['online', 'healthy', 'ok'].includes(normalized)) return 'online';
|
|
462
|
-
if (['offline', 'down', 'disconnected'].includes(normalized)) return 'offline';
|
|
463
|
-
if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'connecting';
|
|
464
|
-
return 'degraded';
|
|
465
|
-
};
|
|
466
|
-
|
|
467
|
-
const formatSystemStatusLabel = (status) => {
|
|
468
|
-
if (status === 'online') return 'online';
|
|
469
|
-
if (status === 'offline') return 'offline';
|
|
470
|
-
if (status === 'connecting') return 'conectando';
|
|
471
|
-
return 'instável';
|
|
472
|
-
};
|
|
286
|
+
if (!packsEl || !stickersEl || !groupsEl) return null;
|
|
473
287
|
|
|
474
288
|
const setFallback = () => {
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
if (proofUsers) proofUsers.textContent = 'n/d';
|
|
480
|
-
if (proofGroups) proofGroups.textContent = 'n/d';
|
|
481
|
-
if (proofSystem) {
|
|
482
|
-
proofSystem.textContent = 'n/d';
|
|
483
|
-
const card = proofSystem.closest('.proof-card');
|
|
484
|
-
if (card) card.dataset.status = 'degraded';
|
|
485
|
-
}
|
|
289
|
+
packsEl.textContent = 'n/d';
|
|
290
|
+
stickersEl.textContent = 'n/d';
|
|
291
|
+
groupsEl.textContent = 'n/d';
|
|
292
|
+
if (statusEl) statusEl.textContent = 'bot pronto';
|
|
486
293
|
};
|
|
487
294
|
|
|
488
|
-
const
|
|
295
|
+
const normalizeStatus = (value) => {
|
|
296
|
+
const normalized = String(value || '').trim().toLowerCase();
|
|
297
|
+
if (!normalized) return 'pronto';
|
|
298
|
+
if (['online', 'ok', 'healthy'].includes(normalized)) return 'online';
|
|
299
|
+
if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'conectando';
|
|
300
|
+
if (['offline', 'down'].includes(normalized)) return 'instável';
|
|
301
|
+
return 'pronto';
|
|
302
|
+
};
|
|
489
303
|
|
|
490
304
|
return runAfterLoadIdle(() => {
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
const
|
|
499
|
-
const
|
|
500
|
-
|
|
501
|
-
const
|
|
502
|
-
const
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
String(host.memory_total || 'n/d') +
|
|
511
|
-
' (' +
|
|
512
|
-
fmt(Number(host.memory_percent)) +
|
|
513
|
-
'%)';
|
|
514
|
-
uptimeEl.textContent = 'Uptime processo: ' + String(process.uptime || 'n/d');
|
|
515
|
-
|
|
516
|
-
const lag = Number(observability.lag_p99_ms);
|
|
517
|
-
const dbTotal = observability.db_total;
|
|
518
|
-
const dbSlow = observability.db_slow;
|
|
519
|
-
obsEl.textContent =
|
|
520
|
-
'Lag p99: ' +
|
|
521
|
-
(Number.isFinite(lag) ? lag.toFixed(2) + 'ms' : 'n/d') +
|
|
522
|
-
' | DB slow: ' +
|
|
523
|
-
(Number.isFinite(Number(dbSlow)) && Number.isFinite(Number(dbTotal)) ? String(dbSlow) + '/' + String(dbTotal) : 'n/d');
|
|
524
|
-
|
|
525
|
-
if (proofUsers) animateCountUp(proofUsers, platform.total_users || 0);
|
|
526
|
-
if (proofGroups) animateCountUp(proofGroups, platform.total_groups || 0);
|
|
527
|
-
if (proofSystem) {
|
|
528
|
-
proofSystem.textContent = formatSystemStatusLabel(systemStatus);
|
|
529
|
-
const card = proofSystem.closest('.proof-card');
|
|
530
|
-
if (card) card.dataset.status = systemStatus;
|
|
305
|
+
Promise.all([
|
|
306
|
+
fetch('/api/sticker-packs/stats'),
|
|
307
|
+
fetch('/api/sticker-packs/system-summary'),
|
|
308
|
+
])
|
|
309
|
+
.then(async ([statsRes, summaryRes]) => {
|
|
310
|
+
if (!statsRes.ok || !summaryRes.ok) throw new Error('Falha ao carregar prova social');
|
|
311
|
+
|
|
312
|
+
const statsPayload = await statsRes.json();
|
|
313
|
+
const summaryPayload = await summaryRes.json();
|
|
314
|
+
|
|
315
|
+
const stats = statsPayload?.data || {};
|
|
316
|
+
const summary = summaryPayload?.data || {};
|
|
317
|
+
|
|
318
|
+
animateCountUp(packsEl, Number(stats.packs_total || 0));
|
|
319
|
+
animateCountUp(stickersEl, Number(stats.stickers_total || 0));
|
|
320
|
+
animateCountUp(groupsEl, Number(summary?.platform?.total_groups || 0));
|
|
321
|
+
|
|
322
|
+
if (statusEl) {
|
|
323
|
+
statusEl.textContent = `bot ${normalizeStatus(summary?.system_status || summary?.bot?.connection_status)}`;
|
|
531
324
|
}
|
|
532
325
|
})
|
|
533
326
|
.catch(() => {
|
|
534
327
|
setFallback();
|
|
535
328
|
});
|
|
536
|
-
}, { delayMs:
|
|
537
|
-
};
|
|
538
|
-
|
|
539
|
-
const initRevealAnimations = () => {
|
|
540
|
-
const rawTargets = Array.from(
|
|
541
|
-
document.querySelectorAll(
|
|
542
|
-
'.market-preview, .section-title, .grid .card, .api, .rank-panel, .final-cta, .hero-stats .chip',
|
|
543
|
-
),
|
|
544
|
-
);
|
|
545
|
-
if (!rawTargets.length) return null;
|
|
546
|
-
|
|
547
|
-
const viewportHeight = window.innerHeight || 0;
|
|
548
|
-
const revealTargets = rawTargets.filter((element) => {
|
|
549
|
-
const rect = element.getBoundingClientRect();
|
|
550
|
-
return rect.top > viewportHeight * 0.7;
|
|
551
|
-
});
|
|
552
|
-
if (!revealTargets.length) return null;
|
|
553
|
-
|
|
554
|
-
revealTargets.forEach((element) => element.classList.add('reveal'));
|
|
555
|
-
if (typeof IntersectionObserver !== 'function') {
|
|
556
|
-
revealTargets.forEach((element) => element.classList.add('in-view'));
|
|
557
|
-
return null;
|
|
558
|
-
}
|
|
559
|
-
|
|
560
|
-
const observer = new IntersectionObserver(
|
|
561
|
-
(entries) => {
|
|
562
|
-
entries.forEach((entry) => {
|
|
563
|
-
if (entry.isIntersecting) {
|
|
564
|
-
entry.target.classList.add('in-view');
|
|
565
|
-
observer.unobserve(entry.target);
|
|
566
|
-
}
|
|
567
|
-
});
|
|
568
|
-
},
|
|
569
|
-
{
|
|
570
|
-
root: null,
|
|
571
|
-
threshold: 0.14,
|
|
572
|
-
rootMargin: '0px 0px -8% 0px',
|
|
573
|
-
},
|
|
574
|
-
);
|
|
575
|
-
|
|
576
|
-
revealTargets.forEach((element) => observer.observe(element));
|
|
577
|
-
return () => observer.disconnect();
|
|
329
|
+
}, { delayMs: 260, timeoutMs: 1500 });
|
|
578
330
|
};
|
|
579
331
|
|
|
580
|
-
const registerCleanup = (cleanups,
|
|
581
|
-
if (typeof
|
|
582
|
-
cleanups.push(cleanupFn);
|
|
583
|
-
}
|
|
332
|
+
const registerCleanup = (cleanups, cleanup) => {
|
|
333
|
+
if (typeof cleanup === 'function') cleanups.push(cleanup);
|
|
584
334
|
};
|
|
585
335
|
|
|
586
336
|
const initHomeApp = () => {
|
|
@@ -588,20 +338,17 @@ const initHomeApp = () => {
|
|
|
588
338
|
window.__omnizapHomeAppReady = true;
|
|
589
339
|
|
|
590
340
|
const cleanups = [];
|
|
591
|
-
registerCleanup(cleanups, initMarketplacePreview());
|
|
592
|
-
registerCleanup(cleanups, initRankingPanel());
|
|
593
341
|
registerCleanup(cleanups, initNavToggle());
|
|
594
342
|
registerCleanup(cleanups, initAuthSession());
|
|
595
|
-
registerCleanup(cleanups,
|
|
596
|
-
registerCleanup(cleanups,
|
|
597
|
-
registerCleanup(cleanups, initRevealAnimations());
|
|
343
|
+
registerCleanup(cleanups, initAddBotCtas());
|
|
344
|
+
registerCleanup(cleanups, initSocialProof());
|
|
598
345
|
|
|
599
346
|
window.addEventListener(
|
|
600
347
|
'pagehide',
|
|
601
348
|
() => {
|
|
602
|
-
cleanups.forEach((
|
|
349
|
+
cleanups.forEach((cleanup) => {
|
|
603
350
|
try {
|
|
604
|
-
|
|
351
|
+
cleanup();
|
|
605
352
|
} catch {
|
|
606
353
|
// no-op
|
|
607
354
|
}
|