Commit graph

186 commits

Author SHA1 Message Date
shakespeare.diy
dbae3a1501 Add soft NIP-05 verification for skaters (open directory + "Verified" filter)
Keeps the skater directory fully open — NIP-05 is a trust signal, not a gate:
- Extract reusable NIP-05 verification into lib/nip05.ts (parse, fetch well-known, verify against expected pubkey); refactor useCrewNip05 to use it
- New useSkaterVerifications hook: batch-fetches kind-0 metadata for all skaters and verifies each nip05 resolves to its own pubkey, returning a Set of verified pubkeys
- SkatersPage: add a "Verified (N)" toggle (disabled when none verified) that filters to genuinely verified skaters; show ✓ badge only for truly-verified skaters; verified-aware empty state
- NIP19Page profile: nip05 line now reflects real verification (lime ShieldCheck + "Verified" pill when the handle resolves to the key, muted grey otherwise)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:51:11 +07:00
shakespeare.diy
bb6245e53c Skater profile: browse real map spots for home spot + always use kind-0 profile pic
Home spot:
- Add "Browse spots" button opening a searchable dialog of real mapped spots (useSkateSpots)
- Picking a spot stores its title + an `a` (30818:pubkey:d) coordinate; shows a linked-spot chip with unlink
- Profile page renders a linked home spot as a link to /spot/:pubkey/:identifier
- Parse homeSpotRef from the `a` tag; document in NIP.md

Profile pic consistency:
- Remove the skate-specific image override/upload — the skater card now always
  uses the kind-0 profile picture so both sections show the identical photo
- "Profile Pic" section is read-only with a "Change profile photo" link to settings
- Drop the `image` tag from published skater events; note the change in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:44:12 +07:00
shakespeare.diy
4c9839eb45 Rename "Action shot" to "Profile Pic" and add custom Level option for skaters
- Become-Skater form: photo section relabeled "Profile Pic"
- Level picker now includes a "Custom" toggle with a free-text input (≤40 chars) for labels like "OG", "Weekend Warrior", "Retired"
- Store custom level as a `level_custom` tag (mutually exclusive with preset `level`)
- Render custom level everywhere presets show: profile (NIP19Page), SkatersPage cards, SkaterDirectoryCard
- Document `level_custom` tag in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:34:59 +07:00
shakespeare.diy
3ef11eda61 Refine Lab banner copy, stance to Regular/Goofy, full profile carry-over chips on Become-Skater, remove Back to Leaderboard from profile
- Lab "Just a skater?" banner: drop redundant "→ join the directory" trailer (arrow icon already present)
- Skater stances distinct emojis (🛹 Regular / 🤙 Goofy); already limited to Regular & Goofy
- Become-Skater: fix missing ShieldCheck/Zap imports; show ALL carried-over kind-0 fields (photo, name, bio, website, nip05, zaps) as read-only chips
- Profile (NIP19Page): remove hardcoded "Back to Leaderboard" link and unused ArrowLeft import

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 21:28:55 +07:00
shakespeare.diy
b42c234709 Add Skater profiles (kind 35000) + /skaters directory
- New app-native Skater Profile kind 35000 with `skateskater` marker tag,
  the anti-spam boundary that keeps generic kind-0 Nostr profiles out of the
  directory (queried only from the community relay).
- Skater type, stance/style/level constants, and parser in types.ts.
- useSkaters + useUserSkaterProfile hooks.
- /skaters directory page: immersive hero, live count, search, style filter
  chips, ranked skater grid with NIP-05 verification badges and lazy author
  metadata.
- /become-skater opt-in flow (bio, photo, styles, stance, level, years, home
  spot, location w/ geohash, socials).
- Wire "Skaters" into the mobile nav and a cross-link banner in The Lab so
  plain skaters finally have a home.
- Document kind 35000 in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:58:51 +07:00
shakespeare.diy
11c67fcf22 Profile: add Lightning, Silent Payment, Sponsors & Cordn fields
Editable in EditProfileForm and displayed on the profile:
- Bitcoin Lightning Address (lud16) — now editable in-form, shown with a
  labeled Lightning row in the Info card.
- Bitcoin Silent Payment Address (BIP-352, custom `bitcoin_silent_payment`)
  — validated (sp1…), shown as a copyable, truncated row.
- Sponsors & Support (custom `sponsors`) — freeform highlight, rendered in
  a dedicated pink "Sponsors & Support" sidebar card.
- Cordn contact link (custom `cordn`) — private MLS messaging link
  (cordn.net), shown as a "Message privately" row in the Info card.

Crews and the CLINK noffer tip button were already present, so they are
left as-is. Added isValidSilentPayment / isValidLightningAddress
validators and documented the custom kind-0 fields in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-09-02 20:00:21 +07:00
shakespeare.diy
8ae852f1b8 Add Judges tab to The Lab
- Flesh out SkateJudge as a full craft-based Lab profile (parseCraftBase),
  with JUDGE_FORMAT / SPECIALTY / LEVEL label sets covering Game of S.K.A.T.E.,
  best trick, jams, contests, am series, world tour stops, etc.
- Wire judge into useLabProfiles (union type, self-tag, parse case)
- Add "judging" ServiceType to LabPage: tab, accent, filter sections, empty
  state, counts, profile card icon, and ?craft=judging deep link
- New BecomeJudgePage + /become-judge route + FAB entry
- CoachDetailPage renders judge profiles (accent, icon, labels, noun, title)
- Community Stats "Judges" card now links to /lab?craft=judging
- Document kind 35012 (skatejudge) in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 15:30:43 +07:00
shakespeare.diy
677ccb8a82 v1.2.0: Spedn label, multi-relay community stats, GrapheneOS Near-Me fix
1. Map filter: renamed the "Bitcoin" toggle to "Spedn" (matches the toolbar
   button); tooltip now says "Spedn your sats".

2. Community stats (/stats): useGrowthStats now queries the same relay set as the
   rest of the app (community relay + user relays) via useSkateRelays instead of
   only the single hardcoded SKATE_RELAY. Lab profiles / spots / etc. that live on
   a user's own relay are now counted, so the numbers match what /lab shows.

3. Near Me on GrapheneOS: even after granting the OS location permission, the
   WebView's navigator.geolocation stays blocked → "permission denied". Fixed by
   fetching the position from the native Capacitor Geolocation plugin first
   (LocationManager, honours the granted permission), falling back to
   navigator.geolocation only if that fails. Also auto-inject
   ACCESS_FINE/COARSE_LOCATION into AndroidManifest via the manifest script.

Bump version to 1.2.0 (versionCode 10200).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:54:00 +07:00
shakespeare.diy
6fb7314f6c Fix real Amber login hang: skip package-name so getPublicKey uses the intent, not the stalling ContentProvider (v1.1.9)
Root cause found in the plugin's native getPublicKey (NostrSignerPlugin.java):
when a packageName is set, it FIRST tries Amber's ContentProvider (background,
no-UI). On GrapheneOS that cross-app ContentResolver query is blocked and never
returns, so it never falls through to startActivityForResult(nostrsigner:) —
the branch that actually opens Amber's approval UI. Result: eternal
"Waiting for Amber".

Fix: getAmberPublicKeyViaPlugin no longer calls setPackageName and no longer
passes packageName; it passes only the get_public_key permission. With no
package name the plugin skips the content-resolver branch and launches the
intent directly, opening Amber's UI. Amber returns both pubkey and its package,
so later signing still works. Paired with the auto-injected <queries> manifest
block so the OS can resolve the intent in the first place.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:29:35 +07:00
shakespeare.diy
07699592da Fix Amber "Waiting…" hang on GrapheneOS: unblock setPackageName + auto-inject <queries> (v1.1.9)
Two concrete root causes for the eternal "Waiting for Amber…":

1. setPackageName could hang forever on GrapheneOS. getPlugin() awaited it with
   no timeout, so getPublicKey() (and its 90s timeout) was never even reached.
   Now setPackageName races a 3s timeout and continues regardless — the Amber
   package is passed explicitly on every call anyway, so it's non-essential.

2. AndroidManifest.xml had no <queries> block. On Android 11+/GrapheneOS the OS
   silently blocks the nostrsigner intent without it, so Amber never opens. Added
   scripts/inject-amber-queries.mjs (idempotent) and wired it into android:init /
   android:sync / android:build so the block is guaranteed present in every APK,
   surviving cap sync regeneration.

Bump version to 1.1.9.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:27:00 +07:00
shakespeare.diy
c6372742a9 Fix Amber native login in APK: reliable Capacitor detection (v1.1.7)
Root cause: LoginDialog and useCurrentUser detected the native app via
window.Capacitor, which is unreliable inside the WebView (false at mount / not
attached in some builds). So the ZapStore APK fell into the web intent:// redirect
flow and never opened Amber ("Waiting for Amber…" hang).

Fix: add isNativeAmberAvailable() in capacitorAmberSigner.ts using the imported
@capacitor/core Capacitor + native plugin presence, and use it (evaluated at
click time) in LoginDialog.handleAmberLogin and in useCurrentUser's signer
selection. The APK now calls getAmberPublicKeyViaPlugin() → native intent → Amber
opens directly, no redirects.

