@kaikybrofc/omnizap-system 2.2.10 → 2.3.1

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 (123) hide show
  1. package/README.md +13 -13
  2. package/app/config/adminIdentity.js +1 -3
  3. package/app/connection/socketController.js +10 -20
  4. package/app/controllers/messageController.js +7 -28
  5. package/app/modules/aiModule/catCommand.js +29 -192
  6. package/app/modules/broadcastModule/noticeCommand.js +28 -97
  7. package/app/modules/gameModule/diceCommand.js +6 -32
  8. package/app/modules/playModule/playCommand.js +57 -258
  9. package/app/modules/quoteModule/quoteCommand.js +2 -4
  10. package/app/modules/rpgPokemonModule/rpgPokemonRepository.js +1 -13
  11. package/app/modules/statsModule/noMessageCommand.js +16 -84
  12. package/app/modules/statsModule/rankingCommand.js +5 -25
  13. package/app/modules/statsModule/rankingCommon.js +1 -9
  14. package/app/modules/stickerModule/convertToWebp.js +4 -27
  15. package/app/modules/stickerModule/stickerCommand.js +13 -24
  16. package/app/modules/stickerModule/stickerTextCommand.js +13 -25
  17. package/app/modules/stickerPackModule/autoPackCollectorService.js +16 -7
  18. package/app/modules/stickerPackModule/domainEventOutboxRepository.js +20 -36
  19. package/app/modules/stickerPackModule/domainEvents.js +2 -11
  20. package/app/modules/stickerPackModule/semanticReclassificationEngine.js +13 -50
  21. package/app/modules/stickerPackModule/semanticReclassificationEngine.test.js +2 -15
  22. package/app/modules/stickerPackModule/semanticThemeClusterService.js +14 -41
  23. package/app/modules/stickerPackModule/stickerAssetClassificationRepository.js +25 -95
  24. package/app/modules/stickerPackModule/stickerAssetRepository.js +12 -31
  25. package/app/modules/stickerPackModule/stickerAssetReprocessQueueRepository.js +13 -18
  26. package/app/modules/stickerPackModule/stickerAutoPackByTagsRuntime.js +284 -709
  27. package/app/modules/stickerPackModule/stickerClassificationBackgroundRuntime.js +27 -106
  28. package/app/modules/stickerPackModule/stickerClassificationService.js +46 -77
  29. package/app/modules/stickerPackModule/stickerDedicatedTaskWorkerRuntime.js +13 -53
  30. package/app/modules/stickerPackModule/stickerDomainEventBus.js +10 -16
  31. package/app/modules/stickerPackModule/stickerDomainEventConsumerRuntime.js +40 -39
  32. package/app/modules/stickerPackModule/stickerMarketplaceDriftService.js +1 -4
  33. package/app/modules/stickerPackModule/stickerObjectStorageService.js +26 -26
  34. package/app/modules/stickerPackModule/stickerPackCommandHandlers.js +32 -187
  35. package/app/modules/stickerPackModule/stickerPackInteractionEventRepository.js +6 -15
  36. package/app/modules/stickerPackModule/stickerPackItemRepository.js +6 -32
  37. package/app/modules/stickerPackModule/stickerPackMarketplaceService.js +12 -36
  38. package/app/modules/stickerPackModule/stickerPackMessageService.js +12 -40
  39. package/app/modules/stickerPackModule/stickerPackRepository.js +23 -66
  40. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRepository.js +9 -21
  41. package/app/modules/stickerPackModule/stickerPackScoreSnapshotRuntime.js +10 -40
  42. package/app/modules/stickerPackModule/stickerPackService.js +50 -115
  43. package/app/modules/stickerPackModule/stickerPackServiceRuntime.js +2 -21
  44. package/app/modules/stickerPackModule/stickerPackUtils.js +13 -3
  45. package/app/modules/stickerPackModule/stickerStorageService.js +16 -65
  46. package/app/modules/stickerPackModule/stickerWorkerPipelineRuntime.js +4 -22
  47. package/app/modules/stickerPackModule/stickerWorkerTaskQueueRepository.js +14 -29
  48. package/app/modules/systemMetricsModule/pingCommand.js +9 -39
  49. package/app/modules/tiktokModule/tiktokCommand.js +17 -109
  50. package/app/modules/userModule/userCommand.js +2 -88
  51. package/app/observability/metrics.js +5 -16
  52. package/app/services/captchaService.js +1 -6
  53. package/app/services/dbWriteQueue.js +3 -18
  54. package/app/services/featureFlagService.js +2 -8
  55. package/app/services/newsBroadcastService.js +0 -1
  56. package/app/services/queueUtils.js +2 -4
  57. package/app/services/whatsappLoginLinkService.js +7 -9
  58. package/app/store/premiumUserStore.js +1 -2
  59. package/app/utils/antiLink/antiLinkModule.js +3 -233
  60. package/app/utils/logger/loggerModule.js +9 -34
  61. package/app/utils/systemMetrics/systemMetricsModule.js +1 -4
  62. package/database/index.js +1 -0
  63. package/database/init.js +1 -8
  64. package/database/migrations/20260228_0027_web_visit_event.sql +15 -0
  65. package/docker-compose.yml +27 -27
  66. package/docs/seo/omnizap-seo-playbook-br-2026-02-28.md +26 -0
  67. package/docs/seo/satellite-page-template.md +2 -0
  68. package/docs/seo/satellite-pages-phase1.json +40 -177
  69. package/eslint.config.js +2 -15
  70. package/index.js +8 -36
  71. package/ml/clip_classifier/README.md +4 -6
  72. package/observability/alert-rules.yml +12 -12
  73. package/observability/grafana/provisioning/dashboards/dashboards.yml +1 -1
  74. package/package.json +6 -3
  75. package/public/api-docs/index.html +220 -193
  76. package/public/bot-whatsapp-para-grupo/index.html +291 -261
  77. package/public/bot-whatsapp-sem-programar/index.html +291 -261
  78. package/public/comandos/index.html +421 -406
  79. package/public/como-automatizar-avisos-no-whatsapp/index.html +291 -261
  80. package/public/como-criar-comandos-whatsapp/index.html +291 -261
  81. package/public/como-evitar-spam-no-whatsapp/index.html +291 -261
  82. package/public/como-moderar-grupo-whatsapp/index.html +291 -261
  83. package/public/como-organizar-comunidade-whatsapp/index.html +291 -261
  84. package/public/css/github-project-panel.css +13 -8
  85. package/public/css/stickers-admin.css +25 -9
  86. package/public/css/styles.css +23 -16
  87. package/public/index.html +1106 -993
  88. package/public/js/apps/apiDocsApp.js +17 -167
  89. package/public/js/apps/createPackApp.js +69 -332
  90. package/public/js/apps/homeApp.js +274 -101
  91. package/public/js/apps/loginApp.js +3 -12
  92. package/public/js/apps/stickersAdminApp.js +190 -181
  93. package/public/js/apps/stickersApp.js +482 -1411
  94. package/public/js/apps/userApp.js +217 -1
  95. package/public/js/catalog.js +11 -74
  96. package/public/js/github-panel/components/ErrorState.js +1 -8
  97. package/public/js/github-panel/components/GithubProjectPanel.js +2 -9
  98. package/public/js/github-panel/components/SkeletonPanel.js +1 -11
  99. package/public/js/github-panel/components/StatCard.js +1 -7
  100. package/public/js/github-panel/vendor/react.js +1 -9
  101. package/public/js/runtime/react-runtime.js +1 -9
  102. package/public/licenca/index.html +200 -86
  103. package/public/login/index.html +315 -325
  104. package/public/melhor-bot-whatsapp-para-grupos/index.html +291 -261
  105. package/public/stickers/admin/index.html +14 -19
  106. package/public/stickers/create/index.html +39 -44
  107. package/public/stickers/index.html +96 -107
  108. package/public/termos-de-uso/index.html +369 -122
  109. package/public/user/index.html +527 -350
  110. package/scripts/cache-bust.mjs +5 -24
  111. package/scripts/generate-seo-satellite-pages.mjs +10 -13
  112. package/scripts/run-prettier-all.mjs +25 -0
  113. package/scripts/sticker-catalog-loadtest.mjs +13 -11
  114. package/scripts/sticker-worker-task.mjs +1 -4
  115. package/scripts/sync-readme-snapshot.mjs +3 -2
  116. package/server/auth/googleWebAuth/googleWebAuthService.js +614 -0
  117. package/server/controllers/stickerCatalogController.js +297 -632
  118. package/server/http/httpServer.js +2 -10
  119. package/server/routes/stickerCatalog/catalogHandlers/catalogAdminHttp.js +1 -8
  120. package/server/routes/stickerCatalog/catalogHandlers/catalogAuthHttp.js +1 -9
  121. package/server/routes/stickerCatalog/catalogHandlers/catalogPublicHttp.js +10 -11
  122. package/server/routes/stickerCatalog/catalogHandlers/catalogUploadHttp.js +1 -10
  123. package/server/routes/stickerCatalog/catalogRouter.js +11 -13
@@ -50,8 +50,7 @@ const normalizeCreatorsSort = (value, fallback = DEFAULT_CREATORS_SORT) => {
50
50
  return fallback;
51
51
  };
52
52
 
53
- const catalogSortLabel = (sort) =>
54
- CATALOG_SORT_OPTIONS.find((entry) => entry.value === normalizeCatalogSort(sort))?.label || 'Ordenar';
53
+ const catalogSortLabel = (sort) => CATALOG_SORT_OPTIONS.find((entry) => entry.value === normalizeCatalogSort(sort))?.label || 'Ordenar';
55
54
 
56
55
  const getPackTrendScore = (pack) => safeNumber(pack?.signals?.trend_score);
57
56
  const getPackRankingScore = (pack) => safeNumber(pack?.signals?.ranking_score);
@@ -277,12 +276,13 @@ const getPackEngagement = (pack) => {
277
276
  };
278
277
  };
279
278
 
280
- const getAvatarUrl = (name) =>
281
- `https://api.dicebear.com/8.x/thumbs/svg?seed=${encodeURIComponent(String(name || 'omnizap'))}`;
279
+ const getAvatarUrl = (name) => `https://api.dicebear.com/8.x/thumbs/svg?seed=${encodeURIComponent(String(name || 'omnizap'))}`;
282
280
 
