Rider Card: badge system, remove shop card, fix print, better license ID
- Remove "Shop Rider Card" type — shop affiliation is now a "Rides For" field
on the Rider License, separate from sponsors (brands/companies)
- Fix print: use visibility:hidden approach with .print-only-card overlay,
properly isolated from Layout wrapper (fixes blank print output)
- Replace "No." top-right with "LICENSE ID" showing SS-XXXXXXX derived from
pubkey + truncated npub underneath — meaningful, not random-looking
- Add Skill Badge system (kind 1293): witnessed badges are signed Nostr events
- "Award Badge" dialog: paste rider npub, pick skills, optional spot name
- Witnessed badges shown in gold ⭐ on card, distinct from self-claimed
- Badges tab shows full history with witness avatar, name, and location
- Park Access Card: only 2 types now (Rider License / Park Access),
added blue info note explaining owner-only issuing constraint
- Rename "Member npub" to "Rider's Nostr Address (npub)" with explanation
- Update NIP.md: document kind 1293 skill badges, remove kind 37097
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
parent
838ef3f355
commit
e2582aa013
3 changed files with 751 additions and 321 deletions
162
NIP.md
162
NIP.md
|
|
@ -1219,13 +1219,15 @@ When a match is found, the app can facilitate P2P trading via encrypted DMs (NIP
|
||||||
3. No price or inventory data is publicly visible unless explicitly set to "public"
|
3. No price or inventory data is publicly visible unless explicitly set to "public"
|
||||||
4. Group DMs (NIP-17) can be used for coordinating trades between multiple parties
|
4. Group DMs (NIP-17) can be used for coordinating trades between multiple parties
|
||||||
|
|
||||||
## Rider Cards (kinds 35679, 37097)
|
## Rider Cards
|
||||||
|
|
||||||
SkateSpots supports two types of cryptographically signed physical-print cards. Both are addressable events issued by one pubkey to another, verifiable on-chain by anyone who scans the card's QR code.
|
SkateSpots supports physical-print cards that are cryptographically signed on Nostr and verifiable by anyone.
|
||||||
|
|
||||||
### Park Access Card (kind 35679)
|
### Park Access Card (kind 35679)
|
||||||
|
|
||||||
Issued by a private park or skatepark owner to a specific rider, granting access rights. The park's Nostr key signs the event — anyone can verify the card is genuine.
|
Issued by a private skatepark owner to a specific rider, granting access rights. The park's Nostr key signs the event — anyone can verify the card is genuine by scanning the QR code.
|
||||||
|
|
||||||
|
**Constraint:** Park Access Cards for private parks (`access=private` in kind 30818) should only be issued by the park's owner pubkey. Clients verify the issuer's pubkey matches the park event's `pubkey` field.
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
|
@ -1244,31 +1246,8 @@ Issued by a private park or skatepark owner to a specific rider, granting access
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
### Shop Rider Card (kind 37097)
|
|
||||||
|
|
||||||
Issued by a skate shop to a rider who represents them. Proves sponsorship/affiliation — team riders, am riders, flow, staff, ambassadors.
|
|
||||||
|
|
||||||
```json
|
|
||||||
{
|
|
||||||
"kind": 37097,
|
|
||||||
"content": "",
|
|
||||||
"tags": [
|
|
||||||
["d", "<unique-id>"],
|
|
||||||
["p", "<rider-pubkey>"],
|
|
||||||
["t", "shop-rider"],
|
|
||||||
["issuer", "<shop name>"],
|
|
||||||
["access-level", "<Am Rider|Team Rider|Flow|Ambassador|Staff|Filmer|Coach>"],
|
|
||||||
["theme", "<thug|lime|chrome|wood|midnight|sunset>"],
|
|
||||||
["valid-thru", "<YYYY or YYYY-MM-DD, optional>"],
|
|
||||||
["alt", "Shop Rider Card"]
|
|
||||||
]
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
### Card Themes
|
### Card Themes
|
||||||
|
|
||||||
Both card types support these visual themes for printing:
|
|
||||||
|
|
||||||
| Value | Description |
|
| Value | Description |
|
||||||
|-------|-------------|
|
|-------|-------------|
|
||||||
| `thug` | Black + Neon Red (default) |
|
| `thug` | Black + Neon Red (default) |
|
||||||
|
|
@ -1286,13 +1265,140 @@ The personal Rider License is generated client-side from the user's kind 0 profi
|
||||||
- Spots added: count of kind 30818 authored by the user
|
- Spots added: count of kind 30818 authored by the user
|
||||||
- Zaps received: count of kind 9735 targeting the user
|
- Zaps received: count of kind 9735 targeting the user
|
||||||
- Events created: count of kind 31923 authored by the user
|
- Events created: count of kind 31923 authored by the user
|
||||||
- Shop cards held: count of kind 37097 with `#p` = user
|
|
||||||
- Park cards held: count of kind 35679 with `#p` = user
|
- Park cards held: count of kind 35679 with `#p` = user
|
||||||
|
- Witnessed skill badges: count of kind 1293 with `#p` = user
|
||||||
|
|
||||||
|
The Rider License includes two categories of skill badges:
|
||||||
|
- **Self-claimed** — selected by the rider themselves, shown in theme color
|
||||||
|
- **Witnessed** — awarded by others (kind 1293), shown in gold ⭐
|
||||||
|
|
||||||
|
The "Rides For" field displays the local shop the rider represents, distinct from sponsors (brands).
|
||||||
|
|
||||||
### Verification
|
### Verification
|
||||||
|
|
||||||
The QR code on printed Park Access and Shop Rider cards encodes the Nostr address (`naddr`) of the issuing event. Verifiers scan the QR, fetch the event from any relay, and confirm the issuer's signature.
|
The QR code on printed Park Access cards encodes the Nostr address (`naddr`) of the issuing event. Verifiers scan the QR, fetch the event from any relay, and confirm the issuer's signature.
|
||||||
|
|
||||||
```
|
```
|
||||||
URL format: https://skate-spots.org/verify/<naddr>
|
URL format: https://skate-spots.org/verify/<naddr>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Skill Badge Awards (kind 1293)
|
||||||
|
|
||||||
|
Anyone who witnesses a rider performing a trick can award them a cryptographically signed skill badge. This creates an on-chain proof of skill — unfakeable because the witness's Nostr key signs the event.
|
||||||
|
|
||||||
|
### Design Rationale
|
||||||
|
|
||||||
|
Skill badges are **regular events** (not addressable) because:
|
||||||
|
- Multiple witnesses can each award the same skill to the same rider independently
|
||||||
|
- Each badge is a distinct attestation with its own timestamp and witness
|
||||||
|
- The full history of attestations is valuable (not just the latest)
|
||||||
|
|
||||||
|
### Event Format
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 1293,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
["p", "<rider-pubkey>"],
|
||||||
|
["skill", "<skill-id>"],
|
||||||
|
["skill", "<skill-id>"],
|
||||||
|
["spot", "<spot name (optional)>"],
|
||||||
|
["t", "skill-badge"],
|
||||||
|
["alt", "Skill badge awarded: <skill>, <skill>"]
|
||||||
|
]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Required Tags
|
||||||
|
|
||||||
|
| Tag | Description |
|
||||||
|
|-----|-------------|
|
||||||
|
| `p` | Pubkey of the rider receiving the badge |
|
||||||
|
| `skill` | Skill identifier (one tag per skill, multiple allowed) |
|
||||||
|
| `t` | Must be `skill-badge` for discoverability |
|
||||||
|
| `alt` | NIP-31 human-readable description |
|
||||||
|
|
||||||
|
### Optional Tags
|
||||||
|
|
||||||
|
| Tag | Description |
|
||||||
|
|-----|-------------|
|
||||||
|
| `spot` | Name of the spot where the trick was witnessed |
|
||||||
|
| `a` | Reference to a kind 30818 spot event |
|
||||||
|
|
||||||
|
### Skill Identifiers
|
||||||
|
|
||||||
|
| Value | Label |
|
||||||
|
|-------|-------|
|
||||||
|
| `kickflip` | Kickflip |
|
||||||
|
| `heelflip` | Heelflip |
|
||||||
|
| `switch` | Switch stance |
|
||||||
|
| `nollie` | Nollie |
|
||||||
|
| `fakie` | Fakie |
|
||||||
|
| `grind` | Grinds |
|
||||||
|
| `slide` | Slides |
|
||||||
|
| `manual` | Manuals |
|
||||||
|
| `vert` | Vert |
|
||||||
|
| `bowl` | Bowl |
|
||||||
|
| `street` | Street |
|
||||||
|
| `park` | Park |
|
||||||
|
| `film` | Filmer |
|
||||||
|
| `diy` | DIY Builder |
|
||||||
|
|
||||||
|
### Trust Model
|
||||||
|
|
||||||
|
The value of a badge is proportional to the reputation of the witness:
|
||||||
|
- NIP-05 verified witnesses carry more weight
|
||||||
|
- Witnesses known in the community (follows, mutual follows) carry more weight
|
||||||
|
- The skill badge feed is public — anyone can query badges for any rider
|
||||||
|
|
||||||
|
### Querying
|
||||||
|
|
||||||
|
**All badges received by a rider:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kinds": [1293],
|
||||||
|
"#p": ["<rider-pubkey>"],
|
||||||
|
"#t": ["skill-badge"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**All badges issued by a witness:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kinds": [1293],
|
||||||
|
"authors": ["<witness-pubkey>"],
|
||||||
|
"#t": ["skill-badge"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Badges for a specific skill:**
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kinds": [1293],
|
||||||
|
"#p": ["<rider-pubkey>"],
|
||||||
|
"#skill": ["kickflip"]
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Example
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"kind": 1293,
|
||||||
|
"pubkey": "witness-pubkey-hex",
|
||||||
|
"created_at": 1703001600,
|
||||||
|
"content": "",
|
||||||
|
"tags": [
|
||||||
|
["p", "rider-pubkey-hex"],
|
||||||
|
["skill", "kickflip"],
|
||||||
|
["skill", "switch"],
|
||||||
|
["spot", "MACBA Barcelona"],
|
||||||
|
["a", "30818:abc123:macba"],
|
||||||
|
["t", "skill-badge"],
|
||||||
|
["alt", "Skill badge awarded: kickflip, switch"]
|
||||||
|
],
|
||||||
|
"id": "...",
|
||||||
|
"sig": "..."
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
|
||||||
|
|
@ -513,30 +513,54 @@ html, body {
|
||||||
.card-theme-sunset .card-muted { color: rgba(255,208,160,0.5); }
|
.card-theme-sunset .card-muted { color: rgba(255,208,160,0.5); }
|
||||||
.card-theme-sunset .card-bg-chip { background: rgba(255,80,0,0.10); border: 1px solid rgba(255,80,0,0.3); }
|
.card-theme-sunset .card-bg-chip { background: rgba(255,80,0,0.10); border: 1px solid rgba(255,80,0,0.3); }
|
||||||
|
|
||||||
|
/* ── Print-only card: invisible on screen, appears on print ── */
|
||||||
|
.print-only-card {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
/* ── Print styles ── */
|
/* ── Print styles ── */
|
||||||
@media print {
|
@media print {
|
||||||
/* Hide everything except the card */
|
/* Hide absolutely everything */
|
||||||
body > *:not(.print-card-root) { display: none !important; }
|
* {
|
||||||
.print-card-root { display: block !important; }
|
visibility: hidden !important;
|
||||||
|
}
|
||||||
|
|
||||||
/* Reset page margins */
|
/* Show only the print-only card and all its children */
|
||||||
|
.print-only-card,
|
||||||
|
.print-only-card * {
|
||||||
|
visibility: visible !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Position the card at top-left of the page */
|
||||||
|
.print-only-card {
|
||||||
|
display: block !important;
|
||||||
|
position: fixed !important;
|
||||||
|
top: 0 !important;
|
||||||
|
left: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
z-index: 99999 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Reset page to card size */
|
||||||
@page {
|
@page {
|
||||||
size: 85.6mm 54mm;
|
size: 85.6mm 54mm;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
.rider-card {
|
/* Card at exact print dimensions */
|
||||||
|
.print-only-card .rider-card {
|
||||||
width: 85.6mm !important;
|
width: 85.6mm !important;
|
||||||
height: 54mm !important;
|
height: 54mm !important;
|
||||||
border-radius: 4mm !important;
|
border-radius: 4mm !important;
|
||||||
page-break-inside: avoid;
|
page-break-inside: avoid;
|
||||||
/* Remove glow shadows for crisp print */
|
|
||||||
box-shadow: none !important;
|
box-shadow: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kill holographic overlay on print */
|
/* Kill holographic overlay on print */
|
||||||
.rider-card-holo::after {
|
.rider-card-holo::after {
|
||||||
display: none !important;
|
display: none !important;
|
||||||
|
visibility: hidden !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Kill all animations */
|
/* Kill all animations */
|
||||||
|
|
@ -544,9 +568,4 @@ html, body {
|
||||||
animation: none !important;
|
animation: none !important;
|
||||||
transition: none !important;
|
transition: none !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Hide UI chrome */
|
|
||||||
header, footer, nav, .no-print {
|
|
||||||
display: none !important;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
File diff suppressed because it is too large
Load diff
Loading…
Reference in a new issue