Bump version to 1.1.7.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 14:00:39 +07:00
shakespeare.diy
4ed1d9a803 Fix crew card layout, Amber web login hang, Stash encryption gate, and add signup welcome step
- CrewCard: rebuilt to fit any screen width — clip overflow, truncate name/location, single wrapping badge row, consistent avatar sizing; responsive header
- Amber web login: after opening Amber, drop user on manual paste step after 2.5s so the spinner never hangs when the callback opens in a new tab or clipboard is blocked; keep polling on the paste step so a late callback still auto-completes
- Stash: hasNip44/hasNip04 now fall back to the active login when pubkey matching fails, so logged-in nsec/amber users are no longer wrongly told "Encryption Required"
- Signup: added an explicit welcome step that explains account creation and only generates the keypair when the user taps "Create my account" (no more silent auto-creation on dialog open)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 13:27:24 +07:00
shakespeare.diy
a83611072a Fix Amber NIP-44 gate on Stash, mobile Crew detail layout, remove Quick Snap
- signerCapabilities: recognize x-amber login type as NIP-44/NIP-04
  capable (Amber implements nip44_encrypt/decrypt via native intents and
  URL redirects). Fixes "Encryption Required" wall on Stash for users
  logged in with Amber on Android.
- CrewDetailPage: restructure hero header so actions wrap below the
  title on mobile instead of squeezing it in a cramped 3-column flex;
  smaller avatar/title on small screens, truncating location.
- MapPage: remove Quick Snap from the mobile Add speed-dial and all its
  handlers, refs, web-fallback input, and now-unused imports.
- zapstore.yaml: explicit orange icon + metadata_sources: [] so the
  correct logo is always used on the Zapstore listing.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 12:31:34 +07:00
shakespeare.diy
8a6d84ca3d Fix map tiles (drop keyed CARTO for free OSM + CSS dark filter), restore corrupted parseSkateCoach in types.ts
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-27 09:56:47 +07:00
shakespeare.diy
13934c0e74 Fix newsletter admin gating: use isAdminPubkey from lib/admins instead of undefined NEWSLETTER_ADMIN_PUBKEYS
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-20 16:16:09 +07:00
shakespeare.diy
538ef67b0b Security: remove hardcoded OpenRouter key (C1) + escape weather HTML (H1)
Address the two genuinely exploitable findings from the OpenCode audit:

C1 (critical) — sasquatchBrain.ts shipped a hardcoded OpenRouter API key in
the client bundle, extractable and abusable by anyone. Removed it; OpenRouter
is now opt-in via VITE_OPENROUTER_API_KEY and skipped when unset. Sasquatch
still works for everyone via the existing keyless Pollinations fallback.
(The leaked key must also be rotated/revoked on openrouter.ai.)

H1 (high) — SpotMap popup interpolated external weather-API data directly into
innerHTML. Added an escapeHtml() helper and applied it to every weather/AQI/
forecast value so a compromised weather API can't inject HTML/JS.

Triage of the rest: C3 (crew agent derived key) is a documented, deliberate
tradeoff — agent holds no funds and only speaks for itself. H2 (chart.tsx
dangerouslySetInnerHTML) is the stock shadcn chart with app-controlled config,
not user input. H3/H4 (GitHub Actions) don't apply — no .github/ in this repo.
C2 (NWC in localStorage) is standard for web Nostr clients. npm audit fix +
key rotation are operator steps done outside the app.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-18 17:04:53 +07:00
shakespeare.diy
8b080fb7f1 Spot detail: make mapper clickable + add Message Mapper button
The "Tip the Mapper" card now links the avatar/name to the mapper's profile
(/{npub} via the NIP-19 router) and adds a "Message Mapper" button that opens
an encrypted DM (/messages/{npub}). Makes it easy to contact whoever added a
spot — e.g. to ask them to add photos to a photoless submission. The message
button is hidden when you're viewing your own spot.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-15 10:03:58 +07:00
shakespeare.diy
6e11aaab7d Crew form UX: expand Crew Type to 7 scenes (Art, Music, Wellness) mapping to new Lab crafts; rethink Crew Band with Lab-music interconnect nudge; surface member visibility to founders; add Artist/Musician open-call options; update Crews page filters + NIP.md
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 22:14:21 +07:00
shakespeare.diy
926470a86e Crews: opt-in member visibility + Lab vouch/web-of-trust anti-fraud + CrewDetail redesign
Step 3 — member visibility (best-of-both, honest):
- MemberVisibility ('public' | 'crew') stored on the member's OWN kind-1985
  attestation via a `visibility` tag, so a public crew can contain a private
  member. Confirmed crew-mates + founder see them; outsiders see anonymised
  "hidden member" slots. Framed honestly in-UI (app-level discovery privacy).
- VisibilityPicker in ConfirmMembershipCard (choose on join) +
  MemberVisibilityControl for confirmed members to change it anytime.
- useCollectiveMembers parses visibility; CollectiveMembers hides crew-only
  members from non-members.

Step 4 — anti-fraud vouch layer:
- useVouches/useVouchFor: NIP-32 vouch labels, trust-WEIGHTED via web-of-trust
  so fake-account vouch rings carry no weight. VouchCard with new/vouched/
  scene-verified tiers, wired into every Lab profile detail page.

Step 4 — CrewDetail UX redesign:
- Activity consolidated into clean Feed/Events/Chat tabs (no more giant scroll)
- Sidebar reordered: Members → Support → Band → Connections → Founder
- Member visibility control surfaced; tighter hero

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 21:01:57 +07:00
shakespeare.diy
0071bcba36 Lab: add Designer/Artist, Graffiti, Tattoo, Trainer/Physio & Musician crafts (kinds 35007–35011)
- New addressable kinds 35007–35011 with types, label maps and parsers
- Shared parseCraftBase helper + parseLabProfile dispatcher in useLabProfiles
- Reusable, config-driven CraftProfileForm (polished shared shell, per-craft
  skill sections + extra fields, entity/collective, CLINK, Pro upgrade)
- Five thin Become* pages wired into AppRouter and the Lab "Offer" menu
- LabPage: extended ServiceType, accents, scrollable tab strip, filter
  sections, counts, empty states and ProfileCard to all 10 crafts
- CoachDetailPage renders all craft types (map-driven accents/icons/labels)
  plus craft-specific fields (studio, books-open, credentials, remote,
  graffiti crew, music links)
- NIP.md documents all five new craft schemas

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-08-10 20:28:59 +07:00
shakespeare.diy
8056b02ad1 Crew band gig booking, hide treasury, CLINK on profiles, friendlier CLINK, remove desktop messages icon
- Add "punk" + "rap/hip-hop" crew vibe presets
- Hide Crew Treasury behind SHOW_CREW_TREASURY flag (detail page + form), code kept
- Improve crew band section into a dedicated card with a "Book them for a gig"
  action that deep-links to a Concert event hosted by the crew
- CreateEventPage: prefill event type + title from ?type= / ?title= params
- CLINK (a): expose CLINK tip code on user profiles (EditProfileForm field +
  "Tip in Bitcoin" button on profile view, stored as custom kind 0 field)
- CLINK (c): relabel "Pay via CLINK" -> "Tip in Bitcoin" with a "what's this?"
  tooltip explaining it in plain language
- Remove standalone desktop Messages icon (kept in the More menu)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 20:29:44 +07:00
shakespeare.diy
0ac5c1019d fix: eliminate all render-path nip44 getter crashes (Browser extension not available)
Root cause: NUser's extension signer exposes .nip44 as a getter that calls
.nostr (window.nostr) and THROWS when the extension isn't present. Any
synchronous access during render crashes the whole tree — the actual culprit
was useStash's `isEncryptionAvailable: !!user?.signer?.nip44` in its return
object, evaluated every render even when logged out.

- Add src/lib/signerCapabilities.ts: hasNip44/hasNip04 helpers that determine
  capability via login type + direct window.nostr probe (no throwing getter).
- useStash: use the helper for hasNip44 + isEncryptionAvailable
- useDirectMessages: replace all render-path `enabled: !!user?.signer?.nip44`
  with a pre-computed nip44Available across all 4 conversation hooks
- CrewChat, CrewRequestToJoin, ConversationPage, DirectMessagesPage,
  ContactsPage: use the safe helper in render/guard paths

Async accesses (inside queryFn/callbacks) are left as-is — they reject
gracefully within react-query and never crash render.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 19:37:08 +07:00
shakespeare.diy
d6b50de349 feat(sasquatch): Pro receipt DM + local Pro FAQ + tougher free-tier answers
1. Pro purchase confirmation: on a paid pass, send the buyer an encrypted
   NIP-17 receipt to their own inbox (tier, expiry, unlocked perks). Best-effort
   so it never blocks the purchase. Success toast now points to the DM.

2. Frictionless Pro answers: add local static-FAQ entries for "sasquatch pro",
   "pro perks/version", pricing, "go pro", "upgrade" — so those questions are
   answered instantly and never dead-end when the free AI model is rate-limited
   (the exact failure seen when asking "what perks has the pro version?").

3. FAQ matching now prefers the longest matching key so specific phrases win
   over broad ones ("sasquatch pro" > "pro").

4. Refresh the free-model fallback list (add deepseek-v3, gemini-2.0-flash-exp,
   qwen-2.5-72b) for better free-tier AI availability.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:21:34 +07:00
shakespeare.diy
1c18163a90 chore(sasquatch): remove joke book, clean up default suggested questions
- Delete sasquatchJokes.ts and all joke usages (idle chat line, fallback
  taglines, Lab/Crew guide messages).
- Tidy the default/generic suggested questions to a clean Nostr-focused set
  (Plan a trip, spots, events, crews, zaps, Nostr, Stash, free).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:08:52 +07:00
shakespeare.diy
1efeae0960 feat(sasquatch): add Trip Planner + tougher free-tier fallback + new joke book
- Trip Planner: `plan_trip` tool orchestrates spots+events+coaches+crews+weather
  into a weather-aware, day-by-day itinerary card (TripItineraryCard). Actions:
  save all spots to a NIP-51 Collection, book/zap a coach via their profile,
  and deep-link out to flights/stays (assist, never fake-book, no KYC).
- Free-tier resilience: local intent parser answers "spots/events/coaches/
  crews/gear/weather in <city>" and full trip plans directly via CVM even when
  both AI providers are rate-limited — no more dead-end fallback.
- New shared joke book (sasquatchJokes.ts): skate humor + gentle roasts of the
  KYC-your-audience Bitcoin-influencer crowd. Idle joke on the empty chat state,
  ethos jokes in fallbacks. Dedupes the old inline joke arrays.
- UX polish: taller chat scroll area, trip-planner suggested questions, FAQ
  entries for the planner.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-30 00:04:28 +07:00
shakespeare.diy
79d1d47823 Sasquatch Pro Phase 3: npub-bound Lightning paywall + gating
Adds the Sasquatch Pro supporter pass with strong, forge-resistant
verification bound to the paying user's npub — no backend required.

Verification (useSasquatchPro):
- STRONG: NIP-57 zap receipt (kind 9735). Purchase signs a zap request
  tagged pro-tier/pro-expiry; the receipt embeds it, cryptographically
  binding payment → identity (unforgeable by the user).
- FALLBACK: self-signed kind-32671 receipt so paying users are never
  locked out if the provider emits no zap receipt.
- Platform owners always Pro. Access = max unexpired expiry.

Purchase (usePurchasePro):
- Zap to skatespots@minibits.cash (falls back to plain invoice), pay via
  NWC → WebLN → manual QR. Publishes the fallback receipt on success.

Pricing (sasquatchPro.ts): 4 tiers — 7d/2k, 1mo/5k, 3mo/12k (popular),
1yr/36k sats.

UI (SasquatchProDialog): upgrade sheet with tiers, perk list, QR
fallback, "already Pro" state, extend flow.

Gating (SasquatchGuide):
- Personal tools (get_my_*) now require login + Pro; prompt only exposes
  them to Pro users; tool switch guards with a pro_required nudge.
- ⚡ Pro badge when active, ⚡ Go Pro button otherwise; local upgrade
  nudge for "my/mine" questions from non-Pro users.

Docs: kind 32671 + verification model documented in NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:33:39 +07:00
shakespeare.diy
5d07524751 Glossary: drop difficulty levels, expand & correct tricks
- Removed difficulty categorization entirely (type, badges, styles) —
  cleaner and no arbitrary skill labels.
- Added many requested/missing tricks with proper research and correct
  alt-names, including:
  • Rotations category: FS/BS 180, FS/BS 360, half-cab, full cab
    (caballerial), fakie 180s.
  • Flip tricks: backside flip, frontside flip, FS/BS heelflip, double
    heelflip, 360 hardflip, 360 pressure flip.
  • Grinds: feeble grind now notes "Grosso grind" aka; added Willy/
    Barley, pole jam; inventor credits (Suski, Losi, etc.).
  • Fundamentals: nose ollie/north, one-foot, no-foot/nothing, nose
    manual, caveman, tic-tac.
  • Shoves: nollie shove, ghetto bird.
  • Grabs: tailbone, airwalk. Stance/slang: pop, line, kook, sticker
    slap, etc. Hardware: durometer.
- Updated the local glossary answerer index to cover all new aka names
  (e.g. "grosso grind", "half cab", "north ollie" now answer locally).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:08:37 +07:00
shakespeare.diy
cc84713fe4 Make Sasquatch resilient: local glossary answers + backup AI provider
Fixes the "brain's catching traffic" dead-end for trick/terminology
questions and reduces reliance on a single flaky free API.

- Local glossary answerer (answerFromGlossary): trick/how-to/terminology
  questions ("what is a feeble grind", "how do I do a kickflip", "grosso
  grind") are now answered instantly from the 150+ entry glossary with
  ZERO API calls — so they never hit a rate limit.
- Wired into SasquatchGuide as (a) a fast path before the AI and (b) a
  last-resort fallback when the AI is unreachable.
- sasquatchBrain now has multi-provider fallback: OpenRouter free models
  (with 429 backoff) → keyless Pollinations backup. Only throws if both
  fail, letting the caller fall back to local knowledge.
- Replaced the unhelpful fallback message with useful local links +
  a nudge to ask about a specific trick.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 23:04:41 +07:00
shakespeare.diy
309d012db1 Massively expand Trick Glossary (tricks + terminology + hardware)
Grew the glossary from ~80 to ~150 entries across 15 categories.

- Added many more tricks per the community list: impossible, gazelle
  flip, pressure/hospital/dolphin flips, bigger spin/flip, suski/willy/
  losi/hurricane grinds, japan/madonna/slob/seatbelt/roastbeef/benihana/
  lien/cannonball/christ grabs, 720/1080, casper/anti-casper, spacewalk,
  walk the dog, g-turn, and more — with inventor credits and aka names.
- New reference categories: "Transition & Vert Terms", "Stance &
  Movement", and "Hardware & Equipment" (deck, trucks, bushings,
  bearings, risers, wheel bite, etc.).
- Reference categories hide difficulty badges (added `reference` flag +
  `hideLevel` on TrickRow); search still spans everything.
- Updated hero copy + SEO description to reflect the broader scope.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:54:40 +07:00
shakespeare.diy
b3cda77224 Add optional crew band / music project to Skate Crews
Skate scenes and local music go hand in hand — crews can now list a band
name + a music link (Spotify/Bandcamp/YouTube/Nostr).

- SkateCrew type: new optional bandName / bandUrl fields.
- Parser reads `band` + `band-url` tags (band-url validated as http(s)).
- CrewForm: dedicated optional "Crew Band" section.
- CrewDetailPage: purple band row (linked when a valid URL is present).
- CrewsPage cards: small "Band" badge when a crew has a band.
- Kept scoped to crews only (not The Lab) since bands aren't a
  skate-specific craft.
- NIP.md: documented the new `band` / `band-url` crew tags.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:44:17 +07:00
shakespeare.diy
a371ff3645 Add Trick Glossary + harden Sasquatch AI rate-limit handling
Glossary:
- New /glossary page: 80+ tricks across 11 categories (fundamentals,
  flips, shoves/spins, grinds, slides, grabs, airs, lip, stalls,
  handplants, boneless, freestyle) with alternate names, difficulty
  badges, live search, and category filters.
- Added to desktop "More" dropdown and mobile burger menu.

Sasquatch AI:
- Fixed/expanded free-model fallback list with valid OpenRouter slugs
  (dropped the non-existent gemma-4 slug).
- Added exponential backoff + one retry on 429 rate limits, respecting
  Retry-After headers.
- SasquatchGuide now reuses the shared sasquatchBrain caller instead of
  a duplicated implementation.
- Friendlier fallback message when the AI is saturated.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-29 22:39:51 +07:00
shakespeare.diy
5ec1c84ef0 Fix Near Me crash on GrapheneOS: avoid Play-Services position API
Root cause: @capacitor/geolocation v8 fetches position via Google Play
Services (play-services-location / iongeolocation-android). On de-Googled
devices (GrapheneOS) those classes don't exist, so the plugin's
getCurrentPosition/watchPosition throw NoClassDefFoundError on the Android
main thread -> FATAL EXCEPTION -> app killed. No JS try/catch can intercept it.

Fix: use the native plugin ONLY for the OS location-permission prompt (pure
Android, no Play Services), then fetch the actual position via the WebView's
navigator.geolocation, which uses Android's LocationManager directly and works
without Google. Removed the now-dead raceForFirstFix() and the native
position/watch typings so the Play-Services code path is never invoked.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 14:08:23 +07:00
shakespeare.diy
6848a72869 Crews: proximity discovery, activity/rep signals, treasury, hiring pipeline, crew-hosted events + reverse links; add Contacts page
Crews page
- "Near me" geolocation toggle with haversine distance sort + distance chips
- Crew count + cities social-proof stat header
- Deterministic per-crew vibe-colour accents (no more wall of teal)
- Activity pulse ("🔥 active this week") + OG/Verified/Established rep badges
- Rich empty state ("Claim your local scene") instead of dead "No crews yet"

Crew synergies
- Crew treasury: transparent, non-custodial NIP-75 zap goal (kind 9041) with
  live progress bar tallied from zap receipts; funds settle to founder's own
  Lightning wallet — no shared wallet or new wallet settings needed
- Crew-hosted events: host a jam/comp as the crew (event a-tag → crew coord),
  "Hosted by [crew]" on event pages, Crew Events list on the crew page
- Crew ↔ Lab hiring pipeline: "Open Call / looking-for" flags (filmer, editor,
  coach, builder, members) surfaced on the crew page
- Request to join: encrypted NIP-17 DM to founder + admins (no founder-push
  bottleneck, no public join event)
- Reverse links: "Crews that skate here" on spot pages

Contacts (0xchat-style + Armada-style privacy)
- New /contacts page with Crew-mates / Follows / Recent DMs / Saved tabs
- Per-contact quick actions: message, zap, view profile, star
- Private encrypted contacts list (NIP-51 kind 30000, NIP-44 encrypted to self)
- Added to burger menu + desktop More menu (gated to logged-in users)

Docs: NIP.md updated with crew treasury, crew-hosted events, looking-for tags,
and the private encrypted contacts list.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 12:38:45 +07:00
shakespeare.diy
59a801d525 Geolocation: GrapheneOS-correct fix — parallel hi/lo watches, 60s, progress
The persistent failure on GrapheneOS (no Play Services, GPS-only) was
caused by the acquisition strategy itself, not the OS:

- The old low-accuracy-only watch never fired without a fused/network
  provider, and the 25s deadline killed it before a cold GPS lock (which
  takes 30–60s on GrapheneOS) could ever deliver.

Rewrite raceForFirstFix to actually work on GPS-only devices:
- Run BOTH a high-accuracy (GPS) and low-accuracy (network) watch in
  parallel — high accuracy is what engages the GPS chip and is usually
  the one that delivers on GrapheneOS.
- Also fire a one-shot high-accuracy getCurrentPosition.
- Extend the deadline to 60s (real cold-GPS lock time) and classify a
  timeout-flavored failure correctly.
- Ignore invalid/null-island (0,0) readings.
- Drop the redundant post-race extra attempt.

UX: show a persistent "Locating you…" toast (dismissed on success/fail)
so the up-to-60s GPS lock doesn't look frozen, plus a "Found you"
confirmation. Failure still surfaces the raw error text for diagnosis.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:36:16 +07:00
shakespeare.diy
a3dad2cbec Geolocation: handle 'no provider' case (GrapheneOS / no Play Services)
Root cause of the persistent Near Me failure: the test device runs
GrapheneOS with no Google Play Services, so there's no network/fused
location provider — only raw GPS, which can't fix indoors. This was
never a code bug.

- Add 'no_provider' GeoErrorKind; detect it from native error text
  (disabled / provider / settings / unavailable).
- Show accurate, actionable messaging: tell the user to enable Network
  Location (Settings → Location) — the GrapheneOS-friendly fix — or use
  GPS outdoors, instead of a vague timeout.
- Refine the timeout copy along the same lines.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-25 00:32:03 +07:00
shakespeare.diy
700ebee17e Geolocation: fast first-fix via watchPosition race (Google-Maps-like)
The real reason Near Me failed indoors: getCurrentPosition with high
accuracy waits for a single satisfactory GPS reading, which needs
satellite visibility and times out indoors. Google Maps feels instant
because it takes the FIRST update from any provider (wifi/cell/fused).

Rewrite the acquisition strategy to do the same:
- raceForFirstFix() starts a low-accuracy watchPosition and resolves on
  the very first update (the fast network/fused fix, ~1s indoors),
  racing it against a cheap cached getCurrentPosition. First to return
  wins; watch is cleared immediately after.
- 25s deadline falls back to one long high-accuracy attempt for the
  cold-outdoor-GPS case.
- Apply the same watchPosition-race to the web path.
- watchPosition/clearWatch added to the plugin proxy interface, guarded
  so builds without them fall back to getCurrentPosition.
- Update timeout toast (drop the "walk to a window" advice; point at the
  GPS/Location quick-setting toggle instead).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 23:30:17 +07:00
shakespeare.diy
a9e0c1dcb7 Add community Spot Status Reports (kind 2259)
Solves the "spots that change appearance or disappear" problem with
crowd-sourced, trust-weighted status reports — since the spot owner
often isn't the one who notices a spot got knobbed/demolished/busted.

New custom kind 2259 (regular, append-only): any user can report a
spot's current physical state — active / bust / skate-stopped / closed /
gone — with an optional note, referencing the spot via its `a`
coordinate.

Client aggregation (useSpotStatus / useBatchSpotStatus):
- recency-weighted (30-day half-life) so fresh reports dominate and a
  rebuilt spot naturally recovers
- trust-weighted via existing useBatchTrustScores (0.2–1.0 multiplier)
- most-recent-report-per-reporter (no ballot stuffing)
- reports older than 180 days ignored

UI:
- SpotStatusBadge (colored consensus badge + tooltip w/ report count)
- ReportSpotStatusDialog (status picker, optional note, recent reports)
- Badge + Report Status button on SpotDetailPage
- Batched status badge on SpotCard; "gone" spots dimmed/grayscaled
- "gone" spots hidden from map markers (still listed dimmed) to keep the
  map uncluttered while preserving the record

Documented the kind, tags, status values, and aggregation rules in
NIP.md.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 23:06:10 +07:00
shakespeare.diy
9c7cd5ec4e Improve geolocation reliability: 3-stage fix with forgiving timeouts
Now that the native crash is fixed (Kotlin 2.1 alignment on the Android
side), the remaining issue was GPS "Location timed out" on a cold first
fix / indoors. Rework the native position acquisition into three
increasingly-forgiving stages that return the first success:

  1. INSTANT — accept a cached/coarse fix up to 5 min old (resolves
     immediately on a warm chip).
  2. PRECISE — high-accuracy GPS with a generous 30s window so a cold
     first-fix has room to complete.
  3. COARSE FALLBACK — network/wifi fix with the longest window to
     rescue the "GPS can't lock indoors" case.

Also bump the web-path timeout to 20s, and make the "Location timed out"
toast actionable on native (suggests stepping near a window / retrying).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 22:47:48 +07:00
shakespeare.diy
71e5c5df1f Harden native geolocation to prevent app-crash on "Near Me"
The "Near Me crashes the app / bounces to ZapStore" bug is a native
FATAL EXCEPTION on the Android main thread, which no JS try/catch can
intercept. registerPlugin('Geolocation') always returns a proxy even
when the native class isn't actually registered in the APK (e.g. after
an incomplete cap sync or missing play-services-location), and invoking
a method on that ghost proxy is what detonates the crash.

Fix: verify the plugin is genuinely registered via
Capacitor.isPluginAvailable() and the live window.Capacitor.Plugins
registry BEFORE ever calling it. If it's not truly present, fall back to
the WebView's navigator.geolocation instead of crashing. Also guard the
requestPermissions step independently so a permission-flow throw can't
take down the app. isNativeGeolocation() now reflects the path actually
in use so error copy matches; added isNativePlatform() helper.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 22:16:25 +07:00
shakespeare.diy
59493ac784 Geolocation: coarse fallback + clearer native error copy (v1.0.75)
The native Geolocation fix only takes effect once the APK is rebuilt — the
current phone build still uses navigator.geolocation, hence the error. To make
the rebuilt version more robust:

- If a high-accuracy fix fails/times out (common indoors or weak GPS), retry
  with a coarse (network/wifi) fix and a longer window before giving up. This
  rescues most "Location unavailable" cases.
- On native, the "unavailable" toast now tells the user to turn on Location/GPS
  AND allow the app permission, instead of the generic web message.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:17:20 +07:00
shakespeare.diy
04b8b0a8b1 Amber signing: add timeout + one-time setPackageName so hangs surface as errors (v1.0.74)
Symptom: crew logo upload spins forever, and crew creation appears to do
nothing. Both go through Amber signEvent. Login (getPublicKey) works because
it uses Amber's silent content-resolver path, but signEvent must launch
Amber's approval Activity and wait for the result — and if that result never
comes back (buggy plugin build, user backs out, missing permission) the
bridge promise never settles, so the UI hangs with no error.

Changes:
- withTimeout() wraps every Amber bridge call (sign/getPublicKey/nip04/nip44)
  with a 90s cap. A hung round-trip now rejects with a clear, actionable
  message instead of spinning forever, which also unblocks the CrewForm
  (isUploading/isPublishing finally resolve).
- setPackageName is now called ONCE per session (cached promise) instead of
  before every op — repeated calls are needless bridge chatter and can wedge
  on some builds.

NOTE (root cause, fixed on the build machine): package.json requests the
GitHub `main` build of nostr-signer-capacitor-plugin, but package-lock.json
still pins the old npm 0.0.5 (which the APK bundles). 0.0.5 is the version
prone to the signEvent activity-result hang. Regenerate the lockfile with
`npm install` so the `main` build is used — see chat for steps.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 21:13:58 +07:00
shakespeare.diy
b3a025ca77 Native geolocation for Near Me + desktop map fits viewport with footer (v1.0.73)
1. Near Me "location blocked" on the APK:
   - navigator.geolocation returns PERMISSION_DENIED inside the Android WebView
     because the WebView never gets the OS location permission, so the app
     showed a bogus "your browser blocked location" help sheet.
   - New src/lib/geolocation.ts: platform-aware getCurrentPosition(). On native
     it uses Capacitor's Geolocation plugin via registerPlugin('Geolocation')
     from bundled @capacitor/core (same runtime pattern as the camera/Amber
     fixes) and calls requestPermissions() to trigger the real Android prompt;
     on web it falls back to navigator.geolocation.
   - MapPage handleNearMe, proximity sort, and quick-snap location now use it.
     On native, a denied permission shows an OS-permission toast instead of the
     browser help sheet.
   - Added @capacitor/geolocation dependency (native side picked up via cap sync;
     no bare import in the JS bundle).

2. Desktop map didn't fit the viewport (had to scroll to see footer):
   - Map wrapper was md:h-[calc(100vh-4rem)] — full height minus header only,
     leaving no room for the footer.
   - Added --footer-desktop token (5rem) and changed the desktop height to
     calc(100vh-4rem-var(--footer-desktop)) so map + footer fit without scroll.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 20:28:06 +07:00
shakespeare.diy
def67d4df2 Fix "Capacitor Amber plugin not available" on upload/signing (v1.0.71)
Root cause (same class as the camera bug): capacitorAmberSigner.ts used
`await import('nostr-signer-capacitor-plugin')`, but that package is marked
external in vite.config.ts and the native build uses inlineDynamicImports, so
the bare specifier survives in the APK bundle and the Android WebView can't
resolve it at runtime. The import threw, getPlugin() returned null, and every
Amber signing op — including the Blossom upload auth event that a Crew logo
upload needs — failed with "Capacitor Amber plugin not available".

Fix: access the native signer via Capacitor's registerPlugin('NostrSignerPlugin')
from the bundled @capacitor/core, and reimplement the plugin's thin JS wrapper
logic directly against the native bridge (options-object API per the plugin's
main source). This removes the broken dynamic import entirely and is version-
independent as long as the native plugin is registered. Covers both native
Amber login (getAmberPublicKeyViaPlugin) and all signing/encryption paths.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:57:55 +07:00
shakespeare.diy
939a101b38 Fix Quick Snap opening gallery instead of camera on native (v1.0.70)
Root cause: quickCapture.ts relied on window.Capacitor.Plugins.Camera,
which is only populated once something imports '@capacitor/camera'. Since
we deliberately never import that package (to avoid the old white-screen
crash), the plugin was never registered, the native check failed, and Quick
Snap fell through to the web file input — opening the gallery.

Fix: use Capacitor's official registerPlugin('Camera') from the bundled
@capacitor/core. This returns a proxy that dispatches to the native Camera
implementation over the bridge without needing the JS @capacitor/camera
package to self-register, and without putting a bare '@capacitor/camera'
specifier in the bundle. Applied the same pattern to CameraCapture.tsx.

Also: on a permission error, show a clear "enable camera access" toast
instead of silently opening the gallery.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:47:36 +07:00
shakespeare.diy
6d0480961c Quick Snap: camera-first on native (runtime Camera plugin), file input on web
The previous file-input-only approach fixed the crash but opened the gallery
on native because capture="environment" is only a hint the OS can ignore.

New platform-aware flow (lib/quickCapture.ts):
- Native (APK): open the OS camera directly via the runtime-injected
  window.Capacitor.Plugins.Camera with source:'CAMERA' — forces the real
  camera and gives built-in preview/retake. Accessed off the runtime object,
  so no '@capacitor/camera' specifier in the bundle (keeps the white-screen
  fix intact). User cancel is treated as a no-op.
- Web: falls back to the file input with capture="environment".
- If the native camera errors, gracefully falls back to the file input.

MapPage Quick Snap now routes through handleQuickSnap() → the captured photo
feeds the existing upload → create-spot flow unchanged. Bump 1.0.69.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:38:46 +07:00
shakespeare.diy
2a55d1b3df Crew Agent Phases 0–2: opt-in AI guide (🐝 Sasquatch) per crew
Phase 0 — identity (lib/crewAgent.ts):
- Deterministic per-crew agent keypair derived from the crew coordinate
  (sha256 of a public salt + coord). Documented security tradeoff: the key
  is reproducible by design and holds no funds — used only for the agent to
  speak for itself. Includes kind-0 profile builder (bot:true, NIP-24) and
  a signAsCrewAgent helper for Phase 3.

Phase 1 — opt-in toggle:
- SkateCrew.agentEnabled parsed from a queryable ["t","crew-agent"] marker.
- "Crew Agent" toggle (Beta) in CrewForm; publishes the marker when on.
- NIP.md documents the crew-agent tag and the agent identity derivation.

Phase 2 — crew-scoped Sasquatch with receipts:
- lib/sasquatchBrain.ts: shared AI caller (model fallback) + tool-call parser.
- useCrewAgentChat: reuses the CVM data tools, seeded with crew context
  (name/location) so "near us" resolves to the crew's turf. Ephemeral/local,
  no keys, no publishing yet.
- CrewAgentPanel: chat dialog rendering agent answers PLUS structured receipt
  cards (spots / events / Lab pros / gear / crews) linking into the app.
- CrewDetailPage: "Ask Sasquatch" card + panel, shown only when the crew
  enabled the agent.

Also: Lab category tabs → fixed 5-across grid (no horizontal scroll). Bump 1.0.68.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 19:26:19 +07:00
shakespeare.diy
9a8609354e Polish native-only wallet card + add Crew↔Lab scene focus
Wallet (Request A): redesign the browser CashuWalletBrowserNotice into a
premium, intentional card — gradient hero header, "App only" badge, and a
"works in your browser now" panel highlighting NWC / Alby / Lightning
address zaps. Keeps the native-only gate; no bug reintroduced.

Crew ↔ Lab interconnection: crews can now declare an optional Lab scene
focus that surfaces them inside the matching Lab category, while regular
crews stay "Mixed" and only appear on the Crews page.
- types: CrewFocus ('mixed'|'builder'|'photo'|'coaching'), labels,
  crew-focus-* t-tag scheme, serviceType→focus mapping; parseSkateCrew
  extracts focus and excludes focus tags from vibes; SkateCrew.focus field.
- CrewForm: "Crew Type" selector (Mixed / DIY & Builders / Photo & Film /
  Coaching); publishes a queryable crew-focus-* t-tag when not mixed.
