Commit graph

84 commits

Author SHA1 Message Date
SkateSpots
30a0aaa42b feat(create-spot): make address + access type required; move them out of the optional collapsible 2026-10-07 11:30:55 +07:00
SkateSpots
efd7e9608d feat(create-spot): pin map, progressive disclosure, mandatory photo+description, inline errors, draft autosave 2026-10-07 10:29:01 +07:00
SkateSpots
24322b3b1c feat(map): tap drops a pin + confirm card instead of jumping into create-spot 2026-10-07 10:26:36 +07:00
SkateSpots
39572998a0 feat(clink): real bech32+TLV decode/validation (lib/clink.ts), spec-correct raw QR/copy, show decoded pricing; validate scanner 2026-10-06 11:16:20 +07:00
SkateSpots
9ca5f2a604 fix(audit UI/UX): 44px slabs, missing grid base cols, aria-labels, dark weather panel, contrast, sheet width, stretched-link SpotCard 2026-10-05 21:47:29 +07:00
SkateSpots
5728c226d5 fix(audit P1): comment cache key, ticket verify fails closed, featured amount check, no empty-object persist, sticky sub-bars 2026-10-05 21:07:07 +07:00
SkateSpots
a9dcb5719c feat(supporters): per-row dates + 'updated', dedicated /donate page, extract shared support cards 2026-10-05 19:54:49 +07:00
SkateSpots
4ffad80728 feat(supporters): ways-to-support trio (purpose per rail, scrolls to each) 2026-10-05 19:44:31 +07:00
SkateSpots
1691d49930 feat(supporters): add silent-payments donation card (BIP-353 name + QR) 2026-10-05 19:40:38 +07:00
SkateSpots
cc1883cf82 feat(supporters): top 21, extravagant auto tiers + tier ladder with supporter avatars 2026-10-05 19:02:25 +07:00
SkateSpots
66d83123cd feat(supporters): Dec-2025 cutoff + collapse list to top 10 (show all) 2026-10-05 18:57:53 +07:00
SkateSpots
24e1369f6c feat(supporters): unified ranked wall, verifiable zaps, tier legend, impact link, pay-for detail, anonymous flag, map chip, OG meta 2026-10-05 18:55:16 +07:00
SkateSpots
87aca51248 feat(supporters): pay-for list + impact stats; scope zaps to the project 2026-10-05 18:46:49 +07:00
SkateSpots
b88379a390 feat(supporters): add tiers/badges and a unified top-zappers block 2026-10-05 18:00:19 +07:00
SkateSpots
1bdaa57aa3 feat(supporters): on-chain verify chips, real avatars/names, fiat total, footer count 2026-10-05 17:56:10 +07:00
SkateSpots
918c289a71 feat(supporters): present Angor as a donation option for small amounts 2026-10-05 17:45:08 +07:00
SkateSpots
3f94d9be30 feat(supporters): show Angor supporters with amounts on /supporters 2026-10-05 17:37:00 +07:00
SkateSpots
b247be13b0 DIY: parametric 2D deck designer with 1:1 printable template
New /diy/deck-designer tool. src/lib/deckDesign.ts holds the pure geometry
(outline, waist, rounded ends, kicks, concave) and the exporters; the component
renders a live top view, side profile and concave section with presets, saved
locally.

Exports: true-size SVG, a tiled 1:1 PDF (A4/Letter, split across sheets with
corner registration crosses), and an R12 DXF (outline polyline + 3/16" truck
holes + centreline, $INSUNITS=4). Truck patterns: New school 53.975x41.275 mm
and Old school 63.5x41.275 mm.

Deliberately 2D: this is the outline a builder traces and cuts, not a SK8CAD
clone (SK8CAD is proprietary; only its JSCAD engine is MIT, and we copied
nothing).
2026-10-05 11:16:34 +07:00
SkateSpots
b90fef1d2e DIY: drop the category chips; add the deck cost calculator
- Removed the sticky category-chip row from /diy (sections still group the cards).
- New in-app tool at /diy/deck-cost (components/DeckCostCalculator.tsx): a
  self-contained pricing model — labour rate + overhead, equipment amortisation,
  operation expenses, materials (with order/unload time), process time,
  packaging, profit margin and scrap → cost per board and a fair selling price
  (no-packaging / with-packaging). State persists to localStorage; reset to the
  worked example. The "Deck cost & pricing" card now links to it instead of
  showing "Coming soon".
