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"
|
||||
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": "..."
|
||||
}
|
||||
```
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue