From 0a8ef645276b741cd9cbc1a155507eaab3bec340 Mon Sep 17 00:00:00 2001 From: Padreug Date: Wed, 27 May 2026 10:44:48 +0200 Subject: [PATCH 01/10] add shadcn-vue MCP server for Claude Code sessions Co-Authored-By: Claude Opus 4.7 (1M context) --- .mcp.json | 8 ++++++++ 1 file changed, 8 insertions(+) create mode 100644 .mcp.json diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 0000000..cffc7d9 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,8 @@ +{ + "mcpServers": { + "shadcn": { + "command": "npx", + "args": ["shadcn-vue@latest", "mcp"] + } + } +} From a70c50826d2a2483efa98a7197ffaaa3d80f00de Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 02/10] build: configure pnpm workspace and presentation route Point / at the presentation deck and add pnpm-workspace.yaml so pnpm dev/build don't hard-fail on the ignored vue-demi build script. Co-Authored-By: Claude Opus 4.8 (1M context) --- pnpm-workspace.yaml | 3 +++ src/router/index.ts | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 pnpm-workspace.yaml diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 0000000..29b3bab --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,3 @@ +allowBuilds: + vue-demi: false +verifyDepsBeforeRun: false diff --git a/src/router/index.ts b/src/router/index.ts index 4f315f4..e1a0ee4 100644 --- a/src/router/index.ts +++ b/src/router/index.ts @@ -3,8 +3,8 @@ import { createRouter, createWebHistory, type RouteRecordRaw } from 'vue-router' const routes: RouteRecordRaw[] = [ { path: '/', - name: 'home', - component: () => import('@/views/HomeView.vue'), + name: 'presentation', + component: () => import('@/views/PresentationView.vue'), }, ] From 88f8d6b681e739944e8a06658150222c55d1b8c8 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 03/10] feat: slide-deck engine and shared slide layouts Keyboard-navigable full-screen deck (arrow keys, progress bar, hash deep-linking that lands directly on the target slide) plus the reusable Shell / Title / Bullets / Keys / Resources layout components. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/BulletsSlide.vue | 33 +++++ src/components/slides/KeysSlide.vue | 40 ++++++ src/components/slides/ResourcesSlide.vue | 43 +++++++ src/components/slides/SlideShell.vue | 31 +++++ src/components/slides/TitleSlide.vue | 32 +++++ src/features/slides/types.ts | 16 +++ src/views/PresentationView.vue | 150 +++++++++++++++++++++++ 7 files changed, 345 insertions(+) create mode 100644 src/components/slides/BulletsSlide.vue create mode 100644 src/components/slides/KeysSlide.vue create mode 100644 src/components/slides/ResourcesSlide.vue create mode 100644 src/components/slides/SlideShell.vue create mode 100644 src/components/slides/TitleSlide.vue create mode 100644 src/features/slides/types.ts create mode 100644 src/views/PresentationView.vue diff --git a/src/components/slides/BulletsSlide.vue b/src/components/slides/BulletsSlide.vue new file mode 100644 index 0000000..2d4a4cb --- /dev/null +++ b/src/components/slides/BulletsSlide.vue @@ -0,0 +1,33 @@ + + + diff --git a/src/components/slides/KeysSlide.vue b/src/components/slides/KeysSlide.vue new file mode 100644 index 0000000..e08c205 --- /dev/null +++ b/src/components/slides/KeysSlide.vue @@ -0,0 +1,40 @@ + + + diff --git a/src/components/slides/ResourcesSlide.vue b/src/components/slides/ResourcesSlide.vue new file mode 100644 index 0000000..8590a4d --- /dev/null +++ b/src/components/slides/ResourcesSlide.vue @@ -0,0 +1,43 @@ + + + diff --git a/src/components/slides/SlideShell.vue b/src/components/slides/SlideShell.vue new file mode 100644 index 0000000..84faf57 --- /dev/null +++ b/src/components/slides/SlideShell.vue @@ -0,0 +1,31 @@ + + + diff --git a/src/components/slides/TitleSlide.vue b/src/components/slides/TitleSlide.vue new file mode 100644 index 0000000..20599df --- /dev/null +++ b/src/components/slides/TitleSlide.vue @@ -0,0 +1,32 @@ + + + diff --git a/src/features/slides/types.ts b/src/features/slides/types.ts new file mode 100644 index 0000000..d3ce619 --- /dev/null +++ b/src/features/slides/types.ts @@ -0,0 +1,16 @@ +import type { Component } from 'vue' + +/** + * A slide is a layout component plus the props that feed it. + * The deck renders ``, so any + * component under `src/components/slides/` can be dropped in here — generic + * layouts for text-heavy slides, bespoke components for the diagram slides. + */ +export interface Slide { + /** Stable id used for the URL hash (e.g. #keys) and nav. */ + id: string + /** Short label for the overview / progress tooltip. */ + label: string + component: Component + props?: Record +} diff --git a/src/views/PresentationView.vue b/src/views/PresentationView.vue new file mode 100644 index 0000000..96e29e8 --- /dev/null +++ b/src/views/PresentationView.vue @@ -0,0 +1,150 @@ + + + + + From 8de72c9ae372766fc02114e872bb5644cc716667 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 04/10] feat: Nostr-vs-HTTP slides with SVG network diagrams Animated one-server-vs-many-relays illustration (respects prefers-reduced-motion) and a point-by-point comparison table on its own slide. Co-Authored-By: Claude Opus 4.8 (1M context) --- .../slides/ProtocolCompareSlide.vue | 138 ++++++++++++++++++ src/components/slides/ProtocolTableSlide.vue | 34 +++++ 2 files changed, 172 insertions(+) create mode 100644 src/components/slides/ProtocolCompareSlide.vue create mode 100644 src/components/slides/ProtocolTableSlide.vue diff --git a/src/components/slides/ProtocolCompareSlide.vue b/src/components/slides/ProtocolCompareSlide.vue new file mode 100644 index 0000000..f630ec7 --- /dev/null +++ b/src/components/slides/ProtocolCompareSlide.vue @@ -0,0 +1,138 @@ + + + + + diff --git a/src/components/slides/ProtocolTableSlide.vue b/src/components/slides/ProtocolTableSlide.vue new file mode 100644 index 0000000..bdf249e --- /dev/null +++ b/src/components/slides/ProtocolTableSlide.vue @@ -0,0 +1,34 @@ + + + From 07f98a04dc7c627185b2c6096ac2dc3296be2311 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 05/10] feat: event anatomy slide NIP-01 event rendered as syntax-highlighted JSON hugged by large nostr-purple braces, with the derived id/sig fields badged and a fill -> hash -> sign strip. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/EventAnatomySlide.vue | 85 +++++++++++++++++++++ 1 file changed, 85 insertions(+) create mode 100644 src/components/slides/EventAnatomySlide.vue diff --git a/src/components/slides/EventAnatomySlide.vue b/src/components/slides/EventAnatomySlide.vue new file mode 100644 index 0000000..9ebaf31 --- /dev/null +++ b/src/components/slides/EventAnatomySlide.vue @@ -0,0 +1,85 @@ + + + From ba9d4912acb53f377960605dd4850f928bb6e029 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 06/10] feat: relay slides (outbox model, categories, access asymmetry) Explain the outbox/gossip model and Amethyst's relay categories, plus a read/write asymmetry table showing why outbox / inbox / DM need different relay sets and what breaks if a list is empty. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/OutboxSlide.vue | 45 +++++++++++ src/components/slides/RelayAccessSlide.vue | 70 +++++++++++++++++ .../slides/RelayCategoriesSlide.vue | 75 +++++++++++++++++++ 3 files changed, 190 insertions(+) create mode 100644 src/components/slides/OutboxSlide.vue create mode 100644 src/components/slides/RelayAccessSlide.vue create mode 100644 src/components/slides/RelayCategoriesSlide.vue diff --git a/src/components/slides/OutboxSlide.vue b/src/components/slides/OutboxSlide.vue new file mode 100644 index 0000000..c650d65 --- /dev/null +++ b/src/components/slides/OutboxSlide.vue @@ -0,0 +1,45 @@ + + + diff --git a/src/components/slides/RelayAccessSlide.vue b/src/components/slides/RelayAccessSlide.vue new file mode 100644 index 0000000..1dc1000 --- /dev/null +++ b/src/components/slides/RelayAccessSlide.vue @@ -0,0 +1,70 @@ + + + diff --git a/src/components/slides/RelayCategoriesSlide.vue b/src/components/slides/RelayCategoriesSlide.vue new file mode 100644 index 0000000..1736053 --- /dev/null +++ b/src/components/slides/RelayCategoriesSlide.vue @@ -0,0 +1,75 @@ + + + From 9ef56223d49c1d8caa3c934e148780d075360692 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 07/10] feat: zap explainer slide Break a zap into its two layers: the plain Lightning payment and the NIP-57 zap receipt (kind 9735) that makes it publicly visible. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/ZapSlide.vue | 56 ++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/components/slides/ZapSlide.vue diff --git a/src/components/slides/ZapSlide.vue b/src/components/slides/ZapSlide.vue new file mode 100644 index 0000000..ba72b33 --- /dev/null +++ b/src/components/slides/ZapSlide.vue @@ -0,0 +1,56 @@ + + + From 18fe550677460e3cdcda39c1bf49da73aab4bed1 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:53:45 +0200 Subject: [PATCH 08/10] feat: assemble the 19-slide intro-to-Nostr deck Wire every slide into the ordered deck definition that PresentationView renders. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/features/slides/slides.ts | 229 ++++++++++++++++++++++++++++++++++ 1 file changed, 229 insertions(+) create mode 100644 src/features/slides/slides.ts diff --git a/src/features/slides/slides.ts b/src/features/slides/slides.ts new file mode 100644 index 0000000..6229036 --- /dev/null +++ b/src/features/slides/slides.ts @@ -0,0 +1,229 @@ +import type { Slide } from './types' + +import TitleSlide from '@/components/slides/TitleSlide.vue' +import BulletsSlide from '@/components/slides/BulletsSlide.vue' +import KeysSlide from '@/components/slides/KeysSlide.vue' +import EventAnatomySlide from '@/components/slides/EventAnatomySlide.vue' +import ProtocolCompareSlide from '@/components/slides/ProtocolCompareSlide.vue' +import ProtocolTableSlide from '@/components/slides/ProtocolTableSlide.vue' +import OutboxSlide from '@/components/slides/OutboxSlide.vue' +import RelayCategoriesSlide from '@/components/slides/RelayCategoriesSlide.vue' +import RelayAccessSlide from '@/components/slides/RelayAccessSlide.vue' +import ZapSlide from '@/components/slides/ZapSlide.vue' +import ResourcesSlide from '@/components/slides/ResourcesSlide.vue' + +/** + * Ordered deck. Each entry maps an id (used for the URL hash) to a layout + * component + its props. Text slides reuse BulletsSlide; the three hard + * concepts (protocol model, outbox, relay categories) get bespoke components. + */ +export const slides: Slide[] = [ + { + id: 'intro', + label: 'Welcome', + component: TitleSlide, + props: { + title: 'Getting on Nostr', + subtitle: 'A decentralized social protocol you actually own — in about 10 minutes.', + tagline: 'Notes and Other Stuff Transmitted by Relays', + }, + }, + { + id: 'what', + label: 'What is Nostr?', + component: BulletsSlide, + props: { + kicker: 'The 30-second version', + title: 'What is Nostr?', + bullets: [ + { lead: 'A protocol, not an app.', text: 'Like email — many apps, one open network.' }, + { lead: 'No signup.', text: 'No email, no phone number, no company approving you.' }, + { lead: 'You own your identity.', text: 'A key pair you carry between apps.' }, + { lead: 'Censorship-resistant.', text: 'No single server can erase you.' }, + ], + }, + }, + { + id: 'vs-http', + label: 'Nostr vs HTTP', + component: ProtocolCompareSlide, + }, + { + id: 'vs-http-table', + label: 'HTTP vs Nostr table', + component: ProtocolTableSlide, + }, + { + id: 'keys', + label: 'Your keys', + component: KeysSlide, + }, + { + id: 'guard-nsec', + label: 'Guard your nsec', + component: BulletsSlide, + props: { + kicker: 'Rule #1', + title: 'Protect your nsec', + bullets: [ + { lead: 'It can’t be reset.', text: 'Lose it and the account is gone forever.' }, + { lead: 'It’s total control.', text: 'Anyone with it can post as you and read your DMs.' }, + { lead: 'Never paste it into websites.', text: 'Legit apps never ask you to.' }, + { lead: 'Use a signer app.', text: 'On Android, Amber holds the key and approves actions for you.' }, + ], + footnote: 'Back it up offline — write it on paper or store it in a password manager.', + }, + }, + { + id: 'signing', + label: 'Signing', + component: BulletsSlide, + props: { + kicker: 'How you log in', + title: 'Every action is signed', + bullets: [ + { lead: 'No password server.', text: 'You prove who you are with a cryptographic signature.' }, + { lead: 'Each post, like & follow', text: 'is signed by your key before it goes out.' }, + { lead: 'Anyone can verify it', text: 'came from your npub — math, not trust.' }, + { lead: 'A signer (Amber) does this', text: 'so your nsec never touches individual apps.' }, + ], + }, + }, + { + id: 'event', + label: 'Anatomy of an event', + component: EventAnatomySlide, + }, + { + id: 'amethyst', + label: 'Get Amethyst', + component: BulletsSlide, + props: { + kicker: 'Hands-on · Android', + title: 'Get started with Amethyst', + bullets: [ + { lead: '1. Install Amethyst', text: 'from the Play Store, Zap.store, or F-Droid (Obtainium).' }, + { lead: '2. Install Amber', text: 'the companion signer — keeps your nsec out of every app.' }, + { lead: '3. Create your key', text: 'Amber generates your npub / nsec pair.' }, + { lead: '4. Back up the nsec', text: 'before you do anything else.' }, + ], + footnote: 'iOS or web instead? Primal, Damus (iOS) and Nos work the same way — concepts carry over.', + }, + }, + { + id: 'profile', + label: 'Your profile', + component: BulletsSlide, + props: { + kicker: 'Make it yours', + title: 'Set up your profile', + bullets: [ + { lead: 'Display name & picture', text: 'so people recognize you.' }, + { lead: 'A short bio', text: 'tell folks what you’re into.' }, + { lead: 'NIP-05 (optional)', text: 'a name@domain handle that verifies you — like a checkmark you control.' }, + ], + }, + }, + { + id: 'follow', + label: 'Find people', + component: BulletsSlide, + props: { + kicker: 'Fill your feed', + title: 'Find people to follow', + bullets: [ + { lead: 'Search a known npub', text: 'paste a friend’s public key.' }, + { lead: 'Follow packs', text: 'Amethyst suggests curated starter lists.' }, + { lead: 'nostr.directory & #nostr', text: 'find people who’ve linked their accounts.' }, + { lead: 'Follow ~20 people', text: 'a fuller feed makes everything click.' }, + ], + }, + }, + { + id: 'relays-what', + label: 'What are relays?', + component: BulletsSlide, + props: { + kicker: 'The infrastructure', + title: 'What is a relay?', + bullets: [ + { lead: 'A dumb server', text: 'that stores notes and rebroadcasts them to whoever asks.' }, + { lead: 'Anyone can run one', text: 'there are thousands; most are free.' }, + { lead: 'You connect to several', text: 'for redundancy — if one goes down, your notes live on the others.' }, + { lead: 'Relays don’t know each other', text: 'your app is what ties them together.' }, + ], + footnote: 'Feed feels slow or empty? It’s almost always your relay choice — not the app.', + }, + }, + { + id: 'outbox', + label: 'Outbox model', + component: OutboxSlide, + }, + { + id: 'relay-categories', + label: 'Relay categories', + component: RelayCategoriesSlide, + }, + { + id: 'relay-access', + label: 'Why the sets differ', + component: RelayAccessSlide, + }, + { + id: 'paid-relays', + label: 'Paid vs free', + component: BulletsSlide, + props: { + kicker: 'Choosing relays', + title: 'Free, paid & where to look', + bullets: [ + { lead: 'Most relays are free', text: 'great to start — just add a few big, reliable ones.' }, + { lead: 'Open relays double up', text: 'a free, openly-readable/writable relay (nos.lol) can sit in several lists at once.' }, + { lead: 'Paid relays stay in their lane', text: 'nostr.wine & co. restrict access — keep them category-specific (e.g. DMs), never your public inbox.' }, + { lead: 'Browse & health-check', text: 'nostr.watch shows which relays are up, fast, free vs. paid, and reputable.' }, + ], + }, + }, + { + id: 'first-actions', + label: 'First actions', + component: BulletsSlide, + props: { + kicker: 'You’re on', + title: 'Post your first note — and zaps', + bullets: [ + { lead: 'Just start typing', text: 'hit post; it goes to your outbox relays and out to the world.' }, + { lead: 'Reply, like, repost', text: 'same as any social app — but portable.' }, + { lead: 'Zaps ⚡', text: 'send real Bitcoin tips over Lightning by tapping the zap icon.' }, + { lead: 'Add a wallet later', text: 'connect a Lightning wallet to send and receive zaps.' }, + ], + }, + }, + { + id: 'zap', + label: 'How a zap works', + component: ZapSlide, + }, + { + id: 'recap', + label: 'Recap', + component: ResourcesSlide, + props: { + kicker: 'You made it', + title: 'Recap & resources', + takeaways: [ + 'Your keys are your identity — guard the nsec.', + 'You publish to relays you choose; followers read from them (outbox).', + 'Set ~3 outbox, ~3 inbox, ~3 DM relays and you’re sorted.', + 'No server owns you — move apps and relays freely.', + ], + links: [ + { label: 'nostr.how — get started', url: 'https://nostr.how/en/get-started' }, + { label: 'Vitor Pamplona — Relay Setup 101', url: 'https://vitor.npub.pro/post/relay-setup/' }, + { label: 'nostr.watch — browse relays', url: 'https://nostr.watch' }, + { label: 'Amethyst on GitHub', url: 'https://github.com/vitorpamplona/amethyst' }, + ], + }, + }, +] From 5f97b48f6d1197b9c7830fa7ed6ae41afa11b4bc Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 02:57:34 +0200 Subject: [PATCH 09/10] feat: animate the event slide with clickable step controls On slide entry, reveal the typed fields first, then flash in the hashed `id` and signed `sig` (~0.9s apart) while the Fill -> Hash -> Sign strip advances. The step boxes are also buttons: click one to jump to (and replay) that state; the first click cancels the auto-play so a presenter can drive it manually. Gated behind prefers-reduced-motion. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/EventAnatomySlide.vue | 99 +++++++++++++++++++-- 1 file changed, 92 insertions(+), 7 deletions(-) diff --git a/src/components/slides/EventAnatomySlide.vue b/src/components/slides/EventAnatomySlide.vue index 9ebaf31..3d08c82 100644 --- a/src/components/slides/EventAnatomySlide.vue +++ b/src/components/slides/EventAnatomySlide.vue @@ -1,4 +1,5 @@ + + From abbfcaf3ef8c7ae15b1454e1e5f69cfdb0545417 Mon Sep 17 00:00:00 2001 From: padreug Date: Wed, 22 Jul 2026 03:09:34 +0200 Subject: [PATCH 10/10] feat: clarify zap receipts and add kind 9735 anatomy slide Rework the zap slide's step 2 to say the recipient's Lightning provider signs the receipt with its OWN nostr key (advertised as nostrPubkey), and add a dedicated 'Anatomy of a zap receipt' slide breaking down the kind 9735 event: who signs it (service), who was zapped (p tag), and who zapped (the embedded signed 9734 in description). Grounded in NIP-57 and the lnbits/lnurlp implementation. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/components/slides/ZapReceiptSlide.vue | 72 +++++++++++++++++++++++ src/components/slides/ZapSlide.vue | 17 +++--- src/features/slides/slides.ts | 6 ++ 3 files changed, 88 insertions(+), 7 deletions(-) create mode 100644 src/components/slides/ZapReceiptSlide.vue diff --git a/src/components/slides/ZapReceiptSlide.vue b/src/components/slides/ZapReceiptSlide.vue new file mode 100644 index 0000000..1ef1fad --- /dev/null +++ b/src/components/slides/ZapReceiptSlide.vue @@ -0,0 +1,72 @@ + + + diff --git a/src/components/slides/ZapSlide.vue b/src/components/slides/ZapSlide.vue index ba72b33..99fe0ff 100644 --- a/src/components/slides/ZapSlide.vue +++ b/src/components/slides/ZapSlide.vue @@ -29,10 +29,12 @@ import SlideShell from './SlideShell.vue' The receipt · the Nostr part -

