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) <noreply@anthropic.com>
This commit is contained in:
parent
88f8d6b681
commit
8de72c9ae3
2 changed files with 172 additions and 0 deletions
138
src/components/slides/ProtocolCompareSlide.vue
Normal file
138
src/components/slides/ProtocolCompareSlide.vue
Normal file
|
|
@ -0,0 +1,138 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideShell from './SlideShell.vue'
|
||||||
|
|
||||||
|
// HTTP: three users funnel into ONE server.
|
||||||
|
const users = [40, 95, 150]
|
||||||
|
|
||||||
|
// Nostr: you broadcast out to MANY relays (a little mesh).
|
||||||
|
const you = { x: 52, y: 95 }
|
||||||
|
const relays = [
|
||||||
|
{ x: 200, y: 32 },
|
||||||
|
{ x: 250, y: 62 },
|
||||||
|
{ x: 262, y: 112 },
|
||||||
|
{ x: 232, y: 158 },
|
||||||
|
{ x: 182, y: 128 },
|
||||||
|
]
|
||||||
|
// Faint relay-to-relay links to imply a gossiping network.
|
||||||
|
const mesh = [
|
||||||
|
[0, 1],
|
||||||
|
[1, 2],
|
||||||
|
[2, 3],
|
||||||
|
[3, 4],
|
||||||
|
[4, 0],
|
||||||
|
[1, 4],
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideShell kicker="Mental model" title="Nostr isn’t HTTP">
|
||||||
|
<div class="grid gap-6 sm:grid-cols-2">
|
||||||
|
<!-- HTTP: many users → one server -->
|
||||||
|
<div class="rounded-xl border border-zinc-700 bg-zinc-900/60 p-6">
|
||||||
|
<p class="mb-2 text-sm font-semibold uppercase tracking-widest text-zinc-400">
|
||||||
|
The web (HTTP)
|
||||||
|
</p>
|
||||||
|
<svg viewBox="0 0 300 190" class="mx-auto w-full max-w-[300px]">
|
||||||
|
<!-- connections -->
|
||||||
|
<g stroke="#52525b" stroke-width="2">
|
||||||
|
<line v-for="y in users" :key="'l' + y" x1="54" :y1="y" x2="206" y2="95" />
|
||||||
|
</g>
|
||||||
|
<!-- users -->
|
||||||
|
<g fill="#a1a1aa">
|
||||||
|
<template v-for="y in users" :key="'u' + y">
|
||||||
|
<circle cx="42" :cy="y - 7" r="6" />
|
||||||
|
<path :d="`M33 ${y + 9} a9 9 0 0 1 18 0`" />
|
||||||
|
</template>
|
||||||
|
</g>
|
||||||
|
<!-- one server -->
|
||||||
|
<rect x="206" y="66" width="66" height="58" rx="8" fill="#27272a" stroke="#a1a1aa" stroke-width="2" />
|
||||||
|
<g stroke="#a1a1aa" stroke-width="2">
|
||||||
|
<line x1="218" y1="84" x2="260" y2="84" />
|
||||||
|
<line x1="218" y1="95" x2="260" y2="95" />
|
||||||
|
<line x1="218" y1="106" x2="260" y2="106" />
|
||||||
|
</g>
|
||||||
|
<circle cx="252" cy="118" r="2.5" fill="#ef4444" />
|
||||||
|
<text x="239" y="150" text-anchor="middle" fill="#71717a" font-size="12">single point</text>
|
||||||
|
</svg>
|
||||||
|
<p class="mt-2 text-center text-sm text-zinc-400">
|
||||||
|
One server holds your account <em>and</em> your data. It bans you? You’re gone.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Nostr: you fan out to many relays -->
|
||||||
|
<div class="rounded-xl border border-violet-600/50 bg-violet-950/20 p-6">
|
||||||
|
<p class="mb-2 text-sm font-semibold uppercase tracking-widest text-violet-400">Nostr</p>
|
||||||
|
<svg viewBox="0 0 300 190" class="mx-auto w-full max-w-[300px]">
|
||||||
|
<!-- faint relay mesh -->
|
||||||
|
<g stroke="#5b21b6" stroke-width="1.5" opacity="0.6">
|
||||||
|
<line
|
||||||
|
v-for="([a, b], i) in mesh"
|
||||||
|
:key="'m' + i"
|
||||||
|
:x1="relays[a].x"
|
||||||
|
:y1="relays[a].y"
|
||||||
|
:x2="relays[b].x"
|
||||||
|
:y2="relays[b].y"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<!-- broadcast lines (animated) -->
|
||||||
|
<g stroke="#8b5cf6" stroke-width="2" class="flow">
|
||||||
|
<line
|
||||||
|
v-for="(r, i) in relays"
|
||||||
|
:key="'b' + i"
|
||||||
|
:x1="you.x + 14"
|
||||||
|
:y1="you.y"
|
||||||
|
:x2="r.x"
|
||||||
|
:y2="r.y"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<!-- relays -->
|
||||||
|
<g>
|
||||||
|
<circle
|
||||||
|
v-for="(r, i) in relays"
|
||||||
|
:key="'r' + i"
|
||||||
|
:cx="r.x"
|
||||||
|
:cy="r.y"
|
||||||
|
r="11"
|
||||||
|
fill="#2e1065"
|
||||||
|
stroke="#a78bfa"
|
||||||
|
stroke-width="2"
|
||||||
|
/>
|
||||||
|
</g>
|
||||||
|
<!-- you (signed) -->
|
||||||
|
<g fill="#8b5cf6">
|
||||||
|
<circle :cx="you.x" :cy="you.y - 8" r="8" />
|
||||||
|
<path :d="`M${you.x - 12} ${you.y + 10} a12 12 0 0 1 24 0`" />
|
||||||
|
</g>
|
||||||
|
<text :x="you.x" :y="you.y + 34" text-anchor="middle" fill="#a78bfa" font-size="12">you</text>
|
||||||
|
<text x="235" y="184" text-anchor="middle" fill="#a78bfa" font-size="12">many relays</text>
|
||||||
|
</svg>
|
||||||
|
<p class="mt-2 text-center text-sm text-zinc-300">
|
||||||
|
You sign notes and broadcast to relays you choose. One drops you? The rest still have it.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-6 text-sm text-zinc-500">
|
||||||
|
Relays actually talk over <code class="text-zinc-300">wss://</code> (WebSockets) — same plumbing
|
||||||
|
as the web. What flips is
|
||||||
|
<span class="text-zinc-300">who owns your identity and where your data lives.</span>
|
||||||
|
</p>
|
||||||
|
</SlideShell>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.flow line {
|
||||||
|
stroke-dasharray: 5 6;
|
||||||
|
animation: flow 0.9s linear infinite;
|
||||||
|
}
|
||||||
|
@keyframes flow {
|
||||||
|
to {
|
||||||
|
stroke-dashoffset: -11;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (prefers-reduced-motion: reduce) {
|
||||||
|
.flow line {
|
||||||
|
animation: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
34
src/components/slides/ProtocolTableSlide.vue
Normal file
34
src/components/slides/ProtocolTableSlide.vue
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import SlideShell from './SlideShell.vue'
|
||||||
|
|
||||||
|
const rows = [
|
||||||
|
{ label: 'Identity', http: 'An account the server owns', nostr: 'A keypair you own' },
|
||||||
|
{ label: 'Data lives', http: 'On one company’s server', nostr: 'Copied across many relays' },
|
||||||
|
{ label: 'Model', http: 'Request → response', nostr: 'Publish signed events (pub/sub)' },
|
||||||
|
{ label: 'Trust', http: 'Trust the host not to lie', nostr: 'Signatures — math, not trust' },
|
||||||
|
{ label: 'If it bans you', http: 'You’re gone', nostr: 'Move relays, keep your identity' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideShell kicker="Mental model" title="HTTP vs Nostr, point by point">
|
||||||
|
<div class="overflow-hidden rounded-xl border border-zinc-700">
|
||||||
|
<table class="w-full text-left text-lg">
|
||||||
|
<thead class="bg-zinc-800/60 text-sm uppercase tracking-wider text-zinc-400">
|
||||||
|
<tr>
|
||||||
|
<th class="px-5 py-3 font-medium"></th>
|
||||||
|
<th class="px-5 py-3 font-medium">HTTP</th>
|
||||||
|
<th class="px-5 py-3 font-medium text-violet-300">Nostr</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody class="divide-y divide-zinc-800">
|
||||||
|
<tr v-for="row in rows" :key="row.label">
|
||||||
|
<td class="px-5 py-4 font-semibold text-white">{{ row.label }}</td>
|
||||||
|
<td class="px-5 py-4 text-zinc-400">{{ row.http }}</td>
|
||||||
|
<td class="px-5 py-4 text-zinc-200">{{ row.nostr }}</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</SlideShell>
|
||||||
|
</template>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue