Commit graph

110 commits

Author SHA1 Message Date
shakespeare.diy
77dc4b562c Add Crew Chat: private E2E-encrypted group chat for crew members
Built on the existing NIP-17 gift-wrapped group DM system (useGroupConversation
+ useSendDirectMessage), so the relay only ever sees encrypted blobs.

- New CrewChat component: message list w/ author profiles, composer
  (Enter to send), auto-scroll, skeleton/empty states
- Participants = founder + all listed members (deduped)
- Only crew members can read/write (others see a locked "ask to join" state);
  warns if the signer lacks NIP-44
- "Private" lock badge makes the E2E-encrypted nature unmistakable
- Surfaced as a card on the crew detail page under the Crew Feed

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:18:39 +07:00
shakespeare.diy
e122ee826b Add "Public" badge to Spot Chat header + clearer privacy note
Makes it unmistakable that Spot Chat messages are public and unencrypted:
- Amber "🌐 Public" badge next to the title with an explanatory tooltip
- Footer note reworded to point users to Direct Messages for private chats

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 15:12:39 +07:00
shakespeare.diy
3b25e7bcf6 Add trust filter to Spot Chat + include crews in community stats
Spot Chat:
- Compact TrustFilter dropdown in the chat header (All / Verified / Trusted / Following)
- Filters messages by author trust via useBatchTrustScores; own messages
  always stay visible; shows a "N hidden by your trust filter" indicator

Community stats:
- useGrowthStats now queries kind 35006 crews (was missing entirely)
- totalCrews added to totals + monthly buckets + content-mix donut
- New "Crews" StatCard on the Community Stats page

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:58:10 +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
dec2e730d2 CVM find_crews: Sasquatch can now live-search crews
- useCVMClient: add findCrews (queries kind 35006 across data relays, filters by
  city/country/vibe/search, returns CrewResult with member_count, vibes, url);
  add crews to getCommunityStats + CrewResult type
- SasquatchGuide: register find_crews tool in system prompt + switch handler,
  add crew keywords to needsLiveData, update app-facts (crews are searchable +
  mention cross-linking and Zap-the-Crew)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:35:16 +07:00
shakespeare.diy
f881c6513f Zap a whole crew: split a zap across confirmed members
- useCrewZap hook: splits a total evenly across recipients, resolves each
  member's Lightning address (batched kind-0 query), fetches an invoice per
  recipient and auto-pays via NWC → WebLN; per-recipient status
  (resolving/paying/success/skipped/failed) with manual-invoice fallback
- splitAmount util distributes remainder fairly (no lost sats)
- ZapCrewDialog: amount presets + custom, per-head preview, live progress rows,
  copy-invoice fallback when no wallet; excludes the current user
- CrewDetailPage: "Zap the Crew" button (recipients = founder + confirmed
  members via useCollectiveMembers); shown only when >1 recipient

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:31:45 +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
ed27f719fa Sasquatch: teach him all the new features + fix zap comment
- Expand AI system prompt with full feature map: Lab (coaches/filmers/
  photographers/editors/builders + individual vs collective), Crews (map-anchored,
  how they differ from the Lab), Stash, Tours, Shops, Gear Swap, Rider Card
- Document money layer interconnections: wallet rails (Cashu/NWC/WebLN), zap
  fallback order, nutzaps (NIP-61, receiving), CLINK/noffer, Geyser supporters
- Instruct Sasquatch that Crews/Stash/Wallet/Tours/Shops have no live search tool
  and must be answered from knowledge (prevents bogus tool calls)
- Add ~15 new STATIC_FAQ offline answers (crews, stash, builder, nutzap, clink,
  noffer, tours, shops, sell gear, supporters)
