@kaikybrofc/omnizap-system 2.2.5 → 2.2.6

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.
@@ -1,6 +1,5 @@
1
- import { React, createRoot, useEffect } from '../runtime/react-runtime.js';
1
+ const FALLBACK_THUMB_URL = '/assets/images/brand-logo-128.webp';
2
2
 
3
- const h = React.createElement;
4
3
  const shortNum = (value) =>
5
4
  new Intl.NumberFormat('pt-BR', {
6
5
  notation: Number(value) >= 1000 ? 'compact' : 'standard',
@@ -39,286 +38,360 @@ const animateCountUp = (element, value, formatter = shortNum, durationMs = 850)
39
38
  requestAnimationFrame(tick);
40
39
  };
41
40
 
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
- }
41
+ const runAfterLoadIdle = (callback, { delayMs = 0, timeoutMs = 2200 } = {}) => {
42
+ let cancelled = false;
43
+ let loadListener = null;
44
+ let timeoutId = null;
45
+ let idleId = null;
64
46
 
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
- };
47
+ const invoke = () => {
48
+ if (cancelled) return;
49
+ callback();
50
+ };
83
51
 
84
- const renderPreview = (packs) => {
85
- previewGrid.innerHTML = '';
86
- if (!Array.isArray(packs) || !packs.length) {
87
- previewStatus.textContent = 'Sem packs em destaque no momento.';
52
+ const schedule = () => {
53
+ if (cancelled) return;
54
+
55
+ const queuedInvoke = () => {
56
+ if (cancelled) return;
57
+ if (delayMs > 0) {
58
+ timeoutId = window.setTimeout(invoke, delayMs);
88
59
  return;
89
60
  }
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
- });
61
+ invoke();
111
62
  };
112
63
 
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
- };
64
+ if (typeof window.requestIdleCallback === 'function') {
65
+ idleId = window.requestIdleCallback(queuedInvoke, { timeout: timeoutMs });
66
+ return;
67
+ }
141
68
 
142
- renderPreviewSkeleton(6);
143
- loadMarketplaceData();
144
- }, []);
69
+ timeoutId = window.setTimeout(queuedInvoke, Math.min(350, delayMs || 120));
70
+ };
145
71
 
146
- useEffect(() => {
147
- const summaryEl = document.getElementById('rank-summary');
148
- const listEl = document.getElementById('rank-list');
149
- if (!summaryEl || !listEl) return;
72
+ if (document.readyState === 'complete') {
73
+ schedule();
74
+ } else {
75
+ loadListener = () => schedule();
76
+ window.addEventListener('load', loadListener, { once: true });
77
+ }
150
78
 
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
- };
79
+ return () => {
80
+ cancelled = true;
81
+ if (loadListener) {
82
+ window.removeEventListener('load', loadListener);
83
+ }
84
+ if (timeoutId !== null) {
85
+ window.clearTimeout(timeoutId);
86
+ }
87
+ if (idleId !== null && typeof window.cancelIdleCallback === 'function') {
88
+ window.cancelIdleCallback(idleId);
89
+ }
90
+ };
91
+ };
156
92
 
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
- };
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
+ }
161
115
 
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
- }
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
+ };
178
133
 
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
- };
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
+ }
201
140
 
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
- };
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, '&quot;')}">` +
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
+ };
229
162
 
230
- toggle.addEventListener('click', handleClick);
231
- return () => toggle.removeEventListener('click', handleClick);
232
- }, []);
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.';
189
+ }
190
+ };
233
191
 