283
281
  const parseCatalogSearchState = (search = '') => {
284
282
  const params = new URLSearchParams(String(search || ''));
285
- const filter = String(params.get('filter') || '').trim().toLowerCase();
283
+ const filter = String(params.get('filter') || '')
284
+ .trim()
285
+ .toLowerCase();
286
286
  const hasTrendingFilter = filter === 'trending';
287
287
  const q = hasTrendingFilter ? '' : String(params.get('q') || '').trim();
288
288
  const category = hasTrendingFilter
@@ -321,10 +321,22 @@ const parseStickersLocation = (webPath = '/stickers') => {
321
321
 
322
322
  try {
323
323
  const firstSegment = decodeURIComponent(firstSegmentRaw);
324
- if (PROFILE_ROUTE_SEGMENTS.has(String(firstSegment || '').trim().toLowerCase())) {
324
+ if (
325
+ PROFILE_ROUTE_SEGMENTS.has(
326
+ String(firstSegment || '')
327
+ .trim()
328
+ .toLowerCase(),
329
+ )
330
+ ) {
325
331
  return { view: 'profile', packKey: '' };
326
332
  }
327
- if (CREATORS_ROUTE_SEGMENTS.has(String(firstSegment || '').trim().toLowerCase())) {
333
+ if (
334
+ CREATORS_ROUTE_SEGMENTS.has(
335
+ String(firstSegment || '')
336
+ .trim()
337
+ .toLowerCase(),
338
+ )
339
+ ) {
328
340
  return { view: 'creators', packKey: '' };
329
341
  }
330
342
  return { view: 'pack', packKey: firstSegment };
@@ -455,17 +467,13 @@ function UploadTaskWidget({ task, onClose }) {
455
467
  </div>
456
468
  <div className="mt-2 flex items-center justify-between text-[11px]">
457
469
  <span className="text-slate-400">${task.current || 0}/${task.total || 0}</span>
458
- <span className=${`${isError ? 'text-rose-300' : isDone ? 'text-emerald-300' : isPaused ? 'text-amber-300' : 'text-cyan-300'} font-semibold`}>
459
- ${progress}%
460
- </span>
470
+ <span className=${`${isError ? 'text-rose-300' : isDone ? 'text-emerald-300' : isPaused ? 'text-amber-300' : 'text-cyan-300'} font-semibold`}> ${progress}% </span>
461
471
  </div>
462
472
  ${(isDone || isPaused) && packUrl
463
473
  ? html`
464
474
  <div className="mt-2 flex gap-2">
465
475
  <a href=${packUrl} className="inline-flex rounded-lg border border-cyan-500/40 bg-cyan-500/10 px-2.5 py-1.5 text-[11px] font-semibold text-cyan-200">Abrir pack</a>
466
- ${isPaused
467
- ? html`<a href="/stickers/create/" className="inline-flex rounded-lg border border-amber-500/40 bg-amber-500/10 px-2.5 py-1.5 text-[11px] font-semibold text-amber-200">Retomar envio</a>`
468
- : null}
476
+ ${isPaused ? html`<a href="/stickers/create/" className="inline-flex rounded-lg border border-amber-500/40 bg-amber-500/10 px-2.5 py-1.5 text-[11px] font-semibold text-amber-200">Retomar envio</a>` : null}
469
477
  </div>
470
478
  `
471
479
  : null}
@@ -488,32 +496,11 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
488
496
  };
489
497
 
490
498
  return html`
491
- <button
492
- type="button"
493
- onClick=${handleOpen}
494
- 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"
495
- >
499
+ <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">
496
500
  <div className="relative aspect-[5/6] sm:aspect-[4/5] bg-slate-900 overflow-hidden">
497
- <img
498
- src=${coverUrl}
499
- alt=${`Capa de ${pack.name}`}
500
- className=${`w-full h-full object-cover transition-transform duration-300 ${
501
- lockedByNsfw ? 'blur-md scale-105' : 'md:group-hover:scale-[1.05] group-active:scale-[1.02]'
502
- }`}
503
- loading="lazy"
504
- />
501
+ <img 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]'}`} loading="lazy" />
505
502
  <div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/60 to-transparent"></div>
506
- <div className="absolute top-2 left-2 flex items-center gap-1">
507
- ${lockedByNsfw
508
- ? 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>`
509
- : null}
510
- ${isTrending
511
- ? 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>`
512
- : null}
513
- ${isNew
514
- ? 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>`
515
- : null}
516
- </div>
503
+ <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>
517
504
 
518
505
  <div className="absolute inset-x-0 bottom-0 p-2">
519
506
  <h3 className="font-semibold text-sm leading-5 line-clamp-2">${pack.name || 'Pack sem nome'}</h3>
@@ -529,40 +516,25 @@ function PackCard({ pack, index, onOpen, hasNsfwAccess = true, onRequireLogin })
529
516
  </div>
530
517
 
531
518
  <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">
532
- <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">
533
- ${lockedByNsfw ? 'Entrar para desbloquear' : 'Abrir pack'}
534
- </span>
519
+ <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>
535
520
  </div>
536
521
  ${lockedByNsfw
537
522
  ? html`
538
523
  <div className="pointer-events-none absolute inset-x-0 top-[42%] flex justify-center px-3">
539
- <span className="inline-flex rounded-xl border border-amber-400/40 bg-slate-950/70 px-2.5 py-1 text-[10px] font-semibold text-amber-100 backdrop-blur">
540
- Conteúdo sensível
541
- </span>
524
+ <span className="inline-flex rounded-xl border border-amber-400/40 bg-slate-950/70 px-2.5 py-1 text-[10px] font-semibold text-amber-100 backdrop-blur"> Conteúdo sensível </span>
542
525
  </div>
543
526
  `
544
527
  : null}
545
528
  </div>
546
529
 
547
530
  <div className="px-2 pb-2 pt-1 bg-slate-900/95 md:hidden">
548
- <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">
549
- ${lockedByNsfw ? 'Entrar para desbloquear' : 'Abrir pack'}
550
- </span>
531
+ <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>
551
532
  </div>
552
533
  </button>
553
534
  `;
554
535
  }
555
536
 
556
- function CatalogMetricCard({
557
- label,
558
- value = '',
559
- valueRaw = null,
560
- numberFormat = 'compact',
561
- icon = '📊',
562
- hint = '',
563
- bars = [],
564
- tone = 'slate',
565
- }) {
537
+ function CatalogMetricCard({ label, value = '', valueRaw = null, numberFormat = 'compact', icon = '📊', hint = '', bars = [], tone = 'slate' }) {
566
538
  const toneMap = {
567
539
  slate: 'border-slate-800 bg-slate-900/60',
568
540
  emerald: 'border-emerald-500/20 bg-emerald-500/5',
@@ -606,29 +578,13 @@ function CatalogMetricCard({
606
578
  };
607
579
  }, [numericTarget]);
608
580
 
609
- const resolvedValue =
610
- numericTarget === null
611
- ? String(value || '0')
612
- : numberFormat === 'full'
613
- ? formatFullNum(animatedValue)
614
- : shortNum(animatedValue);
581
+ const resolvedValue = numericTarget === null ? String(value || '0') : numberFormat === 'full' ? formatFullNum(animatedValue) : shortNum(animatedValue);
615
582
 
616
583
  return html`
617
- <article
618
- className=${`rounded-xl border p-2.5 ${toneMap[tone] || toneMap.slate}`}
619
- title=${hint || label}
620
- >
584
+ <article className=${`rounded-xl border p-2.5 ${toneMap[tone] || toneMap.slate}`} title=${hint || label}>
621
585
  <div className="flex items-center justify-between gap-2">
622
586
  <span className="text-sm">${icon}</span>
623
- <div className="flex items-end gap-0.5">
624
- ${(Array.isArray(bars) ? bars : []).slice(0, 7).map((bar, index) => html`
625
- <span
626
- key=${index}
627
- className="w-1 rounded-full bg-white/15"
628
- style=${{ height: `${Math.max(4, Math.min(16, Number(bar || 0)))}px` }}
629
- ></span>
630
- `)}
631
- </div>
587
+ <div className="flex items-end gap-0.5">${(Array.isArray(bars) ? bars : []).slice(0, 7).map((bar, index) => html` <span key=${index} className="w-1 rounded-full bg-white/15" style=${{ height: `${Math.max(4, Math.min(16, Number(bar || 0)))}px` }}></span> `)}</div>
632
588
  </div>
633
589
  <p className="mt-1 text-base font-bold text-slate-100">${resolvedValue}</p>
634
590
  <p className="text-[11px] text-slate-400">${label}</p>
@@ -648,22 +604,11 @@ function DiscoverPackRowItem({ pack, onOpen, rank = 0, hasNsfwAccess = true, onR
648
604
  onOpen(pack.pack_key);
649
605
  };
650
606
  return html`
651
- <button
652
- type="button"
653
- onClick=${handleOpen}
654
- className="w-full flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 px-2 py-1.5 text-left hover:bg-slate-800/90"
655
- >
656
- <img
657
- src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack.cover_url || DEFAULT_STICKER_PLACEHOLDER_URL}
658
- alt=""
659
- className=${`h-9 w-9 rounded-lg object-cover bg-slate-800 ${lockedByNsfw ? 'blur-sm' : ''}`}
660
- loading="lazy"
661
- />
607
+ <button type="button" onClick=${handleOpen} className="w-full flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 px-2 py-1.5 text-left hover:bg-slate-800/90">
608
+ <img src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack.cover_url || DEFAULT_STICKER_PLACEHOLDER_URL} alt="" className=${`h-9 w-9 rounded-lg object-cover bg-slate-800 ${lockedByNsfw ? 'blur-sm' : ''}`} loading="lazy" />
662
609
  <span className="min-w-0 flex-1">
663
610
  <span className="block truncate text-xs font-medium text-slate-100">${rank > 0 ? `${rank}. ` : ''}${pack.name || 'Pack'}</span>
664
- <span className="block truncate text-[10px] text-slate-400">
665
- ${lockedByNsfw ? '🔒 Entrar para desbloquear' : `${pack.publisher || '-'} · ❤️ ${shortNum(getPackEngagement(pack).likeCount)}`}
666
- </span>
611
+ <span className="block truncate text-[10px] text-slate-400"> ${lockedByNsfw ? '🔒 Entrar para desbloquear' : `${pack.publisher || '-'} · ❤️ ${shortNum(getPackEngagement(pack).likeCount)}`} </span>
667
612
  </span>
668
613
  <span className="text-[10px] text-slate-500">→</span>
669
614
  </button>
@@ -682,28 +627,15 @@ function DiscoverPackMiniCard({ pack, onOpen, hasNsfwAccess = true, onRequireLog
682
627
  onOpen(pack.pack_key);
683
628
  };
684
629
  return html`
685
- <button
686
- type="button"
687
- onClick=${handleOpen}
688
- className="group w-[170px] shrink-0 overflow-hidden rounded-xl border border-slate-800 bg-slate-900/80 text-left"
689
- >
630
+ <button type="button" onClick=${handleOpen} className="group w-[170px] shrink-0 overflow-hidden rounded-xl border border-slate-800 bg-slate-900/80 text-left">
690
631
  <div className="relative h-24 bg-slate-900">
691
- <img
692
- src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack.cover_url || DEFAULT_STICKER_PLACEHOLDER_URL}
693
- alt=""
694
- className=${`h-full w-full object-cover transition-transform duration-200 ${
695
- lockedByNsfw ? 'blur-sm scale-105' : 'group-active:scale-[1.02]'
696
- }`}
697
- loading="lazy"
698
- />
632
+ <img src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack.cover_url || DEFAULT_STICKER_PLACEHOLDER_URL} alt="" className=${`h-full w-full object-cover transition-transform duration-200 ${lockedByNsfw ? 'blur-sm scale-105' : 'group-active:scale-[1.02]'}`} loading="lazy" />
699
633
  <div className="absolute inset-0 bg-gradient-to-t from-slate-950/90 to-transparent"></div>
700
634
  ${lockedByNsfw ? html`<span className="absolute top-1.5 left-1.5 rounded-full border border-amber-300/35 bg-amber-500/25 px-1.5 py-0.5 text-[9px] font-semibold text-amber-100">🔞 Login</span>` : null}
701
635
  </div>
702
636
  <div className="p-2">
703
637
  <p className="truncate text-xs font-semibold text-slate-100">${pack.name || 'Pack'}</p>
704
- <p className="mt-1 truncate text-[10px] text-slate-400">
705
- ${lockedByNsfw ? 'Entrar para desbloquear' : `⬇ ${shortNum(engagement.openCount)} · ❤️ ${shortNum(engagement.likeCount)}`}
706
- </p>
638
+ <p className="mt-1 truncate text-[10px] text-slate-400">${lockedByNsfw ? 'Entrar para desbloquear' : `⬇ ${shortNum(engagement.openCount)} · ❤️ ${shortNum(engagement.likeCount)}`}</p>
707
639
  </div>
708
640
  </button>
709
641
  `;
@@ -712,11 +644,7 @@ function DiscoverPackMiniCard({ pack, onOpen, hasNsfwAccess = true, onRequireLog
712
644
  function DiscoverCreatorMiniCard({ creator, onPick }) {
713
645
  if (!creator?.publisher) return null;
714
646
  return html`
715
- <button
716
- type="button"
717
- onClick=${() => onPick(creator.publisher)}
718
- className="w-[190px] shrink-0 rounded-xl border border-slate-800 bg-slate-900/70 p-2 text-left hover:bg-slate-800/90"
719
- >
647
+ <button type="button" onClick=${() => onPick(creator.publisher)} className="w-[190px] shrink-0 rounded-xl border border-slate-800 bg-slate-900/70 p-2 text-left hover:bg-slate-800/90">
720
648
  <div className="flex items-center gap-2">
721
649
  <img src=${getAvatarUrl(creator.publisher)} alt="" className="h-9 w-9 rounded-full bg-slate-800" />
722
650
  <span className="min-w-0">
@@ -780,70 +708,22 @@ function MyPackCard({ pack, onOpenPublic }) {
780
708
  </div>
781
709
  </div>
782
710
  <div className="mt-3 flex flex-wrap gap-2">
783
- ${shareable
784
- ? html`
785
- <button
786
- type="button"
787
- onClick=${() => onOpenPublic(pack.pack_key)}
788
- className="inline-flex h-9 items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-200 hover:bg-emerald-500/20"
789
- >
790
- Abrir no catálogo
791
- </button>
792
- `
793
- : html`
794
- <span className="inline-flex h-9 items-center justify-center rounded-xl border border-slate-700 bg-slate-900/70 px-3 text-xs text-slate-400">
795
- Não visível no catálogo
796
- </span>
797
- `}
798
- <a
799
- href="/stickers/create/"
800
- className="inline-flex h-9 items-center justify-center rounded-xl border border-cyan-500/35 bg-cyan-500/10 px-3 text-xs font-semibold text-cyan-200 hover:bg-cyan-500/20"
801
- >
802
- Criar/gerenciar packs
803
- </a>
711
+ ${shareable ? html` <button type="button" onClick=${() => onOpenPublic(pack.pack_key)} className="inline-flex h-9 items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-200 hover:bg-emerald-500/20">Abrir no catálogo</button> ` : html` <span className="inline-flex h-9 items-center justify-center rounded-xl border border-slate-700 bg-slate-900/70 px-3 text-xs text-slate-400"> Não visível no catálogo </span> `}
712
+ <a href="/stickers/create/" className="inline-flex h-9 items-center justify-center rounded-xl border border-cyan-500/35 bg-cyan-500/10 px-3 text-xs font-semibold text-cyan-200 hover:bg-cyan-500/20"> Criar/gerenciar packs </a>
804
713
  </div>
805
714
  </article>
806
715
  `;
807
716
  }
808
717
 
809
- function ProfilePage({
810
- googleAuthConfig,
811
- googleAuth,
812
- googleAuthBusy,
813
- googleAuthError,
814
- googleSessionChecked,
815
- googleAuthUiReady,
816
- googleButtonRef,
817
- myPacks,
818
- myPacksLoading,
819
- myPacksError,
820
- myProfileStats,
821
- onBack,
822
- onRefresh,
823
- onLogout,
824
- onOpenPublicPack,
825
- }) {
718
+ function ProfilePage({ googleAuthConfig, googleAuth, googleAuthBusy, googleAuthError, googleSessionChecked, googleAuthUiReady, googleButtonRef, myPacks, myPacksLoading, myPacksError, myProfileStats, onBack, onRefresh, onLogout, onOpenPublicPack }) {
826
719
  const hasGoogleLogin = Boolean(googleAuth?.user?.sub);
827
720
  const googleLoginEnabled = Boolean(googleAuthConfig?.enabled && googleAuthConfig?.clientId);
828
721
 
829
722
  return html`
830
723
  <section className="space-y-4 pb-20 sm:pb-4">
831
724
  <div className="flex flex-wrap items-center justify-between gap-2">
832
- <button
833
- type="button"
834
- onClick=${onBack}
835
- className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800"
836
- >
837
- ← Voltar para catálogo
838
- </button>
839
- <button
840
- type="button"
841
- onClick=${onRefresh}
842
- disabled=${myPacksLoading || googleAuthBusy}
843
- className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60"
844
- >
845
- ${myPacksLoading ? 'Atualizando...' : 'Atualizar'}
846
- </button>
725
+ <button type="button" onClick=${onBack} className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">← Voltar para catálogo</button>
726
+ <button type="button" onClick=${onRefresh} disabled=${myPacksLoading || googleAuthBusy} className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60">${myPacksLoading ? 'Atualizando...' : 'Atualizar'}</button>
847
727
  </div>
848
728
 
849
729
  <section className="rounded-2xl border border-slate-800 bg-slate-900/80 p-4">
@@ -851,39 +731,20 @@ function ProfilePage({
851
731
  <div>
852
732
  <p className="text-xs uppercase tracking-wide text-slate-400">Perfil de criador</p>
853
733
  <h1 className="mt-1 text-xl font-bold">Meus packs</h1>
854
- <p className="mt-1 text-sm text-slate-400">
855
- Faça login com Google para ver e vincular os packs criados pela sua conta.
856
- </p>
734
+ <p className="mt-1 text-sm text-slate-400">Faça login com Google para ver e vincular os packs criados pela sua conta.</p>
857
735
  </div>
858
- ${hasGoogleLogin
859
- ? html`
860
- <button
861
- type="button"
862
- onClick=${onLogout}
863
- disabled=${googleAuthBusy}
864
- className="inline-flex h-10 items-center rounded-xl border border-slate-700 px-3 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60"
865
- >
866
- ${googleAuthBusy ? 'Saindo...' : 'Sair'}
867
- </button>
868
- `
869
- : null}
736
+ ${hasGoogleLogin ? html` <button type="button" onClick=${onLogout} disabled=${googleAuthBusy} className="inline-flex h-10 items-center rounded-xl border border-slate-700 px-3 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60">${googleAuthBusy ? 'Saindo...' : 'Sair'}</button> ` : null}
870
737
  </div>
871
738
 
872
739
  <div className="mt-4 rounded-2xl border border-slate-800 bg-slate-950/50 p-3">
873
740
  ${hasGoogleLogin
874
741
  ? html`
875
742
  <div className="flex flex-wrap items-center gap-3">
876
- <img
877
- src=${googleAuth.user?.picture || getAvatarUrl(googleAuth.user?.name)}
878
- alt="Avatar do Google"
879
- className="h-12 w-12 rounded-full border border-slate-700 bg-slate-900 object-cover"
880
- />
743
+ <img src=${googleAuth.user?.picture || getAvatarUrl(googleAuth.user?.name)} alt="Avatar do Google" className="h-12 w-12 rounded-full border border-slate-700 bg-slate-900 object-cover" />
881
744
  <div className="min-w-0">
882
745
  <p className="truncate text-sm font-semibold text-slate-100">${googleAuth.user?.name || 'Conta Google'}</p>
883
746
  <p className="truncate text-xs text-slate-400">${googleAuth.user?.email || ''}</p>
884
- <p className="truncate text-[11px] text-slate-500">
885
- ${googleAuth.expiresAt ? `Sessão válida até ${new Date(googleAuth.expiresAt).toLocaleString('pt-BR')}` : 'Sessão ativa'}
886
- </p>
747
+ <p className="truncate text-[11px] text-slate-500">${googleAuth.expiresAt ? `Sessão válida até ${new Date(googleAuth.expiresAt).toLocaleString('pt-BR')}` : 'Sessão ativa'}</p>
887
748
  </div>
888
749
  </div>
889
750
  `
@@ -891,22 +752,12 @@ function ProfilePage({
891
752
  <div className="space-y-3">
892
753
  <div className="rounded-xl border border-cyan-500/20 bg-cyan-500/5 p-3">
893
754
  <p className="text-sm font-semibold text-cyan-200">Entrar com Google</p>
894
- <p className="mt-1 text-xs text-slate-300">
895
- ${googleLoginEnabled
896
- ? 'Use a mesma conta Google usada na criação de packs para carregar seus dados e packs automaticamente.'
897
- : 'Login Google indisponível no momento.'}
898
- </p>
755
+ <p className="mt-1 text-xs text-slate-300">${googleLoginEnabled ? 'Use a mesma conta Google usada na criação de packs para carregar seus dados e packs automaticamente.' : 'Login Google indisponível no momento.'}</p>
899
756
  </div>
900
757
  ${googleLoginEnabled
901
758
  ? html`
902
759
  <div ref=${googleButtonRef} className="min-h-[42px] w-full max-w-[320px] overflow-hidden"></div>
903
- ${!googleSessionChecked
904
- ? html`<p className="text-xs text-slate-400">Verificando sessão Google...</p>`
905
- : googleAuthBusy
906
- ? html`<p className="text-xs text-slate-400">Conectando conta Google...</p>`
907
- : !googleAuthUiReady && !googleAuthError
908
- ? html`<p className="text-xs text-slate-400">Carregando login Google...</p>`
909
- : null}
760
+ ${!googleSessionChecked ? html`<p className="text-xs text-slate-400">Verificando sessão Google...</p>` : googleAuthBusy ? html`<p className="text-xs text-slate-400">Conectando conta Google...</p>` : !googleAuthUiReady && !googleAuthError ? html`<p className="text-xs text-slate-400">Carregando login Google...</p>` : null}
910
761
  `
911
762
  : null}
912
763
  </div>
@@ -916,15 +767,29 @@ function ProfilePage({
916
767
  </section>
917
768
 
918
769
  ${myPacksError ? html`<div className="rounded-2xl border border-rose-500/40 bg-rose-500/10 px-4 py-3 text-sm text-rose-200">${myPacksError}</div>` : null}
919
-
920
770
  ${hasGoogleLogin
921
771
  ? html`
922
772
  <section className="grid grid-cols-2 lg:grid-cols-5 gap-3">
923
- <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3"><p className="text-[11px] text-slate-400">Total</p><p className="text-lg font-semibold">${shortNum(myProfileStats?.total || 0)}</p></article>
924
- <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3"><p className="text-[11px] text-slate-400">Publicados</p><p className="text-lg font-semibold">${shortNum(myProfileStats?.published || 0)}</p></article>
925
- <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3"><p className="text-[11px] text-slate-400">Rascunhos</p><p className="text-lg font-semibold">${shortNum(myProfileStats?.drafts || 0)}</p></article>
926
- <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3"><p className="text-[11px] text-slate-400">Privados</p><p className="text-lg font-semibold">${shortNum(myProfileStats?.private || 0)}</p></article>
927
- <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3"><p className="text-[11px] text-slate-400">Não listados</p><p className="text-lg font-semibold">${shortNum(myProfileStats?.unlisted || 0)}</p></article>
773
+ <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3">
774
+ <p className="text-[11px] text-slate-400">Total</p>
775
+ <p className="text-lg font-semibold">${shortNum(myProfileStats?.total || 0)}</p>
776
+ </article>
777
+ <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3">
778
+ <p className="text-[11px] text-slate-400">Publicados</p>
779
+ <p className="text-lg font-semibold">${shortNum(myProfileStats?.published || 0)}</p>
780
+ </article>
781
+ <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3">
782
+ <p className="text-[11px] text-slate-400">Rascunhos</p>
783
+ <p className="text-lg font-semibold">${shortNum(myProfileStats?.drafts || 0)}</p>
784
+ </article>
785
+ <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3">
786
+ <p className="text-[11px] text-slate-400">Privados</p>
787
+ <p className="text-lg font-semibold">${shortNum(myProfileStats?.private || 0)}</p>
788
+ </article>
789
+ <article className="rounded-xl border border-slate-800 bg-slate-900/70 p-3">
790
+ <p className="text-[11px] text-slate-400">Não listados</p>
791
+ <p className="text-lg font-semibold">${shortNum(myProfileStats?.unlisted || 0)}</p>
792
+ </article>
928
793
  </section>
929
794
 
930
795
  <section className="space-y-3">
@@ -933,19 +798,9 @@ function ProfilePage({
933
798
  <span className="text-xs text-slate-400">${myPacksLoading ? 'Carregando...' : `${myPacks.length} pack(s)`}</span>
934
799
  </div>
935
800
  ${myPacksLoading
936
- ? html`
937
- <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
938
- ${Array.from({ length: 6 }).map(
939
- (_, index) => html`<div key=${index} className="h-40 rounded-2xl border border-slate-800 bg-slate-900/70 animate-pulse"></div>`,
940
- )}
941
- </div>
942
- `
801
+ ? html` <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">${Array.from({ length: 6 }).map((_, index) => html`<div key=${index} className="h-40 rounded-2xl border border-slate-800 bg-slate-900/70 animate-pulse"></div>`)}</div> `
943
802
  : myPacks.length
944
- ? html`
945
- <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">
946
- ${myPacks.map((pack) => html`<${MyPackCard} key=${pack.id || pack.pack_key} pack=${pack} onOpenPublic=${onOpenPublicPack} />`)}
947
- </div>
948
- `
803
+ ? html` <div className="grid grid-cols-1 md:grid-cols-2 xl:grid-cols-3 gap-3">${myPacks.map((pack) => html`<${MyPackCard} key=${pack.id || pack.pack_key} pack=${pack} onOpenPublic=${onOpenPublicPack} />`)}</div> `
949
804
  : html`
950
805
  <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/60 p-6 text-center">
951
806
  <p className="text-sm font-semibold text-slate-100">Nenhum pack encontrado para esta conta.</p>
@@ -965,25 +820,10 @@ function ToastStack({ toasts = [], onDismiss }) {
965
820
  <div className="fixed right-3 top-16 z-[90] flex w-[min(92vw,380px)] flex-col gap-2">
966
821
  ${toasts.map(
967
822
  (toast) => html`
968
- <div
969
- key=${toast.id}
970
- className=${`rounded-2xl border px-3 py-2.5 shadow-xl backdrop-blur ${
971
- toast.type === 'error'
972
- ? 'border-rose-500/35 bg-rose-500/15 text-rose-100'
973
- : toast.type === 'warning'
974
- ? 'border-amber-500/35 bg-amber-500/15 text-amber-100'
975
- : 'border-emerald-500/35 bg-emerald-500/15 text-emerald-100'
976
- }`}
977
- >
823
+ <div key=${toast.id} className=${`rounded-2xl border px-3 py-2.5 shadow-xl backdrop-blur ${toast.type === 'error' ? 'border-rose-500/35 bg-rose-500/15 text-rose-100' : toast.type === 'warning' ? 'border-amber-500/35 bg-amber-500/15 text-amber-100' : 'border-emerald-500/35 bg-emerald-500/15 text-emerald-100'}`}>
978
824
  <div className="flex items-start justify-between gap-2">
979
825
  <p className="text-sm leading-5">${toast.message || ''}</p>
980
- <button
981
- type="button"
982
- onClick=${() => onDismiss?.(toast.id)}
983
- className="rounded-md border border-white/10 px-1.5 py-0.5 text-[11px] text-white/70 hover:bg-white/10"
984
- >
985
- fechar
986
- </button>
826
+ <button type="button" onClick=${() => onDismiss?.(toast.id)} className="rounded-md border border-white/10 px-1.5 py-0.5 text-[11px] text-white/70 hover:bg-white/10">fechar</button>
987
827
  </div>
988
828
  </div>
989
829
  `,
@@ -992,17 +832,7 @@ function ToastStack({ toasts = [], onDismiss }) {
992
832
  `;
993
833
  }
994
834
 
995
- function ConfirmDialog({
996
- open = false,
997
- title = 'Confirmar',
998
- message = '',
999
- confirmLabel = 'Confirmar',
1000
- cancelLabel = 'Cancelar',
1001
- busy = false,
1002
- danger = false,
1003
- onCancel,
1004
- onConfirm,
1005
- }) {
835
+ function ConfirmDialog({ open = false, title = 'Confirmar', message = '', confirmLabel = 'Confirmar', cancelLabel = 'Cancelar', busy = false, danger = false, onCancel, onConfirm }) {
1006
836
  if (!open) return null;
1007
837
  return html`
1008
838
  <div className="fixed inset-0 z-[88] flex items-end justify-center bg-black/60 p-3 sm:items-center">
@@ -1011,26 +841,8 @@ function ConfirmDialog({
1011
841
  <h3 className="text-base font-bold text-slate-100">${title}</h3>
1012
842
  <p className="mt-2 text-sm text-slate-300">${message}</p>
1013
843
  <div className="mt-4 flex flex-col-reverse gap-2 sm:flex-row sm:justify-end">
1014
- <button
1015
- type="button"
1016
- onClick=${onCancel}
1017
- disabled=${busy}
1018
- className="h-10 rounded-xl border border-slate-700 px-4 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60"
1019
- >
1020
- ${cancelLabel}
1021
- </button>
1022
- <button
1023
- type="button"
1024
- onClick=${onConfirm}
1025
- disabled=${busy}
1026
- className=${`h-10 rounded-xl border px-4 text-sm font-semibold disabled:opacity-60 ${
1027
- danger
1028
- ? 'border-rose-500/35 bg-rose-500/15 text-rose-100 hover:bg-rose-500/20'
1029
- : 'border-emerald-500/35 bg-emerald-500/15 text-emerald-100 hover:bg-emerald-500/20'
1030
- }`}
1031
- >
1032
- ${busy ? 'Processando...' : confirmLabel}
1033
- </button>
844
+ <button type="button" onClick=${onCancel} disabled=${busy} className="h-10 rounded-xl border border-slate-700 px-4 text-sm text-slate-200 hover:bg-slate-800 disabled:opacity-60">${cancelLabel}</button>
845
+ <button type="button" onClick=${onConfirm} disabled=${busy} className=${`h-10 rounded-xl border px-4 text-sm font-semibold disabled:opacity-60 ${danger ? 'border-rose-500/35 bg-rose-500/15 text-rose-100 hover:bg-rose-500/20' : 'border-emerald-500/35 bg-emerald-500/15 text-emerald-100 hover:bg-emerald-500/20'}`}>${busy ? 'Processando...' : confirmLabel}</button>
1034
846
  </div>
1035
847
  </div>
1036
848
  </div>
@@ -1061,25 +873,15 @@ function PackActionsSheet({ pack, open = false, busyAction = '', onClose, onActi
1061
873
  </div>
1062
874
  </div>
1063
875
  <div className="space-y-1">
1064
- ${PROFILE_PACK_ACTIONS.map((action) => html`
1065
- <button
1066
- key=${action.key}
1067
- type="button"
1068
- onClick=${() => onAction?.(action.key, pack)}
1069
- disabled=${Boolean(busyAction)}
1070
- className=${`w-full rounded-xl border px-3 py-3 text-left text-sm transition disabled:opacity-60 ${
1071
- action.danger
1072
- ? 'border-rose-500/25 bg-rose-500/10 text-rose-100 hover:bg-rose-500/15'
1073
- : 'border-slate-700 bg-slate-950/60 text-slate-100 hover:bg-slate-800'
1074
- }`}
1075
- >
1076
- <span>${busyAction === action.key ? '⏳ ' : ''}${action.label}</span>
1077
- </button>
1078
- `)}
876
+ ${PROFILE_PACK_ACTIONS.map(
877
+ (action) => html`
878
+ <button key=${action.key} type="button" onClick=${() => onAction?.(action.key, pack)} disabled=${Boolean(busyAction)} className=${`w-full rounded-xl border px-3 py-3 text-left text-sm transition disabled:opacity-60 ${action.danger ? 'border-rose-500/25 bg-rose-500/10 text-rose-100 hover:bg-rose-500/15' : 'border-slate-700 bg-slate-950/60 text-slate-100 hover:bg-slate-800'}`}>
879
+ <span>${busyAction === action.key ? '⏳ ' : ''}${action.label}</span>
880
+ </button>
881
+ `,
882
+ )}
1079
883
  </div>
1080
- <button type="button" onClick=${onClose} className="mt-3 h-10 w-full rounded-xl border border-slate-700 text-sm text-slate-200 hover:bg-slate-800">
1081
- Fechar
1082
- </button>
884
+ <button type="button" onClick=${onClose} className="mt-3 h-10 w-full rounded-xl border border-slate-700 text-sm text-slate-200 hover:bg-slate-800">Fechar</button>
1083
885
  </section>
1084
886
  </div>
1085
887
  `;
@@ -1107,10 +909,22 @@ function PackAnalyticsModal({ open = false, pack = null, data = null, loading =
1107
909
  ? html`<div className="mt-4 rounded-xl border border-rose-500/35 bg-rose-500/10 p-3 text-sm text-rose-200">${error}</div>`
1108
910
  : html`
1109
911
  <div className="mt-4 grid grid-cols-2 md:grid-cols-4 gap-3">
1110
- <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3"><p className="text-[11px] text-slate-400">Downloads</p><p className="text-lg font-bold text-slate-100">⬇ ${shortNum(analytics?.downloads || 0)}</p></article>
1111
- <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3"><p className="text-[11px] text-slate-400">Likes</p><p className="text-lg font-bold text-slate-100">❤️ ${shortNum(analytics?.likes || 0)}</p></article>
1112
- <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3"><p className="text-[11px] text-slate-400">Dislikes</p><p className="text-lg font-bold text-slate-100">👎 ${shortNum(analytics?.dislikes || 0)}</p></article>
1113
- <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3"><p className="text-[11px] text-slate-400">Score</p><p className="text-lg font-bold text-slate-100">⭐ ${shortNum(analytics?.score || 0)}</p></article>
912
+ <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3">
913
+ <p className="text-[11px] text-slate-400">Downloads</p>
914
+ <p className="text-lg font-bold text-slate-100">⬇ ${shortNum(analytics?.downloads || 0)}</p>
915
+ </article>
916
+ <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3">
917
+ <p className="text-[11px] text-slate-400">Likes</p>
918
+ <p className="text-lg font-bold text-slate-100">❤️ ${shortNum(analytics?.likes || 0)}</p>
919
+ </article>
920
+ <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3">
921
+ <p className="text-[11px] text-slate-400">Dislikes</p>
922
+ <p className="text-lg font-bold text-slate-100">👎 ${shortNum(analytics?.dislikes || 0)}</p>
923
+ </article>
924
+ <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3">
925
+ <p className="text-[11px] text-slate-400">Score</p>
926
+ <p className="text-lg font-bold text-slate-100">⭐ ${shortNum(analytics?.score || 0)}</p>
927
+ </article>
1114
928
  </div>
1115
929
  <div className="mt-4 grid grid-cols-1 md:grid-cols-2 gap-3">
1116
930
  <article className="rounded-xl border border-slate-800 bg-slate-950/60 p-3">
@@ -1159,14 +973,7 @@ function CreatorStatCard({ icon, label, value, tone = 'slate', sublabel = '' })
1159
973
  `;
1160
974
  }
1161
975
 
1162
- function CreatorPackCardPro({
1163
- pack,
1164
- onOpenPublic,
1165
- onOpenActions,
1166
- onOpenManage,
1167
- onQuickDelete,
1168
- actionBusy = '',
1169
- }) {
976
+ function CreatorPackCardPro({ pack, onOpenPublic, onOpenActions, onOpenManage, onQuickDelete, actionBusy = '' }) {
1170
977
  const visibilityPill = formatVisibilityPill(pack?.visibility);
1171
978
  const statusPill = formatStatusPill(pack?.status);
1172
979
  const engagement = getPackEngagement(pack);
@@ -1183,17 +990,8 @@ function CreatorPackCardPro({
1183
990
  <span className=${`inline-flex rounded-full border px-2 py-0.5 text-[10px] ${statusPill.className}`}>${statusPill.label}</span>
1184
991
  <span className=${`inline-flex rounded-full border px-2 py-0.5 text-[10px] ${visibilityPill.className}`}>${visibilityPill.label}</span>
1185
992
  </div>
1186
- <button
1187
- type="button"
1188
- onClick=${() => onOpenActions?.(pack)}
1189
- className="absolute right-2 top-2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-slate-700/90 bg-slate-950/80 text-slate-100 hover:bg-slate-800"
1190
- title="Ações"
1191
- >
1192
-
1193
- </button>
1194
- ${isCoverHidden
1195
- ? html`<div className="absolute bottom-2 left-2 rounded-full border border-slate-600 bg-slate-950/80 px-2 py-0.5 text-[10px] text-slate-300">🔒 capa oculta no catálogo</div>`
1196
- : null}
993
+ <button type="button" onClick=${() => onOpenActions?.(pack)} className="absolute right-2 top-2 inline-flex h-8 w-8 items-center justify-center rounded-full border border-slate-700/90 bg-slate-950/80 text-slate-100 hover:bg-slate-800" title="Ações">⋮</button>
994
+ ${isCoverHidden ? html`<div className="absolute bottom-2 left-2 rounded-full border border-slate-600 bg-slate-950/80 px-2 py-0.5 text-[10px] text-slate-300">🔒 capa oculta no catálogo</div>` : null}
1197
995
  </div>
1198
996
 
1199
997
  <div className="min-w-0 p-2 space-y-1.5 sm:p-2.5 sm:space-y-2">
@@ -1210,80 +1008,30 @@ function CreatorPackCardPro({
1210
1008
  </div>
1211
1009
 
1212
1010
  <div className="grid grid-cols-3 gap-1.5">
1213
- <button
1214
- type="button"
1215
- onClick=${() => onOpenManage?.(pack)}
1216
- disabled=${Boolean(actionBusy)}
1217
- className="inline-flex h-8 items-center justify-center rounded-lg border border-slate-700 bg-slate-950/60 px-1 text-[11px] text-slate-100 hover:bg-slate-800 disabled:opacity-60"
1218
- title="Adicionar sticker"
1219
- >
1011
+ <button type="button" onClick=${() => onOpenManage?.(pack)} disabled=${Boolean(actionBusy)} className="inline-flex h-8 items-center justify-center rounded-lg border border-slate-700 bg-slate-950/60 px-1 text-[11px] text-slate-100 hover:bg-slate-800 disabled:opacity-60" title="Adicionar sticker">
1220
1012
  <span className="sm:hidden">➕</span>
1221
1013
  <span className="hidden sm:inline">➕ Sticker</span>
1222
1014
  </button>
1223
- <button
1224
- type="button"
1225
- onClick=${() => onOpenManage?.(pack)}
1226
- disabled=${Boolean(actionBusy)}
1227
- className="inline-flex h-8 items-center justify-center rounded-lg border border-slate-700 bg-slate-950/60 px-1 text-[11px] text-slate-100 hover:bg-slate-800 disabled:opacity-60"
1228
- title="Editar pack"
1229
- >
1015
+ <button type="button" onClick=${() => onOpenManage?.(pack)} disabled=${Boolean(actionBusy)} className="inline-flex h-8 items-center justify-center rounded-lg border border-slate-700 bg-slate-950/60 px-1 text-[11px] text-slate-100 hover:bg-slate-800 disabled:opacity-60" title="Editar pack">
1230
1016
  <span className="sm:hidden">✏️</span>
1231
1017
  <span className="hidden sm:inline">✏️ Editar</span>
1232
1018
  </button>
1233
- <button
1234
- type="button"
1235
- onClick=${() => onQuickDelete?.(pack)}
1236
- disabled=${Boolean(actionBusy)}
1237
- className="inline-flex h-8 items-center justify-center rounded-lg border border-rose-500/25 bg-rose-500/10 px-1 text-[11px] text-rose-100 hover:bg-rose-500/15 disabled:opacity-60"
1238
- title="Excluir pack"
1239
- >
1019
+ <button type="button" onClick=${() => onQuickDelete?.(pack)} disabled=${Boolean(actionBusy)} className="inline-flex h-8 items-center justify-center rounded-lg border border-rose-500/25 bg-rose-500/10 px-1 text-[11px] text-rose-100 hover:bg-rose-500/15 disabled:opacity-60" title="Excluir pack">
1240
1020
  <span className="sm:hidden">🗑️</span>
1241
1021
  <span className="hidden sm:inline">🗑️ Excluir</span>
1242
1022
  </button>
1243
1023
  </div>
1244
1024
 
1245
1025
  <div className="flex gap-2">
1246
- <button
1247
- type="button"
1248
- onClick=${() => onOpenManage?.(pack)}
1249
- disabled=${Boolean(actionBusy)}
1250
- className="inline-flex h-8 flex-1 items-center justify-center rounded-xl border border-cyan-500/35 bg-cyan-500/10 px-2.5 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20 disabled:opacity-60 sm:h-9 sm:text-xs"
1251
- >
1252
- ${actionBusy === 'manage' ? 'Abrindo...' : 'Gerenciar pack'}
1253
- </button>
1254
- ${shareable
1255
- ? html`
1256
- <button
1257
- type="button"
1258
- onClick=${() => onOpenPublic?.(pack.pack_key)}
1259
- className="inline-flex h-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950/60 px-2.5 text-[11px] font-medium text-slate-100 hover:bg-slate-800 sm:h-9 sm:px-3 sm:text-xs"
1260
- >
1261
- Abrir
1262
- </button>
1263
- `
1264
- : html`<span className="inline-flex h-9 items-center justify-center rounded-xl border border-slate-800 bg-slate-950/40 px-3 text-[11px] text-slate-500">Sem link público</span>`}
1026
+ <button type="button" onClick=${() => onOpenManage?.(pack)} disabled=${Boolean(actionBusy)} className="inline-flex h-8 flex-1 items-center justify-center rounded-xl border border-cyan-500/35 bg-cyan-500/10 px-2.5 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20 disabled:opacity-60 sm:h-9 sm:text-xs">${actionBusy === 'manage' ? 'Abrindo...' : 'Gerenciar pack'}</button>
1027
+ ${shareable ? html` <button type="button" onClick=${() => onOpenPublic?.(pack.pack_key)} className="inline-flex h-8 items-center justify-center rounded-xl border border-slate-700 bg-slate-950/60 px-2.5 text-[11px] font-medium text-slate-100 hover:bg-slate-800 sm:h-9 sm:px-3 sm:text-xs">Abrir</button> ` : html`<span className="inline-flex h-9 items-center justify-center rounded-xl border border-slate-800 bg-slate-950/40 px-3 text-[11px] text-slate-500">Sem link público</span>`}
1265
1028
  </div>
1266
1029
  </div>
1267
1030
  </article>
1268
1031
  `;
1269
1032
  }
1270
1033
 
1271
- function PackManagerModal({
1272
- open = false,
1273
- data = null,
1274
- loading = false,
1275
- error = '',
1276
- busyAction = '',
1277
- onClose,
1278
- onRefresh,
1279
- onSaveMetadata,
1280
- onAddSticker,
1281
- onRemoveSticker,
1282
- onReplaceSticker,
1283
- onSetCover,
1284
- onReorder,
1285
- onOpenAnalytics,
1286
- }) {
1034
+ function PackManagerModal({ open = false, data = null, loading = false, error = '', busyAction = '', onClose, onRefresh, onSaveMetadata, onAddSticker, onRemoveSticker, onReplaceSticker, onSetCover, onReorder, onOpenAnalytics }) {
1287
1035
  const pack = data?.pack || null;
1288
1036
  const publishState = data?.publish_state || null;
1289
1037
  const analytics = data?.analytics || null;
@@ -1321,9 +1069,7 @@ function PackManagerModal({
1321
1069
 
1322
1070
  const orderMap = new Map(items.map((item) => [item.sticker_id, item]));
1323
1071
  const orderedItems = orderIds.map((id) => orderMap.get(id)).filter(Boolean);
1324
- const orderDirty =
1325
- orderedItems.length === items.length &&
1326
- orderedItems.some((item, index) => String(item?.sticker_id || '') !== String(items[index]?.sticker_id || ''));
1072
+ const orderDirty = orderedItems.length === items.length && orderedItems.some((item, index) => String(item?.sticker_id || '') !== String(items[index]?.sticker_id || ''));
1327
1073
 
1328
1074
  return html`
1329
1075
  <div className="fixed inset-0 z-[85] flex items-end justify-center bg-black/65 p-2 sm:items-center sm:p-4">
@@ -1337,11 +1083,7 @@ function PackManagerModal({
1337
1083
  <p className="truncate text-xs text-slate-500">${pack?.pack_key || '-'}</p>
1338
1084
  </div>
1339
1085
  <div className="flex w-full flex-wrap items-center gap-1.5 sm:w-auto sm:justify-end sm:gap-2">
1340
- <label
1341
- className=${`inline-flex h-9 cursor-pointer items-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-2.5 text-[11px] font-semibold text-emerald-100 hover:bg-emerald-500/20 sm:h-10 sm:px-3 sm:text-xs ${
1342
- busyAction ? 'pointer-events-none opacity-60' : ''
1343
- }`}
1344
- >
1086
+ <label className=${`inline-flex h-9 cursor-pointer items-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-2.5 text-[11px] font-semibold text-emerald-100 hover:bg-emerald-500/20 sm:h-10 sm:px-3 sm:text-xs ${busyAction ? 'pointer-events-none opacity-60' : ''}`}>
1345
1087
  <span className="sm:hidden">➕ Sticker</span>
1346
1088
  <span className="hidden sm:inline">➕ Adicionar sticker</span>
1347
1089
  <input
@@ -1392,10 +1134,22 @@ function PackManagerModal({
1392
1134
  </div>
1393
1135
  </div>
1394
1136
  <div className="grid grid-cols-2 gap-2 text-xs">
1395
- <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2"><p className="text-slate-400">Stickers</p><p className="font-semibold text-slate-100">${shortNum(pack.sticker_count || 0)}</p></div>
1396
- <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2"><p className="text-slate-400">Downloads</p><p className="font-semibold text-slate-100">${shortNum(analytics?.downloads || 0)}</p></div>
1397
- <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2"><p className="text-slate-400">Likes</p><p className="font-semibold text-slate-100">${shortNum(analytics?.likes || 0)}</p></div>
1398
- <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2"><p className="text-slate-400">Pronto p/ publicar</p><p className="font-semibold text-slate-100">${publishState?.consistency?.can_publish ? 'Sim' : 'Não'}</p></div>
1137
+ <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2">
1138
+ <p className="text-slate-400">Stickers</p>
1139
+ <p className="font-semibold text-slate-100">${shortNum(pack.sticker_count || 0)}</p>
1140
+ </div>
1141
+ <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2">
1142
+ <p className="text-slate-400">Downloads</p>
1143
+ <p className="font-semibold text-slate-100">${shortNum(analytics?.downloads || 0)}</p>
1144
+ </div>
1145
+ <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2">
1146
+ <p className="text-slate-400">Likes</p>
1147
+ <p className="font-semibold text-slate-100">${shortNum(analytics?.likes || 0)}</p>
1148
+ </div>
1149
+ <div className="rounded-xl border border-slate-800 bg-slate-900/60 p-2">
1150
+ <p className="text-slate-400">Pronto p/ publicar</p>
1151
+ <p className="font-semibold text-slate-100">${publishState?.consistency?.can_publish ? 'Sim' : 'Não'}</p>
1152
+ </div>
1399
1153
  </div>
1400
1154
  </section>
1401
1155
 
@@ -1414,15 +1168,15 @@ function PackManagerModal({
1414
1168
  >
1415
1169
  <div>
1416
1170
  <label className="mb-1 block text-[11px] uppercase tracking-wide text-slate-400">Nome</label>
1417
- <input value=${name} onChange=${(e) => setName(e.target.value)} maxLength="120" className="h-11 w-full rounded-xl border border-slate-700 bg-slate-900 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40" />
1171
+ <input value=${name} onChange=${(e) => setName(e.target.value)} maxlength="120" className="h-11 w-full rounded-xl border border-slate-700 bg-slate-900 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40" />
1418
1172
  </div>
1419
1173
  <div>
1420
1174
  <label className="mb-1 block text-[11px] uppercase tracking-wide text-slate-400">Publisher</label>
1421
- <input value=${publisher} onChange=${(e) => setPublisher(e.target.value)} maxLength="120" className="h-11 w-full rounded-xl border border-slate-700 bg-slate-900 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40" />
1175
+ <input value=${publisher} onChange=${(e) => setPublisher(e.target.value)} maxlength="120" className="h-11 w-full rounded-xl border border-slate-700 bg-slate-900 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40" />
1422
1176
  </div>
1423
1177
  <div>
1424
1178
  <label className="mb-1 block text-[11px] uppercase tracking-wide text-slate-400">Descrição</label>
1425
- <textarea value=${description} onChange=${(e) => setDescription(e.target.value)} rows="3" maxLength="1024" className="w-full rounded-xl border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400/40"></textarea>
1179
+ <textarea value=${description} onChange=${(e) => setDescription(e.target.value)} rows="3" maxlength="1024" className="w-full rounded-xl border border-slate-700 bg-slate-900 px-3 py-2 text-sm text-slate-100 outline-none focus:border-cyan-400/40"></textarea>
1426
1180
  </div>
1427
1181
  <div>
1428
1182
  <label className="mb-1 block text-[11px] uppercase tracking-wide text-slate-400">Tags (separadas por vírgula)</label>
@@ -1437,9 +1191,7 @@ function PackManagerModal({
1437
1191
  <option value="private">Privado</option>
1438
1192
  </select>
1439
1193
  </div>
1440
- <button type="submit" disabled=${Boolean(busyAction)} className="h-11 w-full rounded-xl border border-cyan-500/35 bg-cyan-500/10 text-sm font-semibold text-cyan-100 hover:bg-cyan-500/20 disabled:opacity-60">
1441
- ${busyAction === 'saveMetadata' ? 'Salvando...' : 'Salvar alterações'}
1442
- </button>
1194
+ <button type="submit" disabled=${Boolean(busyAction)} className="h-11 w-full rounded-xl border border-cyan-500/35 bg-cyan-500/10 text-sm font-semibold text-cyan-100 hover:bg-cyan-500/20 disabled:opacity-60">${busyAction === 'saveMetadata' ? 'Salvando...' : 'Salvar alterações'}</button>
1443
1195
  </form>
1444
1196
  </aside>
1445
1197
 
@@ -1449,18 +1201,7 @@ function PackManagerModal({
1449
1201
  <h4 className="text-base font-bold text-slate-100">Stickers do pack</h4>
1450
1202
  <p className="text-[11px] text-slate-400">Arraste para reordenar. Use ⭐, 🔁 e ❌.</p>
1451
1203
  </div>
1452
- ${orderDirty
1453
- ? html`
1454
- <button
1455
- type="button"
1456
- onClick=${() => onReorder?.(orderIds)}
1457
- disabled=${Boolean(busyAction)}
1458
- className="h-9 rounded-xl border border-amber-500/35 bg-amber-500/10 px-2.5 text-[11px] font-semibold text-amber-100 hover:bg-amber-500/20 disabled:opacity-60 sm:h-10 sm:px-3 sm:text-xs"
1459
- >
1460
- ${busyAction === 'reorder' ? 'Salvando ordem...' : 'Salvar ordem'}
1461
- </button>
1462
- `
1463
- : null}
1204
+ ${orderDirty ? html` <button type="button" onClick=${() => onReorder?.(orderIds)} disabled=${Boolean(busyAction)} className="h-9 rounded-xl border border-amber-500/35 bg-amber-500/10 px-2.5 text-[11px] font-semibold text-amber-100 hover:bg-amber-500/20 disabled:opacity-60 sm:h-10 sm:px-3 sm:text-xs">${busyAction === 'reorder' ? 'Salvando ordem...' : 'Salvar ordem'}</button> ` : null}
1464
1205
  </div>
1465
1206
 
1466
1207
  ${orderedItems.length
@@ -1493,21 +1234,8 @@ function PackManagerModal({
1493
1234
  <div className="p-1.5 space-y-1.5 sm:p-2 sm:space-y-2">
1494
1235
  <p className="truncate text-[10px] text-slate-500">${item.sticker_id}</p>
1495
1236
  <div className="grid grid-cols-3 gap-1.5">
1496
- <button
1497
- type="button"
1498
- title="Definir como capa"
1499
- onClick=${() => onSetCover?.(item.sticker_id)}
1500
- disabled=${Boolean(busyAction)}
1501
- className="h-8 rounded-lg border border-amber-500/30 bg-amber-500/10 text-[11px] text-amber-100 hover:bg-amber-500/15 disabled:opacity-60"
1502
- >
1503
-
1504
- </button>
1505
- <label
1506
- className=${`inline-flex h-8 cursor-pointer items-center justify-center rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-2 text-[11px] text-cyan-100 hover:bg-cyan-500/15 ${
1507
- busyAction ? 'pointer-events-none opacity-60' : ''
1508
- }`}
1509
- title="Substituir sticker"
1510
- >
1237
+ <button type="button" title="Definir como capa" onClick=${() => onSetCover?.(item.sticker_id)} disabled=${Boolean(busyAction)} className="h-8 rounded-lg border border-amber-500/30 bg-amber-500/10 text-[11px] text-amber-100 hover:bg-amber-500/15 disabled:opacity-60">⭐</button>
1238
+ <label className=${`inline-flex h-8 cursor-pointer items-center justify-center rounded-lg border border-cyan-500/30 bg-cyan-500/10 px-2 text-[11px] text-cyan-100 hover:bg-cyan-500/15 ${busyAction ? 'pointer-events-none opacity-60' : ''}`} title="Substituir sticker">
1511
1239
  🔁
1512
1240
  <input
1513
1241
  type="file"
@@ -1522,15 +1250,7 @@ function PackManagerModal({
1522
1250
  }}
1523
1251
  />
1524
1252
  </label>
1525
- <button
1526
- type="button"
1527
- title="Remover sticker"
1528
- onClick=${() => onRemoveSticker?.(item.sticker_id)}
1529
- disabled=${Boolean(busyAction)}
1530
- className="h-8 rounded-lg border border-rose-500/30 bg-rose-500/10 text-[11px] text-rose-100 hover:bg-rose-500/15 disabled:opacity-60"
1531
- >
1532
-
1533
- </button>
1253
+ <button type="button" title="Remover sticker" onClick=${() => onRemoveSticker?.(item.sticker_id)} disabled=${Boolean(busyAction)} className="h-8 rounded-lg border border-rose-500/30 bg-rose-500/10 text-[11px] text-rose-100 hover:bg-rose-500/15 disabled:opacity-60">❌</button>
1534
1254
  </div>
1535
1255
  </div>
1536
1256
  </article>
@@ -1555,25 +1275,7 @@ function PackManagerModal({
1555
1275
  `;
1556
1276
  }
1557
1277
 
1558
- function CreatorProfileDashboard({
1559
- googleAuthConfig,
1560
- googleAuth,
1561
- googleAuthBusy,
1562
- googleAuthError,
1563
- googleSessionChecked,
1564
- myPacks,
1565
- myPacksLoading,
1566
- myPacksError,
1567
- myProfileStats,
1568
- onBack,
1569
- onRefresh,
1570
- onLogout,
1571
- onOpenPublicPack,
1572
- onOpenPackActions,
1573
- onOpenManagePack,
1574
- onRequestDeletePack,
1575
- packActionBusyByKey = {},
1576
- }) {
1278
+ function CreatorProfileDashboard({ googleAuthConfig, googleAuth, googleAuthBusy, googleAuthError, googleSessionChecked, myPacks, myPacksLoading, myPacksError, myProfileStats, onBack, onRefresh, onLogout, onOpenPublicPack, onOpenPackActions, onOpenManagePack, onRequestDeletePack, packActionBusyByKey = {} }) {
1577
1279
  const [packSearch, setPackSearch] = useState('');
1578
1280
  const [packSort, setPackSort] = useState('recent');
1579
1281
  const [packFilter, setPackFilter] = useState('all');
@@ -1605,15 +1307,7 @@ function CreatorProfileDashboard({
1605
1307
  if (packFilter === 'unlisted' && visibility !== 'unlisted') return false;
1606
1308
  }
1607
1309
  if (!q) return true;
1608
- const searchable = [
1609
- pack?.name,
1610
- pack?.publisher,
1611
- pack?.pack_key,
1612
- pack?.description,
1613
- ...(Array.isArray(pack?.manual_tags) ? pack.manual_tags : []),
1614
- ]
1615
- .map((value) => normalizeToken(value))
1616
- .join(' ');
1310
+ const searchable = [pack?.name, pack?.publisher, pack?.pack_key, pack?.description, ...(Array.isArray(pack?.manual_tags) ? pack.manual_tags : [])].map((value) => normalizeToken(value)).join(' ');
1617
1311
  return searchable.includes(q);
1618
1312
  });
1619
1313
 
@@ -1657,11 +1351,7 @@ function CreatorProfileDashboard({
1657
1351
  <div className="relative">
1658
1352
  <div className="flex flex-wrap items-start justify-between gap-3">
1659
1353
  <div className="flex min-w-0 items-center gap-3">
1660
- <img
1661
- src=${googleAuth?.user?.picture || getAvatarUrl(googleAuth?.user?.name || 'creator')}
1662
- alt="Avatar"
1663
- className="h-20 w-20 rounded-2xl border border-slate-700 bg-slate-900 object-cover sm:h-24 sm:w-24"
1664
- />
1354
+ <img src=${googleAuth?.user?.picture || getAvatarUrl(googleAuth?.user?.name || 'creator')} alt="Avatar" className="h-20 w-20 rounded-2xl border border-slate-700 bg-slate-900 object-cover sm:h-24 sm:w-24" />
1665
1355
  <div className="min-w-0">
1666
1356
  <div className="mb-1 inline-flex items-center gap-1 rounded-full border border-cyan-400/25 bg-cyan-500/10 px-2 py-0.5 text-[11px] font-semibold text-cyan-100">🧩 Gestão de stickers</div>
1667
1357
  <h1 className="truncate text-2xl font-extrabold tracking-tight text-slate-100 sm:text-3xl">Gerenciamento de Packs</h1>
@@ -1669,36 +1359,39 @@ function CreatorProfileDashboard({
1669
1359
  <p className="mt-0.5 text-[11px] text-slate-500">Área exclusiva para gerenciamento dos seus packs.</p>
1670
1360
  </div>
1671
1361
  </div>
1672
- ${hasGoogleLogin
1673
- ? html`<button type="button" onClick=${onLogout} disabled=${googleAuthBusy} className="inline-flex h-10 items-center rounded-xl border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:opacity-60">${googleAuthBusy ? 'Saindo...' : 'Sair'}</button>`
1674
- : null}
1362
+ ${hasGoogleLogin ? html`<button type="button" onClick=${onLogout} disabled=${googleAuthBusy} className="inline-flex h-10 items-center rounded-xl border border-slate-700 px-3 text-xs text-slate-200 hover:bg-slate-800 disabled:opacity-60">${googleAuthBusy ? 'Saindo...' : 'Sair'}</button>` : null}
1675
1363
  </div>
1676
1364
 
1677
1365
  ${hasGoogleLogin
1678
1366
  ? html`
1679
1367
  <div className="mt-3 rounded-2xl border border-slate-800 bg-slate-950/35 p-2.5">
1680
1368
  <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
1681
- <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2"><p className="text-[10px] text-slate-400">Packs</p><p className="text-base font-bold text-slate-100">${shortNum(myProfileStats?.total || 0)}</p></div>
1682
- <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2"><p className="text-[10px] text-slate-400">Downloads</p><p className="text-base font-bold text-slate-100">${shortNum(totals.downloads)}</p></div>
1683
- <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2"><p className="text-[10px] text-slate-400">Likes</p><p className="text-base font-bold text-slate-100">${shortNum(totals.likes)}</p></div>
1684
- <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2"><p className="text-[10px] text-slate-400">Stickers publicados</p><p className="text-base font-bold text-slate-100">${shortNum(totals.publishedStickers)}</p></div>
1369
+ <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2">
1370
+ <p className="text-[10px] text-slate-400">Packs</p>
1371
+ <p className="text-base font-bold text-slate-100">${shortNum(myProfileStats?.total || 0)}</p>
1372
+ </div>
1373
+ <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2">
1374
+ <p className="text-[10px] text-slate-400">Downloads</p>
1375
+ <p className="text-base font-bold text-slate-100">${shortNum(totals.downloads)}</p>
1376
+ </div>
1377
+ <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2">
1378
+ <p className="text-[10px] text-slate-400">Likes</p>
1379
+ <p className="text-base font-bold text-slate-100">${shortNum(totals.likes)}</p>
1380
+ </div>
1381
+ <div className="rounded-xl border border-slate-800 bg-slate-900/50 px-2.5 py-2">
1382
+ <p className="text-[10px] text-slate-400">Stickers publicados</p>
1383
+ <p className="text-base font-bold text-slate-100">${shortNum(totals.publishedStickers)}</p>
1384
+ </div>
1685
1385
  </div>
1686
1386
  <div className="mt-2 flex flex-wrap gap-1.5">
1687
- ${profileStatusChips.map((chip) => html`
1688
- <button
1689
- key=${chip.key}
1690
- type="button"
1691
- onClick=${() => setPackFilter((prev) => (prev === chip.key ? 'all' : chip.key))}
1692
- className=${`inline-flex items-center gap-1 rounded-full border px-2 py-1 text-[11px] ${
1693
- packFilter === chip.key
1694
- ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100'
1695
- : 'border-slate-700 bg-slate-900/50 text-slate-300 hover:bg-slate-800'
1696
- }`}
1697
- >
1698
- <span>${chip.label}</span>
1699
- <span className="font-semibold">${shortNum(chip.value)}</span>
1700
- </button>
1701
- `)}
1387
+ ${profileStatusChips.map(
1388
+ (chip) => html`
1389
+ <button key=${chip.key} type="button" onClick=${() => setPackFilter((prev) => (prev === chip.key ? 'all' : chip.key))} className=${`inline-flex items-center gap-1 rounded-full border px-2 py-1 text-[11px] ${packFilter === chip.key ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100' : 'border-slate-700 bg-slate-900/50 text-slate-300 hover:bg-slate-800'}`}>
1390
+ <span>${chip.label}</span>
1391
+ <span className="font-semibold">${shortNum(chip.value)}</span>
1392
+ </button>
1393
+ `,
1394
+ )}
1702
1395
  </div>
1703
1396
  </div>
1704
1397
  `
@@ -1712,16 +1405,9 @@ function CreatorProfileDashboard({
1712
1405
  <div className="space-y-2.5">
1713
1406
  <div className="rounded-xl border border-cyan-500/20 bg-cyan-500/5 p-3">
1714
1407
  <p className="text-sm font-semibold text-cyan-200">Sessão necessária para gerenciamento</p>
1715
- <p className="mt-1 text-xs text-slate-300">
1716
- ${googleLoginEnabled
1717
- ? 'Esta área é exclusiva para gerenciar seus packs e stickers. Redirecionando para o login...'
1718
- : 'Login Google indisponível no momento.'}
1719
- </p>
1408
+ <p className="mt-1 text-xs text-slate-300">${googleLoginEnabled ? 'Esta área é exclusiva para gerenciar seus packs e stickers. Redirecionando para o login...' : 'Login Google indisponível no momento.'}</p>
1720
1409
  </div>
1721
- ${!googleSessionChecked
1722
- ? html`<p className="text-xs text-slate-400">Verificando sessão...</p>`
1723
- : null}
1724
- ${googleAuthError ? html`<p className="text-xs text-rose-300">${googleAuthError}</p>` : null}
1410
+ ${!googleSessionChecked ? html`<p className="text-xs text-slate-400">Verificando sessão...</p>` : null} ${googleAuthError ? html`<p className="text-xs text-rose-300">${googleAuthError}</p>` : null}
1725
1411
  </div>
1726
1412
  </section>
1727
1413
  `
@@ -1741,75 +1427,37 @@ function CreatorProfileDashboard({
1741
1427
  <div className="mt-2 grid gap-2 md:grid-cols-[minmax(0,1fr)_180px]">
1742
1428
  <div className="relative">
1743
1429
  <span className="pointer-events-none absolute left-3 top-1/2 -translate-y-1/2 text-xs text-slate-400">🔎</span>
1744
- <input
1745
- type="search"
1746
- value=${packSearch}
1747
- onChange=${(e) => setPackSearch(e.target.value)}
1748
- placeholder="Buscar packs..."
1749
- className="h-10 w-full rounded-xl border border-slate-700 bg-slate-950/60 pl-9 pr-3 text-sm text-slate-100 placeholder:text-slate-500 outline-none focus:border-cyan-400/40"
1750
- />
1430
+ <input type="search" value=${packSearch} onChange=${(e) => setPackSearch(e.target.value)} placeholder="Buscar packs..." className="h-10 w-full rounded-xl border border-slate-700 bg-slate-950/60 pl-9 pr-3 text-sm text-slate-100 placeholder:text-slate-500 outline-none focus:border-cyan-400/40" />
1751
1431
  </div>
1752
- <select
1753
- value=${packSort}
1754
- onChange=${(e) => setPackSort(e.target.value)}
1755
- className="h-10 rounded-xl border border-slate-700 bg-slate-950/60 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40"
1756
- >
1432
+ <select value=${packSort} onChange=${(e) => setPackSort(e.target.value)} className="h-10 rounded-xl border border-slate-700 bg-slate-950/60 px-3 text-sm text-slate-100 outline-none focus:border-cyan-400/40">
1757
1433
  <option value="recent">Mais recente</option>
1758
1434
  <option value="downloads">Mais downloads</option>
1759
1435
  <option value="likes">Mais likes</option>
1760
1436
  </select>
1761
1437
  </div>
1762
1438
 
1763
- <div className="mt-2 flex flex-wrap gap-1.5">
1764
- ${packFilterOptions.map((option) => html`
1765
- <button
1766
- key=${option.key}
1767
- type="button"
1768
- onClick=${() => setPackFilter(option.key)}
1769
- className=${`h-8 rounded-full border px-2.5 text-[11px] ${
1770
- packFilter === option.key
1771
- ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100'
1772
- : 'border-slate-700 bg-slate-950/50 text-slate-300 hover:bg-slate-800'
1773
- }`}
1774
- >
1775
- ${option.label}
1776
- </button>
1777
- `)}
1778
- </div>
1439
+ <div className="mt-2 flex flex-wrap gap-1.5">${packFilterOptions.map((option) => html` <button key=${option.key} type="button" onClick=${() => setPackFilter(option.key)} className=${`h-8 rounded-full border px-2.5 text-[11px] ${packFilter === option.key ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100' : 'border-slate-700 bg-slate-950/50 text-slate-300 hover:bg-slate-800'}`}>${option.label}</button> `)}</div>
1779
1440
  </div>
1780
1441
 
1781
1442
  ${myPacksLoading
1782
- ? html`
1783
- <div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
1784
- ${Array.from({ length: 6 }).map((_, index) => html`<div key=${index} className="h-56 animate-pulse rounded-2xl border border-slate-800 bg-slate-900/70"></div>`)}
1785
- </div>
1786
- `
1443
+ ? html` <div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">${Array.from({ length: 6 }).map((_, index) => html`<div key=${index} className="h-56 animate-pulse rounded-2xl border border-slate-800 bg-slate-900/70"></div>`)}</div> `
1787
1444
  : filteredSortedPacks.length
1788
- ? html`
1789
- <div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">
1790
- ${filteredSortedPacks.map((pack) => html`
1791
- <${CreatorPackCardPro}
1792
- key=${pack.id || pack.pack_key}
1793
- pack=${pack}
1794
- onOpenPublic=${onOpenPublicPack}
1795
- onOpenActions=${onOpenPackActions}
1796
- onOpenManage=${onOpenManagePack}
1797
- onQuickDelete=${onRequestDeletePack}
1798
- actionBusy=${packActionBusyByKey?.[pack.pack_key] || ''}
1799
- />
1800
- `)}
1801
- </div>
1802
- `
1445
+ ? html` <div className="grid grid-cols-1 gap-3 md:grid-cols-2 xl:grid-cols-3">${filteredSortedPacks.map((pack) => html` <${CreatorPackCardPro} key=${pack.id || pack.pack_key} pack=${pack} onOpenPublic=${onOpenPublicPack} onOpenActions=${onOpenPackActions} onOpenManage=${onOpenManagePack} onQuickDelete=${onRequestDeletePack} actionBusy=${packActionBusyByKey?.[pack.pack_key] || ''} /> `)}</div> `
1803
1446
  : html`
1804
1447
  <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/60 p-5 text-center">
1805
1448
  <p className="text-sm font-semibold text-slate-100">${packs.length ? 'Nenhum pack corresponde aos filtros.' : 'Nenhum pack encontrado para esta conta.'}</p>
1806
- <p className="mt-1 text-xs text-slate-400">
1807
- ${packs.length
1808
- ? 'Tente limpar busca/filtros para ver seus packs.'
1809
- : html`Crie um pack com essa conta Google em <a href="/stickers/create/" className="text-cyan-300 underline">/stickers/create</a> e volte aqui.`}
1810
- </p>
1449
+ <p className="mt-1 text-xs text-slate-400">${packs.length ? 'Tente limpar busca/filtros para ver seus packs.' : html`Crie um pack com essa conta Google em <a href="/stickers/create/" className="text-cyan-300 underline">/stickers/create</a> e volte aqui.`}</p>
1811
1450
  ${packs.length
1812
- ? html`<button type="button" onClick=${() => { setPackSearch(''); setPackFilter('all'); }} className="mt-3 h-9 rounded-xl border border-slate-700 px-3 text-xs text-slate-100 hover:bg-slate-800">Limpar filtros</button>`
1451
+ ? html`<button
1452
+ type="button"
1453
+ onClick=${() => {
1454
+ setPackSearch('');
1455
+ setPackFilter('all');
1456
+ }}
1457
+ className="mt-3 h-9 rounded-xl border border-slate-700 px-3 text-xs text-slate-100 hover:bg-slate-800"
1458
+ >
1459
+ Limpar filtros
1460
+ </button>`
1813
1461
  : null}
1814
1462
  </div>
1815
1463
  `}
@@ -1824,35 +1472,16 @@ function OrphanCard({ sticker, hasNsfwAccess = true, onRequireLogin }) {
1824
1472
  return html`
1825
1473
  <article className="group rounded-2xl border border-slate-700/80 bg-slate-800/70 shadow-soft overflow-hidden transition-all duration-200 hover:-translate-y-0.5">
1826
1474
  <div className="relative aspect-square bg-slate-900 overflow-hidden">
1827
- <img
1828
- src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : sticker.url || DEFAULT_STICKER_PLACEHOLDER_URL}
1829
- alt="Sticker sem pack"
1830
- className=${`w-full h-full object-contain transition-transform duration-300 ${
1831
- lockedByNsfw ? 'blur-md scale-105' : 'group-hover:scale-110'
1832
- }`}
1833
- loading="lazy"
1834
- />
1475
+ <img src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : sticker.url || DEFAULT_STICKER_PLACEHOLDER_URL} alt="Sticker sem pack" className=${`w-full h-full object-contain transition-transform duration-300 ${lockedByNsfw ? 'blur-md scale-105' : 'group-hover:scale-110'}`} loading="lazy" />
1835
1476
  ${lockedByNsfw
1836
1477
  ? html`
1837
1478
  <div className="absolute inset-0 flex items-center justify-center bg-slate-950/35 p-2">
1838
- <button
1839
- type="button"
1840
- onClick=${() => onRequireLogin?.()}
1841
- className="inline-flex h-8 items-center rounded-lg border border-amber-400/35 bg-amber-500/15 px-2.5 text-[10px] font-semibold text-amber-100"
1842
- >
1843
- Entrar para desbloquear
1844
- </button>
1479
+ <button type="button" onClick=${() => onRequireLogin?.()} className="inline-flex h-8 items-center rounded-lg border border-amber-400/35 bg-amber-500/15 px-2.5 text-[10px] font-semibold text-amber-100">Entrar para desbloquear</button>
1845
1480
  </div>
1846
1481
  `
1847
1482
  : null}
1848
1483
  </div>
1849
- <div className="p-2">
1850
- ${primaryTag(sticker)
1851
- ? html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-300">${primaryTag(
1852
- sticker,
1853
- )}</span>`
1854
- : html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-400">sticker</span>`}
1855
- </div>
1484
+ <div className="p-2">${primaryTag(sticker) ? html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-300">${primaryTag(sticker)}</span>` : html`<span className="inline-flex rounded-full border border-slate-600 bg-slate-900/80 px-2 py-0.5 text-[10px] text-slate-400">sticker</span>`}</div>
1856
1485
  </article>
1857
1486
  `;
1858
1487
  }
@@ -1882,13 +1511,7 @@ function EmptyState({ onClear }) {
1882
1511
  <div className="text-5xl mb-2">🧩</div>
1883
1512
  <p className="text-slate-100 font-semibold">Nenhum pack encontrado</p>
1884
1513
  <p className="text-slate-400 text-sm mt-1">Tente outra busca ou remova os filtros ativos.</p>
1885
- <button
1886
- type="button"
1887
- onClick=${onClear}
1888
- 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"
1889
- >
1890
- Limpar filtros
1891
- </button>
1514
+ <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>
1892
1515
  </div>
1893
1516
  `;
1894
1517
  }
@@ -1907,30 +1530,13 @@ function CatalogSortPicker({ open = false, currentSort = DEFAULT_CATALOG_SORT, b
1907
1530
  <p className="text-[11px] uppercase tracking-wide text-slate-400">Ordenar catálogo</p>
1908
1531
  <p className="text-sm font-semibold text-slate-100">Escolha o tipo de ranking</p>
1909
1532
  </div>
1910
- <button
1911
- type="button"
1912
- onClick=${onClose}
1913
- className="h-8 rounded-lg border border-slate-700 px-2.5 text-xs text-slate-300 hover:bg-slate-800 disabled:opacity-60"
1914
- disabled=${busy}
1915
- >
1916
- Fechar
1917
- </button>
1533
+ <button type="button" onClick=${onClose} className="h-8 rounded-lg border border-slate-700 px-2.5 text-xs text-slate-300 hover:bg-slate-800 disabled:opacity-60" disabled=${busy}>Fechar</button>
1918
1534
  </div>
1919
1535
  <div className="space-y-1.5">
1920
1536
  ${CATALOG_SORT_OPTIONS.map((option) => {
1921
1537
  const active = selectedSort === option.value;
1922
1538
  return html`
1923
- <button
1924
- key=${option.value}
1925
- type="button"
1926
- onClick=${() => onSelect?.(option.value)}
1927
- disabled=${busy}
1928
- className=${`w-full rounded-xl border px-3 py-2.5 text-left transition disabled:opacity-60 ${
1929
- active
1930
- ? 'border-emerald-400/40 bg-emerald-500/10 text-emerald-100'
1931
- : 'border-slate-700 bg-slate-900/70 text-slate-200 hover:bg-slate-800'
1932
- }`}
1933
- >
1539
+ <button key=${option.value} type="button" onClick=${() => onSelect?.(option.value)} disabled=${busy} className=${`w-full rounded-xl border px-3 py-2.5 text-left transition disabled:opacity-60 ${active ? 'border-emerald-400/40 bg-emerald-500/10 text-emerald-100' : 'border-slate-700 bg-slate-900/70 text-slate-200 hover:bg-slate-800'}`}>
1934
1540
  <span className="flex items-center justify-between gap-2">
1935
1541
  <span className="inline-flex items-center gap-2">
1936
1542
  <span>${option.icon}</span>
@@ -1962,9 +1568,7 @@ function PackPageSkeleton() {
1962
1568
  <div className="h-20 rounded-xl bg-slate-800"></div>
1963
1569
  </div>
1964
1570
  </div>
1965
- <div className="grid grid-cols-3 gap-2 sm:gap-3">
1966
- ${Array.from({ length: 9 }).map((_, index) => html`<div key=${index} className="aspect-square rounded-xl border border-slate-700 bg-slate-800"></div>`)}
1967
- </div>
1571
+ <div className="grid grid-cols-3 gap-2 sm:gap-3">${Array.from({ length: 9 }).map((_, index) => html`<div key=${index} className="aspect-square rounded-xl border border-slate-700 bg-slate-800"></div>`)}</div>
1968
1572
  </section>
1969
1573
  `;
1970
1574
  }
@@ -1990,36 +1594,16 @@ function CreatorRankingSkeleton({ count = 8 }) {
1990
1594
  `;
1991
1595
  }
1992
1596
 
1993
- function CreatorsRankingPage({
1994
- creators = [],
1995
- loading = false,
1996
- error = '',
1997
- sort = DEFAULT_CREATORS_SORT,
1998
- onSortChange,
1999
- onBack,
2000
- onRetry,
2001
- onOpenCreator,
2002
- onOpenPack,
2003
- }) {
1597
+ function CreatorsRankingPage({ creators = [], loading = false, error = '', sort = DEFAULT_CREATORS_SORT, onSortChange, onBack, onRetry, onOpenCreator, onOpenPack }) {
2004
1598
  const selectedSort = normalizeCreatorsSort(sort);
2005
1599
 
2006
1600
  return html`
2007
1601
  <section className="space-y-4">
2008
1602
  <div className="flex flex-wrap items-center justify-between gap-2">
2009
- <button
2010
- type="button"
2011
- onClick=${onBack}
2012
- className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-700 px-3 text-sm text-slate-200 hover:bg-slate-800"
2013
- >
2014
- ← Voltar para catálogo
2015
- </button>
1603
+ <button type="button" onClick=${onBack} className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-700 px-3 text-sm text-slate-200 hover:bg-slate-800">← Voltar para catálogo</button>
2016
1604
  <div className="flex items-center gap-2">
2017
1605
  <span className="text-xs text-slate-400">Ordenar</span>
2018
- <select
2019
- value=${selectedSort}
2020
- onChange=${(event) => onSortChange?.(event.target.value)}
2021
- className="h-9 rounded-xl border border-slate-700 bg-slate-900 px-3 text-xs text-slate-200 outline-none"
2022
- >
1606
+ <select value=${selectedSort} onChange=${(event) => onSortChange?.(event.target.value)} className="h-9 rounded-xl border border-slate-700 bg-slate-900 px-3 text-xs text-slate-200 outline-none">
2023
1607
  <option value="popular">Popular</option>
2024
1608
  <option value="likes">Likes</option>
2025
1609
  <option value="downloads">Downloads</option>
@@ -2040,19 +1624,11 @@ function CreatorsRankingPage({
2040
1624
  <div className="rounded-2xl border border-rose-500/30 bg-rose-500/10 p-4">
2041
1625
  <p className="text-sm font-semibold text-rose-100">Falha ao carregar criadores</p>
2042
1626
  <p className="mt-1 text-xs text-rose-200/90">${error}</p>
2043
- <button
2044
- type="button"
2045
- onClick=${onRetry}
2046
- className="mt-3 inline-flex h-9 items-center rounded-xl border border-rose-400/40 px-3 text-xs text-rose-100 hover:bg-rose-500/10"
2047
- >
2048
- Tentar novamente
2049
- </button>
1627
+ <button type="button" onClick=${onRetry} className="mt-3 inline-flex h-9 items-center rounded-xl border border-rose-400/40 px-3 text-xs text-rose-100 hover:bg-rose-500/10">Tentar novamente</button>
2050
1628
  </div>
2051
1629
  `
2052
1630
  : null}
2053
-
2054
1631
  ${loading ? html`<${CreatorRankingSkeleton} />` : null}
2055
-
2056
1632
  ${!loading && !error && !creators.length
2057
1633
  ? html`
2058
1634
  <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/50 p-8 text-center">
@@ -2061,46 +1637,31 @@ function CreatorsRankingPage({
2061
1637
  </div>
2062
1638
  `
2063
1639
  : null}
2064
-
2065
1640
  ${!loading && creators.length
2066
1641
  ? html`
2067
1642
  <div className="space-y-2">
2068
- ${creators.map((creator, index) => html`
2069
- <article key=${creator.key || creator.publisher || index} className="fade-card rounded-2xl border border-slate-800 bg-slate-900/65 p-3">
2070
- <div className="flex items-center gap-3">
2071
- <img src=${creator.avatarUrl || getAvatarUrl(creator.publisher)} alt="" className="h-12 w-12 rounded-full bg-slate-800 border border-slate-700" loading="lazy" />
2072
- <div className="min-w-0 flex-1">
2073
- <div className="flex items-center gap-2">
2074
- <p className="truncate text-sm font-semibold text-slate-100">${creator.publisher || 'Criador'}</p>
2075
- ${creator.verified ? html`<span className="rounded-full border border-cyan-400/30 bg-cyan-500/10 px-2 py-0.5 text-[10px] text-cyan-200">Verificado</span>` : null}
2076
- </div>
2077
- <div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-slate-400">
2078
- <span>📦 ${shortNum(creator.packCount || 0)} packs</span>
2079
- <span>⬇ ${shortNum(creator.downloads || 0)} downloads</span>
2080
- <span>❤️ ${shortNum(creator.likes || 0)} likes</span>
1643
+ ${creators.map(
1644
+ (creator, index) => html`
1645
+ <article key=${creator.key || creator.publisher || index} className="fade-card rounded-2xl border border-slate-800 bg-slate-900/65 p-3">
1646
+ <div className="flex items-center gap-3">
1647
+ <img src=${creator.avatarUrl || getAvatarUrl(creator.publisher)} alt="" className="h-12 w-12 rounded-full bg-slate-800 border border-slate-700" loading="lazy" />
1648
+ <div className="min-w-0 flex-1">
1649
+ <div className="flex items-center gap-2">
1650
+ <p className="truncate text-sm font-semibold text-slate-100">${creator.publisher || 'Criador'}</p>
1651
+ ${creator.verified ? html`<span className="rounded-full border border-cyan-400/30 bg-cyan-500/10 px-2 py-0.5 text-[10px] text-cyan-200">Verificado</span>` : null}
1652
+ </div>
1653
+ <div className="mt-1 flex flex-wrap gap-x-3 gap-y-1 text-[11px] text-slate-400">
1654
+ <span>📦 ${shortNum(creator.packCount || 0)} packs</span>
1655
+ <span>⬇ ${shortNum(creator.downloads || 0)} downloads</span>
1656
+ <span>❤️ ${shortNum(creator.likes || 0)} likes</span>
1657
+ </div>
1658
+ ${creator.topPack?.name ? html` <button type="button" onClick=${() => onOpenPack?.(creator.topPack?.pack_key)} className="mt-1 text-[11px] text-cyan-300 hover:text-cyan-200">Top pack: ${creator.topPack.name}</button> ` : null}
2081
1659
  </div>
2082
- ${creator.topPack?.name
2083
- ? html`
2084
- <button
2085
- type="button"
2086
- onClick=${() => onOpenPack?.(creator.topPack?.pack_key)}
2087
- className="mt-1 text-[11px] text-cyan-300 hover:text-cyan-200"
2088
- >
2089
- Top pack: ${creator.topPack.name}
2090
- </button>
2091
- `
2092
- : null}
1660
+ <button type="button" onClick=${() => onOpenCreator?.(creator)} className="shrink-0 h-9 rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-100 hover:bg-emerald-500/20">Ver perfil</button>
2093
1661
  </div>
2094
- <button
2095
- type="button"
2096
- onClick=${() => onOpenCreator?.(creator)}
2097
- className="shrink-0 h-9 rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-100 hover:bg-emerald-500/20"
2098
- >
2099
- Ver perfil
2100
- </button>
2101
- </div>
2102
- </article>
2103
- `)}
1662
+ </article>
1663
+ `,
1664
+ )}
2104
1665
  </div>
2105
1666
  `
2106
1667
  : null}
@@ -2124,24 +1685,14 @@ function StickerPreview({ item, onClose, onPrev, onNext, hasNsfwAccess = true, o
2124
1685
  <button type="button" className="absolute inset-0" aria-label="Fechar preview" onClick=${onClose}></button>
2125
1686
 
2126
1687
  <div className="relative w-full max-w-xl rounded-2xl border border-slate-700 bg-slate-900 p-3">
2127
- <img
2128
- src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : item.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL}
2129
- alt=${item.accessibility_label || 'Sticker'}
2130
- className=${`w-full max-h-[70vh] object-contain rounded-xl bg-slate-950 ${lockedByNsfw ? 'blur-md' : ''}`}
2131
- />
1688
+ <img src=${lockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : item.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL} alt=${item.accessibility_label || 'Sticker'} className=${`w-full max-h-[70vh] object-contain rounded-xl bg-slate-950 ${lockedByNsfw ? 'blur-md' : ''}`} />
2132
1689
  ${lockedByNsfw
2133
1690
  ? html`
2134
1691
  <div className="absolute inset-x-3 top-3 bottom-[64px] flex items-center justify-center rounded-xl bg-slate-950/40 p-4">
2135
1692
  <div className="max-w-sm rounded-xl border border-amber-500/35 bg-slate-950/80 px-4 py-3 text-center">
2136
1693
  <p className="text-sm font-semibold text-amber-100">Conteúdo sensível</p>
2137
1694
  <p className="mt-1 text-xs text-slate-300">Faça login com Google para desbloquear este sticker.</p>
2138
- <button
2139
- type="button"
2140
- onClick=${() => onRequireLogin?.()}
2141
- className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/40 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100"
2142
- >
2143
- Entrar e desbloquear
2144
- </button>
1695
+ <button type="button" onClick=${() => onRequireLogin?.()} className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/40 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100">Entrar e desbloquear</button>
2145
1696
  </div>
2146
1697
  </div>
2147
1698
  `
@@ -2150,9 +1701,7 @@ function StickerPreview({ item, onClose, onPrev, onNext, hasNsfwAccess = true, o
2150
1701
  <div className="mt-3 flex items-center justify-between gap-2">
2151
1702
  <button type="button" onClick=${onPrev} className="rounded-lg border border-slate-600 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">← Anterior</button>
2152
1703
  <div className="flex items-center gap-2">
2153
- ${lockedByNsfw
2154
- ? null
2155
- : html`<button type="button" onClick=${handleCopy} className="rounded-lg border border-slate-600 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">Copiar link</button>`}
1704
+ ${lockedByNsfw ? null : html`<button type="button" onClick=${handleCopy} className="rounded-lg border border-slate-600 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">Copiar link</button>`}
2156
1705
  <button type="button" onClick=${onClose} className="rounded-lg border border-slate-600 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">Fechar</button>
2157
1706
  </div>
2158
1707
  <button type="button" onClick=${onNext} className="rounded-lg border border-slate-600 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">Próximo →</button>
@@ -2162,29 +1711,11 @@ function StickerPreview({ item, onClose, onPrev, onNext, hasNsfwAccess = true, o
2162
1711
  `;
2163
1712
  }
2164
1713
 
2165
- function PackPage({
2166
- pack,
2167
- relatedPacks,
2168
- onBack,
2169
- onOpenRelated,
2170
- onLike,
2171
- onDislike,
2172
- onTagClick,
2173
- reactionLoading = '',
2174
- reactionNotice = null,
2175
- hasNsfwAccess = true,
2176
- onRequireLogin,
2177
- }) {
1714
+ function PackPage({ pack, relatedPacks, onBack, onOpenRelated, onLike, onDislike, onTagClick, reactionLoading = '', reactionNotice = null, hasNsfwAccess = true, onRequireLogin }) {
2178
1715
  if (!pack) {
2179
1716
  return html`
2180
1717
  <section className="space-y-4">
2181
- <button
2182
- type="button"
2183
- onClick=${onBack}
2184
- className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800"
2185
- >
2186
- ← Voltar para catálogo
2187
- </button>
1718
+ <button type="button" onClick=${onBack} className="inline-flex items-center gap-2 rounded-xl border border-slate-700 px-3 py-2 text-sm text-slate-200 hover:bg-slate-800">← Voltar para catálogo</button>
2188
1719
  <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/50 p-8 text-center">
2189
1720
  <p className="text-sm font-semibold text-slate-100">Pack não encontrado</p>
2190
1721
  <p className="mt-1 text-xs text-slate-400">Tente voltar ao catálogo e abrir novamente.</p>
@@ -2196,11 +1727,8 @@ function PackPage({
2196
1727
  const items = Array.isArray(pack?.items) ? pack.items : [];
2197
1728
  const tags = Array.isArray(pack?.tags) ? pack.tags : [];
2198
1729
  const packLockedByNsfw = isPackMarkedNsfw(pack) && !hasNsfwAccess;
2199
- const cover = packLockedByNsfw
2200
- ? NSFW_STICKER_PLACEHOLDER_URL
2201
- : pack?.cover_url || items?.[0]?.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL;
1730
+ const cover = packLockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : pack?.cover_url || items?.[0]?.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL;
2202
1731
  const whatsappUrl = String(pack?.whatsapp?.url || '').trim();
2203
- const packApiPath = `/api/sticker-packs/${encodeURIComponent(String(pack?.pack_key || '').trim())}`;
2204
1732
  const engagement = getPackEngagement(pack);
2205
1733
  const hasReactionRequest = Boolean(reactionLoading);
2206
1734
  const [previewIndex, setPreviewIndex] = useState(-1);
@@ -2208,35 +1736,18 @@ function PackPage({
2208
1736
 
2209
1737
  return html`
2210
1738
  <section className="space-y-4 pb-4">
2211
- <button
2212
- type="button"
2213
- onClick=${onBack}
2214
- className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-700 bg-slate-900/70 px-3 text-sm text-slate-200 hover:bg-slate-800"
2215
- >
2216
- ← Voltar para catálogo
2217
- </button>
1739
+ <button type="button" onClick=${onBack} className="inline-flex h-10 items-center gap-2 rounded-xl border border-slate-700 bg-slate-900/70 px-3 text-sm text-slate-200 hover:bg-slate-800">← Voltar para catálogo</button>
2218
1740
 
2219
1741
  <article className="overflow-hidden rounded-2xl border border-slate-700 bg-slate-900/80 shadow-[0_18px_40px_rgba(2,6,23,0.25)]">
2220
1742
  <div className="relative h-52 sm:h-64 md:h-72 bg-slate-900">
2221
- <img
2222
- src=${cover}
2223
- alt=${`Capa ${pack?.name || 'Pack'}`}
2224
- className=${`h-full w-full object-cover ${packLockedByNsfw ? 'blur-md scale-105' : ''}`}
2225
- loading="lazy"
2226
- />
1743
+ <img src=${cover} alt=${`Capa ${pack?.name || 'Pack'}`} className=${`h-full w-full object-cover ${packLockedByNsfw ? 'blur-md scale-105' : ''}`} loading="lazy" />
2227
1744
  <div className="absolute inset-0 bg-gradient-to-t from-slate-950 via-slate-950/55 to-transparent"></div>
2228
- ${packLockedByNsfw
2229
- ? html`<div className="absolute top-3 left-3 rounded-full border border-amber-300/40 bg-amber-500/25 px-2 py-1 text-[10px] font-semibold text-amber-100">🔞 Conteúdo sensível</div>`
2230
- : null}
1745
+ ${packLockedByNsfw ? html`<div className="absolute top-3 left-3 rounded-full border border-amber-300/40 bg-amber-500/25 px-2 py-1 text-[10px] font-semibold text-amber-100">🔞 Conteúdo sensível</div>` : null}
2231
1746
  <div className="absolute inset-x-0 bottom-0 p-4 sm:p-5">
2232
1747
  <div className="max-w-4xl">
2233
1748
  <p className="text-[11px] uppercase tracking-wide text-slate-300/90">Pack público</p>
2234
- <h1 className="mt-1 text-xl sm:text-2xl font-extrabold tracking-tight text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.35)]">
2235
- ${pack?.name || 'Pack'}
2236
- </h1>
2237
- <p className="mt-1 text-xs sm:text-sm text-slate-200/90">
2238
- ${pack?.publisher || '-'} · ${pack?.created_at ? new Date(pack.created_at).toLocaleDateString('pt-BR') : 'sem data'}
2239
- </p>
1749
+ <h1 className="mt-1 text-xl sm:text-2xl font-extrabold tracking-tight text-white drop-shadow-[0_2px_12px_rgba(0,0,0,0.35)]">${pack?.name || 'Pack'}</h1>
1750
+ <p className="mt-1 text-xs sm:text-sm text-slate-200/90">${pack?.publisher || '-'} · ${pack?.created_at ? new Date(pack.created_at).toLocaleDateString('pt-BR') : 'sem data'}</p>
2240
1751
  </div>
2241
1752
  </div>
2242
1753
  </div>
@@ -2247,13 +1758,7 @@ function PackPage({
2247
1758
  <div className="rounded-xl border border-amber-500/35 bg-amber-500/10 p-3">
2248
1759
  <p className="text-sm font-semibold text-amber-100">Este pack foi marcado como sensível (+18).</p>
2249
1760
  <p className="mt-1 text-xs text-slate-300">Faça login com Google para visualizar os stickers.</p>
2250
- <button
2251
- type="button"
2252
- onClick=${() => onRequireLogin?.()}
2253
- className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/35 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100"
2254
- >
2255
- Entrar para desbloquear
2256
- </button>
1761
+ <button type="button" onClick=${() => onRequireLogin?.()} className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/35 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100">Entrar para desbloquear</button>
2257
1762
  </div>
2258
1763
  `
2259
1764
  : null}
@@ -2264,88 +1769,20 @@ function PackPage({
2264
1769
  <span className="text-slate-200">👁 <strong className="font-semibold">${shortNum(engagement.openCount)}</strong></span>
2265
1770
  </div>
2266
1771
 
2267
- ${whatsappUrl && !packLockedByNsfw
2268
- ? html`
2269
- <a
2270
- href=${whatsappUrl}
2271
- target="_blank"
2272
- rel="noreferrer noopener"
2273
- className="inline-flex h-11 w-full items-center justify-center rounded-xl bg-[#25D366] px-5 text-sm font-bold text-[#042d17] shadow-[0_8px_24px_rgba(37,211,102,0.30)] transition hover:brightness-95"
2274
- >
2275
- 🟢 Adicionar no WhatsApp
2276
- </a>
2277
- `
2278
- : null}
1772
+ ${whatsappUrl && !packLockedByNsfw ? html` <a href=${whatsappUrl} target="_blank" rel="noreferrer noopener" className="inline-flex h-11 w-full items-center justify-center rounded-xl bg-[#25D366] px-5 text-sm font-bold text-[#042d17] shadow-[0_8px_24px_rgba(37,211,102,0.30)] transition hover:brightness-95"> 🟢 Adicionar no WhatsApp </a> ` : null}
2279
1773
 
2280
1774
  <div className="flex flex-wrap items-center gap-2">
2281
- <button
2282
- type="button"
2283
- onClick=${() => onLike?.(pack?.pack_key)}
2284
- disabled=${hasReactionRequest || packLockedByNsfw}
2285
- className="inline-flex h-9 items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-100 transition hover:bg-emerald-500/20 disabled:cursor-not-allowed disabled:opacity-60"
2286
- >
2287
- ${reactionLoading === 'like' ? 'Enviando...' : 'Curtir'}
2288
- </button>
2289
- <button
2290
- type="button"
2291
- onClick=${() => onDislike?.(pack?.pack_key)}
2292
- disabled=${hasReactionRequest || packLockedByNsfw}
2293
- className="inline-flex h-9 items-center justify-center rounded-xl border border-rose-500/35 bg-rose-500/10 px-3 text-xs font-semibold text-rose-100 transition hover:bg-rose-500/20 disabled:cursor-not-allowed disabled:opacity-60"
2294
- >
2295
- ${reactionLoading === 'dislike' ? 'Enviando...' : 'Não curtir'}
2296
- </button>
2297
- ${reactionNotice?.message
2298
- ? html`
2299
- <span className=${`text-xs ${reactionNotice.type === 'error' ? 'text-rose-300' : 'text-emerald-300'}`}>
2300
- ${reactionNotice.message}
2301
- </span>
2302
- `
2303
- : null}
1775
+ <button type="button" onClick=${() => onLike?.(pack?.pack_key)} disabled=${hasReactionRequest || packLockedByNsfw} className="inline-flex h-9 items-center justify-center rounded-xl border border-emerald-500/35 bg-emerald-500/10 px-3 text-xs font-semibold text-emerald-100 transition hover:bg-emerald-500/20 disabled:cursor-not-allowed disabled:opacity-60">${reactionLoading === 'like' ? 'Enviando...' : 'Curtir'}</button>
1776
+ <button type="button" onClick=${() => onDislike?.(pack?.pack_key)} disabled=${hasReactionRequest || packLockedByNsfw} className="inline-flex h-9 items-center justify-center rounded-xl border border-rose-500/35 bg-rose-500/10 px-3 text-xs font-semibold text-rose-100 transition hover:bg-rose-500/20 disabled:cursor-not-allowed disabled:opacity-60">${reactionLoading === 'dislike' ? 'Enviando...' : 'Não curtir'}</button>
1777
+ ${reactionNotice?.message ? html` <span className=${`text-xs ${reactionNotice.type === 'error' ? 'text-rose-300' : 'text-emerald-300'}`}> ${reactionNotice.message} </span> ` : null}
2304
1778
  </div>
2305
1779
 
2306
- ${pack?.description
2307
- ? html`<p className="text-sm leading-6 text-slate-300">${pack.description}</p>`
2308
- : null}
2309
-
2310
- <section className="rounded-xl border border-cyan-500/25 bg-cyan-500/5 p-3">
2311
- <p className="text-[11px] uppercase tracking-wide text-cyan-200">Use este pack no seu bot</p>
2312
- <p className="mt-1 text-sm text-slate-200">
2313
- Este pack faz parte do módulo de stickers da plataforma OmniZap. Você pode consumir por API e conectar ao seu fluxo de automação WhatsApp.
2314
- </p>
2315
- <pre className="mt-2 overflow-auto rounded-lg border border-slate-800 bg-slate-950/60 p-2 text-[11px] text-slate-300">
2316
- GET ${packApiPath}
2317
- POST ${packApiPath}/open
2318
- POST ${packApiPath}/like
2319
- </pre>
2320
- <div className="mt-2 flex flex-wrap gap-2">
2321
- <a href="/api-docs/" className="inline-flex h-8 items-center rounded-lg border border-cyan-500/35 bg-cyan-500/10 px-3 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20">
2322
- Ver API e exemplos
2323
- </a>
2324
- <a href="/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800">
2325
- Plataforma OmniZap
2326
- </a>
2327
- <a href="/stickers/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800">
2328
- Voltar ao catálogo
2329
- </a>
2330
- </div>
2331
- </section>
2332
-
1780
+ ${pack?.description ? html`<p className="text-sm leading-6 text-slate-300">${pack.description}</p>` : null}
2333
1781
  ${tags.length
2334
1782
  ? html`
2335
1783
  <div className="space-y-1">
2336
1784
  <p className="text-[11px] uppercase tracking-wide text-slate-500">Tags</p>
2337
- <div className="chips-scroll -mx-1 flex gap-2 overflow-x-auto px-1 pb-1">
2338
- ${tags.slice(0, 12).map((tag) => html`
2339
- <button
2340
- key=${tag}
2341
- type="button"
2342
- onClick=${() => onTagClick?.(tag)}
2343
- className="chip-item shrink-0 rounded-full border border-slate-700 bg-slate-900/90 px-2.5 py-1 text-[11px] text-slate-200 hover:border-cyan-400/30 hover:bg-cyan-500/10"
2344
- >
2345
- ${tagLabel(tag)}
2346
- </button>
2347
- `)}
2348
- </div>
1785
+ <div className="chips-scroll -mx-1 flex gap-2 overflow-x-auto px-1 pb-1">${tags.slice(0, 12).map((tag) => html` <button key=${tag} type="button" onClick=${() => onTagClick?.(tag)} className="chip-item shrink-0 rounded-full border border-slate-700 bg-slate-900/90 px-2.5 py-1 text-[11px] text-slate-200 hover:border-cyan-400/30 hover:bg-cyan-500/10">${tagLabel(tag)}</button> `)}</div>
2349
1786
  </div>
2350
1787
  `
2351
1788
  : null}
@@ -2363,57 +1800,35 @@ POST ${packApiPath}/like
2363
1800
  <div className="rounded-2xl border border-dashed border-amber-500/35 bg-slate-900/55 p-8 text-center">
2364
1801
  <p className="text-sm font-semibold text-amber-100">Stickers bloqueados por conteúdo sensível.</p>
2365
1802
  <p className="mt-1 text-xs text-slate-300">Entre com Google para liberar a visualização deste pack.</p>
2366
- <button
2367
- type="button"
2368
- onClick=${() => onRequireLogin?.()}
2369
- className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/35 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100"
2370
- >
2371
- Entrar e desbloquear
2372
- </button>
1803
+ <button type="button" onClick=${() => onRequireLogin?.()} className="mt-3 inline-flex h-9 items-center rounded-xl border border-amber-400/35 bg-amber-500/15 px-3 text-xs font-semibold text-amber-100">Entrar e desbloquear</button>
2373
1804
  </div>
2374
1805
  `
2375
1806
  : items.length
2376
- ? html`
2377
- <div className="pack-stickers-grid gap-2 sm:gap-3">
2378
- ${items.map(
2379
- (item, index) => {
1807
+ ? html`
1808
+ <div className="pack-stickers-grid gap-2 sm:gap-3">
1809
+ ${items.map((item, index) => {
2380
1810
  const stickerLockedByNsfw = isStickerMarkedNsfw(item) && !hasNsfwAccess;
2381
1811
  return html`
2382
- <button
2383
- key=${item.sticker_id || item.position || index}
2384
- type="button"
2385
- onClick=${() => (stickerLockedByNsfw ? onRequireLogin?.() : setPreviewIndex(index))}
2386
- className="pack-sticker-card group relative overflow-hidden rounded-xl border border-slate-800 bg-slate-900/80 text-left transition hover:-translate-y-0.5 hover:border-slate-600"
2387
- >
2388
- <img
2389
- src=${stickerLockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : item.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL}
2390
- alt=${item.accessibility_label || 'Sticker'}
2391
- loading="lazy"
2392
- className=${`w-full aspect-square object-contain bg-slate-950 transition-transform duration-300 ${
2393
- stickerLockedByNsfw ? 'blur-md scale-105' : 'group-hover:scale-105'
2394
- }`}
2395
- />
1812
+ <button key=${item.sticker_id || item.position || index} type="button" onClick=${() => (stickerLockedByNsfw ? onRequireLogin?.() : setPreviewIndex(index))} className="pack-sticker-card group relative overflow-hidden rounded-xl border border-slate-800 bg-slate-900/80 text-left transition hover:-translate-y-0.5 hover:border-slate-600">
1813
+ <img src=${stickerLockedByNsfw ? NSFW_STICKER_PLACEHOLDER_URL : item.asset_url || DEFAULT_STICKER_PLACEHOLDER_URL} alt=${item.accessibility_label || 'Sticker'} loading="lazy" className=${`w-full aspect-square object-contain bg-slate-950 transition-transform duration-300 ${stickerLockedByNsfw ? 'blur-md scale-105' : 'group-hover:scale-105'}`} />
2396
1814
  ${stickerLockedByNsfw
2397
1815
  ? html`
2398
1816
  <div className="absolute inset-0 flex items-center justify-center bg-slate-950/35 p-2">
2399
- <span className="rounded-lg border border-amber-400/35 bg-amber-500/15 px-2 py-1 text-[10px] font-semibold text-amber-100">
2400
- Entrar para desbloquear
2401
- </span>
1817
+ <span className="rounded-lg border border-amber-400/35 bg-amber-500/15 px-2 py-1 text-[10px] font-semibold text-amber-100"> Entrar para desbloquear </span>
2402
1818
  </div>
2403
1819
  `
2404
1820
  : null}
2405
1821
  </button>
2406
1822
  `;
2407
- },
2408
- )}
2409
- </div>
2410
- `
2411
- : html`
2412
- <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/40 p-8 text-center">
2413
- <p className="text-sm font-semibold text-slate-100">Este pack ainda não tem stickers visíveis.</p>
2414
- <p className="mt-1 text-xs text-slate-400">Volte mais tarde ou abra outro pack do catálogo.</p>
2415
- </div>
2416
- `}
1823
+ })}
1824
+ </div>
1825
+ `
1826
+ : html`
1827
+ <div className="rounded-2xl border border-dashed border-slate-700 bg-slate-900/40 p-8 text-center">
1828
+ <p className="text-sm font-semibold text-slate-100">Este pack ainda não tem stickers visíveis.</p>
1829
+ <p className="mt-1 text-xs text-slate-400">Volte mais tarde ou abra outro pack do catálogo.</p>
1830
+ </div>
1831
+ `}
2417
1832
  </section>
2418
1833
 
2419
1834
  ${relatedPacks.length
@@ -2423,27 +1838,11 @@ POST ${packApiPath}/like
2423
1838
  <h2 className="text-lg font-bold">Packs relacionados</h2>
2424
1839
  <span className="text-xs text-slate-500">${relatedPacks.length} sugestões</span>
2425
1840
  </div>
2426
- <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2.5 sm:gap-3">
2427
- ${relatedPacks.map(
2428
- (entry, index) => html`<div key=${entry.pack_key || entry.id} className="fade-card"><${PackCard} pack=${entry} index=${index} onOpen=${onOpenRelated} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${onRequireLogin} /></div>`,
2429
- )}
2430
- </div>
1841
+ <div className="grid grid-cols-2 md:grid-cols-3 lg:grid-cols-4 xl:grid-cols-5 gap-2.5 sm:gap-3">${relatedPacks.map((entry, index) => html`<div key=${entry.pack_key || entry.id} className="fade-card"><${PackCard} pack=${entry} index=${index} onOpen=${onOpenRelated} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${onRequireLogin} /></div>`)}</div>
2431
1842
  </section>
2432
1843
  `
2433
1844
  : null}
2434
-
2435
- ${previewIndex >= 0
2436
- ? html`
2437
- <${StickerPreview}
2438
- item=${currentPreviewItem}
2439
- onClose=${() => setPreviewIndex(-1)}
2440
- onPrev=${() => setPreviewIndex((value) => (value <= 0 ? items.length - 1 : value - 1))}
2441
- onNext=${() => setPreviewIndex((value) => (value >= items.length - 1 ? 0 : value + 1))}
2442
- hasNsfwAccess=${hasNsfwAccess}
2443
- onRequireLogin=${onRequireLogin}
2444
- />
2445
- `
2446
- : null}
1845
+ ${previewIndex >= 0 ? html` <${StickerPreview} item=${currentPreviewItem} onClose=${() => setPreviewIndex(-1)} onPrev=${() => setPreviewIndex((value) => (value <= 0 ? items.length - 1 : value - 1))} onNext=${() => setPreviewIndex((value) => (value >= items.length - 1 ? 0 : value + 1))} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${onRequireLogin} /> ` : null}
2447
1846
  </section>
2448
1847
  `;
2449
1848
  }
@@ -2571,11 +1970,7 @@ function StickersApp() {
2571
1970
 
2572
1971
  if (activeCategory && !sortedTags.some((entry) => entry.value === activeCategory)) {
2573
1972
  const meta = CATEGORY_META.get(activeCategory) || null;
2574
- sortedTags.unshift(
2575
- meta
2576
- ? { value: activeCategory, label: `${meta.icon} ${meta.label}`, icon: meta.icon }
2577
- : { value: activeCategory, label: `🏷️ ${activeCategory.replace(/-/g, ' ')}`, icon: '🏷️' },
2578
- );
1973
+ sortedTags.unshift(meta ? { value: activeCategory, label: `${meta.icon} ${meta.label}`, icon: meta.icon } : { value: activeCategory, label: `🏷️ ${activeCategory.replace(/-/g, ' ')}`, icon: '🏷️' });
2579
1974
  }
2580
1975
 
2581
1976
  return [{ value: '', label: '🔥 Em alta', icon: '🔥' }, ...sortedTags];
@@ -2624,9 +2019,7 @@ function StickersApp() {
2624
2019
  icon: '🕘',
2625
2020
  }));
2626
2021
  }
2627
- return tagSuggestions
2628
- .filter((item) => normalizeToken(item.value).includes(q) || normalizeToken(item.label).includes(q))
2629
- .slice(0, 8);
2022
+ return tagSuggestions.filter((item) => normalizeToken(item.value).includes(q) || normalizeToken(item.label).includes(q)).slice(0, 8);
2630
2023
  }, [query, tagSuggestions, recentSearches]);
2631
2024
 
2632
2025
  const sortedPacks = useMemo(() => {
@@ -2662,10 +2055,7 @@ function StickersApp() {
2662
2055
  return list;
2663
2056
  }, [packs, sortBy]);
2664
2057
 
2665
- const categoryActiveLabel =
2666
- catalogFilter === 'trending'
2667
- ? 'Em alta agora'
2668
- : dynamicCategoryOptions.find((entry) => entry.value === activeCategory)?.label?.replace(/^.+?\s/, '') || 'Todas';
2058
+ const categoryActiveLabel = catalogFilter === 'trending' ? 'Em alta agora' : dynamicCategoryOptions.find((entry) => entry.value === activeCategory)?.label?.replace(/^.+?\s/, '') || 'Todas';
2669
2059
  const growingNowPacks = useMemo(() => {
2670
2060
  return [...packs]
2671
2061
  .map((pack) => {
@@ -2779,13 +2169,11 @@ function StickersApp() {
2779
2169
  const recentPublishedPacks = useMemo(
2780
2170
  () =>
2781
2171
  [...packs]
2782
- .sort(
2783
- (a, b) => {
2784
- const completenessDelta = comparePacksByCompleteness(a, b);
2785
- if (completenessDelta !== 0) return completenessDelta;
2786
- return new Date(b?.created_at || b?.updated_at || 0).getTime() - new Date(a?.created_at || a?.updated_at || 0).getTime();
2787
- },
2788
- )
2172
+ .sort((a, b) => {
2173
+ const completenessDelta = comparePacksByCompleteness(a, b);
2174
+ if (completenessDelta !== 0) return completenessDelta;
2175
+ return new Date(b?.created_at || b?.updated_at || 0).getTime() - new Date(a?.created_at || a?.updated_at || 0).getTime();
2176
+ })
2789
2177
  .slice(0, 10),
2790
2178
  [packs],
2791
2179
  );
@@ -2893,8 +2281,7 @@ function StickersApp() {
2893
2281
  const hasGoogleLogin = Boolean(googleAuth.user?.sub);
2894
2282
  const hasNsfwAccess = hasGoogleLogin;
2895
2283
  const googleLoginEnabled = Boolean(googleAuthConfig.enabled && googleAuthConfig.clientId);
2896
- const shouldRenderGoogleButton =
2897
- isProfileView && googleLoginEnabled && !hasGoogleLogin && googleSessionChecked && !googleAuthBusy;
2284
+ const shouldRenderGoogleButton = isProfileView && googleLoginEnabled && !hasGoogleLogin && googleSessionChecked && !googleAuthBusy;
2898
2285
 
2899
2286
  const fetchJson = async (url, options = undefined) => {
2900
2287
  const opts = options && typeof options === 'object' ? { ...options } : {};
@@ -3034,10 +2421,12 @@ function StickersApp() {
3034
2421
  });
3035
2422
  };
3036
2423
 
3037
- const buildManagePackApiPath = (packKey, suffix = '') =>
3038
- `${config.apiBasePath}/${encodeURIComponent(String(packKey || ''))}/manage${suffix}`;
2424
+ const buildManagePackApiPath = (packKey, suffix = '') => `${config.apiBasePath}/${encodeURIComponent(String(packKey || ''))}/manage${suffix}`;
3039
2425
 
3040
- const getManagedMutationStatus = (payload) => String(payload?.data?.status || payload?.status || '').trim().toLowerCase();
2426
+ const getManagedMutationStatus = (payload) =>
2427
+ String(payload?.data?.status || payload?.status || '')
2428
+ .trim()
2429
+ .toLowerCase();
3041
2430
 
3042
2431
  const applyManagedPackToMyList = (managedData) => {
3043
2432
  const pack = managedData?.pack || null;
@@ -3130,8 +2519,7 @@ function StickersApp() {
3130
2519
  applyManagedPackToMyList(managed);
3131
2520
  }
3132
2521
 
3133
- const targetKey =
3134
- String(managed?.pack?.pack_key || envelope?.data?.pack_key || managePackTargetKey || '').trim() || '';
2522
+ const targetKey = String(managed?.pack?.pack_key || envelope?.data?.pack_key || managePackTargetKey || '').trim() || '';
3135
2523
  if (targetKey && managePackOpen && String(managePackTargetKey || '') === targetKey) {
3136
2524
  try {
3137
2525
  await loadManagePackData(targetKey, { openModal: true, silent: true });
@@ -3192,9 +2580,7 @@ function StickersApp() {
3192
2580
  const applyPackEngagement = (packKey, engagement) => {
3193
2581
  if (!packKey || !engagement) return;
3194
2582
  setPacks((prev) => prev.map((entry) => (entry?.pack_key === packKey ? mergeEngagementInPack(entry, engagement) : entry)));
3195
- setRelatedPacks((prev) =>
3196
- prev.map((entry) => (entry?.pack_key === packKey ? mergeEngagementInPack(entry, engagement) : entry)),
3197
- );
2583
+ setRelatedPacks((prev) => prev.map((entry) => (entry?.pack_key === packKey ? mergeEngagementInPack(entry, engagement) : entry)));
3198
2584
  setCurrentPack((prev) => (prev?.pack_key === packKey ? mergeEngagementInPack(prev, engagement) : prev));
3199
2585
  };
3200
2586
 
@@ -3252,7 +2638,7 @@ function StickersApp() {
3252
2638
 
3253
2639
  setPacks((prev) => (reset ? data : prev.concat(data)));
3254
2640
  setPackHasMore(hasMore);
3255
- setPackOffset(Number.isFinite(next) ? next : (reset ? data.length : nextOffset + data.length));
2641
+ setPackOffset(Number.isFinite(next) ? next : reset ? data.length : nextOffset + data.length);
3256
2642
  } catch (err) {
3257
2643
  setError(err?.message || 'Falha ao carregar packs');
3258
2644
  if (reset) setPacks([]);
@@ -3363,9 +2749,7 @@ function StickersApp() {
3363
2749
  else if (Array.isArray(pack?.tags) && pack.tags[0]) relatedParams.set('categories', pack.tags[0]);
3364
2750
 
3365
2751
  const relatedPayload = await fetchJson(`${config.apiBasePath}?${relatedParams.toString()}`);
3366
- const relatedList = (Array.isArray(relatedPayload?.data) ? relatedPayload.data : [])
3367
- .filter((entry) => entry.pack_key && entry.pack_key !== pack?.pack_key)
3368
- .slice(0, 8);
2752
+ const relatedList = (Array.isArray(relatedPayload?.data) ? relatedPayload.data : []).filter((entry) => entry.pack_key && entry.pack_key !== pack?.pack_key).slice(0, 8);
3369
2753
  setRelatedPacks(relatedList);
3370
2754
  };
3371
2755
 
@@ -3414,14 +2798,14 @@ function StickersApp() {
3414
2798
  }
3415
2799
  };
3416
2800
 
3417
- const buildCatalogWebUrl = ({
3418
- q = appliedQuery,
3419
- category = activeCategory,
3420
- sort = sortBy,
3421
- filter = catalogFilter,
3422
- } = {}) => {
2801
+ const buildCatalogWebUrl = ({ q = appliedQuery, category = activeCategory, sort = sortBy, filter = catalogFilter } = {}) => {
3423
2802
  const params = new URLSearchParams();
3424
- const normalizedFilter = String(filter || '').trim().toLowerCase() === 'trending' ? 'trending' : '';
2803
+ const normalizedFilter =
2804
+ String(filter || '')
2805
+ .trim()
2806
+ .toLowerCase() === 'trending'
2807
+ ? 'trending'
2808
+ : '';
3425
2809
  const normalizedSort = normalizeCatalogSort(sort || DEFAULT_CATALOG_SORT);
3426
2810
  if (normalizedFilter === 'trending') {
3427
2811
  params.set('filter', 'trending');
@@ -3441,10 +2825,19 @@ function StickersApp() {
3441
2825
  };
3442
2826
 
3443
2827
  const applyCatalogViewState = ({ q = '', category = '', sort = DEFAULT_CATALOG_SORT, filter = '' } = {}) => {
3444
- const normalizedFilter = String(filter || '').trim().toLowerCase() === 'trending' ? 'trending' : '';
2828
+ const normalizedFilter =
2829
+ String(filter || '')
2830
+ .trim()
2831
+ .toLowerCase() === 'trending'
2832
+ ? 'trending'
2833
+ : '';
3445
2834
  const normalizedSort = normalizeCatalogSort(normalizedFilter ? 'trending' : sort || DEFAULT_CATALOG_SORT);
3446
2835
  const nextQ = normalizedFilter ? '' : String(q || '').trim();
3447
- const nextCategory = normalizedFilter ? '' : String(category || '').trim().toLowerCase();
2836
+ const nextCategory = normalizedFilter
2837
+ ? ''
2838
+ : String(category || '')
2839
+ .trim()
2840
+ .toLowerCase();
3448
2841
 
3449
2842
  setCatalogFilter(normalizedFilter);
3450
2843
  setSortBy(normalizedSort);
@@ -3475,7 +2868,12 @@ function StickersApp() {
3475
2868
  q,
3476
2869
  category,
3477
2870
  sort: normalizeCatalogSort(sort || DEFAULT_CATALOG_SORT),
3478
- filter: String(filter || '').trim().toLowerCase() === 'trending' ? 'trending' : '',
2871
+ filter:
2872
+ String(filter || '')
2873
+ .trim()
2874
+ .toLowerCase() === 'trending'
2875
+ ? 'trending'
2876
+ : '',
3479
2877
  };
3480
2878
  if (push) window.history.pushState({}, '', buildCatalogWebUrl(nextState));
3481
2879
  applyCatalogViewState(nextState);
@@ -3518,7 +2916,9 @@ function StickersApp() {
3518
2916
  };
3519
2917
 
3520
2918
  const openCatalogTagFilter = (tag) => {
3521
- const nextTag = String(tag || '').trim().toLowerCase();
2919
+ const nextTag = String(tag || '')
2920
+ .trim()
2921
+ .toLowerCase();
3522
2922
  if (!nextTag) return;
3523
2923
  openCatalogWithState({
3524
2924
  q: '',
@@ -3834,7 +3234,9 @@ function StickersApp() {
3834
3234
  };
3835
3235
 
3836
3236
  const handleCategoryChipPress = (value) => {
3837
- const nextValue = String(value || '').trim().toLowerCase();
3237
+ const nextValue = String(value || '')
3238
+ .trim()
3239
+ .toLowerCase();
3838
3240
  const previousScrollY = window.scrollY || 0;
3839
3241
 
3840
3242
  if (!nextValue) {
@@ -3871,9 +3273,7 @@ function StickersApp() {
3871
3273
  like_count: safeNumber(sourcePack?.engagement?.like_count),
3872
3274
  dislike_count: safeNumber(sourcePack?.engagement?.dislike_count),
3873
3275
  comment_count: safeNumber(sourcePack?.engagement?.comment_count),
3874
- score:
3875
- safeNumber(sourcePack?.engagement?.score) ||
3876
- safeNumber(sourcePack?.engagement?.like_count) - safeNumber(sourcePack?.engagement?.dislike_count),
3276
+ score: safeNumber(sourcePack?.engagement?.score) || safeNumber(sourcePack?.engagement?.like_count) - safeNumber(sourcePack?.engagement?.dislike_count),
3877
3277
  updated_at: sourcePack?.engagement?.updated_at || null,
3878
3278
  };
3879
3279
  const optimistic = {
@@ -3973,7 +3373,12 @@ function StickersApp() {
3973
3373
  const raw = localStorage.getItem(SEARCH_HISTORY_KEY);
3974
3374
  const parsed = JSON.parse(raw || '[]');
3975
3375
  if (Array.isArray(parsed)) {
3976
- setRecentSearches(parsed.map((entry) => String(entry || '').trim()).filter(Boolean).slice(0, 8));
3376
+ setRecentSearches(
3377
+ parsed
3378
+ .map((entry) => String(entry || '').trim())
3379
+ .filter(Boolean)
3380
+ .slice(0, 8),
3381
+ );
3977
3382
  }
3978
3383
  } catch {}
3979
3384
  }, []);
@@ -4262,9 +3667,7 @@ function StickersApp() {
4262
3667
  }`}
4263
3668
  </style>
4264
3669
 
4265
- <header className=${`sticky top-0 z-30 border-b border-slate-800 bg-slate-950/95 backdrop-blur transition-shadow ${
4266
- isScrolled ? 'shadow-[0_8px_24px_rgba(2,6,23,0.45)]' : ''
4267
- }`}>
3670
+ <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)]' : ''}`}>
4268
3671
  <div className="max-w-7xl mx-auto h-14 px-3 flex items-center gap-2.5">
4269
3672
  <a href="/" className="shrink-0 flex items-center gap-2">
4270
3673
  <img src="https://iili.io/FC3FABe.jpg" alt="OmniZap" className="w-7 h-7 rounded-full border border-slate-700" />
@@ -4274,10 +3677,10 @@ function StickersApp() {
4274
3677
  ${currentView === 'catalog'
4275
3678
  ? html`
4276
3679
  <form onSubmit=${onSubmit} className="flex-1 relative">
4277
- <span className="absolute left-2.5 top-1/2 -translate-y-1/2 text-xs text-slate-400">🔎</span>
4278
- <input
4279
- type="search"
4280
- value=${query}
3680
+ <span className="absolute left-2.5 top-1/2 -translate-y-1/2 text-xs text-slate-400">🔎</span>
3681
+ <input
3682
+ type="search"
3683
+ value=${query}
4281
3684
  onChange=${(e) => setQuery(e.target.value)}
4282
3685
  onFocus=${() => setShowAutocomplete(true)}
4283
3686
  onBlur=${() => setTimeout(() => setShowAutocomplete(false), 120)}
@@ -4286,20 +3689,15 @@ function StickersApp() {
4286
3689
  setShowAutocomplete(false);
4287
3690
  }
4288
3691
  }}
4289
- placeholder="Buscar packs..."
4290
- 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"
4291
- />
3692
+ placeholder="Buscar packs..."
3693
+ 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"
3694
+ />
4292
3695
  ${showAutocomplete && filteredSuggestions.length
4293
3696
  ? html`
4294
3697
  <div className="absolute z-40 mt-2 w-full rounded-2xl border border-slate-800 bg-slate-900 shadow-xl overflow-hidden">
4295
3698
  ${filteredSuggestions.map(
4296
3699
  (item) => html`
4297
- <button
4298
- key=${item.value}
4299
- type="button"
4300
- onClick=${() => applySuggestion(item)}
4301
- 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"
4302
- >
3700
+ <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">
4303
3701
  <span className="inline-flex items-center gap-2">
4304
3702
  <span>${item.icon || '🏷'}</span>
4305
3703
  <span className="truncate">${item.label}</span>
@@ -4316,489 +3714,206 @@ function StickersApp() {
4316
3714
  : html`<div className="flex-1"></div>`}
4317
3715
 
4318
3716
  <div className="flex items-center gap-2">
4319
- <button
4320
- type="button"
4321
- className=${`text-xs rounded-lg border px-3 py-2 ${
4322
- isProfileView
4323
- ? 'border-amber-400/40 bg-amber-400/10 text-amber-200'
4324
- : 'border-amber-500/30 bg-amber-500/5 text-amber-200 hover:bg-amber-500/10'
4325
- }`}
4326
- onClick=${() => openProfile(true)}
4327
- title="Meu perfil e packs"
4328
- >
3717
+ <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">
4329
3718
  <span className="sm:hidden">👤</span>
4330
3719
  <span className="hidden sm:inline">Meus Packs</span>
4331
3720
  </button>
4332
- <a
4333
- 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"
4334
- href="/stickers/create/"
4335
- title="Criar pack"
4336
- >
3721
+ <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">
4337
3722
  <span className="sm:hidden">➕</span>
4338
3723
  <span className="hidden sm:inline">✨ Criar pack agora</span>
4339
3724
  </a>
4340
3725
  ${supportInfo?.url
4341
3726
  ? html`
4342
- <a
4343
- 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"
4344
- href=${supportInfo.url}
4345
- target="_blank"
4346
- rel="noreferrer noopener"
4347
- title="Suporte no WhatsApp"
4348
- >
3727
+ <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">
4349
3728
  <span className="sm:hidden">💬</span>
4350
3729
  <span className="hidden sm:inline">Suporte</span>
4351
3730
  </a>
4352
3731
  `
4353
3732
  : null}
4354
3733
  <div className="hidden sm:flex items-center gap-2">
4355
- <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>
4356
- <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/Kaikygr/omnizap-system" target="_blank" rel="noreferrer noopener">GitHub</a>
3734
+ <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>
3735
+ <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/Kaikygr/omnizap-system" target="_blank" rel="noreferrer noopener">GitHub</a>
4357
3736
  </div>
4358
3737
  </div>
4359
3738
  </div>
4360
3739
  </header>
4361
3740
 
4362
3741
  <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))]">
4363
- ${error
4364
- ? 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>`
4365
- : null}
4366
-
3742
+ ${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}
4367
3743
  ${isProfileView
4368
- ? html`
4369
- <${CreatorProfileDashboard}
4370
- googleAuthConfig=${googleAuthConfig}
4371
- googleAuth=${googleAuth}
4372
- googleAuthBusy=${googleAuthBusy}
4373
- googleAuthError=${googleAuthError}
4374
- googleSessionChecked=${googleSessionChecked}
4375
- myPacks=${myPacks}
4376
- myPacksLoading=${myPacksLoading}
4377
- myPacksError=${myPacksError}
4378
- myProfileStats=${myProfileStats}
4379
- onBack=${goCatalog}
4380
- onRefresh=${() => refreshMyProfile()}
4381
- onLogout=${handleGoogleLogout}
4382
- onOpenPublicPack=${openPack}
4383
- onOpenPackActions=${openPackActionsSheet}
4384
- onOpenManagePack=${(pack) => openManagePackByKey(pack?.pack_key || '')}
4385
- onRequestDeletePack=${requestDeletePack}
4386
- packActionBusyByKey=${packActionBusyByKey}
4387
- />
4388
- `
3744
+ ? 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} /> `
4389
3745
  : isCreatorsView
4390
- ? html`
4391
- <${CreatorsRankingPage}
4392
- creators=${sortedCreatorRanking}
4393
- loading=${creatorRankingLoading}
4394
- error=${creatorRankingError}
4395
- sort=${creatorSort}
4396
- onSortChange=${handleCreatorsSortChange}
4397
- onBack=${goCatalog}
4398
- onRetry=${loadCreatorRanking}
4399
- onOpenCreator=${openCreatorProfileFromRanking}
4400
- onOpenPack=${openPack}
4401
- />
4402
- `
4403
- : currentPackKey
4404
- ? html`
4405
- ${packLoading
4406
- ? html`<${PackPageSkeleton} />`
4407
- : html`<${PackPage}
4408
- pack=${currentPack}
4409
- relatedPacks=${relatedPacks}
4410
- onBack=${goCatalog}
4411
- onOpenRelated=${openPack}
4412
- onLike=${handleLike}
4413
- onDislike=${handleDislike}
4414
- onTagClick=${openCatalogTagFilter}
4415
- reactionLoading=${reactionLoading}
4416
- reactionNotice=${reactionNotice}
4417
- hasNsfwAccess=${hasNsfwAccess}
4418
- onRequireLogin=${requestNsfwUnlock}
4419
- />`}
4420
- `
4421
- : html`
4422
- <section className="rounded-2xl border border-slate-800 bg-slate-900/80 p-3 sm:p-4">
4423
- <p className="text-[11px] uppercase tracking-wide text-slate-400">Módulo de stickers da plataforma OmniZap</p>
4424
- <h1 className="mt-1 text-lg sm:text-xl font-extrabold tracking-tight text-slate-100">
4425
- Catálogo de stickers integrável via API
4426
- </h1>
4427
- <p className="mt-1 text-sm text-slate-300">
4428
- Os stickers são uma feature do OmniZap para bots e automação WhatsApp. Explore packs públicos e integre no seu sistema com endpoints documentados.
4429
- </p>
4430
- <div className="mt-2 flex flex-wrap gap-2">
4431
- <a href="/api-docs/" className="inline-flex h-8 items-center rounded-lg border border-cyan-500/35 bg-cyan-500/10 px-3 text-[11px] font-semibold text-cyan-100 hover:bg-cyan-500/20">
4432
- Área de Desenvolvedor
4433
- </a>
4434
- <a href="/" className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/70 px-3 text-[11px] text-slate-200 hover:bg-slate-800">
4435
- Página principal OmniZap
4436
- </a>
4437
- </div>
4438
- </section>
4439
-
4440
- <div className="lg:grid lg:grid-cols-[220px_minmax(0,1fr)] lg:gap-4">
4441
- <aside className="hidden lg:block">
4442
- <div className="sticky top-[72px] space-y-2.5 rounded-2xl border border-slate-800 bg-slate-900/80 p-2.5">
4443
- <div className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
4444
- <div className="flex items-center justify-between gap-2">
4445
- <h3 className="text-xs font-semibold uppercase tracking-wide text-slate-300">Filtros</h3>
4446
- <button
4447
- type="button"
4448
- onClick=${clearFilters}
4449
- className="h-8 rounded-lg border border-slate-700 px-2 text-[11px] text-slate-200 hover:bg-slate-800"
4450
- >
4451
- Limpar
4452
- </button>
4453
- </div>
4454
- <p className="mt-1 text-[11px] text-slate-500">${packs.length}${packHasMore ? '+' : ''} packs · ${orphans.length} sem pack</p>
4455
- </div>
3746
+ ? html` <${CreatorsRankingPage} creators=${sortedCreatorRanking} loading=${creatorRankingLoading} error=${creatorRankingError} sort=${creatorSort} onSortChange=${handleCreatorsSortChange} onBack=${goCatalog} onRetry=${loadCreatorRanking} onOpenCreator=${openCreatorProfileFromRanking} onOpenPack=${openPack} /> `
3747
+ : currentPackKey
3748
+ ? html` ${packLoading ? html`<${PackPageSkeleton} />` : html`<${PackPage} pack=${currentPack} relatedPacks=${relatedPacks} onBack=${goCatalog} onOpenRelated=${openPack} onLike=${handleLike} onDislike=${handleDislike} onTagClick=${openCatalogTagFilter} reactionLoading=${reactionLoading} reactionNotice=${reactionNotice} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`} `
3749
+ : html`
3750
+ <div className="lg:grid lg:grid-cols-[220px_minmax(0,1fr)] lg:gap-4">
3751
+ <aside className="hidden lg:block">
3752
+ <div className="sticky top-[72px] space-y-2.5 rounded-2xl border border-slate-800 bg-slate-900/80 p-2.5">
3753
+ <div className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
3754
+ <div className="flex items-center justify-between gap-2">
3755
+ <h3 className="text-xs font-semibold uppercase tracking-wide text-slate-300">Filtros</h3>
3756
+ <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>
3757
+ </div>
3758
+ <p className="mt-1 text-[11px] text-slate-500">${packs.length}${packHasMore ? '+' : ''} packs · ${orphans.length} sem pack</p>
3759
+ </div>
4456
3760
 
4457
- <details open className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
4458
- <summary className="cursor-pointer list-none text-xs font-semibold text-slate-200">
4459
- Ordenar catálogo
4460
- </summary>
4461
- <div className="mt-2 space-y-1.5">
4462
- ${CATALOG_SORT_OPTIONS.map((option) => html`
4463
- <button
4464
- key=${option.value}
4465
- type="button"
4466
- onClick=${() => handleCatalogSortSelection(option.value)}
4467
- disabled=${sortPickerBusy}
4468
- className=${`w-full h-9 rounded-xl border text-xs disabled:opacity-60 ${
4469
- normalizeCatalogSort(sortBy) === option.value
4470
- ? 'border-emerald-400 bg-emerald-400/10 text-emerald-200'
4471
- : 'border-slate-700 text-slate-300 hover:bg-slate-800'
4472
- }`}
4473
- >
4474
- ${option.icon} ${option.label}
4475
- </button>
4476
- `)}
4477
- </div>
4478
- </details>
4479
-
4480
- ${supportInfo?.url
4481
- ? html`
4482
- <a
4483
- href=${supportInfo.url}
4484
- target="_blank"
4485
- rel="noreferrer noopener"
4486
- 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"
4487
- >
4488
- 💬 Suporte no WhatsApp
4489
- </a>
4490
- `
4491
- : null}
4492
- </div>
4493
- </aside>
4494
-
4495
- <div className="space-y-3 min-w-0">
4496
- <section className="space-y-2 min-w-0">
4497
- <div className="relative min-w-0">
4498
- <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>
4499
- <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>
4500
- <div className="chips-scroll flex max-w-full gap-1.5 overflow-x-auto pb-1 pr-1">
4501
- ${dynamicCategoryOptions.map(
4502
- (item) => html`
4503
- <button
4504
- key=${item.value || 'all'}
4505
- type="button"
4506
- onClick=${() => handleCategoryChipPress(item.value)}
4507
- className=${`chip-item h-8 whitespace-nowrap rounded-full px-3 text-[11px] border transition ${
4508
- activeCategory === item.value
4509
- ? 'bg-emerald-400 text-slate-900 border-emerald-300 font-semibold shadow-[0_0_0_2px_rgba(16,185,129,0.18)]'
4510
- : 'bg-slate-900 text-slate-300 border-slate-800 hover:bg-slate-800'
4511
- }`}
4512
- >
4513
- ${item.label}
4514
- </button>
4515
- `,
4516
- )}
3761
+ <details open className="rounded-xl border border-slate-800 bg-slate-950/40 p-2">
3762
+ <summary className="cursor-pointer list-none text-xs font-semibold text-slate-200">Ordenar catálogo</summary>
3763
+ <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>
3764
+ </details>
3765
+
3766
+ ${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}
4517
3767
  </div>
4518
- </div>
4519
- </section>
3768
+ </aside>
3769
+
3770
+ <div className="space-y-3 min-w-0">
3771
+ <section className="space-y-2 min-w-0">
3772
+ <div className="relative min-w-0">
3773
+ <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>
3774
+ <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>
3775
+ <div className="chips-scroll flex max-w-full gap-1.5 overflow-x-auto pb-1 pr-1">${dynamicCategoryOptions.map((item) => html` <button key=${item.value || 'all'} type="button" onClick=${() => handleCategoryChipPress(item.value)} className=${`chip-item h-8 whitespace-nowrap rounded-full px-3 text-[11px] border transition ${activeCategory === item.value ? 'bg-emerald-400 text-slate-900 border-emerald-300 font-semibold shadow-[0_0_0_2px_rgba(16,185,129,0.18)]' : 'bg-slate-900 text-slate-300 border-slate-800 hover:bg-slate-800'}`}>${item.label}</button> `)}</div>
3776
+ </div>
3777
+ </section>
4520
3778
 
4521
- ${packs.length
4522
- ? html`
4523
- <section className="space-y-2">
4524
- <div className="rounded-2xl border border-slate-800 bg-slate-900/70 p-2.5">
4525
- <div className="flex flex-wrap items-center justify-between gap-2">
4526
- <div>
4527
- <p className="text-[11px] uppercase tracking-wide text-slate-400">Descobrir</p>
4528
- <h3 className="text-sm font-semibold text-slate-100">Painel oficial do marketplace</h3>
4529
- <p className="text-[11px] text-slate-500">
4530
- Métricas globais reais da plataforma (cache ~${globalMarketplaceStats?.cacheSeconds || 45}s, atualização automática).
4531
- </p>
4532
- </div>
4533
- <div className="flex items-center gap-2">
4534
- ${globalMarketplaceStatsLoading && !globalMarketplaceStats
4535
- ? html`<span className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/60 px-3 text-[11px] text-slate-300">Carregando métricas...</span>`
4536
- : null}
4537
- ${globalMarketplaceStatsError
4538
- ? html`<span className="inline-flex h-8 items-center rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 text-[11px] text-amber-100">Fallback local</span>`
4539
- : null}
4540
- <a href="/stickers/create/" className="inline-flex h-8 items-center rounded-lg border border-emerald-500/35 bg-emerald-500/10 px-3 text-[11px] font-semibold text-emerald-200 hover:bg-emerald-500/20">
4541
- Criar pack agora
4542
- </a>
3779
+ ${packs.length
3780
+ ? html`
3781
+ <section className="space-y-2">
3782
+ <div className="rounded-2xl border border-slate-800 bg-slate-900/70 p-2.5">
3783
+ <div className="flex flex-wrap items-center justify-between gap-2">
3784
+ <div>
3785
+ <p className="text-[11px] uppercase tracking-wide text-slate-400">Descobrir</p>
3786
+ <h3 className="text-sm font-semibold text-slate-100">Painel oficial do marketplace</h3>
3787
+ <p className="text-[11px] text-slate-500">Métricas globais reais da plataforma (cache ~${globalMarketplaceStats?.cacheSeconds || 45}s, atualização automática).</p>
3788
+ </div>
3789
+ <div className="flex items-center gap-2">
3790
+ ${globalMarketplaceStatsLoading && !globalMarketplaceStats ? html`<span className="inline-flex h-8 items-center rounded-lg border border-slate-700 bg-slate-900/60 px-3 text-[11px] text-slate-300">Carregando métricas...</span>` : null} ${globalMarketplaceStatsError ? html`<span className="inline-flex h-8 items-center rounded-lg border border-amber-500/30 bg-amber-500/10 px-3 text-[11px] text-amber-100">Fallback local</span>` : null}
3791
+ <a href="/stickers/create/" className="inline-flex h-8 items-center rounded-lg border border-emerald-500/35 bg-emerald-500/10 px-3 text-[11px] font-semibold text-emerald-200 hover:bg-emerald-500/20"> ✨ Criar pack agora </a>
3792
+ </div>
3793
+ </div>
3794
+
3795
+ <div className="mt-2 flex flex-wrap gap-1.5">
3796
+ ${[
3797
+ { key: 'growing', label: '🔥 Crescendo' },
3798
+ { key: 'top', label: '🏆 Top 10' },
3799
+ { key: 'creators', label: '⭐ Criadores' },
3800
+ ].map((tab) => html` <button key=${tab.key} type="button" onClick=${() => setDiscoverTab(tab.key)} className=${`h-8 rounded-full border px-2.5 text-[11px] touch-manipulation ${discoverTab === tab.key ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100' : 'border-slate-700 bg-slate-950/40 text-slate-300 hover:bg-slate-800'}`}>${tab.label}</button> `)}
3801
+ </div>
3802
+
3803
+ <div className="mt-2 hidden lg:block">
3804
+ ${discoverTab === 'growing'
3805
+ ? html` <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">${growingNowPacks.slice(0, 6).map((entry) => html`<${DiscoverPackRowItem} key=${`grow-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div> `
3806
+ : discoverTab === 'top'
3807
+ ? html` <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">${topWeekPacks.slice(0, 8).map((entry, idx) => html`<${DiscoverPackRowItem} key=${`top-${entry.pack_key}`} pack=${entry} onOpen=${openPack} rank=${idx + 1} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div> `
3808
+ : html`
3809
+ <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">
3810
+ ${featuredCreators.map(
3811
+ (creator) => html`
3812
+ <button key=${creator.publisher} onClick=${() => openCatalogWithState({ q: creator.publisher, category: '', sort: DEFAULT_CATALOG_SORT, filter: '', push: true })} className="w-full flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 px-2 py-1.5 text-left hover:bg-slate-800/90">
3813
+ <img src=${getAvatarUrl(creator.publisher)} alt="" className="w-9 h-9 rounded-full bg-slate-800" />
3814
+ <span className="min-w-0 flex-1">
3815
+ <span className="block truncate text-xs font-medium text-slate-100">${creator.publisher}</span>
3816
+ <span className="block truncate text-[10px] text-slate-400">${creator.packCount} packs · ❤️ ${shortNum(creator.likes)} · ⬇ ${shortNum(creator.opens)}</span>
3817
+ </span>
3818
+ <span className="text-[10px] text-slate-500">filtrar</span>
3819
+ </button>
3820
+ `,
3821
+ )}
3822
+ </div>
3823
+ `}
3824
+ </div>
3825
+
3826
+ <div className="mt-2 lg:hidden">
3827
+ ${discoverTab === 'growing'
3828
+ ? html`
3829
+ <section className="space-y-1.5">
3830
+ <div className="flex items-center justify-between">
3831
+ <h4 className="text-xs font-semibold text-slate-200">🔥 Em alta agora</h4>
3832
+ <button type="button" onClick=${openTrendingCatalog} className="text-[10px] text-cyan-300">ver lista</button>
3833
+ </div>
3834
+ <div className="flex gap-2 overflow-x-auto pb-1">${growingNowPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-grow-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
3835
+ </section>
3836
+ `
3837
+ : discoverTab === 'top'
3838
+ ? html`
3839
+ <section className="space-y-1.5">
3840
+ <div className="flex items-center justify-between">
3841
+ <h4 className="text-xs font-semibold text-slate-200">🏆 Top 10 da semana</h4>
3842
+ <button type="button" onClick=${() => openCatalogWithState({ q: '', category: '', sort: 'trending', filter: '', push: true })} className="text-[10px] text-cyan-300">ver lista</button>
3843
+ </div>
3844
+ <div className="flex gap-2 overflow-x-auto pb-1">${topWeekPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-top-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
3845
+ </section>
3846
+ `
3847
+ : html`
3848
+ <section className="space-y-1.5">
3849
+ <div className="flex items-center justify-between">
3850
+ <h4 className="text-xs font-semibold text-slate-200">👑 Criadores populares</h4>
3851
+ <button type="button" onClick=${() => openCreatorsRanking('popular', true)} className="text-[10px] text-cyan-300">ver lista</button>
3852
+ </div>
3853
+ <div className="flex gap-2 overflow-x-auto pb-1">${featuredCreators.map((creator) => html` <${DiscoverCreatorMiniCard} key=${`mobile-tab-creator-${creator.publisher}`} creator=${creator} onPick=${(publisher) => openCatalogWithState({ q: publisher, category: '', sort: DEFAULT_CATALOG_SORT, filter: '', push: true })} /> `)}</div>
3854
+ </section>
3855
+ `}
3856
+ </div>
4543
3857
  </div>
4544
- </div>
4545
-
4546
- <div className="mt-2 flex flex-wrap gap-1.5">
4547
- ${[
4548
- { key: 'growing', label: '🔥 Crescendo' },
4549
- { key: 'top', label: '🏆 Top 10' },
4550
- { key: 'creators', label: '⭐ Criadores' },
4551
- ].map((tab) => html`
4552
- <button
4553
- key=${tab.key}
4554
- type="button"
4555
- onClick=${() => setDiscoverTab(tab.key)}
4556
- className=${`h-8 rounded-full border px-2.5 text-[11px] touch-manipulation ${
4557
- discoverTab === tab.key
4558
- ? 'border-cyan-400/35 bg-cyan-500/10 text-cyan-100'
4559
- : 'border-slate-700 bg-slate-950/40 text-slate-300 hover:bg-slate-800'
4560
- }`}
4561
- >
4562
- ${tab.label}
4563
- </button>
4564
- `)}
4565
- </div>
4566
-
4567
- <div className="mt-2 hidden lg:block">
4568
- ${discoverTab === 'growing'
4569
- ? html`
4570
- <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">
4571
- ${growingNowPacks.slice(0, 6).map(
4572
- (entry) => html`<${DiscoverPackRowItem}
4573
- key=${`grow-${entry.pack_key}`}
4574
- pack=${entry}
4575
- onOpen=${openPack}
4576
- hasNsfwAccess=${hasNsfwAccess}
4577
- onRequireLogin=${requestNsfwUnlock}
4578
- />`,
4579
- )}
4580
- </div>
4581
- `
4582
- : discoverTab === 'top'
4583
- ? html`
4584
- <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">
4585
- ${topWeekPacks.slice(0, 8).map(
4586
- (entry, idx) => html`<${DiscoverPackRowItem}
4587
- key=${`top-${entry.pack_key}`}
4588
- pack=${entry}
4589
- onOpen=${openPack}
4590
- rank=${idx + 1}
4591
- hasNsfwAccess=${hasNsfwAccess}
4592
- onRequireLogin=${requestNsfwUnlock}
4593
- />`,
4594
- )}
4595
- </div>
4596
- `
4597
- : html`
4598
- <div className="grid grid-cols-1 xl:grid-cols-2 gap-2">
4599
- ${featuredCreators.map((creator) => html`
4600
- <button
4601
- key=${creator.publisher}
4602
- onClick=${() => openCatalogWithState({ q: creator.publisher, category: '', sort: DEFAULT_CATALOG_SORT, filter: '', push: true })}
4603
- className="w-full flex items-center gap-2 rounded-xl border border-slate-800 bg-slate-900/50 px-2 py-1.5 text-left hover:bg-slate-800/90"
4604
- >
4605
- <img src=${getAvatarUrl(creator.publisher)} alt="" className="w-9 h-9 rounded-full bg-slate-800" />
4606
- <span className="min-w-0 flex-1">
4607
- <span className="block truncate text-xs font-medium text-slate-100">${creator.publisher}</span>
4608
- <span className="block truncate text-[10px] text-slate-400">${creator.packCount} packs · ❤️ ${shortNum(creator.likes)} · ⬇ ${shortNum(creator.opens)}</span>
4609
- </span>
4610
- <span className="text-[10px] text-slate-500">filtrar</span>
4611
- </button>
4612
- `)}
4613
- </div>
4614
- `}
4615
- </div>
4616
-
4617
- <div className="mt-2 lg:hidden">
4618
- ${discoverTab === 'growing'
4619
- ? html`
4620
- <section className="space-y-1.5">
4621
- <div className="flex items-center justify-between">
4622
- <h4 className="text-xs font-semibold text-slate-200">🔥 Em alta agora</h4>
4623
- <button type="button" onClick=${openTrendingCatalog} className="text-[10px] text-cyan-300">ver lista</button>
4624
- </div>
4625
- <div className="flex gap-2 overflow-x-auto pb-1">
4626
- ${growingNowPacks.slice(0, 8).map(
4627
- (entry) => html`<${DiscoverPackMiniCard}
4628
- key=${`mobile-grow-${entry.pack_key}`}
4629
- pack=${entry}
4630
- onOpen=${openPack}
4631
- hasNsfwAccess=${hasNsfwAccess}
4632
- onRequireLogin=${requestNsfwUnlock}
4633
- />`,
4634
- )}
4635
- </div>
4636
- </section>
4637
- `
4638
- : discoverTab === 'top'
4639
- ? html`
4640
- <section className="space-y-1.5">
4641
- <div className="flex items-center justify-between">
4642
- <h4 className="text-xs font-semibold text-slate-200">🏆 Top 10 da semana</h4>
4643
- <button
4644
- type="button"
4645
- onClick=${() => openCatalogWithState({ q: '', category: '', sort: 'trending', filter: '', push: true })}
4646
- className="text-[10px] text-cyan-300"
4647
- >
4648
- ver lista
4649
- </button>
4650
- </div>
4651
- <div className="flex gap-2 overflow-x-auto pb-1">
4652
- ${topWeekPacks.slice(0, 8).map(
4653
- (entry) => html`<${DiscoverPackMiniCard}
4654
- key=${`mobile-top-${entry.pack_key}`}
4655
- pack=${entry}
4656
- onOpen=${openPack}
4657
- hasNsfwAccess=${hasNsfwAccess}
4658
- onRequireLogin=${requestNsfwUnlock}
4659
- />`,
4660
- )}
4661
- </div>
4662
- </section>
4663
- `
4664
- : html`
4665
- <section className="space-y-1.5">
4666
- <div className="flex items-center justify-between">
4667
- <h4 className="text-xs font-semibold text-slate-200">👑 Criadores populares</h4>
4668
- <button type="button" onClick=${() => openCreatorsRanking('popular', true)} className="text-[10px] text-cyan-300">ver lista</button>
4669
- </div>
4670
- <div className="flex gap-2 overflow-x-auto pb-1">
4671
- ${featuredCreators.map((creator) => html`
4672
- <${DiscoverCreatorMiniCard}
4673
- key=${`mobile-tab-creator-${creator.publisher}`}
4674
- creator=${creator}
4675
- onPick=${(publisher) => openCatalogWithState({ q: publisher, category: '', sort: DEFAULT_CATALOG_SORT, filter: '', push: true })}
4676
- />
4677
- `)}
4678
- </div>
4679
- </section>
4680
- `}
4681
- </div>
4682
- </div>
4683
3858
 
4684
- <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">
4685
- ${catalogMetricCards.map(
4686
- (card) => html`<${CatalogMetricCard}
4687
- key=${card.key}
4688
- label=${card.label}
4689
- value=${card.value}
4690
- valueRaw=${card.valueRaw}
4691
- numberFormat=${card.numberFormat}
4692
- icon=${card.icon}
4693
- hint=${card.hint}
4694
- bars=${card.bars}
4695
- tone=${card.tone}
4696
- />`,
4697
- )}
4698
- </div>
3859
+ <div className="grid grid-cols-2 gap-2 sm:grid-cols-4">${catalogMetricCards.map((card) => html`<${CatalogMetricCard} key=${card.key} label=${card.label} value=${card.value} valueRaw=${card.valueRaw} numberFormat=${card.numberFormat} icon=${card.icon} hint=${card.hint} bars=${card.bars} tone=${card.tone} />`)}</div>
4699
3860
 
4700
- <div className="lg:hidden space-y-2">
4701
- <section className="space-y-1.5">
4702
- <div className="flex items-center justify-between">
4703
- <h4 className="text-xs font-semibold text-slate-200">🆕 Recém publicados</h4>
4704
- <button type="button" onClick=${openSortPicker} disabled=${sortPickerBusy} className="text-[10px] text-cyan-300 disabled:opacity-50">ordenar</button>
3861
+ <div className="lg:hidden space-y-2">
3862
+ <section className="space-y-1.5">
3863
+ <div className="flex items-center justify-between">
3864
+ <h4 className="text-xs font-semibold text-slate-200">🆕 Recém publicados</h4>
3865
+ <button type="button" onClick=${openSortPicker} disabled=${sortPickerBusy} className="text-[10px] text-cyan-300 disabled:opacity-50">ordenar</button>
3866
+ </div>
3867
+ <div className="flex gap-2 overflow-x-auto pb-1">${recentPublishedPacks.slice(0, 8).map((entry) => html`<${DiscoverPackMiniCard} key=${`mobile-new-${entry.pack_key}`} pack=${entry} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} />`)}</div>
3868
+ </section>
4705
3869
  </div>
4706
- <div className="flex gap-2 overflow-x-auto pb-1">
4707
- ${recentPublishedPacks.slice(0, 8).map(
4708
- (entry) => html`<${DiscoverPackMiniCard}
4709
- key=${`mobile-new-${entry.pack_key}`}
4710
- pack=${entry}
4711
- onOpen=${openPack}
4712
- hasNsfwAccess=${hasNsfwAccess}
4713
- onRequireLogin=${requestNsfwUnlock}
4714
- />`,
4715
- )}
3870
+
3871
+ <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">
3872
+ <div className="flex items-center justify-between gap-3">
3873
+ <div>
3874
+ <p className="text-xs font-semibold text-emerald-100">Quer aparecer em destaque?</p>
3875
+ <p className="text-[11px] text-slate-300">Publique seu pack e melhore capa/tags para ganhar mais cliques.</p>
3876
+ </div>
3877
+ <a href="/stickers/create/" className="inline-flex h-8 items-center rounded-lg border border-emerald-400/35 bg-emerald-500/10 px-3 text-[11px] font-semibold text-emerald-100 hover:bg-emerald-500/20"> Publicar pack </a>
3878
+ </div>
4716
3879
  </div>
4717
3880
  </section>
4718
- </div>
4719
-
4720
- <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">
4721
- <div className="flex items-center justify-between gap-3">
4722
- <div>
4723
- <p className="text-xs font-semibold text-emerald-100">Quer aparecer em destaque?</p>
4724
- <p className="text-[11px] text-slate-300">Publique seu pack e melhore capa/tags para ganhar mais cliques.</p>
3881
+ `
3882
+ : null}
3883
+ ${packs.length
3884
+ ? html`
3885
+ <section className="space-y-3 min-w-0">
3886
+ <div className="flex items-end justify-between gap-3">
3887
+ <div>
3888
+ <h2 className="text-lg sm:text-xl font-bold">Packs</h2>
3889
+ <p className="text-xs text-slate-400">${sortedPacks.length}${packHasMore ? '+' : ''} resultados · ${categoryActiveLabel}</p>
3890
+ </div>
3891
+ <div className="hidden md:flex items-center gap-2">
3892
+ <span className="text-xs text-slate-400">Ordenar por</span>
3893
+ <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">
3894
+ <span>${catalogSortLabel(sortBy)}</span>
3895
+ <span className="text-[10px] text-slate-400">▾</span>
3896
+ </button>
3897
+ </div>
4725
3898
  </div>
4726
- <a href="/stickers/create/" className="inline-flex h-8 items-center rounded-lg border border-emerald-400/35 bg-emerald-500/10 px-3 text-[11px] font-semibold text-emerald-100 hover:bg-emerald-500/20">
4727
- Publicar pack
4728
- </a>
4729
- </div>
4730
- </div>
4731
- </section>
4732
- `
4733
- : null}
4734
-
4735
- ${packs.length
4736
- ? html`
4737
- <section className="space-y-3 min-w-0">
4738
- <div className="flex items-end justify-between gap-3">
4739
- <div>
4740
- <h2 className="text-lg sm:text-xl font-bold">Packs</h2>
4741
- <p className="text-xs text-slate-400">${sortedPacks.length}${packHasMore ? '+' : ''} resultados · ${categoryActiveLabel}</p>
4742
- </div>
4743
- <div className="hidden md:flex items-center gap-2">
4744
- <span className="text-xs text-slate-400">Ordenar por</span>
4745
- <button
4746
- type="button"
4747
- onClick=${openSortPicker}
4748
- disabled=${sortPickerBusy}
4749
- 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"
4750
- >
4751
- <span>${catalogSortLabel(sortBy)}</span>
4752
- <span className="text-[10px] text-slate-400">▾</span>
4753
- </button>
4754
- </div>
4755
- </div>
4756
- <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">
4757
- ${sortedPacks.map(
4758
- (pack, index) => html`<div key=${pack.pack_key || pack.id} className="fade-card"><${PackCard}
4759
- pack=${pack}
4760
- index=${index}
4761
- onOpen=${openPack}
4762
- hasNsfwAccess=${hasNsfwAccess}
4763
- onRequireLogin=${requestNsfwUnlock}
4764
- /></div>`,
4765
- )}
4766
- </div>
4767
- <div ref=${setSentinel} className="h-8 flex items-center justify-center text-xs text-slate-500">
4768
- ${packsLoadingMore ? 'Carregando mais packs...' : packHasMore ? 'Role para carregar mais' : 'Fim da lista'}
4769
- </div>
4770
- </section>
4771
- `
4772
- : null}
3899
+ <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">${sortedPacks.map((pack, index) => html`<div key=${pack.pack_key || pack.id} className="fade-card"><${PackCard} pack=${pack} index=${index} onOpen=${openPack} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} /></div>`)}</div>
3900
+ <div ref=${setSentinel} className="h-8 flex items-center justify-center text-xs text-slate-500">${packsLoadingMore ? 'Carregando mais packs...' : packHasMore ? 'Role para carregar mais' : 'Fim da lista'}</div>
3901
+ </section>
3902
+ `
3903
+ : null}
3904
+ ${packsLoading ? html`<${SkeletonGrid} count=${10} />` : null} ${!packsLoading && !hasAnyResult ? html`<${EmptyState} onClear=${clearFilters} />` : null}
4773
3905
 
4774
- ${packsLoading ? html`<${SkeletonGrid} count=${10} />` : null}
4775
- ${!packsLoading && !hasAnyResult ? html`<${EmptyState} onClear=${clearFilters} />` : null}
3906
+ <section className="space-y-2.5">
3907
+ <div className="flex items-center justify-between">
3908
+ <h2 className="text-base sm:text-lg font-bold">Stickers sem pack</h2>
3909
+ <span className="text-xs text-slate-400">${orphans.length} resultados</span>
3910
+ </div>
4776
3911
 
4777
- <section className="space-y-2.5">
4778
- <div className="flex items-center justify-between">
4779
- <h2 className="text-base sm:text-lg font-bold">Stickers sem pack</h2>
4780
- <span className="text-xs text-slate-400">${orphans.length} resultados</span>
3912
+ ${orphansLoading ? html`<div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">${Array.from({ length: 16 }).map((_, i) => html`<div key=${i} className="rounded-2xl border border-slate-700 bg-slate-800 animate-pulse aspect-square"></div>`)}</div>` : html` <div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">${orphans.map((item) => html`<div key=${item.id} className="fade-card"><${OrphanCard} sticker=${item} hasNsfwAccess=${hasNsfwAccess} onRequireLogin=${requestNsfwUnlock} /></div>`)}</div> `}
3913
+ </section>
4781
3914
  </div>
4782
-
4783
- ${orphansLoading
4784
- ? html`<div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">${Array.from({ length: 16 }).map(
4785
- (_, i) => html`<div key=${i} className="rounded-2xl border border-slate-700 bg-slate-800 animate-pulse aspect-square"></div>`,
4786
- )}</div>`
4787
- : html`
4788
- <div className="grid grid-cols-3 md:grid-cols-5 lg:grid-cols-8 gap-2.5 sm:gap-3">
4789
- ${orphans.map(
4790
- (item) => html`<div key=${item.id} className="fade-card"><${OrphanCard}
4791
- sticker=${item}
4792
- hasNsfwAccess=${hasNsfwAccess}
4793
- onRequireLogin=${requestNsfwUnlock}
4794
- /></div>`,
4795
- )}
4796
- </div>
4797
- `}
4798
- </section>
4799
- </div>
4800
- </div>
4801
- `}
3915
+ </div>
3916
+ `}
4802
3917
  </main>
4803
3918
  <${UploadTaskWidget}
4804
3919
  task=${uploadTask}
@@ -4809,55 +3924,11 @@ function StickersApp() {
4809
3924
  setUploadTask(null);
4810
3925
  }}
4811
3926
  />
4812
- <${CatalogSortPicker}
4813
- open=${sortPickerOpen}
4814
- currentSort=${sortBy}
4815
- busy=${sortPickerBusy}
4816
- onClose=${closeSortPicker}
4817
- onSelect=${handleCatalogSortSelection}
4818
- />
4819
- <${PackActionsSheet}
4820
- open=${Boolean(packActionsSheetPack)}
4821
- pack=${packActionsSheetPack}
4822
- busyAction=${packActionsSheetPack ? packActionBusyByKey?.[packActionsSheetPack.pack_key] || '' : ''}
4823
- onClose=${closePackActionsSheet}
4824
- onAction=${handlePackActionsSheetAction}
4825
- />
4826
- <${PackManagerModal}
4827
- open=${managePackOpen}
4828
- data=${managePackData}
4829
- loading=${managePackLoading}
4830
- error=${managePackError}
4831
- busyAction=${managePackBusyAction}
4832
- onClose=${closeManagePackModal}
4833
- onRefresh=${refreshManagePackData}
4834
- onSaveMetadata=${handleManageSaveMetadata}
4835
- onAddSticker=${handleManageAddSticker}
4836
- onRemoveSticker=${handleManageRemoveSticker}
4837
- onReplaceSticker=${handleManageReplaceSticker}
4838
- onSetCover=${handleManageSetCover}
4839
- onReorder=${handleManageReorder}
4840
- onOpenAnalytics=${() => openAnalyticsModalForPack(managePackData?.pack || null)}
4841
- />
4842
- <${PackAnalyticsModal}
4843
- open=${analyticsModalOpen}
4844
- pack=${analyticsModalPack}
4845
- data=${analyticsModalData}
4846
- loading=${analyticsModalLoading}
4847
- error=${analyticsModalError}
4848
- onClose=${closeAnalyticsModal}
4849
- />
4850
- <${ConfirmDialog}
4851
- open=${Boolean(confirmDeletePack)}
4852
- title="Apagar pack"
4853
- message=${confirmDeletePack ? `Tem certeza que deseja apagar o pack "${confirmDeletePack.name || confirmDeletePack.pack_key}"? Essa ação remove o pack do seu painel.` : ''}
4854
- confirmLabel="Apagar pack"
4855
- cancelLabel="Cancelar"
4856
- danger=${true}
4857
- busy=${confirmDeleteBusy}
4858
- onCancel=${() => (confirmDeleteBusy ? null : setConfirmDeletePack(null))}
4859
- onConfirm=${handleDeletePackConfirmed}
4860
- />
3927
+ <${CatalogSortPicker} open=${sortPickerOpen} currentSort=${sortBy} busy=${sortPickerBusy} onClose=${closeSortPicker} onSelect=${handleCatalogSortSelection} />
3928
+ <${PackActionsSheet} open=${Boolean(packActionsSheetPack)} pack=${packActionsSheetPack} busyAction=${packActionsSheetPack ? packActionBusyByKey?.[packActionsSheetPack.pack_key] || '' : ''} onClose=${closePackActionsSheet} onAction=${handlePackActionsSheetAction} />
3929
+ <${PackManagerModal} open=${managePackOpen} data=${managePackData} loading=${managePackLoading} error=${managePackError} busyAction=${managePackBusyAction} onClose=${closeManagePackModal} onRefresh=${refreshManagePackData} onSaveMetadata=${handleManageSaveMetadata} onAddSticker=${handleManageAddSticker} onRemoveSticker=${handleManageRemoveSticker} onReplaceSticker=${handleManageReplaceSticker} onSetCover=${handleManageSetCover} onReorder=${handleManageReorder} onOpenAnalytics=${() => openAnalyticsModalForPack(managePackData?.pack || null)} />
3930
+ <${PackAnalyticsModal} open=${analyticsModalOpen} pack=${analyticsModalPack} data=${analyticsModalData} loading=${analyticsModalLoading} error=${analyticsModalError} onClose=${closeAnalyticsModal} />
3931
+ <${ConfirmDialog} open=${Boolean(confirmDeletePack)} title="Apagar pack" message=${confirmDeletePack ? `Tem certeza que deseja apagar o pack "${confirmDeletePack.name || confirmDeletePack.pack_key}"? Essa ação remove o pack do seu painel.` : ''} confirmLabel="Apagar pack" cancelLabel="Cancelar" danger=${true} busy=${confirmDeleteBusy} onCancel=${() => (confirmDeleteBusy ? null : setConfirmDeletePack(null))} onConfirm=${handleDeletePackConfirmed} />
4861
3932
  <${ToastStack} toasts=${profileToasts} onDismiss=${dismissProfileToast} />
4862
3933
  </div>
4863
3934
  `;