@omnizap-system/omnizap 2.6.0 → 2.6.2

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 (261) hide show
  1. package/.env.example +58 -13
  2. package/.github/workflows/ci.yml +5 -5
  3. package/.github/workflows/codeql.yml +1 -1
  4. package/.github/workflows/db-migration-check.yml +2 -2
  5. package/.github/workflows/dependency-review.yml +1 -1
  6. package/.github/workflows/deploy.yml +2 -2
  7. package/.github/workflows/release.yml +2 -2
  8. package/.github/workflows/security-attest-provenance.yml +2 -2
  9. package/.github/workflows/security-gitleaks.yml +13 -4
  10. package/.github/workflows/security-runner-hardening.yml +2 -2
  11. package/.github/workflows/security-scorecard.yml +1 -1
  12. package/.github/workflows/security-zap-baseline.yml +1 -1
  13. package/.github/workflows/security-zap-full-scan.yml +2 -1
  14. package/.github/workflows/security-zizmor.yml +1 -1
  15. package/.github/workflows/wiki-sync.yml +1 -1
  16. package/.gitleaksignore +9 -0
  17. package/CODE_OF_CONDUCT.md +2 -2
  18. package/GEMINI.md +64 -0
  19. package/README.md +52 -82
  20. package/SECURITY.md +1 -1
  21. package/app/config/index.js +2 -0
  22. package/app/configParts/adminIdentity.js +5 -5
  23. package/app/configParts/baileysConfig.js +230 -58
  24. package/app/configParts/groupUtils.js +5 -0
  25. package/app/configParts/messagePersistenceService.js +145 -4
  26. package/app/configParts/sessionConfig.js +157 -0
  27. package/app/connection/baileysCompatibility.test.js +1 -1
  28. package/app/connection/groupOwnerWriteStateResolver.js +109 -0
  29. package/app/connection/socketController.js +660 -158
  30. package/app/connection/socketController.multiSession.test.js +108 -0
  31. package/app/controllers/messageController.js +1 -1
  32. package/app/controllers/messagePipeline/commandMiddleware.js +12 -10
  33. package/app/controllers/messagePipeline/conversationMiddleware.js +2 -1
  34. package/app/controllers/messagePipeline/messagePipelineMiddlewares.test.js +104 -0
  35. package/app/controllers/messagePipeline/preProcessingMiddlewares.js +80 -2
  36. package/app/controllers/messageProcessingPipeline.js +93 -13
  37. package/app/controllers/messageProcessingPipeline.test.js +200 -0
  38. package/app/modules/adminModule/AGENT.md +1 -1
  39. package/app/modules/adminModule/commandConfig.json +3318 -1347
  40. package/app/modules/adminModule/groupCommandHandlers.js +858 -15
  41. package/app/modules/adminModule/groupCommandHandlers.test.js +378 -11
  42. package/app/modules/adminModule/groupWarningRepository.js +152 -0
  43. package/app/modules/aiModule/AGENT.md +47 -30
  44. package/app/modules/aiModule/aiConfigRuntime.js +1 -0
  45. package/app/modules/aiModule/catCommand.js +135 -27
  46. package/app/modules/aiModule/commandConfig.json +114 -28
  47. package/app/modules/analyticsModule/messageAnalysisEventRepository.js +54 -6
  48. package/app/modules/gameModule/AGENT.md +1 -1
  49. package/app/modules/gameModule/commandConfig.json +29 -0
  50. package/app/modules/menuModule/AGENT.md +1 -1
  51. package/app/modules/menuModule/commandConfig.json +45 -10
  52. package/app/modules/menuModule/menuCatalogService.js +190 -0
  53. package/app/modules/menuModule/menuCommandUsageRepository.js +109 -0
  54. package/app/modules/menuModule/menuDynamicService.js +511 -0
  55. package/app/modules/menuModule/menuDynamicService.test.js +141 -0
  56. package/app/modules/menuModule/menus.js +36 -5
  57. package/app/modules/playModule/AGENT.md +10 -5
  58. package/app/modules/playModule/commandConfig.json +140 -12
  59. package/app/modules/playModule/playCommand.js +1 -1417
  60. package/app/modules/playModule/playCommandConstants.js +80 -0
  61. package/app/modules/playModule/playCommandCore.js +361 -0
  62. package/app/modules/playModule/playCommandHandlers.js +41 -0
  63. package/app/modules/playModule/playCommandMediaClient.js +1872 -0
  64. package/app/modules/playModule/playConfigRuntime.js +245 -4
  65. package/app/modules/playModule/playModuleCriticalFlows.test.js +152 -0
  66. package/app/modules/quoteModule/AGENT.md +1 -1
  67. package/app/modules/quoteModule/commandConfig.json +29 -0
  68. package/app/modules/quoteModule/quoteCommand.js +3 -2
  69. package/app/modules/rpgPokemonModule/AGENT.md +1 -1
  70. package/app/modules/rpgPokemonModule/commandConfig.json +29 -0
  71. package/app/modules/rpgPokemonModule/rpgBattleCanvasRenderer.js +5 -4
  72. package/app/modules/rpgPokemonModule/rpgBattleService.test.js +2 -1
  73. package/app/modules/rpgPokemonModule/rpgPokemonDomain.js +2 -1
  74. package/app/modules/rpgPokemonModule/rpgPokemonService.js +38 -37
  75. package/app/modules/rpgPokemonModule/rpgProfileCanvasRenderer.js +4 -3
  76. package/app/modules/statsModule/AGENT.md +1 -1
  77. package/app/modules/statsModule/commandConfig.json +58 -0
  78. package/app/modules/statsModule/rankingCommon.js +5 -4
  79. package/app/modules/stickerModule/AGENT.md +1 -1
  80. package/app/modules/stickerModule/addStickerMetadata.js +4 -3
  81. package/app/modules/stickerModule/commandConfig.json +145 -0
  82. package/app/modules/stickerModule/stickerCommand.js +1 -1
  83. package/app/modules/stickerPackModule/AGENT.md +1 -1
  84. package/app/modules/stickerPackModule/autoPackCollectorService.js +5 -1
  85. package/app/modules/stickerPackModule/commandConfig.json +29 -0
  86. package/app/modules/stickerPackModule/semanticThemeClusterService.js +7 -6
  87. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +10 -9
  88. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +9 -8
  89. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +3 -2
  90. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +2 -1
  91. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +80 -58
  92. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +2 -1
  93. package/app/modules/stickerPackModule/stickerPackRepository.js +2 -1
  94. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +5 -4
  95. package/app/modules/stickerPackModule/stickerPackService.js +13 -6
  96. package/app/modules/stickerPackModule/stickerStorageService.js +3 -2
  97. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +2 -1
  98. package/app/modules/systemMetricsModule/AGENT.md +1 -1
  99. package/app/modules/systemMetricsModule/commandConfig.json +29 -0
  100. package/app/modules/systemMetricsModule/pingCommand.js +6 -5
  101. package/app/modules/tiktokModule/AGENT.md +1 -1
  102. package/app/modules/tiktokModule/commandConfig.json +29 -0
  103. package/app/modules/tiktokModule/tiktokCommand.js +2 -1
  104. package/app/modules/userModule/AGENT.md +1 -1
  105. package/app/modules/userModule/commandConfig.json +29 -0
  106. package/app/modules/userModule/userCommand.js +72 -23
  107. package/app/modules/waifuPicsModule/AGENT.md +57 -27
  108. package/app/modules/waifuPicsModule/commandConfig.json +87 -0
  109. package/app/modules/waifuPicsModule/waifuPicsCommand.js +3 -2
  110. package/app/observability/metrics.js +136 -0
  111. package/app/services/ai/commandConfigEnrichmentService.js +229 -47
  112. package/app/services/ai/conversationRouterService.js +4 -3
  113. package/app/services/ai/geminiService.js +132 -7
  114. package/app/services/ai/geminiService.test.js +59 -2
  115. package/app/services/ai/globalModuleAiHelpService.js +3 -2
  116. package/app/services/ai/messageCommandExecutionService.js +2 -1
  117. package/app/services/ai/moduleAiHelpCoreService.js +45 -14
  118. package/app/services/ai/moduleToolExecutorService.js +3 -2
  119. package/app/services/ai/moduleToolRegistryService.js +2 -1
  120. package/app/services/ai/toolCandidateSelectorService.js +6 -5
  121. package/app/services/auth/googleWebLinkService.js +3 -2
  122. package/app/services/auth/whatsappLoginLinkService.js +3 -2
  123. package/app/services/external/pokeApiService.js +4 -3
  124. package/app/services/group/groupMetadataService.js +24 -1
  125. package/app/services/infra/dbWriteQueue.js +57 -26
  126. package/app/services/infra/featureFlagService.js +2 -1
  127. package/app/services/messaging/captchaService.js +3 -2
  128. package/app/services/messaging/newsBroadcastService.js +846 -29
  129. package/app/services/multiSession/assignmentBalancerService.js +457 -0
  130. package/app/services/multiSession/groupOwnershipRepository.js +381 -0
  131. package/app/services/multiSession/groupOwnershipService.js +890 -0
  132. package/app/services/multiSession/groupOwnershipService.test.js +309 -0
  133. package/app/services/multiSession/sessionRegistryService.js +293 -0
  134. package/app/services/sticker/stickerFocusService.js +11 -10
  135. package/app/store/aiPromptStore.js +36 -19
  136. package/app/store/conversationSessionStore.js +7 -6
  137. package/app/store/groupConfigStore.js +41 -5
  138. package/app/store/premiumUserStore.js +21 -7
  139. package/app/utils/antiLink/antiLinkModule.js +352 -16
  140. package/app/workers/aiHelperContinuousLearningWorker.js +512 -0
  141. package/app/workers/aiLearningWorker.js +6 -5
  142. package/app/workers/commandConfigEnrichmentWorker.js +4 -3
  143. package/database/index.js +14 -8
  144. package/database/migrations/20260307_d0_hardening_down.sql +1 -1
  145. package/database/migrations/20260314_d7_canonical_sender_down.sql +1 -1
  146. package/database/migrations/20260406_d30_security_analytics_down.sql +1 -1
  147. package/database/migrations/20260411_d35_group_community_metadata_down.sql +59 -0
  148. package/database/migrations/20260411_d35_group_community_metadata_up.sql +62 -0
  149. package/database/migrations/20260412_d36_system_config_tables_down.sql +32 -0
  150. package/database/migrations/20260412_d36_system_config_tables_up.sql +66 -0
  151. package/database/migrations/20260413_d37_group_user_warnings_down.sql +11 -0
  152. package/database/migrations/20260413_d37_group_user_warnings_up.sql +24 -0
  153. package/database/migrations/20260414_d38_multi_session_foundation_down.sql +72 -0
  154. package/database/migrations/20260414_d38_multi_session_foundation_up.sql +125 -0
  155. package/database/migrations/20260414_d39_multi_session_cutover_down.sql +103 -0
  156. package/database/migrations/20260414_d39_multi_session_cutover_up.sql +83 -0
  157. package/database/schema.sql +102 -1
  158. package/docker-compose.yml +4 -1
  159. package/docs/compliance/acceptable-use-policy-2026-03-07.md +1 -1
  160. package/docs/compliance/dpa-b2b-standard-2026-03-07.md +1 -1
  161. package/docs/compliance/privacy-policy-2026-03-07.md +4 -4
  162. package/docs/security/dsar-lgpd-runbook-2026-03-07.md +1 -1
  163. package/docs/security/incident-response-lgpd-anpd-runbook-2026-03-07.md +1 -1
  164. package/docs/security/network-hardening-runbook-2026-03-07.md +53 -0
  165. package/docs/security/omnizap-static-security-headers.conf +25 -0
  166. package/docs/wiki/Home.md +1 -1
  167. package/ecosystem.prod.config.cjs +32 -12
  168. package/index.js +57 -23
  169. package/observability/alert-rules.yml +20 -0
  170. package/observability/grafana/dashboards/omnizap-system-admin.json +229 -0
  171. package/observability/mysql-setup.sql +4 -4
  172. package/observability/system-admin-observability.md +26 -0
  173. package/package.json +20 -6
  174. package/public/apple-touch-icon.png +0 -0
  175. package/public/comandos/commands-catalog.json +2853 -3326
  176. package/public/favicon-16x16.png +0 -0
  177. package/public/favicon-32x32.png +0 -0
  178. package/public/favicon.ico +0 -0
  179. package/public/js/apps/apiDocsApp.js +3 -2
  180. package/public/js/apps/commandsReactApp.js +280 -99
  181. package/public/js/apps/createPackApp.js +11 -10
  182. package/public/js/apps/homeReactApp.js +181 -130
  183. package/public/js/apps/loginReactApp.js +1 -1
  184. package/public/js/apps/stickersApp.js +263 -110
  185. package/public/js/apps/termsReactApp.js +73 -24
  186. package/public/js/apps/userApp.js +4 -3
  187. package/public/js/apps/userPasswordResetReactApp.js +406 -0
  188. package/public/js/apps/userReactApp.js +355 -280
  189. package/public/js/apps/userSystemAdmReactApp.js +1506 -0
  190. package/public/pages/api-docs.html +1 -1
  191. package/public/pages/aup.html +2 -2
  192. package/public/pages/dpa.html +3 -3
  193. package/public/pages/licenca.html +4 -4
  194. package/public/pages/login.html +1 -1
  195. package/public/pages/notice-and-takedown.html +2 -2
  196. package/public/pages/politica-de-privacidade.html +6 -6
  197. package/public/pages/seo-bot-whatsapp-para-grupo.html +3 -3
  198. package/public/pages/seo-bot-whatsapp-sem-programar.html +3 -3
  199. package/public/pages/seo-como-automatizar-avisos-no-whatsapp.html +3 -3
  200. package/public/pages/seo-como-criar-comandos-whatsapp.html +3 -3
  201. package/public/pages/seo-como-evitar-spam-no-whatsapp.html +3 -3
  202. package/public/pages/seo-como-moderar-grupo-whatsapp.html +3 -3
  203. package/public/pages/seo-como-organizar-comunidade-whatsapp.html +3 -3
  204. package/public/pages/seo-melhor-bot-whatsapp-para-grupos.html +3 -3
  205. package/public/pages/stickers-admin.html +1 -1
  206. package/public/pages/stickers-create.html +1 -1
  207. package/public/pages/stickers.html +6 -6
  208. package/public/pages/suboperadores.html +2 -2
  209. package/public/pages/termos-de-uso-texto-integral.html +6 -6
  210. package/public/pages/termos-de-uso.html +3 -3
  211. package/public/pages/user-password-reset.html +4 -5
  212. package/public/pages/user-systemadm.html +9 -463
  213. package/public/pages/user.html +2 -2
  214. package/scripts/clear-whatsapp-session.sh +123 -0
  215. package/scripts/core-ai-mode.mjs +163 -0
  216. package/scripts/deploy.sh +11 -1
  217. package/scripts/email-broadcast-terms-update.mjs +2 -1
  218. package/scripts/enrich-command-config-ux-openai.mjs +492 -0
  219. package/scripts/generate-commands-catalog.mjs +166 -2
  220. package/scripts/generate-module-agents.mjs +2 -1
  221. package/scripts/generate-seo-satellite-pages.mjs +5 -4
  222. package/scripts/github-deploy-notify.mjs +2 -1
  223. package/scripts/github-release-notify.mjs +25 -10
  224. package/scripts/new-whatsapp-session.sh +317 -0
  225. package/scripts/release.sh +2 -19
  226. package/scripts/security-smoketest.mjs +6 -5
  227. package/scripts/security-web-surface-check.mjs +218 -0
  228. package/scripts/sticker-catalog-loadtest.mjs +5 -4
  229. package/server/auth/googleWebAuth/googleWebAuthService.js +8 -7
  230. package/server/auth/jwt/webJwtService.js +1 -1
  231. package/server/auth/stickerCatalogAuthContext.js +2 -1
  232. package/server/auth/termsAcceptance/termsAcceptanceHandler.js +2 -1
  233. package/server/auth/userPassword/userPasswordAuthService.js +2 -1
  234. package/server/auth/userPassword/userPasswordRecoveryService.js +4 -3
  235. package/server/auth/webAccount/webAccountHandlers.js +9 -10
  236. package/server/controllers/admin/adminPanelHandlers.js +267 -16
  237. package/server/controllers/admin/systemAdminController.js +267 -0
  238. package/server/controllers/seo/stickerCatalogSeoContext.js +10 -9
  239. package/server/controllers/sticker/nonCatalogHandlers.js +2 -1
  240. package/server/controllers/sticker/stickerCatalogController.js +23 -36
  241. package/server/controllers/system/contactController.js +9 -17
  242. package/server/controllers/system/githubController.js +3 -2
  243. package/server/controllers/system/stickerCatalogSystemContext.js +41 -19
  244. package/server/controllers/system/systemController.js +254 -1
  245. package/server/controllers/system/systemMetricsController.js +2 -1
  246. package/server/controllers/userController.js +6 -0
  247. package/server/email/emailTemplateService.js +5 -3
  248. package/server/http/httpServer.js +11 -6
  249. package/server/middleware/rateLimit.js +2 -1
  250. package/server/middleware/securityHeaders.js +20 -1
  251. package/server/routes/admin/systemAdminRouter.js +6 -0
  252. package/server/routes/indexRouter.js +30 -6
  253. package/server/routes/observability/grafanaProxyRouter.js +254 -0
  254. package/server/routes/static/staticPageRouter.js +27 -1
  255. package/server/utils/publicContact.js +31 -0
  256. package/utils/time/timeModule.js +135 -0
  257. package/utils/time/timeModule.test.js +65 -0
  258. package/utils/whatsapp/contactEnv.js +39 -0
  259. package/vite.config.mjs +7 -1
  260. package/public/assets/images/brand-icon-192.png +0 -0
  261. package/scripts/sync-readme-snapshot.mjs +0 -133