Their Lightning server publishes a signed zap receipt.

+

Their Lightning provider signs a zap receipt.

- Once the invoice clears, the server signs a receipt proving payment and posts it to relays. - Now the zap shows up publicly under the note — amount, sender, comment. + The provider has its own Nostr key. When the invoice clears it + signs a kind 9735 event — tagged with + who was zapped and embedding your signed request (which says who + zapped, how much, and any comment) — then posts it to the relays you named.

@@ -47,10 +49,11 @@ import SlideShell from './SlideShell.vue'

- Under the hood (NIP-57): your client attaches a signed - zap request (kind 9734); the recipient’s server returns a - zap receipt (kind 9735). The server must opt in via - allowsNostr. + Under the hood (NIP-57): your signed + zap request (kind 9734) rides along with the LNURL-pay invoice; the + provider advertises allowsNostr + its + nostrPubkey, and clients only trust a + 9735 receipt signed by that key. (Next: its anatomy →)

diff --git a/src/features/slides/slides.ts b/src/features/slides/slides.ts index 6229036..9425035 100644 --- a/src/features/slides/slides.ts +++ b/src/features/slides/slides.ts @@ -10,6 +10,7 @@ import OutboxSlide from '@/components/slides/OutboxSlide.vue' import RelayCategoriesSlide from '@/components/slides/RelayCategoriesSlide.vue' import RelayAccessSlide from '@/components/slides/RelayAccessSlide.vue' import ZapSlide from '@/components/slides/ZapSlide.vue' +import ZapReceiptSlide from '@/components/slides/ZapReceiptSlide.vue' import ResourcesSlide from '@/components/slides/ResourcesSlide.vue' /** @@ -205,6 +206,11 @@ export const slides: Slide[] = [ label: 'How a zap works', component: ZapSlide, }, + { + id: 'zap-receipt', + label: 'Zap receipt anatomy', + component: ZapReceiptSlide, + }, { id: 'recap', label: 'Recap',