-
+
+
+
+
+
+
+
+
+
ZAP!
+
+ {{ zapAmount?.toLocaleString() }} sats sent
+
+
+ {{ successQuote }}
+
+
+
-
Zap Sent!
-
You zapped {{ zapAmount?.toLocaleString() }} sats to the creator
-
@@ -158,7 +173,7 @@
@@ -493,6 +585,76 @@ function closeModal() {
transform: translateY(0);
}
+/* ─── Success Celebration ─── */
+.zap-celebration {
+ position: absolute;
+ inset: 0;
+ pointer-events: none;
+ overflow: hidden;
+}
+
+.zap-bolt {
+ position: absolute;
+ top: -20px;
+ color: rgba(247, 147, 26, 0.3);
+ animation: zapFall linear infinite;
+ filter: blur(0.5px);
+}
+
+.zap-bolt:nth-child(odd) {
+ color: rgba(247, 147, 26, 0.15);
+}
+
+.zap-bolt:nth-child(3n) {
+ color: rgba(255, 200, 50, 0.25);
+}
+
+@keyframes zapFall {
+ 0% {
+ transform: translateY(-20px) rotate(-15deg) scale(0.5);
+ opacity: 0;
+ }
+ 15% {
+ opacity: 1;
+ }
+ 85% {
+ opacity: 0.6;
+ }
+ 100% {
+ transform: translateY(350px) rotate(15deg) scale(1);
+ opacity: 0;
+ }
+}
+
+.zap-success-icon {
+ width: 88px;
+ height: 88px;
+ border-radius: 50%;
+ background: radial-gradient(circle, rgba(247, 147, 26, 0.25), rgba(247, 147, 26, 0.05));
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ animation: zapPulse 1.5s ease-in-out infinite;
+ box-shadow:
+ 0 0 40px rgba(247, 147, 26, 0.3),
+ 0 0 80px rgba(247, 147, 26, 0.15);
+}
+
+@keyframes zapPulse {
+ 0%, 100% {
+ box-shadow:
+ 0 0 40px rgba(247, 147, 26, 0.3),
+ 0 0 80px rgba(247, 147, 26, 0.15);
+ transform: scale(1);
+ }
+ 50% {
+ box-shadow:
+ 0 0 60px rgba(247, 147, 26, 0.4),
+ 0 0 120px rgba(247, 147, 26, 0.2);
+ transform: scale(1.05);
+ }
+}
+
/* Modal Transitions */
.modal-fade-enter-active,
.modal-fade-leave-active {
diff --git a/src/composables/useContentDiscovery.ts b/src/composables/useContentDiscovery.ts
index b2cb234..66919e0 100644
--- a/src/composables/useContentDiscovery.ts
+++ b/src/composables/useContentDiscovery.ts
@@ -129,13 +129,29 @@ function rebuildStats() {
}
// Process zap receipts (kind 9735) from the EventStore.
- // Zap receipts reference content via the 'i' tag (external identifiers)
- // or via embedded zap request description.
+ // Zap receipts reference content via:
+ // 1. Direct 'i' tag on the receipt itself
+ // 2. 'i' tag embedded in the zap request JSON (description tag)
const zapReceipts = eventStore.getByFilters([{ kinds: [9735] }])
if (zapReceipts) {
for (const event of zapReceipts) {
- // Try to find the external content ID from the zap receipt tags
- const externalId = getTagValue(event, 'i')
+ // Try direct 'i' tag first
+ let externalId = getTagValue(event, 'i')
+
+ // Fallback: parse the embedded zap request from the description tag
+ if (!externalId) {
+ const descTag = event.tags.find((t) => t[0] === 'description')?.[1]
+ if (descTag) {
+ try {
+ const zapRequest = JSON.parse(descTag)
+ if (zapRequest.tags) {
+ const iTag = zapRequest.tags.find((t: string[]) => t[0] === 'i')
+ if (iTag) externalId = iTag[1]
+ }
+ } catch { /* not valid JSON */ }
+ }
+ }
+
if (!externalId) continue
const stats = getOrCreate(externalId)
diff --git a/src/composables/useNostr.ts b/src/composables/useNostr.ts
index 7668311..3d66af3 100644
--- a/src/composables/useNostr.ts
+++ b/src/composables/useNostr.ts
@@ -120,6 +120,8 @@ export function useNostr(contentId?: string) {
const reactions = ref
([])
// Per-comment reactions: eventId -> NostrEvent[]
const commentReactions = ref