- /card gains a fourth type: Event. Renders an event (kind 31923) you organise
or RSVP'd going/interested to (useMyRsvpCoords, kind 31925) as a passport/
wristband: title, date/time, venue, cover, who it admits, going count.
Derived from the event's own signed event — its QR verifies that event;
/verify/:naddr renders kind 31923 too. React + canvas renderers; i18n.
- /review gains a dedicated Claims tab (pending private-spot ownership claims),
replacing the banner that sat above the tabs.
- /card gains a third type: Crew. Pick one of your crews and render a printable
crew card (name, logo, home turf, founded, members, spots, vibes, Open call).
- No new event: the card is derived from the crew's own signed kind 35006, so its
QR verifies that event (the founder's key IS the signature).
- Rendered in the React card + canvas export; /verify/:naddr now renders kind
35006 with a link to the crew page.
- i18n cardUi.crew.* / verify.kindCrew; NIP.md + AGENTS.md updated.
- Card shows crew crests (kind 35006 memberships), a local badge (skater
profile location/home_spot), and Lab role chips (kinds 35001-35014) — all
DERIVED LIVE, so a card can't claim a role/crew the skater doesn't have.
- Rendered in the React card + the canvas export (print/PNG); local/crews as
text lines, roles as blue chips.
- Per-facet show toggles in the editor; card schema bumped to v2 so the new
flags default ON for cards saved before them.
- Also fixed: verifyUrl wasn't passed to the canvas renderer (QR fell back to
the profile path).
- i18n cardUi.facets.* / cardUi.roles.*; NIP.md + AGENTS.md updated.
- Publish the Rider Card as an addressable event (kind 32271, d=rider-card) and
hydrate the editor from it, so customization persists across devices.
- New routes: /card/:npub (public read-only card) and /verify/:naddr
(signature check + park venue binding).
- Park Access Cards can bind to the issuer's own spot (a tag 30818:...) and the
verify page confirms the issuer owns the venue.
- QR on the card (+ canvas export) now encodes the verify URL; the real QR
renders in the React card too (was a placeholder / broken profile path).
- Extracted shared card visuals to src/components/RiderCard.tsx; helpers in
src/lib/riderCard.ts; hooks useRiderCard/useRiderBadges.
- Documented in NIP.md; i18n cardUi namespace (EN/ES); smoke test covers the
new routes.
- First-visit language auto-detect (Spanish devices land in es; stored choice wins).
- Sasquatch AI replies in the app language (skate trick names kept English).
- i18n safety net: scripts/check-i18n.mjs wired into npm test — fails on any t()
key missing from en/es or any en/es key drift.
- ~1,680 new keys (2,297 total) across mapUi / spotUi / marketUi / labUi and
~65 files wired to t(): Map, spots (detail/create/edit/chat), marketplace,
trades, offers, escrow, shipping, The Lab, /become/* forms, coaches, skaters.
- Policy: only skate TRICK names stay English; user-generated content untouched.
- AppRouter wraps every route (except /amber-callback and the 404) in ONE
<Route element={<Layout/>}> with an <Outlet/>; the chrome now persists across
navigation instead of being torn down and rebuilt on every page.
- Layout: children optional (falls back to <Outlet/>); the map is full-bleed via
a route check (isFullBleed) instead of a per-page noPadding prop.
- All 59 pages + CraftProfileForm/ErrorState/NewsletterIssueView now render
fragments instead of <Layout> (structure-preserving), imports dropped.
Removes the whole remount class: chrome no longer re-runs its hooks per
navigation, Sasquatch chat state survives navigation, and the header can't
re-open Amber on every page change.
showAwardDialog was never set true, so the dialog could never open; the inline
AwardBadgeInline form is the live flow. Removes ~142 lines plus its now-unused
Dialog imports, state and helper.
- Logged-out state: proper hero with value prop copy instead of bare login form
- Hero card preview: full-width dark stage with theme-matched glow behind card
- Card slightly larger (360x227) with depth shadow
- Top tab bar: My Card / Badges (with count) / Award — replaces buried sections
- Single-column max-w-2xl layout — no more awkward 2-col grid on mobile
- Primary CTA: one big Share button, Print/Save/Copy as secondary 3-col row
- Card type as prominent 2-up tile choice, not a buried dropdown-style control
- Theme picker: 6-up square grid with emoji + label, ring on active
- Rides For + Sponsors side by side (saves vertical space)
- Stats as interactive tiles showing the actual numbers
- Skills as pill grid with live counter (X/6 selected)
- Badges tab: shows ReceivedBadgesPanel (was previously never rendered)
- Award tab: inline form, no dialog — cleaner flow, redirects to Badges on success
- AwardBadgeDialog kept for backward compat but Award tab uses AwardBadgeInline
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The drawCardToCanvas function had an old version of the card rendering logic
that leaked outside the function body (lines 505-731 were orphaned code).
This code referenced local variables (ctx, p, data, qrX, qrSize, PAD, etc.)
that only existed inside the function, causing ReferenceError at module load time
and crashing the entire app with a blank screen.
Also added missing lucide-react imports (Printer, Loader2, Share2, Download,
Check, Copy, Award, Zap, Info, Palette, Shield, ChevronUp, ChevronDown) that
were used in the JSX but not imported, causing the secondary Printer is not
defined ReferenceError.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The qrcode library internally uses var ctx and canvas.getContext('2d') at
initialization time. In the Shakespeare preview iframe environment, canvas
API is restricted causing "ctx is not defined" console errors on every render.
Fix: replace QRCodeCanvas in the DOM screen preview with a static decorative
QR placeholder (5x5 pixel grid). The real, scannable QR code is still
generated correctly in the exported PNG via QRCode.toDataURL() in the
drawCardToCanvas() async function - this is unaffected by the env restriction
since it only runs when the user clicks Print/Save/Share.
Also remove unused QRCodeCanvas import from RiderCardPage.
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Canvas renderer overhaul:
- New 3-zone layout: left sidebar (avatar + stats + since) | right zone
(name/identity/shop/sponsors/skills) | top-right QR block
- Left sidebar has a subtle tinted background with accent-colored separator
- Stats shown as balanced column grid under the avatar
- Shop: "RIDES FOR" label + bold italic Georgia serif in theme shopColor (distinct from accent)
- Sponsors: "SPONSORS" label + regular weight in theme sponsorColor (warm gold/orange)
- Each theme now has shopColor + sponsorColor + sidebarBg + chipBorder fields
- Skills row appears only when skills/badges exist, with accent border on chips
- Full npub (44+remainder chars) shown at bottom of right zone in monospace
- Bottom bar: subtle fill + "SKATE-SPOTS.ORG" left, year right
- Park Access card: same sidebar layout, access level chip in solid accent fill
Share button:
- Uses Web Share API (navigator.share) if device supports it — opens native
share sheet (WhatsApp, iMessage, Telegram, AirDrop, etc.) with PNG file
- Falls back to copying profile URL to clipboard
- Lime green color to visually stand out from Print button
UI cleanup:
- Remove Badges tab entirely — was cluttering the nav
- Award Badge moved inline to secondary button row (yellow, subtle)
- Primary actions: Print | Share | Save PNG (all same size, flex-wrap)
- Secondary actions: Copy npub | Award Badge | Publish to Nostr
- Fixed stray closing divs from tab removal
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
QR fix (critical):
- loadImage() was routing ALL urls through proxy.shakespeare.diy including
data: URIs from QRCode.toDataURL() — proxy rejected them, QR never loaded
- Fix: only proxy http/https URLs; data: and blob: URIs load directly
- QR now renders reliably; scanning opens skate-spots.org/<npub> profile
npub display:
- Split full npub across two lines (32 chars each) instead of truncating
- Both lines shown in monospace below the brand header
- npub is now fully readable on the card
Explicit labeled sections:
- "SKATE SHOP" label in muted above shop name in accent color
- "SPONSORS" label in muted above sponsors text
- Sections only appear if data is entered — no empty space when blank
- Stats (Spots/Zaps/Events) shown as 3-column grid with label+value
Layout improvements:
- QR moved to top-right, rendered before other content (ensures it draws)
- Avatar sized 180px with "SINCE YYYY" underneath
- Identity column flows: name → nip05 → style chip → shop → sponsors → stats → skills
- Bottom divider line + year issued in footer
- fillTextFit() helper for clean text truncation with ellipsis
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
html2canvas was capturing the full browser viewport instead of just the
340×214px card element — causing the giant oversized/wrong layout in the PNG.
New approach: drawCardToCanvas() draws the card entirely via Canvas 2D API:
- Creates an 856×540px canvas (10× credit-card mm dimensions, print-ready)
- Draws gradient background, top stripe, avatar (with CORS proxy), text,
skill chips, stats, and QR code using direct canvas drawing commands
- QR code generated via qrcode lib toDataURL(), then drawn as image on canvas
- Avatar loaded via proxy.shakespeare.diy for CORS, with 4s timeout fallback
- Theme colors from THEME_PALETTE map — same 6 themes as the screen card
- No DOM capture, no scaling issues, no CSS stripping — output is pixel-perfect
QR code explanation:
- Encodes https://skate-spots.org/<npub>
- Scanning opens the rider's SkateSpots profile (NIP19Page handles /npub1...)
- White background patch behind QR for reliable scanning on dark card themes
Other changes:
- "Rider License" → "Skater License" everywhere
- Remove html2canvas dependency usage (kept package but unused for now)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Problems fixed:
- Card was rendering too tall: replaced flex-1/justify-between layout with
strict pixel-height rows (row1: 26px header, row2: 118px body, fixed bottom bar)
— content no longer floats or overflows
- No QR code: added QRCodeCanvas (existing component) in bottom-right of card
body, encoding https://skate-spots.org/<npub> — scanning takes you to the
rider's profile for verification
- License ID removed: replaced SS-XXXXXXX with shortNpub (npub1xxx...yyyy)
in top-right. The npub IS the identity — no made-up number needed
- Added crossOrigin="anonymous" on avatar img for html2canvas CORS handling
- Updated html2canvas onclone to lock card to 340×214px during capture
- Download filename uses npub prefix instead of licenseId
- verifyUrl() helper generates skate-spots.org/<npub> for QR content
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
The root cause of white/blank print: browsers strip CSS background gradients
and colors by default even with print-color-adjust: exact, especially inside
nested components wrapped by Layout.
Solution: use html2canvas to render the live card DOM to a canvas at 4x scale,
convert to PNG, then open a dedicated print window containing ONLY that image.
Images always print in full color — no CSS override needed.
Changes:
- Add html2canvas dependency
- renderCardToImage(): captures card DOM element at 4x scale with useCORS for
cross-origin avatars, temporarily strips holo animation for clean capture
- handlePrint(): calls renderCardToImage, opens popup window with CSS @page
set to 85.6×54mm, injects the PNG, auto-triggers print dialog
- handleDownload(): same render pipeline, saves as PNG file (for sharing
digitally, WhatsApp, iMessage, or taking to a print shop)
- Added Download / Save PNG button alongside Print Card button
- Added Loader2 spinner on buttons while rendering
- Removed print-only-card div and broken @media print CSS (no longer needed)
- Clean up CSS print block to minimal fallback only
- Fix "on-chain proof" → "Signed proof" in badge dialog
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
Print fixes:
- Add -webkit-print-color-adjust: exact + print-color-adjust: exact to ALL
card theme classes so dark backgrounds/gradients actually print (browsers
strip them by default unless forced)
- Increase chip/muted opacity values slightly for print legibility
- Fix multiple-card issue: hide body > * on print, show only .print-only-card
- Remove complex repeating-linear-gradient from wood theme (simplify for print)
Language fixes:
- Remove "on-chain" terminology (Nostr is not a blockchain)
- Replace with plain explanation: "signed message on the Nostr network"
- Park access note now explains exactly what gets verified and how
- Park card info box now lists exactly what shows on a scan:
who issued it, who it was issued to, access level, expiry, timestamp
- Clarifies why a rider can't forge the card (would need your private key)
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
- 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>