- Smoke test walks /diy/deck-cost.
2026-10-05 10:08:01 +07:00
SkateSpots
30d46ddfed DIY: use internal links, mark the rest "coming soon"
Reworked the DIY hub per feedback: point at our own app sections where we have
relevant content, and render the topics we don't cover yet as unclickable cards
with a "Coming soon" badge — no external links.

- Live internal cards: Map your DIY spot (/create-spot), Buy parts & gear
  (/marketplace), plus the "Do it with people" actions (Builder, crews, spot,
  listing, coaching).
- Coming-soon cards: deck building, SK8CAD, cost/pricing, ledge/box, quarterpipe,
  flatbar, concrete basics, coping, regrip, bearings/trucks, 3D-printed molds,
  mold tools.
- Dropped external URLs/sanitizeUrl from the page; updated the footer copy.
2026-10-05 10:03:11 +07:00
SkateSpots
84fde55ddd Add the DIY section (resources for boards, ramps & concrete)
New /diy hub: curated resources for making your own gear, with cross-links into
the rest of the app.

- src/lib/diyGuides.ts: seed data — categories (Boards, Ramps & wooden
  obstacles, Concrete & DIY spots, Repairs & tuning, Tools & plans), resource
  cards (Open Source Skateboards / SK8CAD / Instructables / YouTube), and an
  actions block (find a Builder, find a DIY crew, map a DIY spot, sell builds,
  find a coach).
- src/pages/DiyPage.tsx renders it (category chips + card grid + actions);
  external links go through sanitizeUrl and credit the source.
- Route /diy (lazyWithRetry) + a nav-catalog entry (id diy) so it appears in the
  desktop "More" menu and the mobile burger; also linked from the site footer
  and the map links strip. Smoke test walks /diy.