- useCrews: new useCrewsByFocus hook (relay-level #t filtering).
- CrewsPage: focus badge on cards + focus filter chips.
- LabPage: "Crews in this scene" horizontal strip beneath results, mapping
  building→builder, filming/photography/editing→photo, coaching→coaching.
- NIP.md: document the crew-focus-<scene> tag.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 18:08:51 +07:00
shakespeare.diy
fc7564a6f8 Platform-aware wallet mints (Option C: native + browser scoping)
- Add @capacitor/core + src/lib/platform.ts (isNativePlatform/getPlatform, web-safe)
- mintReachability: on native (no CORS jail) short-circuit probe to 'ok' so ANY mint works, including mint.minibits.cash
- useCashu default mint: native prefers minibits (matches skatespots@minibits.cash brand identity) then coinos/21mint; browser uses CORS-friendly candidates
- MintPrivacyManager recommended list is platform-aware: native leads with Minibits (recommended), web leads with Coinos and flags Minibits as native-only
- CashuWallet: 'This mint can't be reached from your browser' banner for the browser CORS case (never shows on native), pointing users to a web-friendly mint or the native app
- Lightning invoice error mentions the native-app option

Same codebase now uses minibits directly when running as the native app, and a CORS-safe mint when running in a web browser.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:22:41 +07:00
shakespeare.diy
71d3d60d09 Harden wallet mints: browser-side CORS reachability probe
- Add mintReachability.ts: probe a mint's /v1/info from the user's browser to detect CORS-blocked ('cors-blocked') vs down ('unreachable') vs 'ok'
- useMintManager.handleAddMint: probe before adding; reject CORS-blocked/unreachable mints with a clear message instead of letting them fail later on invoice
- useCashu default mint: instead of hardcoding one (unverifiable at build time), probe an ordered list of web-friendly candidates and add the first that's reachable in-browser; minibits excluded (mobile-app only)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:04:43 +07:00
shakespeare.diy
f5483a0dbc Add crew admin moderation + invite actions
- useCrewModeration: fetch NIP-32 `hidden` labels authored by founder+admins for a crew; useHideCrewPost publishes a kind-1985 hidden label (L/l org.skatespots.moderation, e=post, a=crew)
- CrewDetailPage feed: moderators can hide posts via an EyeOff button + confirm dialog; hidden posts filtered from the feed for everyone, shown greyed/"Hidden from feed" to moderators (reversible/auditable)
- CrewInviteDialog: founder+admins share an invite link/message (native share + copy); founder gets a shortcut to add a member key in crew settings
- Gate all admin actions on useCrewRole().canManage; moderators = founder + delegated admins
- Add MODERATION_LABEL_NAMESPACE/HIDDEN constants
- NIP.md: document feed moderation label format (author must be founder/admin) + invite semantics

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 17:01:28 +07:00
shakespeare.diy
40e4a27565 Add crew NIP-05 (any-domain, founder-verified) + founder/delegated-admin governance
- SkateCrew: add nip05 + admins fields; parse `admin` role marker on p tags (admins = subset of members)
- CrewForm: NIP-05 input + per-member "Make admin" toggle; write nip05 tag + admin-marked p tags (works on create & edit via shared form)
- useCrewNip05: verify crew handle on ANY domain against the founder pubkey (verified/mismatch/unreachable), CORS-proxy fallback + caching
- CrewNip05Badge: green ✓ / red ⚠ / muted handle states with tooltips
- CollectiveMembers: show Admin chips, sort admins first, admin count in summary
- useCrewRole: centralized founder/admin/member/none roles (canManage, canEditProfile) — founder owns identity, admins get delegated powers with their own keys; egalitarian shared-editing intentionally unsupported
- CrewDetailPage: NIP-05 badge by crew name, viewer role badges (admin/member)
- NIP.md: document crew nip05 verification, admin role marker, and governance/conflict-resolution model

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 16:46:20 +07:00
shakespeare.diy
e48a5e6bc5 Add duration tiers + layered NIP-57 zap verification, extend featured/Pro to tours
- Add 4 duration tiers (7d/30d/90d/365d) with per-day price discrimination to usePlatformFee
- PlatformFeePayment: tier selector UI + attempt NIP-57 zap request (a/p tags), graceful fallback to plain LNURL invoice
- Add getZapProviderPubkey + getZapInvoiceFromLightningAddress helpers
- Add useFeaturedVerification hook (verified/claimed/none) querying kind 9735 zap receipts
- ProBadge: optional entityCoord to show green Verified state on detail pages
- Wire entityCoord through all 5 Become* Lab pages + CoachDetailPage
- Add featuredTour fee type; FeaturedUpgrade generalized for events + tours
- CreateTourPage: featured tag on publish + FeaturedUpgrade UI
- TourCard/TourDetailPage: render ProBadge; sort featured tours first
- Document tiers + zap-receipt verification scheme in NIP.md

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 16:18:15 +07:00
shakespeare.diy
5c0d4715d5 Rework Lab fee into optional "Pro" upgrade (Option B, no paywall)
Basic Lab profiles are now always free to publish. The 5,000 sat fee is
opt-in and unlocks a Pro badge + featured placement instead of gating
publication behind a rate.

- types.ts: `featuredUntil` field on all 5 Lab types + SkateEvent,
  parseFeaturedUntil() + isFeaturedActive() helpers, wired into parsers
- usePlatformFee: exposes featuredUntilSeconds for the `featured` tag
  (platform owners get a 1yr window)
- New ProBadge + LabProUpgrade (opt-in toggle → reveals PlatformFeePayment)
- All 5 Become pages: removed rate-based fee gate and wallet-required gate;
  publish is free; when Pro is active a `["featured", "<expiry>"]` tag is added

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:31:49 +07:00
shakespeare.diy
ba3921b15c Add Spot Chat: ephemeral geohash-anchored local chat channels
Inspired by BitChat's location-channel concept, reframed for skaters and
kept fully SkateSpots-native (own kind + tag conventions, no interop).

- New ephemeral kind 24899 tagged with a geohash `g` (documented in NIP.md)
- useSpotChat / useSendSpotChat hooks (polled, community-relay aware)
- SpotChat component: live message list, composer, author profiles,
  Block/Neighborhood/City precision toggle, and light IRC-style flavor
  (/me action messages, /who participant count)
- Spot detail pages now show a per-spot block-level channel
- Map gains a "Local Chat" sheet anchored to the map center with the
  precision selector

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:52:48 +07:00
shakespeare.diy
d8d1ba32c9 Nutzap safety gate + crew↔Lab cross-linking
Nutzap:
- Gate sending behind NUTZAP_SENDING_ENABLED=false with clear docs; sending
  would require P2PK-locking proofs (fund-loss risk if wrong), so it's disabled
  until implementable/auditable. Receiving + reading unchanged. No dead-end
  button exists (hook was never wired to UI).

Cross-linking crews ↔ Lab collectives:
- types: parseAddrCoord/parseCrewRef/parseLabRefs helpers + LAB_KINDS; add
  crewRef to all 5 Lab interfaces/parsers and labRefs[] to SkateCrew
- CrewLinkField (pick one of your own crews) added to all 5 Become-* forms,
  shown only for collectives; publishes an a-tag on the Lab profile
- LabLinkField (pick your own Lab profiles) added to CrewForm; publishes a-tags
- CrossLinkCards: LinkedCrewCard on Lab detail ("Part of a Crew"), LinkedLabCard
  on crew detail ("Hire This Crew"), resolving coordinates via useCrew/useLabProfile

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:27:51 +07:00
shakespeare.diy
28f4ef8c34 Crew form: refine vibes + multiple socials
- Remove LGBTQ+ and Longboard preset vibes
- Rename "Girls / Womxn" → "Girl / Woman"
- Add custom vibe/focus field: users define their own #hashtag focus
  (normalized to a lowercase hyphenated slug, published as t tags)
- Support multiple "Other Socials" links (SkateCrew.socials[]) with add/remove UI;
  parser collects all social tags; detail page renders each (URL or plain handle)
- Update NIP.md crew vibe taxonomy

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:06:37 +07:00
shakespeare.diy
f10aa7971c Step 3: Crews — map-anchored collective feature (kind 35006)
- New SkateCrew kind 35006 + type/parser + useCrews/useCrew hooks
- /crews directory, /create-crew, /edit-crew, /crew/:pubkey/:identifier pages
- Reuses the collective engine: NIP-32 membership attestation (confirmed/pending),
  CollectiveMembers + ConfirmMembershipCard, ClinkPayButton, SpotPicker
- Activity v1: home spots + crew member feed (no events, per plan)
- Map: teal/cyan Crews layer at home turf with crew markers + popups;
  wired through SpotMap props, layer toggles (mobile popover + filters sheet),
  and mobileLayerCount badge
- Nav: Crews added to navItemKeys (desktop header, mobile bottom bar, sidebar)
  with en/es i18n keys
- NIP.md: documents kind 35006, tag taxonomy, and shared membership mechanism

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:59:50 +07:00
shakespeare.diy
657c36d264 Add Builder category to The Lab (kind 35005), collective-aware + spot-linked
Step 2 of the collective plan:
- types.ts: SKATE_BUILDER_KIND 35005, BUILDER_SERVICE/SKILL/SCALE label maps,
  SkateBuilder interface + parseSkateBuilder (entity/members/noffer/spotRefs +
  service_area & insured fields)
- useLabProfiles/useLabProfile: query + parse builders (profileType 'builder')
- BecomeBuilderPage (/become-builder): amber accent, build-type/materials/scale
  chip selectors, "how we work" gear, service area, licensed/insured switch,
  entity toggle (defaults to collective), noffer, and "spots you've built" picker
- LabPage: 'building' ServiceType — amber accent, Builders tab (5 tabs now,
  horizontally scrollable on mobile), OfferFAB item, filter sections, card
  type-icon/labels, empty state, counts & filtering
- CoachDetailPage: builder accent/icon/kind, "What We Build" + "How We Work"
  sections, service-area & insured cards, portfolio/social handling
- NIP.md: document Builder kind 35005 with tag taxonomy + collective example

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:33:52 +07:00
shakespeare.diy
9cb8d1181d Add individual/collective entity type + NIP-32 membership attestation to The Lab
Step 1 of the collective plan (shared primitive on existing Lab categories):
- types.ts: EntityType + entity/members fields on coach/filmer/editor/
  photographer interfaces & parsers; membership label namespace constants +
  parseMemberPubkeys / parseEntityType helpers
- useCollectiveMembers: cross-references claimed `p` tags against members'
  own NIP-32 (kind 1985) attestations -> confirmed / pending status
- useConfirmMembership: publishes a member's self-signed membership label
- EntityTypeField: individual/collective toggle + member npub picker (per-
  category accent) wired into all 4 Become-* forms
- CollectiveMembers: sidebar card with confirmed/pending trust chips
- ConfirmMembershipCard: one-tap confirm shown only to listed pending members
- CoachDetailPage: collective badge, members card, confirm-membership prompt
- Lab cards: compact collective badge with member count
- NIP.md: document entity tag + collective membership attestation flow

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 13:18:18 +07:00
shakespeare.diy
feddbaf5ef Add optional CLINK (noffer) self-custodial payments to Lab profiles + shops
Phase 1 CLINK integration (recognize & display only):
- types.ts: optional noffer field + isValidNoffer guard on SkateCoach,
  SkateFilmer, SkateEditor, SkatePhotographer, SkateShop; parsers read it
- NofferField: reusable opt-in input with live validation, added to all 4
  Become-* Lab forms and both Create/Edit shop forms
- ClinkPayButton: secondary "Pay via CLINK" action (QR + copy + nostr: deep
  link), rendered on CoachDetailPage and ShopDetailPage only when a valid
  noffer is present; never replaces the existing zap / Lightning Address flow
- useQRCodeScanner: classify scanned noffer1/ndebit1 CLINK codes
- NIP.md: document the noffer tag on Lab kinds + shops and a CLINK section

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 12:34:50 +07:00
shakespeare.diy
adeab62aa4 Add Photographer card to The Lab (kind 35004) + improve Filmer section
New Photographers category (blue-indigo) — separate from Filmers:
- New addressable kind 35004 + SkatePhotographer type + parseSkatePhotographer.
- Rich photographer taxonomy as queryable `t` tags: services (action,
  sequence, editorial, product, events…), camera bodies, lenses, lighting
  (Profoto/Elinchrom/Godox/triggers…), support/rig, and shooting style,
  plus a freeform "gear" tag and a portfolio link.
- BecomePhotographerPage form with chip-grid selectors for all gear
  categories + "what's in the bag" textarea, wired to /become-photographer.
- The Lab: 4th "Photo" tab (indigo accent) with its own filters, cards,
  empty state; OfferFAB gains "Offer Photography"; useLabProfiles fetches
  35004; CoachDetailPage renders photographers (and editors) properly with
  per-type accent, gear + portfolio display.
- NIP.md: documented the whole Lab (kinds 35001-35004) incl. photographer.

Filmer improvements (now video-focused, photography split out):
- Removed the token "Action Photography" service; added follow/line filming.
- Richer equipment list: cinema camera, HVX, action cam, gimbal, 4K/HFR.
- Filmers gain a freeform "what's in the bag" gear field + a reel/portfolio
  link, both surfaced on the detail page.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 11:27:22 +07:00
shakespeare.diy
6331a937f2 feat: map as homepage, 3D globe, camera fix, Sasquatch upgrades, UI/UX polish
## 1. Map as Homepage
- `/` now renders MapPage (the globe/map) — removed old Index homepage
- `/home` redirects to Index (legacy), `/map` alias still works
- Navbar: removed HOME link, SPOTS (MapPin) is now first nav item
- isActive() treats both `/` and `/map` as active for SPOTS tab

## 2. 3D Globe View
- New SkateGlobe component using pure Three.js (no h3-js/CDN issues)
- Earth night texture with atmospheric neon red glow
- Spot pins (red) + shop pins (lime) on globe surface
- Click pin → zoom camera in + show info card
- Drag to rotate, scroll/pinch to zoom, auto-rotation when idle
- Globe toggle button added to map/list view switcher
- Selected marker ring + info card with View Spot/Shop links
- Lazy-loaded with Suspense skeleton

## 3. Settings → Stash Link
- Added My Stash card in Settings > Wallet tab
- Links to /stash with Package icon and description
- Smooth hover animation with ChevronRight

## 4. Camera Fix (Capacitor Native)
- CameraCapture now detects native Capacitor platform at runtime
- On native Android/iOS: uses @capacitor/camera (OS-level camera, no WebView permission popup)
- On browser: falls back to getUserMedia stream (existing behaviour)
- Added Upload from Gallery fallback when camera permission denied
- Better error handling for user cancellation

## 5. Sasquatch Upgrades
- FAQ page: replaced static accordion wall with Sasquatch-powered UI
  - Quick question chips that pre-load questions into Sasquatch
  - "Open The Sasquatch" CTA button
  - Quick facts grid, clean modern design
- About page: added Sasquatch CTA card at bottom
- SasquatchGuide: listens for sasquatch:open and sasquatch:ask custom events
  - External pages can now trigger and pre-fill the Sasquatch
  - Sasquatch now shows even without a page-level message prop (for external triggers)

## 6. UI/UX Audit & Polish
- Focus rings: on-brand red (WCAG accessible)
- Card hover micro-interactions: subtle translateY + shadow
- Button/link transition consistency (150ms ease)
- Reduced motion support (@media prefers-reduced-motion)
- Modal backdrop blur improvement
- Skeleton shimmer animation utility
- Globe CSS utilities (touch-action: none for canvas)
- Typography helpers (text-balance, text-pretty)
- Improved map-full-height responsive calculation

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-22 22:47:39 +07:00
shakespeare.diy
de5de7abec Fix getPublicKey: JSON parse JSObject result + restore AMBER_PACKAGE arg
- JSObject from Capacitor needs JSON.parse(JSON.stringify()) to be
  accessible as plain JS object with property access
- Restore AMBER_PACKAGE as argument (null was causing broken URI)
- Remove debug toast from LoginDialog

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 14:28:58 +07:00
shakespeare.diy
1c577f7392 Add debug logging to getAmberPublicKeyViaPlugin to trace exact return value
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 13:43:26 +07:00
shakespeare.diy
0d9cfa61fb Fix getPublicKey: pass null packageName to force Amber UI path
The Content Resolver silent path returns a cached/wrong key.
Passing null skips it and always opens Amber UI so the user
explicitly selects their key and the correct pubkey is returned.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 13:28:26 +07:00
shakespeare.diy
da1d208579 Fix getPublicKey: Java returns hex not npub despite field name
NostrSigner.npubToHex() is called before putting result in "npub" field,
so the value is already a 64-char hex pubkey. Skip nip19.decode() and
use it directly. This was causing the "data must be at least 8 chars" error.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 12:50:37 +07:00
shakespeare.diy
9eda3d5c86 Fix capacitorAmberSigner: rewrite with correct positional API
The actual plugin source uses positional args, not object params:
- getPublicKey(packageName)
- signEvent(packageName, eventJson, id, npub)
- nip04Encrypt/Decrypt(packageName, text, id, pubKey, npub)
- nip44Encrypt/Decrypt(packageName, text, id, pubKey, npub)
- isExternalSignerInstalled(packageName)

Previous implementation was based on wrong README docs.
Now based on actual dist/esm/index.js source.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 12:34:10 +07:00
shakespeare.diy
0f758d7b1f Fix capacitorAmberSigner: correct named export and setPackageName API
- Use named export { NostrSignerPlugin } not mod.default
- setPackageName takes a plain string not { packageName: string }
- isExternalSignerInstalled takes packageName as argument
- These were the exact bugs causing "plugin not available" error

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 12:25:25 +07:00
shakespeare.diy
8256f05ca8 Wire up nostr-signer-capacitor-plugin for Amber login and signing
- Add CapacitorAmberSigner: a full NostrSigner implementation backed by
  the native nostr-signer-capacitor-plugin (login, signEvent, nip04,
  nip44 all go through Amber via native Android intents)
- LoginDialog: on Capacitor, call getAmberPublicKeyViaPlugin() directly
  instead of the intent:// URL redirect — returns a Promise, no
  callback URL or polling needed. Shows clear error if Amber not installed.
- useCurrentUser: on Capacitor amber logins use CapacitorAmberSigner
  so every publish/sign goes through the plugin, not window.location.href
- Web/PWA path unchanged — still uses URL redirect + AmberSigner

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 11:38:23 +07:00
shakespeare.diy
712b871e1a fix: Amber intent: format + # in callbackUrl breaks intent parsing
Three compounding bugs fixed:

1. WRONG FORMAT: Was using nostrsigner: directly, then intent: with encoded
   values. Amber README shows intent: with raw unencoded S.key=value params.

2. # IN CALLBACKURL: Our callbackUrl was https://.../#/amber-callback which
   contains a # — but intent: uses # as its own delimiter (#Intent;...;end).
   Having a # inside the callbackUrl value breaks the entire intent string.
   Fixed: callbackUrl is now https://.../amber-callback?event= (no hash).

3. HASHROUTER MISMATCH: Amber opens /amber-callback (no hash) but HashRouter
   only handles /#/route paths. Fixed in main.tsx: pre-React IIFE detects
   /amber-callback path, saves ?event= to sessionStorage, then redirects to
   /#/amber-callback so React Router matches AmberCallbackPage correctly.

Final correct intent URL format:
  intent:#Intent;scheme=nostrsigner;S.compressionType=none;S.returnType=signature;S.type=get_public_key;S.callbackUrl=https://skate-spots.org/amber-callback?event=;end

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:36:03 +07:00
shakespeare.diy
6f303d1956 fix: revert to nostrsigner: format, fix double-encoding of callbackUrl
Root cause of "malformed request":
- buildIntentUrl() was calling encodeURIComponent() on ALL values including
  callbackUrl, which was ALREADY a full URL with special chars
- This double-encoded the callbackUrl, arriving at Amber as garbage
- The intent: wrapper format was also unnecessary

Fix: back to clean nostrsigner: format per NIP-55 spec
- Only callbackUrl gets encodeURIComponent (once)
- All other params are plain query string values
- Matches exactly what NIP-55 spec shows in its example code

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:26:42 +07:00
shakespeare.diy
c30546f700 fix: use Android intent: URL format for Amber (not nostrsigner: directly)
The nostrsigner: scheme used directly caused "malformed request" in Amber
when opened from a browser context. The correct format per Amber's README
for web clients is the Android intent: URL format:

  intent:<payload>#Intent;scheme=nostrsigner;S.type=...;S.callbackUrl=...;end

Updated all Amber operations (login, sign, nip04/nip44 encrypt/decrypt)
to use buildIntentUrl() helper instead of raw nostrsigner: URLs.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-07 19:35:00 +07:00
shakespeare.diy
db157efda4 fix: use window.location.href for Amber NIP-55 intent (not window.open)
window.open(..., '_blank') opens a new browser tab instead of firing
the Android intent — causing Amber's "malformed request" error.

Per NIP-55 spec the correct method is window.location.href = url,
which triggers the nostrsigner: intent that Amber is registered to handle.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 14:17:51 +07:00
shakespeare.diy
59eda93641 fix: Amber login callback URL for HashRouter
BUG #1 (critical): buildCallbackUrl() was returning
  https://skate-spots.org/amber-callback?event=
but the app uses HashRouter, so React Router never matched /amber-callback.
Amber would open the callback URL in a new tab, land on the homepage, and
AmberCallbackPage would never render — login was silently broken.

Fixed to:
  https://skate-spots.org/#/amber-callback?event=

BUG #2: AmberCallbackHandler (global, in App.tsx) was competing with
AmberCallbackPage on the /amber-callback route, causing a race condition
where the global handler could consume the pubkey before the page component.
Fixed by detecting the hash path and early-returning when on /amber-callback.

BONUS: openAmberForLogin() now persists amber_return_path from the current
hash path so AmberCallbackPage can navigate back to where the user was.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 13:58:41 +07:00
shakespeare.diy
00d9b69f81 fix: use window.open(_blank) to trigger Amber's web/browser intent path
Amber has two code paths:
- Native path (no Browser.EXTRA_APPLICATION_ID): reads type from intent
  EXTRAS — impossible to set from a WebView URL navigation
- Web/browser path (Browser.EXTRA_APPLICATION_ID set): reads type from
  URL query params — triggered when WebView opens URL as browser intent

window.open(url, '_blank') causes Android WebView to fire a browser
ACTION_VIEW intent which sets Browser.EXTRA_APPLICATION_ID, routing
Amber to parse our nostrsigner: URL query params correctly.

Previous attempts:
- window.location.href: WebView intercepts as asset URL
- window.open(_system): strips query params  
- anchor click: goes through native path (no extras = INVALID type)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:18:02 +07:00
shakespeare.diy
f534c413fd fix: use anchor click to open nostrsigner: URLs in Capacitor WebView
window.location.href causes WebView to try loading nostrsigner: as an
asset URL. window.open(_system) strips query params. The only reliable
method in Capacitor WebView is creating a temporary <a> element and
clicking it — this correctly triggers Android's intent system with the
full URL intact.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:10:13 +07:00
shakespeare.diy
fdebfc0d37 fix: use window.location.href for nostrsigner: URLs instead of window.open(_system)
window.open(_system) was stripping query parameters from the nostrsigner:
URL, causing Amber to receive a request with no type parameter and
rejecting it as malformed.

window.location.href works correctly in Capacitor WebView — Android
intercepts nostrsigner: scheme via intent-filter with full URL intact.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 23:02:33 +07:00
shakespeare.diy
c3ef0247fc fix: switch to HashRouter for Capacitor Android compatibility
The primary cause of the white screen in the APK was BrowserRouter.
Capacitor serves files via https://localhost/ and the HTML5 History API
routing (BrowserRouter) fails to resolve routes in this context,
resulting in a blank white page.

Changes:
- Switch BrowserRouter → HashRouter in AppRouter.tsx
- Add DeepLinkHandler component (handles appUrlOpen for Amber callbacks
  and other deep links in native context)
- Fix Amber callback URL builder to use org.skatespots.app:// custom
  scheme when running in Capacitor (so Android fires an intent back into
  the app instead of navigating a browser)
- Fix getSearchParams() in amber.ts to extract query params from hash
  fragment (HashRouter URLs look like /#/amber-callback?event=XXX)
- Fix captureAmberCallback in main.tsx to check hash fragment too
- Remove debug error overlay (no longer needed)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 21:56:23 +07:00
shakespeare.diy
a677e8687d fix: Amber Capacitor APK + mobile UI improvements
Amber login (critical):
- Replace window.location.href with window.open(_system) in Capacitor WebViews
- Capacitor's WebView throws "malformed nostrsigner request" on href navigation
- New openAmberUrl() helper detects Capacitor via window.Capacitor.isNativePlatform()
- Add openAmberForLoginNoCallback() for PWA/Capacitor clipboard flow
- Fix all 6 Amber URL opens in amber.ts + LoginDialog.tsx

Mobile nav:
- Newsletter and Sasquatch AI (/pyramid) added to hamburger menu
- Sasquatch floating guide now shows on mobile (removed hidden md:block)

Lab page mobile:
- Action buttons (Offer Coaching/Filming/Editing/Get Card) now 2-col grid on mobile
- Service type filter (Coaches/Filmers/Editors) now 3-col grid, full width
- Reduced top padding on mobile (py-6 → py-12 on md+)
- Text truncates cleanly, icons don't overflow

Wallet page mobile:
- Reduced padding (py-6 md:py-8)
- Tab triggers use h-auto + py-2.5 to avoid overflow
- Smaller header text on mobile

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 20:48:11 +07:00
shakespeare.diy
f19851da92 security: implement all 8 protection measures
## 1. sanitizeUrl() (already existed, verified applied)
- Applied to ShopDetailPage, CoachDetailPage with urlDomain() tooltip hints
- Blocks javascript:, data:, vbscript: and all non-http(s) schemes
- Returns null for invalid URLs — link simply doesn't render

## 2. Geohash validation in parsers
- Added isValidGeohash() in types.ts using ngeohash.decode()
- Min 4 chars (country-level precision rejected), max 12 chars
- Rejects lat/lng outside Earth bounds
- Rejects 0,0 "null island" — classic bot artifact
- VPN/Tor unaffected: geohash comes from map pin, not user IP

## 3. Content length limits in all parsers
- MAX_TITLE_LEN=100, MAX_DESCRIPTION_LEN=3000, MAX_LOCATION_LEN=200
- MIN_TITLE_LEN=2, MIN_GEOHASH_LEN=4
- Applied to parseSkateSpot, parseSkateShop, parseSkateCoach,
  parseSkateFilmer, parseSkateEditor, parseSkateEvent

## 4. Smart WoT default (already in codebase, verified correct)
- Logged out / <5 follows → 'all' (don't punish new/anon users)
- Logged in with ≥5 follows → 'trusted' (sort trusted first)
- Soft mode: trusted content floats to top, nothing hidden
- Active in MapPage and EventsPage

## 5. Trusted count stats in useGrowthStats
- Added trustedSpots and trustedMappers to GrowthStats type
- Heuristic: pubkeys with ≥2 published items = organic community member
- CommunityStatsPage now shows "🛡 X from active accounts" below spot count
- Purely additive — both numbers always visible

## 6. OfficialBadge component (already existed, wired up)
- Added to SpotCard, EventCard, ShopDetailPage, CoachDetailPage
- Only renders for FOUNDER_PUBKEY (20e7c953...)
- Zero-friction: invisible for all other pubkeys
- Impersonation attempts immediately visible

## 7. Leaflet marker clustering
- Installed leaflet.markercluster v1.5.3 + @types
- MarkerClusterGroup replaces direct marker.addTo(map)
- Custom cluster icon: red/orange gradient circle with count
- maxClusterRadius: 60px, spiderfyOnMaxZoom: true
- MarkerCluster CSS loaded from CDN in index.html

## 8. DM phishing warning banner
- PhishingWarningBanner component in DirectMessagesPage
- One-time dismissible (localStorage key: dm-phishing-warning-dismissed)
- Message: SkateSpots will never DM you asking for keys/payments
- Uses useLocalStorage hook, Shield icon, X dismiss button

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 14:03:15 +07:00
shakespeare.diy
03d0fe93b0 Improve Amber login flow - zero friction, auto-complete on return
- Add dedicated /amber-callback route so Amber always has a stable return URL
  - Handles get_public_key (login), sign_event, nip44/nip04 encrypt/decrypt
  - Shows animated processing screen while completing, then redirects back
- Add subscribeToAmberReturn() helper in amber.ts using visibilitychange
  - Auto-detects when user returns to the tab after cancelling in Amber
  - Resets login UI to initial state gracefully
- Update buildCallbackUrl() to use /amber-callback instead of current page URL
- Rewrite Amber tab in LoginDialog:
  - Remove 2-step clipboard paste flow entirely
  - Single tap opens Amber, login completes automatically on return
  - Waiting state shows animated dots + cancel button
  - No manual pubkey entry needed
- Save return path in sessionStorage so AmberCallbackPage redirects user back

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 22:29:06 +07:00
shakespeare.diy
ebbf2d6326 fix: remove orphaned canvas code from RiderCardPage causing ctx/Printer is not defined crash
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>
2026-06-16 17:02:59 +07:00
shakespeare.diy
4e227f0367 feat: License page, Video Editors in Lab, Buy/Sell Gear, Stash locations, Supporters page
1. /license page
   - New LicensePage with "YOU ARE FREE TO SKATEBOARD" license text
   - Copyright 2025-∞ with neon THUG2 styling
   - Footer: "License" and "Supporters" links added next to OpenSats

2. /lab — Video Editor category
   - New SKATE_EDITOR_KIND (35003) in types.ts
   - EDITOR_SERVICE_LABELS/DESCRIPTIONS (8 edit types)
   - EDITOR_SKILL_LABELS/DESCRIPTIONS (8 software/hardware skills)
   - SkateEditor interface + parseSkateEditor() parser
   - useLabProfiles updated to query kind 35003 + 'skateeditor' tag
   - LabPage: new "Video Editors" tab (purple, Scissors icon)
   - LabPage: "Offer Editing" button in header
   - BecomeEditorPage: full form with edit types, skills, hardware,
     software, portfolio, contact, location, wallet requirement
   - Route /become-editor registered in AppRouter

3. /map Quick Actions
   - "Sell Gear" → "Buy/Sell Gear" with ArrowRightLeft icon
   - Button now links to /marketplace (browse + sell)

4. /stash — Storage locations
   - StashItem.storageLocation field: home/warehouse/friends/parents/wardrobe/other
   - AddItemDialog: 6-button grid to tag where gear is stored
   - StashItemCard: shows location badge on each card
   - Filter bar: pill filter chips to filter stash by location
   - storageLocation saved/restored via onAdd handler

5. /supporters page
   - btcmap.org-style supporters page
   - Live Geyser GraphQL leaderboard (skatespots project)
   - Avatar grid with sat amounts, anon fallbacks
   - Mission section + "Send Sats" section (Lightning address + Geyser)
   - Added to footer and desktop "More" dropdown and mobile menu

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 11:05:03 +07:00
shakespeare.diy
122bdfb102 Sync with skate-spots: THUG2 neon theme, remove old AuthDialog and unused lib files
- Replace generic MKStack default CSS with THUG2-inspired neon theme:
  - Neon red primary (#ff0040) and neon lime accent (#ccff00)
  - Pure black dark mode background
  - Custom CSS variables for gradients, sidebar, and animations
  - Leaflet map dark mode overrides
  - Custom scrollbar styling
  - Mobile touch target improvements and iOS input zoom fix
  - Safe area insets for notched devices
  - scrollbar-hide utility class
  - Animation classes: neon-glow, stagger, fadeIn, slideUp, pulse-glow
  - Glass effect and gradient-text utilities
- Remove AuthDialog.tsx (old Sign Up flow replaced by newer LoginArea/SignupDialog)
- Remove appBlossom.ts and appRelays.ts (not used in this project)
- Remove useLoginActions.test.tsx (not present in reference version)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 23:00:01 +07:00
shakespeare.diy
e29d35f1e4 fix: restore full skate-spots source tree from misplaced src/src/ copy
The entire skate-spots codebase was accidentally copied into src/src/ and
public/public/ instead of src/ and public/ directly. This left the real
src/ directory as blank MKStack template stubs, explaining why the landing
page showed the default template instead of SkateSpots.

Changes made:
- Copied all files from src/src/ up to src/ (App.tsx, AppRouter.tsx, main.tsx,
  all pages, components, hooks, contexts, lib, i18n, test)
- Copied all files from public/public/ up to public/ (images, manifest, etc.)
- Deleted src/src/ and public/public/ nested directories
- Removed HelloWorld.tsx template stub from pages/
- Retained the previously fixed index.css (Tailwind v3 directives)

Full skate-spots feature set is now active:
- All 30+ routes (map, events, spots, coaches, marketplace, wallet, etc.)
- NWCProvider, LanguageProvider, PWAInstallPrompt, AmberCallbackHandler
- Skate community relays (goskatespots.nostr1.com, wotr.relatr.xyz, etc.)
- Outfit Variable font, Amber pre-check in main.tsx
- All hooks (useSkateSpots, useCoaches, useZaps, etc.)
- i18n support (en/es)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:18:28 +07:00
shakespeare.diy
06fae294b8 New project created with Shakespeare
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 19:49:07 +07:00