Commit graph

127 commits

Author SHA1 Message Date
shakespeare.diy
a08871318d chore: bump version to 1.0.35
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-23 17:07:11 +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
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
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
0b6af1bcdf chore: bump to 1.0.28, update release notes for Amber fix
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-08 11:09:58 +07:00
shakespeare.diy
9ffc961015 chore: bump to 1.0.27, update release notes
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 14:37:36 +07:00
shakespeare.diy
b69756e491 chore: fix zapstore release_notes (file path, not inline), bump to 1.0.26
- release_notes must be a file path for zsp, not inline YAML text
- add release-notes.txt with v1.0.26 changelog
- sync package.json version to 1.0.26 to match APK versionName

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 14:08:39 +07:00
shakespeare.diy
bc5eaf44d6 chore: bump version to 1.0.1, update zapstore.yaml
- package.json: 1.0.0 → 1.0.1
- zapstore.yaml: add NIP-55 to supported_nips (Amber signer is now working)
- zapstore.yaml: add release_notes section with v1.0.1 changelog

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-07-03 14:02:33 +07:00
shakespeare.diy
3f654f4a35 fix: single bundle APK build, remove debug banner, clean build scripts
- Remove debug red banner from index.html and main.tsx
- Add android:build:apk script that uses CAPACITOR_BUILD=1 for single
  bundle output + strips Shakespeare build artifacts from dist/index.html
- Update all android:* scripts to use android:build:apk

Root cause of white screen: 100+ JS chunk files overwhelm Android
WebView local file serving. Single bundle with inlineDynamicImports
fixes this.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 22:51:49 +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
fad3a3323d Replace Impact with Bebas Neue + Barlow Condensed for authentic skate typography
- Install @fontsource/bebas-neue and @fontsource/barlow-condensed
- Add .font-display (Bebas Neue) and .font-condensed (Barlow Condensed) CSS utilities
- Hero headline: split into two lines SKATE / SPOTS with clamp() fluid sizing,
  Bebas Neue replaces Impact throughout
- Subtitle: Barlow Condensed 700 with wide letter-spacing
- Description: Barlow Condensed italic
- All section headings (Featured Spots, Events, The Lab, CTA, New Here?): Bebas Neue
- Section subheadings: Barlow Condensed with tracked uppercase
- Desktop + mobile header logo: Bebas Neue
- Footer logo: Bebas Neue
- Tagline badge: Barlow Condensed 600

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-18 00:37:32 +07:00
shakespeare.diy
1441855eb1 feat: add Capacitor + Zapstore config for Android APK distribution
- capacitor.config.ts: Capacitor config with dark theme, WebSocket support,
  splash screen, status bar, keyboard plugins
- zapstore.yaml: Zapstore publish config with npub identity verification
- ZAPSTORE.md: full step-by-step guide (clone → build APK → publish to Zapstore)
- package.json: android:init, android:sync, android:open, android:build scripts
- App ID: org.skatespots.app

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-17 16:11:28 +07:00
shakespeare.diy
046a109eca Fix card printing: html2canvas renders card to PNG before printing
The root cause of white/blank print: browsers strip CSS background gradients
and colors by default even with print-color-adjust: exact, especially inside
nested components wrapped by Layout.

Solution: use html2canvas to render the live card DOM to a canvas at 4x scale,
convert to PNG, then open a dedicated print window containing ONLY that image.
Images always print in full color — no CSS override needed.

Changes:
- Add html2canvas dependency
- renderCardToImage(): captures card DOM element at 4x scale with useCORS for
  cross-origin avatars, temporarily strips holo animation for clean capture
- handlePrint(): calls renderCardToImage, opens popup window with CSS @page
  set to 85.6×54mm, injects the PNG, auto-triggers print dialog
- handleDownload(): same render pipeline, saves as PNG file (for sharing
  digitally, WhatsApp, iMessage, or taking to a print shop)
- Added Download / Save PNG button alongside Print Card button
- Added Loader2 spinner on buttons while rendering
- Removed print-only-card div and broken @media print CSS (no longer needed)
- Clean up CSS print block to minimal fallback only
- Fix "on-chain proof" → "Signed proof" in badge dialog

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 15:22:13 +07:00
shakespeare.diy
f9327c952d fix: update license page tagline
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-16 13:27:00 +07:00
shakespeare.diy
451ec5d3ed Sync package.json and package-lock.json to exactly match skate-spots
- react-day-picker: ^9.0.0 → ^8.10.1 (match skate-spots)
- zod: ^3.23.8 → ^3.25.71 (match skate-spots)
- Copy package-lock.json verbatim from skate-spots for identical dependency resolution

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 23:25:43 +07:00
shakespeare.diy
85c2bdc5cc fix: replace radix-ui umbrella imports with direct @radix-ui/react-* packages
The shadcn/ui components were using the new "radix-ui" umbrella package
import style (e.g. `import { Tooltip as TooltipPrimitive } from "radix-ui"`).
However, radix-ui@1.5.0 re-exports ALL radix primitives including ones not
installed (@radix-ui/react-accessible-icon, @radix-ui/react-form,
@radix-ui/react-one-time-password-field, @radix-ui/react-password-toggle-field,
@radix-ui/react-toolbar), causing esbuild to fail with 5 unresolved import
errors outside Shakespeare.

Fix: rewrite all 32 imports across the ui/ components to import directly
from their individual @radix-ui/react-* packages (which are all already
installed). Remove the radix-ui umbrella package entirely from package.json
and package-lock.json.

Affected files (32 import changes):
accordion, alert-dialog, aspect-ratio, avatar, badge, breadcrumb, button,
checkbox, collapsible, context-menu, dialog, dropdown-menu, form, hover-card,
label, menubar, navigation-menu, popover, progress, radio-group, scroll-area,
select, separator, sheet, sidebar, slider, switch, tabs, toggle-group, toggle,
tooltip

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 22:01:07 +07:00
shakespeare.diy
9b58f412cd fix: add missing radix-ui umbrella package dependency
All shadcn/ui components in src/components/ui/ were already written against
the new shadcn convention of importing from the "radix-ui" umbrella package
(e.g. `import { Tooltip as TooltipPrimitive } from "radix-ui"`), but
"radix-ui" was never declared in package.json — only the individual
@radix-ui/* packages were listed.

This caused `npm i` + `vite build` to fail outside Shakespeare with:
  [plugin:vite:import-analysis] Failed to resolve import "radix-ui"

Fix: add `"radix-ui": "^1.0.1"` to dependencies (resolved to 1.5.0 in
the lock file). No code changes needed.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:52:11 +07:00
shakespeare.diy
93e607ea1f fix: upgrade react-day-picker to v9 to resolve date-fns v4 peer dep conflict
react-day-picker@8 requires date-fns@^2||^3, which conflicts with the
project's date-fns@^4.1.0. react-day-picker@9 dropped the date-fns peer
dependency entirely (bundles its own date helpers), so `npm i` now resolves
cleanly without --legacy-peer-deps.

- package.json: ^8.10.1 → ^9.0.0
- package-lock.json: updated version/resolved/integrity/peerDependencies
  for node_modules/react-day-picker to 9.14.0

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:42:42 +07:00
shakespeare.diy
947bb2784b fix: resolve Zod compatibility issue
- Change z.url() to z.string().url() in AppProvider.tsx for Zod compatibility
- Update Zod version from ^3.25.71 to ^3.23.8 (valid published version)

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:26:53 +07:00
shakespeare.diy
e5f26f4b1f Revert "fix: resolve Zod compatibility issues for clean build"
This reverts commit be47816c64.


Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 21:24:25 +07:00
shakespeare.diy
be47816c64 fix: resolve Zod compatibility issues for clean build
- Update Zod version from ^3.25.71 to ^3.23.8 (valid published version)
- Replace z.url() with z.string().url() in AppProvider.tsx for compatibility
- Remove unused npubcash-sdk dependency
- Remove duplicate nested src/src directory that was incorrectly created

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 20:53:41 +07:00
shakespeare.diy
27e14994b5 Import files from skate-spots project
Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
2026-06-15 20:04:15 +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