@@ -1,3 +1,4 @@
1
+ import { now as __timeNow, nowIso as __timeNowIso, toUnixMs as __timeNowMs } from '#time';
1
2
  import React, { useEffect, useMemo, useRef, useState } from 'react';
2
3
  import { createRoot } from 'react-dom/client';
3
4
  import htm from 'htm';
@@ -150,7 +151,7 @@ const readGoogleAuthCache = () => {
150
151
  if (!raw) return null;
151
152
  const parsed = JSON.parse(raw);
152
153
  const savedAt = Number(parsed?.savedAt || 0);
153
- if (savedAt && Date.now() - savedAt > GOOGLE_AUTH_CACHE_MAX_STALE_MS) {
154
+ if (savedAt && __timeNowMs() - savedAt > GOOGLE_AUTH_CACHE_MAX_STALE_MS) {
154
155
  localStorage.removeItem(GOOGLE_AUTH_CACHE_KEY);
155
156
  return null;
156
157
  }
@@ -161,7 +162,7 @@ const readGoogleAuthCache = () => {
161
162
  }
162
163
  if (normalized.expiresAt) {
163
164
  const expiresAt = Number(new Date(normalized.expiresAt));
164
- if (Number.isFinite(expiresAt) && expiresAt <= Date.now()) {
165
+ if (Number.isFinite(expiresAt) && expiresAt <= __timeNowMs()) {
165
166
  localStorage.removeItem(GOOGLE_AUTH_CACHE_KEY);
166
167
  return null;
167
168
  }
@@ -183,7 +184,7 @@ const writeGoogleAuthCache = (authState) => {
183
184
  GOOGLE_AUTH_CACHE_KEY,
184
185
  JSON.stringify({
185
186
  auth: normalized,
186
- savedAt: Date.now(),
187
+ savedAt: __timeNowMs(),
187
188
  }),
188
189
  );
189
190
  } catch {
@@ -477,7 +478,7 @@ const isRecent = (dateString) => {
477
478
  if (!dateString) return false;
478
479
  const created = new Date(dateString).getTime();
479
480
  if (!Number.isFinite(created)) return false;
480
- return Date.now() - created <= 1000 * 60 * 60 * 24 * 7;
481
+ return __timeNowMs() - created <= 1000 * 60 * 60 * 24 * 7;
481
482
  };
482
483
 
483
484
  const sleep = (ms) => new Promise((resolve) => window.setTimeout(resolve, Math.max(0, Number(ms) || 0)));
@@ -586,14 +587,14 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
586
587
  };
587
588
 
588
589
  return html`
589
- <button type="button" onClick=${handleOpen} className="group w-full text-left rounded-2xl border border-slate-800 bg-slate-900/90 shadow-soft overflow-hidden transition-all duration-200 active:scale-[0.985] md:hover:scale-[1.02] hover:-translate-y-0.5 hover:shadow-lg touch-manipulation">
590
- <div className="relative aspect-[5/6] sm:aspect-[4/5] bg-slate-900 overflow-hidden">
591
- <${LazyCatalogImage} src=${coverUrl} alt=${`Capa de ${pack.name}`} className=${`w-full h-full object-cover transition-transform duration-300 ${lockedByNsfw ? 'blur-md scale-105' : 'md:group-hover:scale-[1.05] group-active:scale-[1.02]'}`} />
590
+ <button type="button" onClick=${handleOpen} className="group w-full text-left rounded-[1rem] border border-slate-700/75 bg-slate-900/90 shadow-[0_16px_34px_rgba(2,6,23,0.34)] overflow-hidden transition-all duration-300 active:scale-[0.985] md:hover:scale-[1.02] hover:-translate-y-0.5 hover:border-cyan-400/35 hover:shadow-[0_24px_40px_rgba(2,6,23,0.5)] touch-manipulation">
591
+ <div className="relative aspect-[4/5] bg-slate-900 overflow-hidden">
592
+ <${LazyCatalogImage} src=${coverUrl} alt=${`Capa de ${pack.name}`} className=${`w-full h-full object-cover transition-transform duration-300 ${lockedByNsfw ? 'blur-md scale-105' : 'md:group-hover:scale-[1.06] group-active:scale-[1.02]'}`} />
592
593
  <div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/60 to-transparent"></div>
593
- <div className="absolute top-2 left-2 flex items-center gap-1">${lockedByNsfw ? html`<span className="rounded-full border border-amber-300/35 bg-amber-500/25 backdrop-blur px-1.5 py-0.5 text-[9px] font-bold text-amber-100">🔞 Login</span>` : null} ${isTrending ? html`<span className="rounded-full border border-emerald-300/30 bg-emerald-400/80 backdrop-blur px-1.5 py-0.5 text-[9px] font-bold text-slate-900">Trending</span>` : null} ${isNew ? html`<span className="rounded-full border border-white/15 bg-black/45 backdrop-blur px-1.5 py-0.5 text-[9px] font-semibold text-slate-100">Novo</span>` : null}</div>
594
+ <div className="absolute top-2 left-2 flex items-center gap-1">${lockedByNsfw ? html`<span className="rounded-full border border-amber-300/40 bg-amber-500/30 backdrop-blur px-1.5 py-0.5 text-[9px] font-bold text-amber-100">🔞 Login</span>` : null} ${isTrending ? html`<span className="rounded-full border border-emerald-300/35 bg-emerald-400/85 backdrop-blur px-1.5 py-0.5 text-[9px] font-bold text-slate-900">Trending</span>` : null} ${isNew ? html`<span className="rounded-full border border-white/20 bg-black/55 backdrop-blur px-1.5 py-0.5 text-[9px] font-semibold text-slate-100">Novo</span>` : null}</div>
594
595
 
595
596
  <div className="absolute inset-x-0 bottom-0 p-2">
596
- <h3 className="font-semibold text-sm leading-5 line-clamp-2">${pack.name || 'Pack sem nome'}</h3>
597
+ <h3 className="font-semibold text-sm leading-5 line-clamp-2 text-slate-100">${pack.name || 'Pack sem nome'}</h3>
597
598
  <div className="mt-1 flex items-center gap-1.5 text-[10px] text-slate-300">
598
599
  <${LazyCatalogImage} src=${getAvatarUrl(pack.publisher)} alt="Criador" className="w-4 h-4 rounded-full bg-slate-700" />
599
600
  <span className="truncate">${pack.publisher || 'Criador não informado'}</span>
@@ -606,7 +607,7 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
606
607
  </div>
607
608
 
608
609
  <div className="pointer-events-none absolute inset-x-2 bottom-2 hidden md:flex justify-center opacity-0 transition-opacity duration-200 group-hover:opacity-100">
609
- <span className="inline-flex h-8 w-full items-center justify-center rounded-xl border border-emerald-400/35 bg-emerald-400/12 px-3 text-xs font-semibold text-emerald-200 backdrop-blur"> ${lockedByNsfw ? 'Entrar para desbloquear' : 'Abrir pack'} </span>
610
+ <span className="inline-flex h-8 w-full items-center justify-center rounded-xl border border-cyan-400/35 bg-cyan-500/15 px-3 text-xs font-semibold text-cyan-100 backdrop-blur"> ${lockedByNsfw ? 'Entrar para desbloquear' : 'Ver pack'} </span>
610
611
  </div>
611
612
  ${lockedByNsfw
612
613
  ? html`
@@ -617,8 +618,8 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
617
618
  : null}
618
619
  </div>
619
620
 
620
- <div className="px-2 pb-2 pt-1 bg-slate-900/95 md:hidden">
621
- <span className="inline-flex h-[34px] w-full items-center justify-center rounded-xl border border-emerald-400/30 bg-emerald-400/10 text-xs font-semibold text-emerald-200 transition group-active:brightness-110"> ${lockedByNsfw ? 'Entrar para desbloquear' : 'Abrir pack'} </span>
621
+ <div className="px-2 pb-2 pt-1 bg-slate-950/88 md:hidden">
622
+ <span className="inline-flex h-[34px] w-full items-center justify-center rounded-xl border border-cyan-400/35 bg-cyan-500/15 text-xs font-semibold text-cyan-100 transition group-active:brightness-110"> ${lockedByNsfw ? 'Entrar para desbloquear' : 'Ver pack'} </span>
622
623
  </div>
623
624
  </button>
624
625
  `;
@@ -1396,7 +1397,7 @@ function CreatorProfileDashboard({ googleAuthConfig, googleAuth, googleAuthBusy,
1396
1397
 
1397
1398
  function SkeletonGrid({ count = 10 }) {
1398
1399
  return html`
1399
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-5 xl:grid-cols-6 gap-3">
1400
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 gap-3">
1400
1401
  ${Array.from({ length: count }).map(
1401
1402
  (_, index) => html`
1402
1403
  <div key=${index} className="rounded-2xl border border-slate-700 bg-slate-800 overflow-hidden animate-pulse">
@@ -1415,11 +1416,14 @@ function SkeletonGrid({ count = 10 }) {
1415
1416
 
1416
1417
  function EmptyState({ onClear }) {
1417
1418
  return html`
1418
- <div className="rounded-2xl border border-dashed border-slate-600 bg-slate-800/60 p-10 text-center">
1419
- <div className="text-5xl mb-2">🧩</div>
1420
- <p className="text-slate-100 font-semibold">Nenhum pack encontrado</p>
1421
- <p className="text-slate-400 text-sm mt-1">Tente outra busca ou remova os filtros ativos.</p>
1422
- <button type="button" onClick=${onClear} className="mt-4 inline-flex items-center justify-center rounded-xl border border-slate-600 px-4 py-2 text-sm text-slate-200 hover:bg-slate-700 transition">Limpar filtros</button>
1419
+ <div className="market-glass rounded-3xl p-8 text-center sm:p-10">
1420
+ <div className="mx-auto mb-3 flex h-14 w-14 items-center justify-center rounded-2xl border border-cyan-400/35 bg-cyan-500/12 text-2xl">🧩</div>
1421
+ <p className="text-slate-100 font-semibold text-lg">Nenhum pack encontrado</p>
1422
+ <p className="text-slate-400 text-sm mt-1">Tente outra busca, categoria ou limpe os filtros para descobrir novos packs.</p>
1423
+ <div className="mt-4 flex flex-wrap items-center justify-center gap-2">
1424
+ <button type="button" onClick=${onClear} className="inline-flex items-center justify-center rounded-xl border border-slate-600 px-4 py-2 text-sm text-slate-200 hover:bg-slate-800 transition">Limpar filtros</button>
1425
+ <a href="/stickers/create/" className="inline-flex items-center justify-center rounded-xl border border-emerald-400/35 bg-emerald-500/12 px-4 py-2 text-sm font-semibold text-emerald-100 hover:bg-emerald-500/22 transition">Criar pack</a>
1426
+ </div>
1423
1427
  </div>
1424
1428
  `;
1425
1429
  }
@@ -1990,7 +1994,7 @@ function StickersApp() {
1990
1994
  .map((pack) => {
1991
1995
  const engagement = getPackEngagement(pack);
1992
1996
  const createdAt = new Date(pack?.created_at || pack?.updated_at || 0).getTime();
1993
- const recentBonus = Date.now() - createdAt <= 1000 * 60 * 60 * 24 * 7 ? 18 : 0;
1997
+ const recentBonus = __timeNowMs() - createdAt <= 1000 * 60 * 60 * 24 * 7 ? 18 : 0;
1994
1998
  const growth = engagement.openCount * 1.5 + engagement.likeCount * 3 - engagement.dislikeCount + recentBonus;
1995
1999
  return { pack, growth };
1996
2000
  })
@@ -2088,6 +2092,28 @@ function StickersApp() {
2088
2092
  .slice(0, MOBILE_DISCOVER_CAROUSEL_LIMIT),
2089
2093
  [packs],
2090
2094
  );
2095
+ const catalogTotals = useMemo(() => {
2096
+ let stickers = 0;
2097
+ let likes = 0;
2098
+ let downloads = 0;
2099
+ const creators = new Set();
2100
+ for (const pack of packs) {
2101
+ stickers += Math.max(0, Number(pack?.sticker_count || 0));
2102
+ const engagement = getPackEngagement(pack);
2103
+ likes += Math.max(0, Number(engagement.likeCount || 0));
2104
+ downloads += Math.max(0, Number(engagement.openCount || 0));
2105
+ const publisher = String(pack?.publisher || '').trim();
2106
+ if (publisher) creators.add(publisher);
2107
+ }
2108
+ return {
2109
+ packs: packs.length,
2110
+ stickers,
2111
+ likes,
2112
+ downloads,
2113
+ creators: creators.size,
2114
+ };
2115
+ }, [packs]);
2116
+ const highlightCreator = String(featuredCreators?.[0]?.publisher || '').trim();
2091
2117
 
2092
2118
  const hasAnyResult = packs.length > 0;
2093
2119
  const canGoCatalogPrev = catalogPage > FIRST_CATALOG_PAGE && !packsLoading;
@@ -2216,7 +2242,7 @@ function StickersApp() {
2216
2242
  };
2217
2243
 
2218
2244
  const pushProfileToast = (message, type = 'success') => {
2219
- const id = `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
2245
+ const id = `${__timeNowMs()}-${Math.random().toString(36).slice(2, 8)}`;
2220
2246
  setProfileToasts((prev) => [...prev, { id, message: String(message || ''), type }].slice(-5));
2221
2247
  window.setTimeout(() => {
2222
2248
  setProfileToasts((prev) => prev.filter((item) => item.id !== id));
@@ -3105,7 +3131,7 @@ function StickersApp() {
3105
3131
  };
3106
3132
  const optimistic = {
3107
3133
  ...previous,
3108
- updated_at: new Date().toISOString(),
3134
+ updated_at: __timeNowIso(),
3109
3135
  };
3110
3136
  if (action === 'like') {
3111
3137
  optimistic.like_count += 1;
@@ -3448,11 +3474,74 @@ function StickersApp() {
3448
3474
  };
3449
3475
 
3450
3476
  return html`
3451
- <div className="min-h-screen bg-slate-950 text-slate-100">
3477
+ <div className="min-h-screen bg-slate-950 text-slate-100 stickers-market-shell">
3452
3478
  <style>
3453
3479
  ${`@keyframes fadeInCard { from { opacity: 0; transform: translateY(8px); } to { opacity: 1; transform: translateY(0); } }
3480
+ @keyframes pulseGlow { 0%, 100% { opacity: 0.58; } 50% { opacity: 0.94; } }
3454
3481
  .fade-card { animation: fadeInCard 260ms ease both; }
3455
3482
  .line-clamp-2 { display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
3483
+ .stickers-market-shell { --market-blue: #0f172a; --market-cyan: #22d3ee; --market-green: #34d399; scroll-behavior: smooth; }
3484
+ .market-shell-glow {
3485
+ pointer-events: none;
3486
+ position: fixed;
3487
+ inset: 0;
3488
+ z-index: 0;
3489
+ background:
3490
+ radial-gradient(48rem 28rem at 4% -10%, rgba(52, 211, 153, 0.14), transparent 62%),
3491
+ radial-gradient(40rem 24rem at 96% -8%, rgba(34, 211, 238, 0.15), transparent 60%),
3492
+ radial-gradient(52rem 30rem at 50% 120%, rgba(15, 23, 42, 0.84), transparent 70%);
3493
+ }
3494
+ .market-glass {
3495
+ border: 1px solid rgba(71, 85, 105, 0.52);
3496
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.78), rgba(2, 6, 23, 0.72));
3497
+ box-shadow: 0 20px 42px rgba(2, 6, 23, 0.36);
3498
+ backdrop-filter: blur(14px);
3499
+ }
3500
+ .market-header-glass {
3501
+ border-bottom: 1px solid rgba(71, 85, 105, 0.45);
3502
+ background: linear-gradient(180deg, rgba(2, 6, 23, 0.84), rgba(2, 6, 23, 0.68));
3503
+ backdrop-filter: blur(16px);
3504
+ }
3505
+ .market-search-shell {
3506
+ border: 1px solid rgba(71, 85, 105, 0.55);
3507
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.95), rgba(15, 23, 42, 0.85));
3508
+ box-shadow: inset 0 0 0 1px rgba(15, 23, 42, 0.35);
3509
+ }
3510
+ .catalog-hero {
3511
+ position: relative;
3512
+ overflow: hidden;
3513
+ }
3514
+ .catalog-hero::before {
3515
+ content: '';
3516
+ position: absolute;
3517
+ inset: -24% -10% auto;
3518
+ height: 82%;
3519
+ background: radial-gradient(26rem 14rem at 22% 0%, rgba(52, 211, 153, 0.18), transparent 65%);
3520
+ animation: pulseGlow 6.8s ease-in-out infinite;
3521
+ pointer-events: none;
3522
+ }
3523
+ .catalog-hero::after {
3524
+ content: '';
3525
+ position: absolute;
3526
+ inset: auto -20% -38% -15%;
3527
+ height: 70%;
3528
+ background: radial-gradient(26rem 12rem at 70% 60%, rgba(34, 211, 238, 0.13), transparent 70%);
3529
+ pointer-events: none;
3530
+ }
3531
+ .kpi-card {
3532
+ border: 1px solid rgba(71, 85, 105, 0.5);
3533
+ background: rgba(2, 6, 23, 0.42);
3534
+ }
3535
+ .metric-card {
3536
+ border: 1px solid rgba(71, 85, 105, 0.5);
3537
+ background: linear-gradient(180deg, rgba(2, 6, 23, 0.62), rgba(2, 6, 23, 0.38));
3538
+ border-radius: 14px;
3539
+ }
3540
+ .market-sidebar-panel {
3541
+ border: 1px solid rgba(71, 85, 105, 0.54);
3542
+ background: linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(2, 6, 23, 0.72));
3543
+ backdrop-filter: blur(10px);
3544
+ }
3456
3545
  .chips-scroll {
3457
3546
  scroll-snap-type: x mandatory;
3458
3547
  -webkit-overflow-scrolling: touch;
@@ -3464,86 +3553,101 @@ function StickersApp() {
3464
3553
  .chips-scroll::-webkit-scrollbar { display: none; }
3465
3554
  .chip-item { scroll-snap-align: start; touch-action: manipulation; -webkit-tap-highlight-color: transparent; }
3466
3555
  .pack-stickers-grid { display: grid; grid-template-columns: repeat(3, minmax(0, 1fr)); }
3556
+ .market-catalog-grid { display: grid; gap: 0.8rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
3557
+ @media (min-width: 768px) {
3558
+ .market-catalog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
3559
+ }
3467
3560
  @media (min-width: 1024px) {
3468
3561
  .pack-stickers-grid { grid-template-columns: repeat(auto-fill, minmax(160px, 1fr)); }
3562
+ .market-catalog-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
3563
+ }
3564
+ @media (min-width: 1280px) {
3565
+ .market-catalog-grid { grid-template-columns: repeat(4, minmax(0, 1fr)); }
3566
+ }
3567
+ @media (min-width: 1536px) {
3568
+ .market-catalog-grid { grid-template-columns: repeat(5, minmax(0, 1fr)); }
3569
+ }
3570
+ @media (max-width: 767px) {
3571
+ .market-header-action-label {
3572
+ display: none;
3573
+ }
3469
3574
  }
3470
3575
  @media (prefers-reduced-motion: reduce) {
3471
3576
  .fade-card { animation: none; }
3577
+ .catalog-hero::before { animation: none; }
3472
3578
  }`}
3473
3579
  </style>
3474
3580
 
3475
- <header className=${`sticky top-0 z-30 border-b border-slate-800 bg-slate-950/95 backdrop-blur transition-shadow ${isScrolled ? 'shadow-[0_8px_24px_rgba(2,6,23,0.45)]' : ''}`}>
3476
- <div className="max-w-7xl mx-auto h-14 px-3 flex items-center gap-2.5">
3477
- <a href="/" className="shrink-0 flex items-center gap-2">
3478
- <img src=${OMNIZAP_LOGO_DATA_URL} alt="OmniZap" className="w-7 h-7 rounded-full border border-slate-700" decoding="async" />
3479
- <span className="hidden sm:inline text-sm font-semibold">OmniZap</span>
3480
- </a>
3481
-
3482
- ${currentView === 'catalog'
3483
- ? html`
3484
- <form onSubmit=${onSubmit} className="flex-1 relative">
3485
- <span className="absolute left-2.5 top-1/2 -translate-y-1/2 text-xs text-slate-400">🔎</span>
3486
- <input
3487
- type="search"
3488
- value=${query}
3489
- onChange=${(e) => setQuery(e.target.value)}
3490
- onFocus=${() => setShowAutocomplete(true)}
3491
- onBlur=${() => setTimeout(() => setShowAutocomplete(false), 120)}
3492
- onKeyDown=${(event) => {
3493
- if (event.key === 'Escape') {
3494
- setShowAutocomplete(false);
3495
- }
3496
- }}
3497
- placeholder="Buscar packs..."
3498
- className="w-full h-9 sm:h-10 rounded-2xl border border-slate-800 bg-slate-900 pl-[34px] sm:pl-9 pr-3 text-sm text-slate-100 placeholder:text-slate-500 outline-none transition focus:border-emerald-400/50 focus:ring-2 focus:ring-emerald-400/15"
3499
- />
3500
- ${showAutocomplete && filteredSuggestions.length
3501
- ? html`
3502
- <div className="absolute z-40 mt-2 w-full rounded-2xl border border-slate-800 bg-slate-900 shadow-xl overflow-hidden">
3503
- ${filteredSuggestions.map(
3504
- (item) => html`
3505
- <button key=${item.value} type="button" onClick=${() => applySuggestion(item)} className="w-full px-3 py-2.5 text-left text-sm text-slate-200 hover:bg-slate-800 flex items-center justify-between gap-2 border-b border-slate-800 last:border-b-0">
3506
- <span className="inline-flex items-center gap-2">
3507
- <span>${item.icon || '🏷'}</span>
3508
- <span className="truncate">${item.label}</span>
3509
- </span>
3510
- <span className="text-xs text-slate-400 truncate">${item.value}</span>
3511
- </button>
3512
- `,
3513
- )}
3514
- </div>
3515
- `
3516
- : null}
3517
- </form>
3518
- `
3519
- : html`<div className="flex-1"></div>`}
3581
+ <div className="market-shell-glow"></div>
3520
3582
 
3521
- <div className="flex items-center gap-2">
3522
- <button type="button" className=${`text-xs rounded-lg border px-3 py-2 ${isProfileView ? 'border-amber-400/40 bg-amber-400/10 text-amber-200' : 'border-amber-500/30 bg-amber-500/5 text-amber-200 hover:bg-amber-500/10'}`} onClick=${() => openProfile(true)} title="Meu perfil e packs">
3523
- <span className="sm:hidden">👤</span>
3524
- <span className="hidden sm:inline">Meus Packs</span>
3525
- </button>
3526
- <a className="text-xs rounded-lg border border-cyan-500/40 bg-cyan-500/10 px-3 py-2 text-cyan-200 hover:bg-cyan-500/20" href="/stickers/create/" title="Criar pack">
3527
- <span className="sm:hidden">➕</span>
3528
- <span className="hidden sm:inline">✨ Criar pack agora</span>
3583
+ <header className=${`sticky top-0 z-30 market-header-glass transition-shadow ${isScrolled ? 'shadow-[0_16px_44px_rgba(2,6,23,0.52)]' : ''}`}>
3584
+ <div className="mx-auto w-full max-w-[1400px] px-3 sm:px-4 lg:px-6">
3585
+ <div className="flex h-[74px] items-center gap-2.5 md:gap-3.5">
3586
+ <a href="/" className="shrink-0 flex items-center gap-2.5">
3587
+ <img src=${OMNIZAP_LOGO_DATA_URL} alt="OmniZap" className="h-9 w-9 rounded-full border border-slate-700/90 ring-2 ring-slate-800/70" decoding="async" />
3588
+ <span className="hidden md:inline text-sm font-semibold tracking-wide text-slate-100">OmniZap Marketplace</span>
3529
3589
  </a>
3530
- ${supportInfo?.url
3590
+
3591
+ ${currentView === 'catalog'
3531
3592
  ? html`
3532
- <a className="text-xs rounded-lg border border-emerald-500/40 bg-emerald-500/10 px-3 py-2 text-emerald-200 hover:bg-emerald-500/20" href=${supportInfo.url} target="_blank" rel="noreferrer noopener" title="Suporte no WhatsApp">
3533
- <span className="sm:hidden">💬</span>
3534
- <span className="hidden sm:inline">Suporte</span>
3535
- </a>
3593
+ <form onSubmit=${onSubmit} className="relative mx-auto w-full max-w-[760px]">
3594
+ <div className="market-search-shell h-11 rounded-2xl">
3595
+ <span className="absolute left-3 top-1/2 -translate-y-1/2 text-[13px] text-slate-400">🔎</span>
3596
+ <input
3597
+ type="search"
3598
+ value=${query}
3599
+ onChange=${(e) => setQuery(e.target.value)}
3600
+ onFocus=${() => setShowAutocomplete(true)}
3601
+ onBlur=${() => setTimeout(() => setShowAutocomplete(false), 120)}
3602
+ onKeyDown=${(event) => {
3603
+ if (event.key === 'Escape') {
3604
+ setShowAutocomplete(false);
3605
+ }
3606
+ }}
3607
+ placeholder="Buscar packs, temas e criadores"
3608
+ className="h-full w-full rounded-2xl bg-transparent pl-10 pr-3 text-sm text-slate-100 placeholder:text-slate-500 outline-none transition focus:ring-2 focus:ring-cyan-400/25"
3609
+ />
3610
+ </div>
3611
+ ${showAutocomplete && filteredSuggestions.length
3612
+ ? html`
3613
+ <div className="absolute z-40 mt-2 w-full overflow-hidden rounded-2xl border border-slate-700 bg-slate-900 shadow-xl">
3614
+ ${filteredSuggestions.map(
3615
+ (item) => html`
3616
+ <button key=${item.value} type="button" onClick=${() => applySuggestion(item)} className="w-full border-b border-slate-800 px-3 py-2.5 text-left text-sm text-slate-200 hover:bg-slate-800 last:border-b-0 flex items-center justify-between gap-2">
3617
+ <span className="inline-flex items-center gap-2">
3618
+ <span>${item.icon || '🏷'}</span>
3619
+ <span className="truncate">${item.label}</span>
3620
+ </span>
3621
+ <span className="truncate text-xs text-slate-400">${item.value}</span>
3622
+ </button>
3623
+ `,
3624
+ )}
3625
+ </div>
3626
+ `
3627
+ : null}
3628
+ </form>
3536
3629
  `
3537
- : null}
3538
- <div className="hidden sm:flex items-center gap-2">
3539
- <a className="text-xs rounded-lg border border-slate-700 px-3 py-2 text-slate-300 hover:bg-slate-800" href="/api-docs/">API</a>
3540
- <a className="text-xs rounded-lg border border-slate-700 px-3 py-2 text-slate-300 hover:bg-slate-800" href="https://github.com/Omnizap-System/omnizap" target="_blank" rel="noreferrer noopener">GitHub</a>
3630
+ : html`<div className="flex-1"></div>`}
3631
+
3632
+ <div className="flex shrink-0 items-center gap-1.5 sm:gap-2">
3633
+ <button type="button" className=${`inline-flex h-10 items-center gap-1.5 rounded-xl border px-2.5 text-xs transition ${isProfileView ? 'border-cyan-400/45 bg-cyan-500/12 text-cyan-100' : 'border-slate-700 bg-slate-900/65 text-slate-200 hover:bg-slate-800'}`} onClick=${() => openProfile(true)} title="Meus packs">
3634
+ <span>📦</span>
3635
+ <span className="market-header-action-label">Meus Packs</span>
3636
+ </button>
3637
+ <a className="inline-flex h-10 items-center gap-1.5 rounded-xl border border-emerald-400/35 bg-emerald-500/14 px-2.5 text-xs font-semibold text-emerald-100 hover:bg-emerald-500/24 transition" href="/stickers/create/" title="Criar pack">
3638
+ <span>✨</span>
3639
+ <span className="market-header-action-label">Criar Pack</span>
3640
+ </a>
3641
+ <button type="button" onClick=${() => openProfile(true)} className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-700 bg-slate-900/65 px-2 text-xs text-slate-200 hover:bg-slate-800 transition" title="Perfil">
3642
+ ${hasGoogleLogin && googleAuth?.user?.picture ? html`<img src=${googleAuth.user.picture} alt="Perfil" className="h-6 w-6 rounded-full border border-slate-700 object-cover" />` : html`<span className="inline-flex h-6 w-6 items-center justify-center rounded-full border border-slate-700 bg-slate-800">👤</span>`}
3643
+ <span className="market-header-action-label">Perfil</span>
3644
+ </button>
3541
3645
  </div>
3542
3646
  </div>
3543
3647
  </div>
3544
3648
  </header>
3545
3649
 
3546
- <main className="max-w-7xl mx-auto px-3 py-2.5 sm:py-3 space-y-3 pb-[calc(1rem+env(safe-area-inset-bottom))]">
3650
+ <main className="relative z-[1] mx-auto w-full max-w-[1400px] px-3 py-3.5 sm:px-4 sm:py-4 lg:px-6 space-y-3.5 pb-[calc(1rem+env(safe-area-inset-bottom))]">
3547
3651
  ${error ? html`<div className="rounded-2xl border border-red-500/40 bg-red-500/10 px-4 py-3 text-sm text-red-200">${error}</div>` : null}
3548
3652
  ${isProfileView
3549
3653
  ? html` <${CreatorProfileDashboard} googleAuthConfig=${googleAuthConfig} googleAuth=${googleAuth} googleAuthBusy=${googleAuthBusy} googleAuthError=${googleAuthError} googleSessionChecked=${googleSessionChecked} myPacks=${myPacks} myPacksLoading=${myPacksLoading} myPacksError=${myPacksError} myProfileStats=${myProfileStats} onBack=${goCatalog} onRefresh=${() => refreshMyProfile()} onLogout=${handleGoogleLogout} onOpenPublicPack=${openPack} onOpenPackActions=${openPackActionsSheet} onOpenManagePack=${(pack) => openManagePackByKey(pack?.pack_key || '')} onRequestDeletePack=${requestDeletePack} packActionBusyByKey=${packActionBusyByKey} /> `
@@ -3552,28 +3656,77 @@ function StickersApp() {
3552
3656
  : currentPackKey
3553
3657
  ? html` ${packLoading ? html`<${PackPageSkeleton} />` : html`<${PackPage} pack=${currentPack} relatedPacks=${relatedPacks} relatedLoading=${relatedPacksLoading} onLoadRelated=${requestRelatedPacksForCurrentPack} onBack=${goCatalog} onOpenRelated=${openPack} onLike=${handleLike} onDislike=${handleDislike} onTagClick=${openCatalogTagFilter} reactionLoading=${reactionLoading} reactionNotice=${reactionNotice} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`} `
3554
3658
  : html`
3555
- <div className="lg:grid lg:grid-cols-[220px_minmax(0,1fr)] lg:gap-4">
3659
+ <section className="catalog-hero market-glass rounded-[28px] p-4 sm:p-5 lg:p-6">
3660
+ <div className="relative z-[1] flex flex-col gap-4 lg:flex-row lg:items-end lg:justify-between">
3661
+ <div className="max-w-3xl">
3662
+ <p className="text-[11px] uppercase tracking-[0.16em] text-emerald-200/85">OmniZap Marketplace</p>
3663
+ <h1 className="mt-1 text-xl font-semibold leading-tight text-slate-100 sm:text-2xl lg:text-3xl">Packs de stickers prontos para seu WhatsApp</h1>
3664
+ <p className="mt-2 text-sm leading-relaxed text-slate-300">Explore coleções curadas, descubra criadores com melhor performance e publique novos packs com mais conversão.</p>
3665
+ ${highlightCreator ? html`<p className="mt-2 text-xs text-cyan-200/90">⭐ Criador em destaque hoje: <span className="font-semibold">${highlightCreator}</span></p>` : null}
3666
+ </div>
3667
+ <div className="flex flex-wrap items-center gap-2">
3668
+ <button type="button" onClick=${openTrendingCatalog} className="inline-flex h-10 items-center gap-1.5 rounded-xl border border-cyan-400/35 bg-cyan-500/14 px-3 text-xs font-semibold text-cyan-100 hover:bg-cyan-500/24 transition">🔎 Explorar packs</button>
3669
+ <a href="/stickers/create/" className="inline-flex h-10 items-center gap-1.5 rounded-xl border border-emerald-400/35 bg-emerald-500/14 px-3 text-xs font-semibold text-emerald-100 hover:bg-emerald-500/24 transition">✨ Criar pack</a>
3670
+ <button type="button" onClick=${() => openCreatorsRanking('popular', true)} className="inline-flex h-10 items-center gap-1.5 rounded-xl border border-indigo-400/35 bg-indigo-500/10 px-3 text-xs font-semibold text-indigo-100 hover:bg-indigo-500/20 transition">⭐ Ver criadores</button>
3671
+ </div>
3672
+ </div>
3673
+ <div className="relative z-[1] mt-4 grid grid-cols-2 gap-2 sm:grid-cols-3 xl:grid-cols-5">
3674
+ <article className="metric-card px-3 py-2.5">
3675
+ <p className="text-[10px] uppercase tracking-wide text-slate-400">Packs</p>
3676
+ <p className="mt-1 text-base font-semibold text-slate-100">${shortNum(catalogTotals.packs)}</p>
3677
+ </article>
3678
+ <article className="metric-card px-3 py-2.5">
3679
+ <p className="text-[10px] uppercase tracking-wide text-slate-400">Stickers</p>
3680
+ <p className="mt-1 text-base font-semibold text-slate-100">${shortNum(catalogTotals.stickers)}</p>
3681
+ </article>
3682
+ <article className="metric-card px-3 py-2.5">
3683
+ <p className="text-[10px] uppercase tracking-wide text-slate-400">Likes</p>
3684
+ <p className="mt-1 text-base font-semibold text-slate-100">${shortNum(catalogTotals.likes)}</p>
3685
+ </article>
3686
+ <article className="metric-card px-3 py-2.5">
3687
+ <p className="text-[10px] uppercase tracking-wide text-slate-400">Downloads</p>
3688
+ <p className="mt-1 text-base font-semibold text-slate-100">${shortNum(catalogTotals.downloads)}</p>
3689
+ </article>
3690
+ <article className="metric-card col-span-2 px-3 py-2.5 sm:col-span-1">
3691
+ <p className="text-[10px] uppercase tracking-wide text-slate-400">Criadores ativos</p>
3692
+ <p className="mt-1 text-base font-semibold text-slate-100">${shortNum(catalogTotals.creators)}</p>
3693
+ </article>
3694
+ </div>
3695
+ </section>
3696
+ <div className="lg:grid lg:grid-cols-[280px_minmax(0,1fr)] lg:gap-5">
3556
3697
  <aside className="hidden lg:block">
3557
- <div className="sticky top-[72px] space-y-2.5 rounded-2xl border border-slate-800 bg-slate-900/80 p-2.5">
3558
- <div className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
3698
+ <div className="market-sidebar-panel sticky top-[98px] space-y-3 rounded-2xl p-3">
3699
+ <div className="rounded-xl border border-slate-800 bg-slate-950/50 p-3">
3559
3700
  <div className="flex items-center justify-between gap-2">
3560
- <h3 className="text-xs font-semibold uppercase tracking-wide text-slate-300">Filtros</h3>
3561
- <button type="button" onClick=${clearFilters} className="h-8 rounded-lg border border-slate-700 px-2 text-[11px] text-slate-200 hover:bg-slate-800">Limpar</button>
3701
+ <h3 className="text-xs font-semibold uppercase tracking-wide text-slate-200">Filtros</h3>
3702
+ <button type="button" onClick=${clearFilters} className="h-8 rounded-lg border border-slate-700 px-2.5 text-[11px] text-slate-200 hover:bg-slate-800 transition">Limpar</button>
3562
3703
  </div>
3563
- <p className="mt-1 text-[11px] text-slate-500">${packs.length} packs nesta página</p>
3704
+ <p className="mt-1 text-[11px] text-slate-500">${sortedPacks.length} packs nesta página</p>
3564
3705
  </div>
3565
3706
 
3566
- <details open className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
3567
- <summary className="cursor-pointer list-none text-xs font-semibold text-slate-200">Ordenar catálogo</summary>
3568
- <div className="mt-2 space-y-1.5">${CATALOG_SORT_OPTIONS.map((option) => html` <button key=${option.value} type="button" onClick=${() => handleCatalogSortSelection(option.value)} disabled=${sortPickerBusy} className=${`w-full h-9 rounded-xl border text-xs disabled:opacity-60 ${normalizeCatalogSort(sortBy) === option.value ? 'border-emerald-400 bg-emerald-400/10 text-emerald-200' : 'border-slate-700 text-slate-300 hover:bg-slate-800'}`}>${option.icon} ${option.label}</button> `)}</div>
3569
- </details>
3707
+ <div className="rounded-xl border border-slate-800 bg-slate-950/50 p-3">
3708
+ <p className="text-xs font-semibold text-slate-100">Ordenação rápida</p>
3709
+ <div className="mt-2 space-y-1.5">
3710
+ ${[
3711
+ { value: 'recent', label: 'Mais recentes', icon: '🆕' },
3712
+ { value: 'likes', label: 'Mais curtidos', icon: '👍' },
3713
+ { value: 'downloads', label: 'Mais baixados', icon: '⬇️' },
3714
+ ].map((option) => html` <button key=${option.value} type="button" onClick=${() => handleCatalogSortSelection(option.value)} disabled=${sortPickerBusy} className=${`w-full h-9 rounded-xl border px-2.5 text-left text-xs transition disabled:opacity-60 ${normalizeCatalogSort(sortBy) === option.value ? 'border-cyan-400/40 bg-cyan-500/14 text-cyan-100' : 'border-slate-700 text-slate-200 hover:bg-slate-800'}`}>${option.icon} ${option.label}</button> `)}
3715
+ </div>
3716
+ </div>
3717
+
3718
+ <div className="rounded-xl border border-slate-800 bg-slate-950/50 p-3">
3719
+ <p className="text-xs font-semibold text-slate-100">Categorias</p>
3720
+ <div className="mt-2 flex flex-wrap gap-1.5">${dynamicCategoryOptions.slice(0, 12).map((item) => html` <button key=${`sidebar-${item.value || 'all'}`} type="button" onClick=${() => handleCategoryChipPress(item.value)} className=${`h-8 rounded-full border px-2.5 text-[11px] transition ${activeCategory === item.value ? 'border-emerald-300 bg-emerald-400 text-slate-900 font-semibold' : 'border-slate-700 bg-slate-900 text-slate-300 hover:bg-slate-800'}`}>${item.label}</button> `)}</div>
3721
+ </div>
3570
3722
 
3571
- ${supportInfo?.url ? html` <a href=${supportInfo.url} target="_blank" rel="noreferrer noopener" className="w-full h-9 inline-flex items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 text-xs text-emerald-200 hover:bg-emerald-500/20"> 💬 Suporte no WhatsApp </a> ` : null}
3723
+ <button type="button" onClick=${openTrendingCatalog} className="inline-flex h-9 w-full items-center justify-center gap-1.5 rounded-xl border border-cyan-400/35 bg-cyan-500/12 text-xs font-semibold text-cyan-100 hover:bg-cyan-500/22 transition">🔥 Em alta agora</button>
3724
+ ${supportInfo?.url ? html` <a href=${supportInfo.url} target="_blank" rel="noreferrer noopener" className="inline-flex h-9 w-full items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 text-xs text-emerald-200 hover:bg-emerald-500/20 transition"> 💬 Suporte no WhatsApp </a> ` : null}
3572
3725
  </div>
3573
3726
  </aside>
3574
3727
 
3575
3728
  <div className="space-y-3 min-w-0">
3576
- <section className="space-y-2 min-w-0">
3729
+ <section className="space-y-2 min-w-0 rounded-2xl border border-slate-800/80 bg-slate-900/65 px-2.5 py-2 lg:hidden">
3577
3730
  <div className="relative min-w-0">
3578
3731
  <div className="absolute left-0 top-0 bottom-0 w-5 bg-gradient-to-r from-slate-950 to-transparent pointer-events-none z-10"></div>
3579
3732
  <div className="absolute right-0 top-0 bottom-0 w-5 bg-gradient-to-l from-slate-950 to-transparent pointer-events-none z-10"></div>
@@ -3584,7 +3737,7 @@ function StickersApp() {
3584
3737
  ${packs.length
3585
3738
  ? html`
3586
3739
  <section className="space-y-2">
3587
- <div className="rounded-2xl border border-slate-800 bg-slate-900/70 p-2.5">
3740
+ <div className="market-glass rounded-2xl p-3">
3588
3741
  <div className="flex flex-wrap items-center justify-between gap-2">
3589
3742
  <div>
3590
3743
  <p className="text-[11px] uppercase tracking-wide text-slate-400">Descobrir</p>
@@ -3712,7 +3865,7 @@ function StickersApp() {
3712
3865
  </div>
3713
3866
  </div>
3714
3867
 
3715
- <div className="hidden lg:block rounded-2xl border border-emerald-500/20 bg-gradient-to-r from-emerald-500/10 to-cyan-500/5 p-2.5">
3868
+ <div className="hidden lg:block rounded-2xl border border-emerald-500/20 bg-gradient-to-r from-emerald-500/10 to-cyan-500/5 p-3">
3716
3869
  <div className="flex items-center justify-between gap-3">
3717
3870
  <div>
3718
3871
  <p className="text-xs font-semibold text-emerald-100">Quer aparecer em destaque?</p>
@@ -3726,28 +3879,28 @@ function StickersApp() {
3726
3879
  : null}
3727
3880
  ${packs.length
3728
3881
  ? html`
3729
- <section id="catalog-packs-section" className="space-y-3 min-w-0">
3730
- <div className="flex items-end justify-between gap-3">
3882
+ <section id="catalog-packs-section" className="space-y-3.5 min-w-0">
3883
+ <div className="flex flex-wrap items-end justify-between gap-3">
3731
3884
  <div>
3732
- <h2 className="text-lg sm:text-xl font-bold">Packs</h2>
3885
+ <h2 className="text-lg sm:text-xl font-bold">Catálogo de Packs</h2>
3733
3886
  <p className="text-xs text-slate-400">Página ${catalogPage} · ${sortedPacks.length} resultados · ${categoryActiveLabel}</p>
3734
3887
  </div>
3735
3888
  <div className="hidden md:flex items-center gap-2">
3736
3889
  <span className="text-xs text-slate-400">Ordenar por</span>
3737
- <button type="button" onClick=${openSortPicker} disabled=${sortPickerBusy} className="inline-flex h-8 items-center gap-2 rounded-xl border border-slate-700 bg-slate-900 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:opacity-60">
3890
+ <button type="button" onClick=${openSortPicker} disabled=${sortPickerBusy} className="inline-flex h-8 items-center gap-2 rounded-xl border border-slate-700 bg-slate-900 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:opacity-60 transition">
3738
3891
  <span>${catalogSortLabel(sortBy)}</span>
3739
3892
  <span className="text-[10px] text-slate-400">▾</span>
3740
3893
  </button>
3741
3894
  </div>
3742
3895
  </div>
3743
- <div className="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-slate-800 bg-slate-900/60 px-3 py-2">
3896
+ <div className="market-glass flex flex-wrap items-center justify-between gap-2 rounded-xl px-3 py-2.5">
3744
3897
  <span className="text-xs text-slate-400">Página ${catalogPage}${packHasMore ? ' · há mais resultados' : ' · fim da lista'}</span>
3745
3898
  <div className="flex items-center gap-2">
3746
- <button type="button" onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })} disabled=${!canGoCatalogPrev} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50">Anterior</button>
3747
- <button type="button" onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })} disabled=${!canGoCatalogNext} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50">Próxima</button>
3899
+ <button type="button" onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })} disabled=${!canGoCatalogPrev} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50 transition">Anterior</button>
3900
+ <button type="button" onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })} disabled=${!canGoCatalogNext} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50 transition">Próxima</button>
3748
3901
  </div>
