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:
shakespeare.diy 2026-06-16 14:55:10 +07:00
parent 838ef3f355
commit e2582aa013
3 changed files with 751 additions and 321 deletions

162
NIP.md
View file

@ -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"
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)
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
{
@ -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
Both card types support these visual themes for printing:
| Value | Description |
|-------|-------------|
| `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
- Zaps received: count of kind 9735 targeting 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
- 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
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>
```
## 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": "..."
}
```

View file

@ -513,30 +513,54 @@ html, body {
.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); }
/* ── Print-only card: invisible on screen, appears on print ── */
.print-only-card {
display: none;
}
/* ── Print styles ── */
@media print {
/* Hide everything except the card */
body > *:not(.print-card-root) { display: none !important; }
.print-card-root { display: block !important; }
/* Hide absolutely everything */
* {
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 {
size: 85.6mm 54mm;
margin: 0;
}
.rider-card {
/* Card at exact print dimensions */
.print-only-card .rider-card {
width: 85.6mm !important;
height: 54mm !important;
border-radius: 4mm !important;
page-break-inside: avoid;
/* Remove glow shadows for crisp print */
box-shadow: none !important;
}
/* Kill holographic overlay on print */
.rider-card-holo::after {
display: none !important;
visibility: hidden !important;
}
/* Kill all animations */
@ -544,9 +568,4 @@ html, body {
animation: 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