234
- useEffect(() => {
235
- const authLink = document.getElementById('nav-auth-link');
236
- const schedulerLink = document.getElementById('nav-scheduler-link');
237
- const heroLoginCta = document.getElementById('hero-login-cta');
238
- if (!authLink) return;
192
+ renderPreviewSkeleton(6);
193
+ return runAfterLoadIdle(() => {
194
+ void loadMarketplaceData();
195
+ }, { timeoutMs: 1200 });
196
+ };
239
197
 
240
- const fallbackAvatar = 'https://iili.io/FC3FABe.jpg';
241
- const clearChildren = (node) => {
242
- while (node.firstChild) {
243
- node.removeChild(node.firstChild);
244
- }
245
- };
198
+ const initRankingPanel = () => {
199
+ const summaryEl = document.getElementById('rank-summary');
200
+ const listEl = document.getElementById('rank-list');
201
+ if (!summaryEl || !listEl) return null;
246
202
 
247
- const showLoginButton = () => {
248
- document.body.classList.remove('home-authenticated');
249
- authLink.classList.remove('nav-user-chip');
250
- authLink.href = '/login/';
251
- authLink.removeAttribute('title');
252
- authLink.removeAttribute('aria-label');
253
- clearChildren(authLink);
254
-
255
- if (schedulerLink) {
256
- schedulerLink.hidden = false;
257
- schedulerLink.removeAttribute('aria-hidden');
258
- }
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
+ };
259
208
 
260
- if (heroLoginCta) {
261
- heroLoginCta.hidden = false;
262
- heroLoginCta.removeAttribute('aria-hidden');
263
- }
209
+ const renderFallback = (message) => {
210
+ summaryEl.textContent = message || 'Ranking indisponível no momento.';
211
+ listEl.innerHTML = '<li class="rank-item"><span class="rank-name">Sem dados no momento</span><span class="rank-value">--</span></li>';
212
+ };
264
213
 
265
- const icon = document.createElement('i');
266
- icon.className = 'fa-solid fa-right-to-bracket icon-inline';
267
- icon.setAttribute('aria-hidden', 'true');
268
- authLink.append(icon, document.createTextNode('Login'));
269
- };
214
+ const renderRanking = (data) => {
215
+ const rows = Array.isArray(data?.rows) ? data.rows : [];
216
+ const topType = data?.top_type ? `${data.top_type} (${data.top_type_count || 0})` : 'N/D';
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
+ }
270
230
 
271
- const showLoggedUser = (sessionData) => {
272
- const profile = sessionData?.user || {};
273
- const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
274
- const resolvedPhoto = String(profile?.picture || '').trim() || fallbackAvatar;
275
-
276
- document.body.classList.add('home-authenticated');
277
- authLink.classList.add('nav-user-chip');
278
- authLink.href = '/user/';
279
- authLink.title = `${resolvedName} (sessão ativa)`;
280
- authLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
281
- clearChildren(authLink);
282
-
283
- if (schedulerLink) {
284
- schedulerLink.hidden = true;
285
- schedulerLink.setAttribute('aria-hidden', 'true');
286
- }
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
+ });
259
+ };
287
260
 
288
- if (heroLoginCta) {
289
- heroLoginCta.hidden = true;
290
- heroLoginCta.setAttribute('aria-hidden', 'true');
291
- }
261
+ const loadRanking = () =>
262
+ fetch('/api/sticker-packs/global-ranking-summary')
263
+ .then((response) => {
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
+ });
292
273
 
293
- const avatarBubble = document.createElement('span');
294
- avatarBubble.className = 'nav-user-avatar-bubble';
295
-
296
- const photo = document.createElement('img');
297
- photo.className = 'nav-user-photo';
298
- photo.src = resolvedPhoto;
299
- photo.alt = `Foto de ${resolvedName}`;
300
- photo.loading = 'lazy';
301
- photo.decoding = 'async';
302
- photo.onerror = () => {
303
- photo.src = fallbackAvatar;
304
- };
305
- avatarBubble.appendChild(photo);
274
+ let intervalId = null;
275
+ const cancelScheduledLoad = runAfterLoadIdle(() => {
276
+ void loadRanking();
277
+ intervalId = window.setInterval(() => {
278
+ void loadRanking();
279
+ }, 10 * 60 * 1000);
280
+ }, { delayMs: 450, timeoutMs: 2200 });
281
+
282
+ return () => {
283
+ cancelScheduledLoad();
284
+ if (intervalId !== null) {
285
+ window.clearInterval(intervalId);
286
+ }
287
+ };
288
+ };
306
289
 