3749
3902
  </div>
3750
- <div className="grid min-w-0 grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 2xl:grid-cols-6 gap-2.5 sm:gap-3">
3903
+ <div className="market-catalog-grid min-w-0">
3751
3904
  ${sortedPacks.map(
3752
3905
  (pack, index) =>
3753
3906
  html`<div key=${pack.pack_key || pack.id} className="fade-card">
@@ -3755,11 +3908,11 @@ function StickersApp() {
3755
3908
  </div>`,
3756
3909
  )}
3757
3910
  </div>
3758
- <div className="flex flex-wrap items-center justify-between gap-2 rounded-xl border border-slate-800 bg-slate-900/60 px-3 py-2">
3911
+ <div className="market-glass flex flex-wrap items-center justify-between gap-2 rounded-xl px-3 py-2.5">
3759
3912
  <span className="text-xs text-slate-400">Página ${catalogPage}${packHasMore ? ' · há mais resultados' : ' · fim da lista'}</span>
3760
3913
  <div className="flex items-center gap-2">
3761
- <button type="button" onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })} disabled=${!canGoCatalogPrev} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50">Anterior</button>
3762
- <button type="button" onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })} disabled=${!canGoCatalogNext} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50">Próxima</button>
3914
+ <button type="button" onClick=${() => goToCatalogPage(catalogPage - 1, { push: true })} disabled=${!canGoCatalogPrev} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50 transition">Anterior</button>
3915
+ <button type="button" onClick=${() => goToCatalogPage(catalogPage + 1, { push: true })} disabled=${!canGoCatalogNext} className="inline-flex h-8 items-center rounded-lg border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:cursor-not-allowed disabled:opacity-50 transition">Próxima</button>
3763
3916
  </div>
3764
3917
  </div>
3765
3918
  </section>