2026-10-05 09:55:26 +07:00
SkateSpots
b37d627138 Add a slim, dismissible site-links strip to the map
The desktop footer is hidden on full-bleed map routes, leaving the
Newsletter/License/Privacy/Collaborators links (and the "not sponsored by
OpenSats" note) unreachable from the homepage. MapFooterStrip overlays them
inside the map container without stealing map height:

- Desktop: a slim full-width bottom bar.
- Mobile: a tiny collapsed "Links" chip that expands to a compact, scrollable
  row.
- Dismissible (useMapFooterHidden, device-local, same-tab synced) and
  restorable from Settings -> Appearance.
- The map's OSM attribution moved up (md:bottom-7) so the bar doesn't cover it.
2026-10-05 09:15:51 +07:00
SkateSpots
084c002c45 Fix mobile grid overflow + add a Top Shops leaderboard
The /lab profile card and /stats Top Mappers were overflowing off the right
edge: Tailwind `grid md:grid-cols-2` (no base column) renders one implicit
`auto` track on mobile that can exceed the container when a name is long, so the
whole card got pushed off-screen. Added the base `grid-cols-1` repo-wide (plus
`min-w-0` on truncating flex/grid children).

/stats also gained a "Top Shops — most shops added" leaderboard (kind 30017)
beside Top Mappers (spots): `topShops` in useGrowthStats, a generalized
MapperRow (count + unit), and EN/ES copy. PERSIST_BUSTER → v6 (new persisted
field).
2026-10-04 18:01:49 +07:00
SkateSpots
fb42f4f0d1 Add Cashu escrow settings + a "require escrow" listing flag
- useEscrowPrefs (device-local): default lock window, preferred escrow mint,
  require-escrow default for new listings.
- EscrowSettingsCard on the Wallet (native-only, beside EscrowKeyCard).
- EscrowRequestDialog now defaults its lock window + funding mint from the prefs.
- Listings gain an optional ['escrow','required'] tag: a toggle on
  CreateListingPage (defaulted from the pref) and a 🔒 badge on the card.
2026-10-04 17:54:42 +07:00
SkateSpots
0e405eaa00 Polish Sasquatch bubble; full mint list; link shops to events
Sasquatch (the look regressed): I over-corrected last pass by shrinking the
bubble on the map and bolting an always-visible ✕ over the avatar on mobile.
Restored the full-size bubble + glow on every route (the map only loses the
pulse), moved Hide to a desktop-hover ✕ and an EyeOff "Hide assistant" button
in the panel header (Settings → Appearance still re-enables it).

Mints: the add-mint dialog only offered a hand-picked "Recommended" shortlist.
It now also lists the full searchable MintRadar directory (useMintDirectory)
below the recommendations.

Events: CreateEventPage could only link a skate spot. Added an optional
"Link to a Shop" picker (useSkateShops, search, location autofill) that writes
['a','30017:…'] + ['venue-name', …].
2026-10-04 17:45:42 +07:00
SkateSpots
c8778aa3c9 Wallet: fix Manage chip overflow + plainer escrow copy
- The wallet status bar (Cashu/WebLN/NWC pills + Manage) was a rigid
  justify-between row that pushed the Manage chip off the right edge on phones.
  It now wraps, with Manage right-aligned (ml-auto), so it stays on screen.
- The escrow card copy was jargon ("advertise a public key ... if the deal falls
  through"). Reworded seller-facing: "turn on escrow ... a buyer can lock their
  sats to you ... you release them when you deliver — if you don't, the buyer can
  reclaim them after the agreed time." EN/ES.
2026-10-04 17:36:41 +07:00
SkateSpots
4cad201716 Add a Settings → Pro & Support hub
One discoverable home for every paid extra, so the pricing model is legible
instead of scattered across the Sasquatch panel and each offer form:

- components/ProSupportScreen.tsx (Settings ?section=pro + a menu row): honest
  "prepaid, not a subscription" framing; Sasquatch Pro status (days left) +
  PRO_TIERS with per-day sats and a Go Pro / Extend button; Featured placement
  (PLATFORM_FEES x DURATION_TIERS with per-day + best-value); your currently
  featured Lab listings (featuredUntil); and the free paths (PoW listing, free
  AI FAQ).
- Distinct naming in copy: Sasquatch Pro (AI) vs Featured placement (visibility).
- Smoke test now walks /settings?section=pro.
2026-10-04 14:28:13 +07:00
SkateSpots
8cdc41cc15 Quieter Sasquatch bubble: no perpetual pulse, hideable, map-aware
Addresses "the floating Sasquatch flashing is annoying":

- Pulse is now state-driven: the animate-ping halo shows only when there's an
  unread tip (message), never perpetually; the unread dot matches.
- Honors prefers-reduced-motion (motion-reduce:hidden).
- Map-aware: Layout passes isFullBleed, so on the map the bubble runs compact
  (smaller, no glow, no pulse) instead of competing with map controls.
- Hideable: a ✕ on the FAB hides it (useSasquatchHidden, localStorage
  sasquatch-hidden, same-tab synced via an event), with a "Show Sasquatch
  assistant" toggle in Settings → Appearance to bring it back.
2026-10-04 14:20:46 +07:00
SkateSpots
bbe4ba2e14 Lab forms: multi-step wizard (shared form + coach) and bespoke scroll-to
- FormWizard/WizardStep: a step shell for the long offer forms. Steps stay
  mounted (hidden, not unmounted) so nested components with their own state
  (LabExtrasFields) keep their values; a controlled step lets the form jump to
  the offending step on validation.
- Applied to CraftProfileForm (the 8 data-driven crafts, 5 steps: Identity /
  What you offer / Rates / Contact / Media) and the coach page (5 steps).
  Submit + Preview live in the wizard footer; submit errors jump to the right
  step before scrolling.
- The 4 bespoke pages now scroll to their inline contact error on a failed
  submit.
2026-10-04 12:29:42 +07:00
SkateSpots
3d52f07144 Lab forms: profile-completeness progress + scroll-to/inline validation
Progress (interpreted as a completeness indicator, not a wizard — the form
stays one scroll):
- LabProfileProgress shows photo/bio/skills/rate/location/contact as done/todo
  with a % bar; wired into CraftProfileForm, coach, and the 4 bespoke pages.

Validation (was toast-only):
- CraftProfileForm splits email/website/portfolio into per-field inline errors
  with a red field border, gives the first skill section + contact fields ids,
  and scrolls to the first problem on submit; required sections show an inline
  message after a submit attempt.
- Coach page likewise splits email/website, shows inline errors + red borders,
  and scrolls to the contact field or the skills grid.
2026-10-04 11:39:31 +07:00
SkateSpots
3e84310384 Autosave Lab profile drafts so long forms survive nav/reload
- useLabDraft persists the form snapshot to localStorage (content-debounced)
  and only offers a saved draft as resumable when it differs from the current
  state, so opening an existing profile never shows a spurious resume banner and
  a draft can't silently clobber a freshly loaded profile.
- LabDraftBanner offers Resume / Discard; wired into CraftProfileForm (8 crafts)
  and the coach + filmer/editor/photographer/builder pages.
- The draft clears on a successful publish.
2026-10-04 11:35:41 +07:00
SkateSpots
81b5fd0fab Add a live profile preview to every Lab offer form
Filling the long form was blind until publish. LabProfilePreview renders a
faithful profile card from the current draft — title, bio, image, location,
skills, structured rate, languages/availability/payments and gallery — with a
not-published-yet note.

- Wired into CraftProfileForm (8 crafts) and the coach + filmer/editor/
  photographer/builder pages via a Preview button next to Submit.
- parseLabExtras now accepts any { tags } so a draft tag list can be previewed
  without a published event.
- i18n for the preview copy (EN/ES).

Still open: draft/autosave and a multi-step layout.
2026-10-04 11:25:58 +07:00
SkateSpots
dd437693ab Lab offer UX: post-publish moment + "Your Lab profiles" quick access
- After publishing, land with ?published=1 and show a one-time "You're live —
  share it" banner on the profile (with a Share button), dismissible.
- /lab gains a "Your Lab profiles" strip listing the viewer's own profiles,
  each deep-linking to view/edit — so multi-craft providers aren't hunting
  through the list.
2026-10-04 11:21:40 +07:00
SkateSpots
9d1a6999ea Add owner Edit + Share to Lab provider profiles
The offer → publish loop had two dead ends: a provider's own page only offered
Delete (no Edit), and there was no way to share a listing (the whole point of
announcing a service).

- Owner "Manage" card gains an "Edit profile" button deep-linking to
  /become/<craft> (prefills the existing profile). Admins still get only
  take-down, not a misleading self-edit link.
- A Share button on every provider page opens ShareProfileDialog (QR + copy +
  native share) with the service URL /coach/<pubkey>/<identifier>.
- ShareProfileDialog accepts an optional `path` to override the default
  /<npub> share URL.
- main.tsx clean-link shim now also rewrites /coach/<pubkey>/<d> to the hash
  route, so shared /coach/... links resolve on the live site.

Follow-ups (not done): live preview before publishing, draft/autosave for the
long forms, a multi-step/progress layout, and a "your Lab profiles" manage
strip on /lab.
2026-10-04 11:16:54 +07:00
SkateSpots
681eab9592 Add craft-specific detail fields with matching display sections
Per-craft freeform fields, each wired end-to-end (form + detail display) so
none are stored invisibly:

- Artist: deliverables, turnaround
- Graffiti: surfaces, max_size
- Musician: set_length, tech_rider
- Judge: travel, certifications
- Organizer: insurance, permits
- Tattoo: deposit, minimum
- Trainer: modalities
- Barber: duration
- Filmer/Editor/Photographer: turnaround, deliverables (filmer adds drone)
- Builder: lead_time, warranty

Data-driven crafts declare them via CraftProfileForm's extraFields (now with
labelKey); the bespoke pages add matching state/inputs/prefill/save.
src/lib/craftFields.ts (CRAFT_DETAIL_FIELDS) maps each tag to a display label,
rendered on the shared detail page; toggle tags render as a localized Yes.
Labels are translated (EN/ES); placeholders stay as examples.
2026-10-04 10:53:57 +07:00
SkateSpots
88a3e4d1b4 Generalize the coach profile enrichment to every Lab craft
The coach portfolio/experience/payments treatment is now a shared layer instead
of 12 copies:

- src/lib/labExtras.ts: parseLabExtras + shared option sets (languages,
  availability, structured rate, payment methods, booking fee, gallery/reel).
- components/LabExtrasFields.tsx: the self-contained form block — prefills from
  the existing event, owns its state, and reports the managed tags via onChange.
- components/LabExtrasSection.tsx: the detail block (all crafts).

Wired into CraftProfileForm (the 8 data-driven crafts) and the four bespoke
pages (filmer/editor/photographer/builder); CoachDetailPage renders the shared
section for every craft. Per-craft buying units (rateUnits) are set per craft in
becomeRoles.tsx (e.g. barber 'per cut', musician 'per gig', photographer 'per
shoot'). /lab facet filters now read parseLabExtras, so availability/languages
filter across crafts.

Coach-specific formats/levels/ages/credentials remain coach-only. Money stays
custody-free: declared methods + a booking fee paid directly to the provider.
2026-10-04 10:49:48 +07:00
SkateSpots
357d042a13 Expand coach credentials with skate roles + a free-text note
- Rename 'Federation coach' -> 'National federation coach'.
- Add 'Coaching qualification' and 'Competition judge'.
- Add a free-form cert_note so a coach can name an exact cert/issuer.

The core safety chips (first aid, CPR, safeguarding, background check,
insured) stay - they're the load-bearing trust signals. cert_note is
displayed under the chips and included in the same self-attested disclaimer.
2026-10-04 10:35:23 +07:00
SkateSpots
2db13612d1 Drop PayPal from coach payment methods
Keep Cash on site / Lightning / Cashu / Bank transfer / Other. Existing
profiles that already carry a 'paypal' payment_method still render it (raw
fallback), so no data is lost. NIP.md + AGENTS.md updated.
2026-10-04 10:32:27 +07:00
SkateSpots
080a6edf8b Add declared payment methods, PWYC and a direct booking fee for coaches
Coaching payments, kept honest and custody-free:

- payment_method (cash/lightning/cashu/bank/paypal/other) + payment_note:
  a declaration of what the coach accepts, not an integration. 'Cash on site'
  lets a walletless coach list without being nagged.
- pay_what_you_can flag (sliding scale).
- booking_fee + booking_fee_currency: a small fee to confirm a booking, paid
  DIRECTLY to the coach's kind-0 lud16 via the existing PayWithSats rail
  (mounted only while the sheet is open). Only offered for sats amounts when
  the coach has a Lightning address; copy states plainly that SkateSpots does
  not hold or refund it.

Deliberately NOT added: fiat/card rails (no KYC/custody) and a refundable
deposit (needs the native-only, unverified Cashu escrow). NIP.md documents the
model; AGENTS.md records the decisions.
2026-10-04 10:27:53 +07:00
SkateSpots
fa0b9cfe1c Fix raw i18n keys on coach chips; add structured rate + form polish
The coach option chips rendered raw 'labUi.coachForm.…' keys because the 29
per-option translation keys were referenced dynamically (t(option.key)) and had
never been added — the literal-only i18n guard missed them. Add every option
key (EN/ES) and extend scripts/check-i18n.mjs to also scan data tables for
key/titleKey/labelKey/badgeKey entries so this can't recur.

Also fix the chip layout: the global mobile 'button { min-height:44px }' rule
turned every chip into a 44px slab; add min-h-0 to the coach option chips,
language chips, Lab facet chips and Lab active-filter chips.

Remaining nice-to-haves:
- Structured rate: rate_amount + rate_currency (sats/USD/EUR/GBP) + rate_unit
  (hour/session/half-day/day/camp), rendered on the detail page; legacy
  freeform 'rate' still shows and acts as an optional note.
- Section the long coach form with a FormSection divider.
- Restyle the coach identity block from a red error-looking box to a neutral
  avatar card.
2026-10-04 10:05:11 +07:00
SkateSpots
3e534c5ffc Add coach portfolio fields and gallery to Lab coach profile
Enrich the coach (kind 35001) beyond the shared base schema with the
experience/portfolio fields that actually win a booking:

- Session formats, levels, age groups (repeated tags, client-filtered)
- Self-attested credentials/safety (first-aid, safeguarding, …) with an
  honest 'not verified' label
- Languages spoken (free-form) and availability
- Optional booking link
- Portfolio photo gallery (up to 12) reusing PhotoDropzone + ImageLightbox
- Highlight-reel video link (YouTube/Instagram)

Option sets live in src/lib/coachOptions.ts with EN/ES keys. parseSkateCoach
exposes the new fields; NIP.md documents the tags.

Also fix the pre-existing ErrorBoundary test (ErrorFallback uses
useTranslation but the test rendered no LanguageProvider).
2026-10-04 09:44:15 +07:00
SkateSpots
b285db907d feat(map): show community confidence on spot cards
- New `useBatchSpotCommunityScores(spots)` — ONE query for many spots (same
  signals/weights as the single-spot hook; counts distinct accounts, drops the
  mapper, caps per account; skip WoT/activity weighting at this scale).
- SpotCard takes an optional `community` prop and shows a small "Community"
  (green) or "Seen" chip in the footer.
- MapPage scores the visible spot cards (shops excluded — different kind) and
  passes each card its score.
2026-10-03 19:28:28 +07:00
SkateSpots
77a3d592ea feat(spots): crews + Lab links count as strong signals; add a verify nudge
- useSpotCommunityScore now also reads crews (35006, `a` ref, weight 3) and Lab
  profile links (35001-35014, `a` ref, weight 2) as effortful signals — a whole
  crew listing the spot, or a Lab pro linking it, is strong evidence. A crew
  ref alone reaches "community". (Tours/events use geohash, not a spot coord,
  so they're not counted.)
- Spot page: on an unconfirmed spot, a logged-in non-owner now sees a "Be the
  first to confirm this spot" nudge with Add-photo + "I've skated here".
- Breakdown line/tooltip include crew + Lab counts.
2026-10-03 18:48:19 +07:00
SkateSpots
0e4e8c548a feat(spots): aggregate community confidence from all engagement signals
New `useSpotCommunityScore(spot)` — ONE batched relay query covering photos,
status reports, comments, reactions, zaps, collections and tag suggestions for
a spot, then a distinct-account, tier-weighted score:

- counts DISTINCT accounts and never the mapper's own signals;
- weights costly signals higher (zap 3, photo/status 2, comment/reaction/
  collection/tag 1) and caps each account's contribution at 3;
- multiplies by the actor's web-of-trust + real-activity (reusing
  useBatchTrustScores/useBatchActivity) so a ring of empty accounts can't move it;
- returns level (unverified | seen | community) + a breakdown.

SpotDetailPage now derives the Community-verified badge from this (not just
photos+status) and shows a small "why" line (e.g. "2 photos · 1 zap · 3
comments") under the badges. No new event kinds.
2026-10-03 18:39:54 +07:00
SkateSpots
4d30240df9 feat(crew): validate & normalise website, band and social links
Website/band/social inputs stored whatever was typed, so a bare "crew.com"
wasn't clickable. Now they run through normalizeUrlInput + sanitizeUrl (bare
domains become https://), with an inline "enter a valid link" hint on website
and band URL. A non-URL social (e.g. a @handle) is kept as typed so nothing is
dropped. Matches the profile/craft forms.
2026-10-03 18:05:07 +07:00
SkateSpots
0548a7a7c6 feat(crew): tidy the create-crew "Extras" step into collapsible groups
The Extras step stacked 9 ungrouped blocks. Now:
- Identity (NIP-05, links) — open by default.
- Sasquatch AI — open when the agent is on.
- Showcase (video, sponsor, band).
- Payments & treasury (treasury + the CLINK tip jar, moved out of "Links").
- Integrations (Lab link).
- Advanced (Coordinator) — collapsed + last, so a new founder isn't asked
  about experimental plumbing.

Other fixes:
- The crew AI agent is now labelled "Sasquatch · Pro" (it runs on the same
  Pro-gated Sasquatch proxy, falling back to a free model), with an honest note.
- crew founded year is clamped to 1954..current on save and bounded in the input.
- "Skip to end" is no longer hidden on mobile.
- Removed the coordinator copy keys left unused by the previous pass.
2026-10-03 18:02:12 +07:00
SkateSpots
3cc16d18ca feat(crew): clearer, consistent crew-creation form
- Coordinator block: shorter copy, a Remove (✕) button, and an honest
  "pointer only — real Cordn MLS isn't wired yet" note (the old paragraph was a
  wall of jargon).
- Save now blocks on a non-empty but unparseable coordinator (was silently
  dropped) instead of publishing a crew with no coordinator.
- One consistent block order for create (wizard "Extras") and edit, grouped:
  core -> squad/places -> identity (NIP-05, links, band) -> agent -> showcase
  (video, sponsor) -> treasury -> integrations (Lab link, coordinator).
2026-10-03 17:33:35 +07:00
SkateSpots
127819e3da feat(nip05): multiple handles for profiles, crews and shops
A NIP-05 name maps to one key, so "who owns it" decides where it lives:
- Crew: repeatable nip05 tags (up to 5), verified against the founder key.
- Shop: same on kind 30017, verified against the owner key (new field).
- Profile: kind-0 has ONE standard nip05; extra personal handles live in a
  separate NIP-78 record (kind 30078, d=skatespots:profile-handles) so another
  client re-saving the profile can't wipe them.

Shared pieces:
- components/Nip05Badges.tsx — generic verify-and-render chip row (any owner).
- components/Nip05HandlesEditor.tsx — shared add/remove editor with live
  verified/mismatch status; used by profile, crew and shop forms.
- CrewNip05Badge now delegates to Nip05Badges; CrewForm uses the shared editor.
- Profile page shows primary + extra handles, and each crew/shop chips its own
  verified handle.
- lib/profileHandles.ts + hooks/useProfileHandles.ts (read/publish the record).
- types: SkateShop gains nip05/nip05s; parseSkateShop parses repeatable nip05s.
- NIP.md + AGENTS.md + i18n (EN/ES) updated.
2026-10-03 17:26:59 +07:00
SkateSpots
2daa0a9832 feat(crews): support multiple NIP-05 handles per crew
A crew is our own tag convention (unlike a kind-0 profile, whose nip05 is a
single string), so it can now advertise up to 5 handles, each verified
independently against the founder key.

- parseSkateCrew: collect all nip05 tags -> SkateCrew.nip05s (normalised,
  deduped, regex-validated, cap 5); nip05 stays = first (back-compat).
- CrewForm: add/remove handle rows with a live verified/mismatch/loading status
  (useCrewNip05 per row); format validation + max-5 hint; Enter adds.
- CrewNip05Badge renders every handle; CrewHero passes nip05s.
- NIP.md: nip05 documented as repeatable on kind 35006. i18n EN/ES.
2026-10-03 17:13:26 +07:00
SkateSpots
7aeda11041 feat(stash): password strength meter + filename hint on encrypted export
- Export dialog shows a 4-segment strength meter (length + character classes)
  with a Weak/Fair/Good/Strong label as you type the password.
- Shows the filename that will be saved; encrypted exports get an
  '-encrypted' suffix so the file is self-describing at a glance.
2026-10-03 11:29:04 +07:00