- Add Crews page guide context + crew jokes; refresh Lab suggestions
- Fix leftover "Zapped with Engorile!" default comment → SkateSpots branding

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 14:12:30 +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
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
88a36e52ae Add Events as an opt-in map layer
Events can now be discovered directly on the map:
- New "Events" layer (lime #ccff00, calendar pin marker) plotting upcoming,
  geo-located events. Off by default — users opt in via the Layers control.
- Event markers show a popup with title, date/time, location, and a
  "View Event" button linking to the event detail page.
- Only future/ongoing events with a valid geohash are plotted (past events
  are filtered out to keep the map clean).
- Added to the mobile Layers popover, desktop Layers popover, and the mobile
  filter drawer's "Show on Map" grid. Layer count badge now tracks 5 layers.
- SpotMap gains `events` + `showEvents` props and a createEventIcon marker.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:48:24 +07:00
shakespeare.diy
1d1e6d18b1 Redesign mobile map controls: Layers popover + Add speed-dial FAB
Replaces the crowded 12-item vertical right rail with a clean, minimal set:
- Right edge now has just two 44px controls: Near Me and a Layers button.
- Layers opens a labelled popover (Spots/Shops/Gear/Tours) with color dots
  and checkmarks, plus a count badge on the button so state is obvious.
- All "add" actions consolidated into a single red speed-dial FAB
  (bottom-right) that fans out to Add Spot / Add Shop / Offer Tour /
  Quick Snap, with a tap-catcher backdrop to dismiss.
- Sasquatch bubble moved to bottom-left on mobile so it no longer competes
  with the Add FAB for the bottom-right corner.
- Larger touch targets (44px) for better native/ZapStore ergonomics.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:18:35 +07:00
shakespeare.diy
d107c0a860 Fix Sasquatch auto-popup on / and /lab; move map attribution above mobile nav
- Sasquatch panel no longer auto-opens on the map (/) or The Lab (/lab).
  Added an `autoShow` flag to GuideMessage; only opt-in messages (onboarding)
  auto-open. The bubble stays minimized until tapped, and context messages
  still populate the panel when opened manually.
- Map CARTO/OSM attribution now sits above the mobile bottom nav (safe-area
  aware) instead of overlapping the Shops & Spots buttons; made it smaller
  and more subtle. Desktop keeps it in the bottom-left corner.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 10:09:15 +07:00
shakespeare.diy
a74225fe0d feat: platform monetization — listing fees for paid events & Lab profiles v1.0.62
Architecture:
- usePlatformFee hook: central fee config (featuredEvent/labProfile/featuredShop)
  5,000 sats each, 30-day validity, localStorage persistence, owner bypass
- PlatformFeePayment component: generic reusable payment gate
  auto-pay via NWC/WebLN, QR fallback, active/expired state display
  all fees route to skatespots@minibits.cash

Events (CreateEventPage):
- New optional "Entry Fee" field
- Triggers 5,000 sat platform fee when filled
- Stores entry-fee + paid tags on published event
- Free events: zero friction unchanged

The Lab (Become{Coach,Filmer,Editor}Page):
- Triggers 5,000 sat fee when "Rate" field is filled
- Submit disabled with clear CTA until fee paid
- Free/volunteer profiles: zero friction unchanged

Rule: "pay only when you earn"
- No rate set → publish instantly, no fee
- Rate set → 5,000 sats to SkateSpots, valid 30 days
- Platform owners always bypass

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 02:19:45 +07:00
shakespeare.diy
f39c251e90 fix: Sheet z-index — burger menu now covers all map floating buttons
Map FABs and controls sit at z-[1000]–z-[1001]. The Sheet overlay/content
were at z-50 (= 50), so the map buttons punched through on top.

Bumped SheetOverlay and SheetContent from z-50 → z-[2100], which is above
the highest map element (z-[2001] for the spot detail modal) so the slide-in
drawer always fully covers the map UI.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-24 01:25:46 +07:00
shakespeare.diy
ceff6962ec Bottom nav: rename Me→Profile, link to settings root instead of edit profile
- "Me" label renamed to "Profile" — clearer intent
- Link changed from /settings?section=profile to /settings — lands on the
  settings root menu so users see all options, not just the edit form

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 22:03:52 +07:00
shakespeare.diy
f48844b495 Fix Sasquatch / Add Spot FAB overlap on mobile map
- SasquatchGuide now dispatches 'sasquatch:panelchange' event when panel opens/closes
- MapPage listens to the event and hides the Add Spot FAB (with scale-down animation)
  whenever the Sasquatch panel is open — no more overlap
- FAB reappears with smooth opacity + scale transition when panel closes
- Added a visible close (X) button to the Sasquatch panel header alongside the existing
  minimize button — gives users a clear way to fully dismiss the panel
- Sasquatch panel bottom edge stays 10px above the nav bar on all screen sizes

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 22:00:50 +07:00
shakespeare.diy
a1cd936bc4 UX audit round 2: Near Me fix, map height, touch targets, iOS dirs, a11y, mobile layout
CRITICAL:
- Near Me button: completely rewritten — was silently discarding ALL errors. Now shows
  toast for denied, timeout, unavailable with actionable copy. Button shows red LocateOff
  icon when denied so user sees the state at a glance. Raised to 44×44px (Apple minimum).
- Map height on mobile: switched to CSS variable system (--header-mobile, --bottom-nav)
  for pixel-accurate height. Added Layout noPadding prop so the map page gets full bleed
  without the 80px bottom-padding fighting the map container calc.
- Layer toggle pills: raised from 28×28 to 40×40px — was below touch target minimum
- Right-side control column: now hides when spot bottom sheet is open (was always visible)

iOS/Directions:
- SpotDetailPage directions button: now detects Apple platform and opens Apple Maps on
  iOS/macOS, Google Maps everywhere else (was always Google Maps)
- Added aria-label to directions button

Accessibility:
- LoginDialog: was missing DialogTitle, causing console errors on every login open
- CameraCapture: fallback dialog was missing DialogTitle (sr-only)
- CommandDialog: added sr-only DialogTitle

Layout:
- SpotDetailPage: reduced top padding mobile pt-4 (was py-8 = 32px wasted space)
- Directions popover: centered in viewport (top-1/2 left-1/2) with zoom-in animation,
  never clips behind Sheet stacking context

CSS:
- Added --header-mobile and --bottom-nav CSS variables for consistent calculations
- Added .page-fullbleed helper

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 21:56:11 +07:00
shakespeare.diy
dd85644fca UX audit fixes: iOS directions, Me tab profile link, onboarding banner, empty states, stats deltas, newsletter pill
- Fix iOS directions: Apple Maps first on Apple platforms, removed broken geo: URI,
  added Waze option, fixed popover z-index clipping behind Sheet component
- Fix 'Me' bottom nav: now deep-links to /settings?section=profile for logged-in users
  so tapping 'Me' shows your profile, not a generic settings menu
- Add SettingsPage URL param support: ?section=X opens sub-screen directly
- Add onboarding welcome banner: dismissible strip at top of map for logged-out users
  with 'Get Started' CTA pointing to /onboarding, stored in localStorage
- Fix Lab empty state: FAQ link + login prompt for logged-out users
- Fix Events empty state: FAQ link added to all empty state instances
- Fix GearSwap empty state: actionable 'Clear Filters' button + FAQ link
- Community stats: add 'this month' delta badges (+N this month) on all stat cards
- Newsletter pill: hide on map page to eliminate bottom sheet overlap conflict

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 21:36:33 +07:00
shakespeare.diy
08e20b0f39 newsletter: fix hash-router URL (#/newsletter), add to desktop More dropdown
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 20:59:29 +07:00
shakespeare.diy
c9bb5abfff sasquatch: left-anchor panel on mobile, leave right side free for Quick Snap + filter pills
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 20:08:03 +07:00
shakespeare.diy
be4e0bc986 map: remove duplicate sasquatch, reposition original bubble to bottom-right below Quick Snap
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 20:05:46 +07:00
shakespeare.diy
889eb922eb map: move attribution to top-right below Near Me, add Vibed with Shakespeare below Quick Snap
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 20:01:28 +07:00
shakespeare.diy
f8b14f1056 map: switch tile layer to CartoDB Voyager
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 19:59:11 +07:00
shakespeare.diy
682e4d802b map: dark tiles, near me button, bottom sheet, floating filter pills
- Swap OSM tiles for CartoDB Dark Matter (near-black bg, neon markers pop)
- Add floating Near Me button (top-right, crosshair icon, flies to user location)
- Add SpotBottomSheet: tap a marker → drawer slides up from bottom with
  spot thumbnail, title, location, type badges, author, View Details + Directions CTAs
  (map stays fully visible behind the sheet)
- Add flyTo prop to SpotMap for programmatic map navigation
- Add floating layer filter pills (spots/shops/gear/tours) on map surface
  bottom-left, always visible, no toolbar required

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 19:57:06 +07:00
shakespeare.diy
0dc0d272e1 fix: filter panel overlap, Wallet & Stash rename, Sasquatch on all pages
Filter panel (MapPage):
- Sheet now h-[82vh] max-h-[680px] with flex-col layout
- Scrollable content area uses flex-1 min-h-0 (proper flex children)
- "Show Results" button is now flex-shrink-0 at bottom of column
- No more overlap between Show Results and Stash/Tour quick action buttons

Wallet & Stash rename:
- Settings tab renamed from "Wallet" to "Wallet & Stash"
- "Me" dropdown in mobile header shows "Wallet & Stash" for the wallet link

Sasquatch persistent button (all pages):
- Minimized bubble is now ALWAYS visible on every page (not just pages with a guide message)
- Tapping the bubble on any page opens Sasquatch with a generic helpful greeting
- Pages with specific context (/, /map, /lab, /onboarding) still show their custom message
- Pages without context (/wallet, /settings, /stash, /events, etc.) now show a generic "Ask me anything" greeting with common questions
- Removed the separate X close button (minimize was already the same) — bubble stays visible as entry point at all times
- onPointerUp now also sets isVisible=true so panel opens on pages without a message

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:45:11 +07:00
shakespeare.diy
340f6fd89f fix(mobile): redesign wallet page UI for mobile + global mobile polish
Wallet page (/wallet):
- Add BalanceHero banner at top showing Cashu balance + wallet status pills
- Cashu/Lightning tabs with colored active states (orange/yellow)
- Remove nested balance display in CashuWalletCard (now shown in hero)
- Compact balance row in CashuWalletCard
- Stacked full-width inputs on mobile (no side-by-side on small screens)
- h-11 touch targets on all inputs and buttons (44px min)
- inputMode="numeric" on number fields (prevents iOS zoom)
- Receive/Send tabs with green/red active colors for clear context
- History items now 52px min height with colored amount text
- WalletComparisonGuide stays collapsed by default
- Fix: add Nut icon import to CashuWalletCard (was crashing)
- All modals use w-[calc(100%-1rem)] for proper mobile sizing

Settings page (/settings):
- 7-tab bar now scrollable horizontally on mobile (no overflow/wrapping)
- -mx-3 bleed trick so scrollable area goes edge-to-edge
- Wallet tab buttons full-width on mobile
- Container max-w-3xl for better desktop centering

FAQ page (/faq):
- Compact hero on mobile (smaller icon, tighter padding)
- py-6 on mobile instead of py-12

CSS utilities:
- Add xs: breakpoint (375px) variants: flex, flex-row, px-4, -mx-4, gap-2, text-xs/sm, hidden

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:35:49 +07:00
shakespeare.diy
9da1796774 fix: cap map zoom out, prevent grey bands and tile duplication
- minZoom: 2 — stops user zooming past a single world view
- maxBounds: full lat range with infinite lng — locks vertical pan to the world
- maxBoundsViscosity: 1.0 — hard clamp, no elastic bounce into grey space
- TileLayer minZoom: 2 to match

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:21:58 +07:00
shakespeare.diy
e8151bee17 revert: remove 3D globe, restore flat map as default
- Removed SkateGlobe component from view toggle
- ViewMode back to 'map' | 'list', defaulting to 'map'
- Removed Globe import, selectedGlobeMarker state, and all globe render blocks
- Flat Leaflet map loads as default again

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 16:18:24 +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
fc1c8aca26 Add visible debug toast showing pubkey returned by Amber after login
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 13:50:28 +07:00
shakespeare.diy
ef22eb2de7 Fix Amber "not installed" false positive - skip isExternalSignerInstalled() check
Android 11+ package visibility rules cause isExternalSignerInstalled() to
return false even when Amber is installed, unless <queries> is declared in
AndroidManifest.xml. Skip the pre-check and attempt login directly instead,
with better error messages for cancel vs not-found vs other errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-09 11:49:16 +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
7d071787d0 Fix Amber login for ZapStore/Capacitor app
- LoginDialog now uses openAmberForLogin() which sends the proper
  intent:// URL with a callbackUrl instead of the old nostrsigner:
  scheme that only copies to clipboard (broken on most setups)
- Add polling loop (500ms, 3min timeout) for amber_pending_pubkey
  written by AmberCallbackPage — works even when Amber opens the
  callback in a new browser tab (GrapheneOS/Android behaviour)
- visibilitychange listener now waits for app to regain focus before
  attempting clipboard read, fixing race condition
- DeepLinkHandler pre-captures ?event= param into sessionStorage
  when Amber fires appUrlOpen deep link in Capacitor, so
  AmberCallbackPage can always find the result
- Add 'paste' to amberStep union type (was missing, caused TS error)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 22:29:26 +07:00
shakespeare.diy
132b6cae7c fix: Amber login using confirmed correct URL from Amber source code
Read Amber's actual IntentUtils.kt to confirm exact URL parsing:
- Strips "nostrsigner:" prefix
- URLDecodes the remainder
- Parses ?key=value&key=value query params
- "type" param controls operation
- No callbackUrl → copies result to clipboard

Correct URL: nostrsigner:?type=get_public_key&compressionType=none&returnType=signature

Flow:
1. Tap "Open Amber" → window.location.href = nostrsigner: URL
2. Amber opens, user approves, copies hex pubkey to clipboard
3. App regains focus → tries navigator.clipboard.readText() auto
4. If clipboard denied → shows paste input (accepts hex or npub1)
5. Login complete

Removed broken Capacitor plugin dependency (couldn't install git dep).

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 12:22:58 +07:00
shakespeare.diy
9e6d6f1c39 fix: switch to Coracle's nostr-signer-capacitor-plugin fork + correct API
The chebizarro npm version has a different API to the Coracle fork that
Welshman/Coracle actually use in production. Now using:

  github:coracle-social/nostr-signer-capacitor-plugin#main

Correct API (from Welshman nip55.js source):
  NostrSignerPlugin.setPackageName(packageName: string)  // string not object
  NostrSignerPlugin.getPublicKey(packageName?: string)   // packageName as arg
  NostrSignerPlugin.isExternalSignerInstalled(packageName?)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 12:11:18 +07:00
shakespeare.diy
f010a61608 fix: use nostr-signer-capacitor-plugin for Amber NIP-55 login
All previous URL-scheme approaches (nostrsigner:, intent:) were fundamentally
wrong for a Capacitor app. JavaScript in a WebView cannot reliably dispatch
Android intents via URL navigation.

The correct solution — used by Coracle and other Capacitor-based Nostr apps —
is the nostr-signer-capacitor-plugin which communicates with Amber via native
Android intents through the Capacitor bridge:

  NostrSignerPlugin.setPackageName({ packageName: 'com.greenart7c3.nostrsigner' })
  NostrSignerPlugin.isExternalSignerInstalled()
  NostrSignerPlugin.getPublicKey() → { npub }

This is synchronous from the JS side, handles the entire Android intent
roundtrip natively, and returns the pubkey directly. No URL encoding,
no callback URLs, no redirects, no malformed request errors.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:59:04 +07:00
shakespeare.diy
92f317f5e5 fix: Amber login for GrapheneOS — clipboard + paste flow
GrapheneOS Vanadium browser blocks intent: URLs from web pages as a
security hardening measure. This is why every URL format we tried
caused "malformed request" — Vanadium was blocking the intent before
Amber ever saw it.

New flow that works on ALL Android browsers including GrapheneOS:
1. Tap "Open Amber" → navigates to nostrsigner: URL with no callbackUrl
   (Amber copies pubkey to clipboard when no callbackUrl is given)
2. User approves in Amber, comes back to app
3. App tries navigator.clipboard.readText() automatically on visibilitychange
4. If clipboard permission denied → shows paste input for hex pubkey
5. User pastes pubkey → logged in

No intent: scheme, no callback URL, no redirect loop. Simple and bulletproof.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:43:59 +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
4f7852fec7 Fix Amber login: replace nostrconnect/relay flow with proper NIP-55 intent flow
The entire nostrconnect relay approach was wrong for a native Android/Capacitor app.
Amber on Android uses NIP-55 intents (nostrsigner: URI scheme), not NIP-46 relays.

New flow (same as Amethyst):
1. openAmberForLogin() fires nostrsigner:?type=get_public_key&callbackUrl=...
2. Amber opens, user taps approve
3. Amber calls back to org.skatespots.app://amber-callback?event=<pubkey>
4. AmberCallbackPage writes pubkey to localStorage('amber_pending_pubkey')
5. LoginDialog polls localStorage every 500ms and calls login.amber(pubkey)
6. Done — no relays, no waiting, no spinning

Also added login.amber(pubkey) method to useLoginActions.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 13:24:46 +07:00
shakespeare.diy
e3dd66d078 Fix Amber login: store as 'amber' type (NIP-55) not 'bunker' (NIP-46)
The nostrconnect flow was incorrectly creating a bunker login and trying
to do NIP-46 key exchange. Amber signs as the user's own key directly,
so the pubkey from the connect response IS the user pubkey. Now stores
a proper amber login which uses AmberSigner via useCurrentUser.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:38:56 +07:00
shakespeare.diy
03ea569a2a Amber login: poll relay every 3s after returning from Amber, add elapsed timer + Check Now button
- Replace one-shot visibility handler with pollRelay() called immediately on foreground + every 3s
- Add elapsed time counter (M:SS) so user can see the app is still trying
- Add 'Check Now' button for manual relay poll without generating a new URI
- Poll interval auto-clears on success, timeout, or cancel

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:34:20 +07:00
shakespeare.diy
dfb60674c5 Fix Amber timeout: increase to 3 minutes so user has time to approve in Amber and return
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 12:31:48 +07:00
shakespeare.diy
bd149fdb2c Fix Amber nostrconnect flow: 20s timeout, error state, stale closure fix, better relays, debug logs
- Replace wss://relay.primal.net with wss://nos.lol (more reliable for NIP-46)
- Shorten timeout from 10 minutes to 20 seconds
- Fix stale closure bug: track amberStep in amberStepRef so timeout can correctly reset UI
- Add 'error' step with red UI and 'Try Again' button instead of endless spinner
- Extend since filter from 5s to 10s in the past
- Add console logs for URI, relays, incoming events, decryption result, secret matching

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 11:48:03 +07:00
shakespeare.diy
24d71679af Layout: remove Sasquatch AI (/pyramid) from mobile nav and clean up Bot icon import
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-28 13:05:18 +07:00
shakespeare.diy
4ffa39ee95 Various UI cleanups: remove Nostr footer link, hide Map&Earn, update NostrHub links
- Index.tsx: remove Nostr.com button from homepage footer (next to FAQ/About)
- SupportersPage: change 'View on GitHub' → 'View on NostrHub' with correct naddr URL
- Layout: remove 'Map & Earn' (/bounties) from desktop More dropdown menu
- Layout: remove 'Map & Earn' (/bounties) from mobile slide-out nav
- Layout: clean up unused Gift icon import and stale isActive check

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 21:27:10 +07:00
shakespeare.diy
baf4c5e10d feat: redesign /supporters with 4 sections + footer label update
- Supporters: existing Geyser leaderboard (unchanged)
- Collaborators: new section, empty placeholder with GitHub CTA
- Partners: Geyser, Angor, Zapstore, ContextVM, MKStack
- Friends: placeholder grid (5 slots) + logo spec instructions
- Footer: "Supporters" → "Collaborators & Friends"
- Static COLLABORATORS/PARTNERS/FRIENDS arrays at top of file for easy editing

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-27 17:51:11 +07:00