307
- const nameBubble = document.createElement('span');
308
- nameBubble.className = 'nav-user-name-bubble';
290
+ const initNavToggle = () => {
291
+ const toggle = document.getElementById('nav-toggle');
292
+ const nav = document.getElementById('main-nav');
293
+ if (!toggle || !nav) return null;
309
294
 
310
- const icon = document.createElement('i');
311
- icon.className = 'fa-solid fa-user nav-user-icon';
312
- icon.setAttribute('aria-hidden', 'true');
295
+ const handleClick = () => {
296
+ const isOpen = nav.classList.toggle('open');
297
+ toggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
298
+ };
313
299
 
314
- const name = document.createElement('span');
315
- name.className = 'nav-user-name';
316
- name.textContent = resolvedName;
300
+ toggle.addEventListener('click', handleClick);
301
+ return () => toggle.removeEventListener('click', handleClick);
302
+ };
303
+
304
+ const initAuthSession = () => {
305
+ const authLink = document.getElementById('nav-auth-link');
306
+ const schedulerLink = document.getElementById('nav-scheduler-link');
307
+ const heroLoginCta = document.getElementById('hero-login-cta');
308
+ if (!authLink) return null;
309
+
310
+ const clearChildren = (node) => {
311
+ while (node.firstChild) {
312
+ node.removeChild(node.firstChild);
313
+ }
314
+ };
315
+
316
+ const showLoginButton = () => {
317
+ document.body.classList.remove('home-authenticated');
318
+ authLink.classList.remove('nav-user-chip');
319
+ authLink.href = '/login/';
320
+ authLink.removeAttribute('title');
321
+ authLink.removeAttribute('aria-label');
322
+ clearChildren(authLink);
323
+
324
+ if (schedulerLink) {
325
+ schedulerLink.hidden = false;
326
+ schedulerLink.removeAttribute('aria-hidden');
327
+ }
328
+
329
+ if (heroLoginCta) {
330
+ heroLoginCta.hidden = false;
331
+ heroLoginCta.removeAttribute('aria-hidden');
332
+ }
333
+
334
+ const icon = document.createElement('i');
335
+ icon.className = 'fa-solid fa-right-to-bracket icon-inline';
336
+ icon.setAttribute('aria-hidden', 'true');
337
+ authLink.append(icon, document.createTextNode('Login'));
338
+ };
339
+
340
+ const showLoggedUser = (sessionData) => {
341
+ const profile = sessionData?.user || {};
342
+ const resolvedName = String(profile?.name || profile?.email || 'Conta Google').trim() || 'Conta Google';
343
+ const resolvedPhoto = String(profile?.picture || '').trim() || FALLBACK_THUMB_URL;
344
+
345
+ document.body.classList.add('home-authenticated');
346
+ authLink.classList.add('nav-user-chip');
347
+ authLink.href = '/user/';
348
+ authLink.title = `${resolvedName} (sessão ativa)`;
349
+ authLink.setAttribute('aria-label', `Sessão ativa de ${resolvedName}`);
350
+ clearChildren(authLink);
351
+
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
+ }
317
361
 
318
- nameBubble.append(icon, name);
319
- authLink.append(avatarBubble, nameBubble);
362
+ const avatarBubble = document.createElement('span');
363
+ avatarBubble.className = 'nav-user-avatar-bubble';
364
+
365
+ const photo = document.createElement('img');
366
+ photo.className = 'nav-user-photo';
367
+ photo.src = resolvedPhoto;
368
+ photo.alt = `Foto de ${resolvedName}`;
369
+ photo.loading = 'lazy';
370
+ photo.decoding = 'async';
371
+ photo.setAttribute('fetchpriority', 'low');
372
+ photo.width = 34;
373
+ photo.height = 34;
374
+ photo.onerror = () => {
375
+ photo.src = FALLBACK_THUMB_URL;
320
376
  };
377
+ avatarBubble.appendChild(photo);
321
378
 
379
+ const nameBubble = document.createElement('span');
380
+ nameBubble.className = 'nav-user-name-bubble';
381
+
382
+ const icon = document.createElement('i');
383
+ icon.className = 'fa-solid fa-user nav-user-icon';
384
+ icon.setAttribute('aria-hidden', 'true');
385
+
386
+ const name = document.createElement('span');
387
+ name.className = 'nav-user-name';
388
+ name.textContent = resolvedName;
389
+
390
+ nameBubble.append(icon, name);
391
+ authLink.append(avatarBubble, nameBubble);
392
+ };
393
+
394
+ return runAfterLoadIdle(() => {
322
395
  fetch('/api/sticker-packs/auth/google/session', { credentials: 'include' })
323
396
  .then((response) => {
324
397
  if (!response.ok) throw new Error('Sessão indisponível');
@@ -335,23 +408,25 @@ function HomeEffects() {
335
408
  .catch(() => {
336
409
  showLoginButton();
337
410
  });
338
- }, []);
339
-
340
- useEffect(() => {
341
- const wppButton = document.getElementById('wpp-float');
342
- if (!wppButton) return;
343
-
344
- const command = 'iniciar';
345
- const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
346
- const buildUrl = (phone) => `https://wa.me/${phone}?text=${encodeURIComponent(command)}`;
347
- const applyLink = (phone) => {
348
- const digits = normalizeDigits(phone);
349
- if (!digits) return false;
350
- wppButton.href = buildUrl(digits);
351
- wppButton.hidden = false;
352
- return true;
353
- };
411
+ }, { delayMs: 200, timeoutMs: 1400 });
412
+ };
354
413
 
414
+ const initWhatsappFloatingButton = () => {
415
+ const wppButton = document.getElementById('wpp-float');
416
+ if (!wppButton) return null;
417
+
418
+ const command = 'iniciar';
419
+ const normalizeDigits = (value) => String(value || '').replace(/\D+/g, '');
420
+ const buildUrl = (phone) => `https://wa.me/${phone}?text=${encodeURIComponent(command)}`;
421
+ const applyLink = (phone) => {
422
+ const digits = normalizeDigits(phone);
423
+ if (!digits) return false;
424
+ wppButton.href = buildUrl(digits);
425
+ wppButton.hidden = false;
426
+ return true;
427
+ };
428
+
429
+ return runAfterLoadIdle(() => {
355
430
  fetch('/api/sticker-packs?visibility=public&limit=1')
356
431
  .then((response) => {
357
432
  if (!response.ok) throw new Error('Falha ao buscar bot');
@@ -365,52 +440,54 @@ function HomeEffects() {
365
440
  .catch(() => {
366
441
  wppButton.hidden = true;
367
442
  });
368
- }, []);
369
-
370
- useEffect(() => {
371
- const cpuEl = document.getElementById('metric-host-cpu');
372
- const memEl = document.getElementById('metric-host-memory');
373
- const uptimeEl = document.getElementById('metric-process-uptime');
374
- const obsEl = document.getElementById('metric-observability');
375
- const proofUsers = document.getElementById('proof-users');
376
- const proofGroups = document.getElementById('proof-groups');
377
- const proofSystem = document.getElementById('proof-system');
378
- if (!cpuEl || !memEl || !uptimeEl || !obsEl) return;
379
-
380
- const normalizeStatus = (value) => {
381
- const normalized = String(value || '')
382
- .trim()
383
- .toLowerCase();
384
- if (!normalized) return 'degraded';
385
- if (['online', 'healthy', 'ok'].includes(normalized)) return 'online';
386
- if (['offline', 'down', 'disconnected'].includes(normalized)) return 'offline';
387
- if (['connecting', 'opening', 'reconnecting'].includes(normalized)) return 'connecting';
388
- return 'degraded';
389
- };
443
+ }, { delayMs: 500, timeoutMs: 2400 });
444
+ };
390
445
 
391
- const formatSystemStatusLabel = (status) => {
392
- if (status === 'online') return 'online';
393
- if (status === 'offline') return 'offline';
394
- if (status === 'connecting') return 'conectando';
395
- return 'instável';
396
- };
446
+ const initSystemSummary = () => {
447
+ const cpuEl = document.getElementById('metric-host-cpu');
448
+ const memEl = document.getElementById('metric-host-memory');
449
+ const uptimeEl = document.getElementById('metric-process-uptime');
450
+ const obsEl = document.getElementById('metric-observability');
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;
455
+
456
+ const normalizeStatus = (value) => {
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
+ };
397
466
 
398
- const setFallback = () => {
399
- cpuEl.textContent = 'CPU host: n/d';
400
- memEl.textContent = 'RAM host: n/d';
401
- uptimeEl.textContent = 'Uptime processo: n/d';
402
- obsEl.textContent = 'Observabilidade: API em /api/sticker-packs';
403
- if (proofUsers) proofUsers.textContent = 'n/d';
404
- if (proofGroups) proofGroups.textContent = 'n/d';
405
- if (proofSystem) {
406
- proofSystem.textContent = 'n/d';
407
- const card = proofSystem.closest('.proof-card');
408
- if (card) card.dataset.status = 'degraded';
409
- }
410
- };
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
+ };
411
473
 
412
- const fmt = (value) => (Number.isFinite(value) ? value.toFixed(2) : 'n/d');
474
+ const setFallback = () => {
475
+ cpuEl.textContent = 'CPU host: n/d';
476
+ memEl.textContent = 'RAM host: n/d';
477
+ uptimeEl.textContent = 'Uptime processo: n/d';
478
+ obsEl.textContent = 'Observabilidade: API em /api/sticker-packs';
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
+ }
486
+ };
413
487
 
488
+ const fmt = (value) => (Number.isFinite(value) ? value.toFixed(2) : 'n/d');
489
+
490
+ return runAfterLoadIdle(() => {
414
491
  fetch('/api/sticker-packs/system-summary')
415
492
  .then((response) => {
416
493
  if (!response.ok) throw new Error('Falha ao carregar métricas');
@@ -456,47 +533,86 @@ function HomeEffects() {
456
533
  .catch(() => {
457
534
  setFallback();
458
535
  });
459
- }, []);
460
-
461
- useEffect(() => {
462
- const revealTargets = Array.from(
463
- document.querySelectorAll(
464
- '.hero, .market-preview, .section-title, .grid .card, .api, .rank-panel, .final-cta, .hero-stats .chip, .hero-proof .proof-card',
465
- ),
466
- );
467
- if (!revealTargets.length) return undefined;
468
-
469
- revealTargets.forEach((element) => element.classList.add('reveal'));
470
- if (typeof IntersectionObserver !== 'function') {
471
- revealTargets.forEach((element) => element.classList.add('in-view'));
472
- return undefined;
473
- }
536
+ }, { delayMs: 650, timeoutMs: 2600 });
537
+ };
474
538
 
475
- const observer = new IntersectionObserver(
476
- (entries) => {
477
- entries.forEach((entry) => {
478
- if (entry.isIntersecting) {
479
- entry.target.classList.add('in-view');
480
- observer.unobserve(entry.target);
481
- }
482
- });
483
- },
484
- {
485
- root: null,
486
- threshold: 0.14,
487
- rootMargin: '0px 0px -8% 0px',
488
- },
489
- );
490
-
491
- revealTargets.forEach((element) => observer.observe(element));
492
- return () => observer.disconnect();
493
- }, []);
494
-
495
- return null;
496
- }
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();
578
+ };
579
+
580
+ const registerCleanup = (cleanups, cleanupFn) => {
581
+ if (typeof cleanupFn === 'function') {
582
+ cleanups.push(cleanupFn);
583
+ }
584
+ };
585
+
586
+ const initHomeApp = () => {
587
+ if (window.__omnizapHomeAppReady) return;
588
+ window.__omnizapHomeAppReady = true;
589
+
590
+ const cleanups = [];
591
+ registerCleanup(cleanups, initMarketplacePreview());
592
+ registerCleanup(cleanups, initRankingPanel());
593
+ registerCleanup(cleanups, initNavToggle());
594
+ registerCleanup(cleanups, initAuthSession());
595
+ registerCleanup(cleanups, initWhatsappFloatingButton());
596
+ registerCleanup(cleanups, initSystemSummary());
597
+ registerCleanup(cleanups, initRevealAnimations());
598
+
599
+ window.addEventListener(
600
+ 'pagehide',
601
+ () => {
602
+ cleanups.forEach((cleanupFn) => {
603
+ try {
604
+ cleanupFn();
605
+ } catch {
606
+ // no-op
607
+ }
608
+ });
609
+ },
610
+ { once: true },
611
+ );
612
+ };
497
613
 
498
- const rootEl = document.getElementById('home-react-root');
499
- if (rootEl) {
500
- const root = createRoot(rootEl);
501
- root.render(h(HomeEffects));
614
+ if (document.readyState === 'loading') {
615
+ document.addEventListener('DOMContentLoaded', initHomeApp, { once: true });
616
+ } else {
617
+ initHomeApp();
502
618
  }