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) <noreply@anthropic.com>
This commit is contained in:
parent
8de72c9ae3
commit
07f98a04dc
1 changed files with 85 additions and 0 deletions
85
src/components/slides/EventAnatomySlide.vue
Normal file
85
src/components/slides/EventAnatomySlide.vue
Normal file
|
|
@ -0,0 +1,85 @@
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { PenLine, Hash, KeyRound, ArrowRight } from '@lucide/vue'
|
||||||
|
import SlideShell from './SlideShell.vue'
|
||||||
|
|
||||||
|
// Each field of a NIP-01 event: the key, a sample value, and what it means.
|
||||||
|
// `derived` fields aren't typed by you — they're computed when the event is signed.
|
||||||
|
interface Field {
|
||||||
|
key: string
|
||||||
|
value: string
|
||||||
|
kind: 'string' | 'number' | 'array'
|
||||||
|
comment: string
|
||||||
|
derived?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const fields: Field[] = [
|
||||||
|
{ key: 'pubkey', value: '"3bf0c6…2a"', kind: 'string', comment: 'who wrote it — your public key' },
|
||||||
|
{ key: 'created_at', value: '1721600000', kind: 'number', comment: 'when — unix seconds' },
|
||||||
|
{ key: 'kind', value: '1', kind: 'number', comment: '1 = short text note' },
|
||||||
|
{ key: 'tags', value: '[["e","…"],["p","…"]]', kind: 'array', comment: 'references: reply-to, mentions' },
|
||||||
|
{ key: 'content', value: '"gm nostr ⚡"', kind: 'string', comment: 'the message itself' },
|
||||||
|
{ key: 'id', value: '"0a1b2c…9f"', kind: 'string', comment: 'sha256 of the fields above', derived: true },
|
||||||
|
{ key: 'sig', value: '"d4e5…c7"', kind: 'string', comment: 'Schnorr sig over id — proof it’s you', derived: true },
|
||||||
|
]
|
||||||
|
|
||||||
|
const valueClass = { string: 'text-emerald-300', number: 'text-amber-300', array: 'text-zinc-300' }
|
||||||
|
|
||||||
|
const steps = [
|
||||||
|
{ icon: PenLine, label: 'Fill the fields', sub: 'pubkey · kind · tags · content · time' },
|
||||||
|
{ icon: Hash, label: 'Hash them → id', sub: 'sha256 of the serialized event' },
|
||||||
|
{ icon: KeyRound, label: 'Sign id → sig', sub: 'Schnorr signature with your nsec' },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<SlideShell kicker="NIP-01 · the atom of Nostr" title="Anatomy of an event">
|
||||||
|
<!-- The event, as JSON, hugged by big nostr-purple braces -->
|
||||||
|
<div class="flex items-center justify-center gap-3 sm:gap-6">
|
||||||
|
<span class="select-none text-[9rem] font-thin leading-none text-violet-500/80 sm:text-[11rem]">{</span>
|
||||||
|
|
||||||
|
<div class="grid grid-cols-[auto_auto_1fr] items-center gap-x-5 gap-y-1.5 font-mono text-base sm:text-lg">
|
||||||
|
<template v-for="(f, i) in fields" :key="f.key">
|
||||||
|
<span
|
||||||
|
class="justify-self-end"
|
||||||
|
:class="f.derived ? 'font-semibold text-violet-300' : 'text-violet-300'"
|
||||||
|
>
|
||||||
|
"{{ f.key }}"<span class="text-zinc-500">:</span>
|
||||||
|
</span>
|
||||||
|
<span class="whitespace-nowrap" :class="valueClass[f.kind]"
|
||||||
|
>{{ f.value }}<span v-if="i < fields.length - 1" class="text-zinc-500">,</span></span
|
||||||
|
>
|
||||||
|
<span class="whitespace-nowrap text-sm text-zinc-600">
|
||||||
|
<span class="text-zinc-500">// </span>{{ f.comment
|
||||||
|
}}<span v-if="f.derived" class="ml-1 rounded bg-violet-500/15 px-1 text-xs text-violet-300"
|
||||||
|
>computed</span
|
||||||
|
>
|
||||||
|
</span>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<span class="select-none text-[9rem] font-thin leading-none text-violet-500/80 sm:text-[11rem]">}</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- How it's built & signed -->
|
||||||
|
<div class="mt-8 flex flex-col items-stretch gap-3 sm:flex-row sm:items-center sm:justify-center">
|
||||||
|
<template v-for="(s, i) in steps" :key="s.label">
|
||||||
|
<div class="flex items-center gap-3 rounded-xl border border-zinc-700 bg-zinc-900/60 px-4 py-3">
|
||||||
|
<component :is="s.icon" class="h-6 w-6 flex-none text-violet-400" />
|
||||||
|
<div>
|
||||||
|
<p class="font-semibold text-white">{{ s.label }}</p>
|
||||||
|
<p class="text-xs text-zinc-400">{{ s.sub }}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<ArrowRight
|
||||||
|
v-if="i < steps.length - 1"
|
||||||
|
class="mx-auto h-5 w-5 flex-none rotate-90 text-zinc-500 sm:rotate-0"
|
||||||
|
/>
|
||||||
|
</template>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<p class="mt-6 text-center text-sm text-zinc-500">
|
||||||
|
Anyone recomputes the hash and checks <code class="text-zinc-300">sig</code> against your
|
||||||
|
<code class="text-zinc-300">pubkey</code> — tamper with one character and it fails. No server vouches for you; the math does.
|
||||||
|
</p>
|
||||||
|
</SlideShell>
|
||||||
|
</template>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue