Commit graph

236 commits

Author SHA1 Message Date
SkateSpots
72693732dd docs: record dropdown scroll fix + language picker search gotchas 2026-10-08 20:01:18 +07:00
SkateSpots
a7c3a9f130 docs(AGENTS): 12 locales (all 100%), lazy-loading, NK flag, one-writer rule 2026-10-08 00:02:08 +07:00
SkateSpots
7ab9a5b399 docs(AGENTS): refresh legacyMiscUi scope 2026-10-07 18:31:31 +07:00
SkateSpots
ac1b56ce76 docs(AGENTS): record the hardcoded-string sweep + legacy fragment layer 2026-10-07 18:12:30 +07:00
SkateSpots
af0a138d1a fix(nav): inline language switcher in the mobile sheet (portaled dropdown was unclickable inside a dialog) 2026-10-07 17:54:32 +07:00
SkateSpots
b4411c930b docs(AGENTS): record th + ja at 100% coverage 2026-10-07 17:48:33 +07:00
SkateSpots
ab05973ff0 i18n(ja): add Japanese as fourth locale (plumbing + font + batch 1 shell) 2026-10-07 17:21:18 +07:00
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
c08592144a perf(create-spot): cap reverse-geocode at 2.5s so publish can't stall before signing 2026-10-07 10:31:07 +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
0a5449d24f docs(AGENTS): park identity/conflict ideas until there are users (triggers + Stash≠key-recovery note) 2026-10-07 10:18:09 +07:00
SkateSpots
b8c306125c i18n(th): batch 2 — map (mapUi + mapPage) + AGENTS note 2026-10-06 13:35:20 +07:00
SkateSpots
fb16ea74c0 fix(dev): self-healing lazyWithRetry (retries + cooldown reload); add dev:fresh; auto-restart dev on dep changes 2026-10-05 23:37:46 +07:00
SkateSpots
1510255b81 docs(AGENTS): add the full Zapstore publish sequence 2026-10-05 21:49:09 +07:00
SkateSpots
0c4d4f6b29 fix(supporters): drop dead BTC address; fix Zapstore version duplication in release notes 2026-10-05 17:42:09 +07:00
SkateSpots
3f94d9be30 feat(supporters): show Angor supporters with amounts on /supporters 2026-10-05 17:37:00 +07:00
SkateSpots
3d7fc9af4f docs(AGENTS): document the Angor project metadata workflow (:8097 era tooling, relay gotchas) 2026-10-05 17:15:02 +07:00
SkateSpots
4572179914 docs(AGENTS): record the :8097 phone-frame preview (durable, not in /tmp) 2026-10-05 15:44:14 +07:00
SkateSpots
5baec5fb80 Deck calculator: fix the mobile layout (no horizontal scrolling)
The sheet tables carry 5-8 columns and were unusable on a phone: values were
truncated ("900" -> "90") and columns fell off the edge, and the cost-vs-quantity
table scrolled sideways.

- On max-width:639px the four data tables render as stacked cards (.ss-sheet-table
  in index.css): thead hidden, each td a label/value row (label via data-label +
  td::before), cells flex-wrap so a long label plus input never overflows.
- The cost-vs-quantity block is now a vertical list instead of a table, so it
  never scrolls horizontally.

Verified at 390px: values are full and each row is a readable card.
2026-10-05 15:30:12 +07:00
SkateSpots
277b6a910f AGENTS: tidy the deck-designer frozen note wording 2026-10-05 12:53:47 +07:00
SkateSpots
e3576fcfec Freeze/hide the Deck designer (not ready for regular users)
- Remove the deckDesigner card from the DIY hub, so it is not listed publicly.
- Gate /diy/deck-designer to platform admins: DeckDesignerPage redirects to /diy
  for everyone else and shows an "Internal preview" badge to admins.

Code and i18n keys are kept. To un-freeze, remove the Navigate guard and re-add
the card in diyGuides.ts (both noted in comments and AGENTS.md).
2026-10-05 12:53:13 +07:00
SkateSpots
bed0fb8e34 Deck calculator polish: collapse quote fields, duplicate rows, tax-aware hand-off
- The Client quote fields (15+ inputs) are now a collapsed <details>, so the
  always-visible Margins card stays short; the chevron rotates on open.
- CalcSection buttons set aria-expanded.
- Every equipment/material/process/packaging row gains a duplicate (Copy) action.
- The sticky summary chip shows the order total when quantity > 1 and uses the
  setup-inclusive cost.
- "List a deck at this price" passes a buyer-inclusive unit price (tax added on
  top unless tax is marked inclusive).
2026-10-05 12:45:23 +07:00
SkateSpots
475497b205 Deck calculator: Advanced mode — per-op rate/scrap, batch setup, tax, cost curve
A Simple <-> Advanced switch (not "Pro" — that name is Sasquatch Pro). Advanced
reveals four things a working shop needs:

- Per-operation labour rate (0 = shop rate) and per-operation scrap, which
  compounds with the general scrap into an effective waste %.
- One-off batch setup (hours + cost), amortised over the order quantity, so the
  unit price depends on qty; cost/board now uses the setup-inclusive unit cost.
- A tax / VAT line (inclusive or added on top), shown on the client quote as
  Subtotal / Tax / Total or "includes X%".
- A cost-vs-quantity curve (1/5/10/25/50/100 decks).

All fields round-trip through CSV. The sheet is merged over DEFAULT_CALC on read
and clampPct is NaN-safe, so an old saved sheet or preset can't produce NaN;
currency switching also converts the new money fields.
2026-10-05 12:38:58 +07:00
SkateSpots
df92399abf Deck calculator: collapsible sections, templates, batch/trade pricing, warnings
A+B+C pass on /deck-cost.

A - Declutter: each sheet section is now an accordion (CalcSection) with its
live per-board subtotal in the header (Labor open by default, Margins always
visible), plus quick-start templates (Hobby starter / Small shop) that prefll a
realistic sheet.

B - Batch & trade: a quantity field (unit price vs order total), a wholesale
markup giving a wholesale/unit price, profit per deck, and a breakeven
(fixed / profit) with an optional target-monthly-profit goal. The client quote
shows "x N" and the order total; the deposit is taken on the total.

C - Guards: an amber warning block for markup < 1x, wholesale >= retail, rows
with 0 boards, fixed costs with 0 boards/month, no materials, equipment+overhead
over 60% of cost, and equipment spread over a single board; number inputs clamp
negatives to 0.

New state round-trips through CSV (margins rows). The default sheet produces no
warnings.
2026-10-05 12:25:27 +07:00
SkateSpots
3b8fadad2c Deck calculator: make labels match the maths (burden, loaded rate, markup)
The arithmetic was already consistent, but three labels described something
other than what the code computes:
- "Overhead factor" multiplied labour only while a separate section held the
  real overhead, inviting double-counting -> "Labour burden (x)" + a hint.
- "Labor cost" showed wage x burden, i.e. the loaded rate, not a per-board
  cost -> "Loaded rate".
- "Profit margin (x)" was a markup -> "Markup (x)", with the implied gross
  margin % shown so the name matches the number.

Also: distinct board-count columns (Lifetime boards / Boards per order /
Boards per run) instead of three identical "# boards"; "Scrap / waste (%)".
PDF and Markdown exports use the same names. CSV machine keys are unchanged so
existing exports still round-trip.
2026-10-05 11:21:38 +07:00
SkateSpots
5b9616a86d Deck calculator quotes: payment terms, methods, deposit and optional Incoterms
QuoteProfile gains payment methods (multi-select chips), a deposit percent
(PDF splits "Deposit due now" / "Balance on completion"), free-text payment
terms, separate payment details (bank/PayPal), and an optional Incoterm
(EXW/FCA/DAP/DDP). The client PDF gains a payment block; the PNG shows the
deposit. Quote state is now merged over DEFAULT_QUOTE on read so a quote saved
before a new field existed cannot crash the tool.
2026-10-05 11:16:37 +07:00
SkateSpots
a55aa7b53b Deck calculator quotes: optional Nostr identity (NIP-05, npub, Lightning)
The Quote details block gains NIP-05, npub and a Lightning address (lud16), plus
a "Fill from profile" button that autofills name/website/NIP-05/Lightning/npub
from the logged-in kind-0 profile. The client PDF lists the NIP-05 and npub in
the contact block and "Pay via Lightning: <lud16>" under the total; the PNG shows
the NIP-05 (or npub) under the business name. A PDF is a snapshot — the identity
is verifiable on Nostr, not on the paper.
2026-10-05 11:10:15 +07:00
SkateSpots
3f90967f6c Deck calculator: quote number/validity, logo on PNG, shared quote profile
- Extract the client-quote identity into its own store (QuoteProfile,
  skatespots:deck-cost:quote) so it is NOT part of CalcState — loading or saving
  a preset now keeps the same business name/logo/contact details.
- Client quote PDF: a real quote number counter (Q-0007, with a +1 button) and a
  configurable validity window, shown as "Quote valid until <date>".
- PNG summary card now also draws the logo (top-right) and the business name.
2026-10-05 11:04:20 +07:00
SkateSpots
c8cbb848e1 Deck cost calculator: quote logo + business/contact details + notes
A "Quote details" block (in the Result section) lets the user set a business/
shop name, a logo (stored as a data URL — no upload/network, capped 500 KB),
email/phone/website/address and notes/terms. The client quote PDF now renders a
logo + contact header, the line item + total, and the notes/terms. Bumped the
localStorage key to :v2 (and guarded preset/JSON loads) so an older saved sheet
without the quote block can't crash the tool.
2026-10-05 10:59:28 +07:00
SkateSpots
1a8c483682 Deck cost calculator: quote PDF, PNG, XLSX, copy, supplier import
Export ▾ now offers: PDF (full sheet), PDF (client quote — uses an optional
businessName), PNG summary card (canvas), XLSX multi-sheet workbook (lazy
SheetJS), CSV, Markdown, JSON, and Copy summary (clipboard).

Import auto-detects the CSV shape: a `name,order_cost,…` header is treated as a
supplier price list and replaces the Materials table; otherwise the CSV
round-trips the whole sheet. JSON still loads a backup.
2026-10-05 10:54:37 +07:00
SkateSpots
daeb3fbf3e Deck cost calculator: CSV + Markdown export, CSV import
- Export is now a menu: PDF (quote/print), CSV (spreadsheet-friendly
  section,item,field,value rows), Markdown, JSON (backup).
- Import auto-detects .json / .csv: JSON loads a backup, CSV round-trips the
  whole sheet (hand-rolled CSV quote/parse so commas in material names are safe).
2026-10-05 10:47:23 +07:00
SkateSpots
885e818e3b Deck cost calculator: BTC denomination + a full materials checklist
- Currency picker now offers BOTH Bitcoin units — ₿ sats and ₿ BTC (plus fiat).
  BTC is 1 per BTC, sats 1e8; switching converts between them and fiats
  (BTC shows up to 8 decimals, trimmed).
- Default Materials is now a complete deck-build checklist, grouped: wood & core
  (veneer/edge tape/logs), adhesives & fillers (glue/epoxy/filler), press/mold
  consumables (release/vacuum film), shaping (sandpaper/saw+drill bits/masking
  tape), finish & graphics (stain/primer/paint/clearcoat/grip) and Other. New
  rows default to 0 so the worked example is unchanged.
2026-10-05 10:43:26 +07:00
SkateSpots
71f53bcc51 Deck cost calculator: export a PDF by default
The toolbar's primary Export now generates a real PDF (lazy-loaded jspdf) — a
formatted one-page summary: labour, equipment, operations, materials, processes,
packaging, margins and the selling prices, plus the date/currency. Print-to-PDF
was rejected because window.print() doesn't work in the Capacitor WebView.

A secondary JSON export stays (for Import/backup); Import still reads JSON.
jspdf is dynamically imported so it never enters the initial bundle.
2026-10-05 10:36:25 +07:00
SkateSpots
f3242e2fed Deck cost calculator: correctness + presets + a marketplace hand-off
- Equipment now amortises setup time too: (cost + setupHrs × labor/hr) ÷ boards,
  so the Setup (h) column actually does something.
- sats values round to whole numbers on currency conversion.
- Each section shows a per-board subtotal (equipment/materials/processes/packaging).
- Sticky summary bar (under the header) keeps cost + selling price visible while
  you edit.
- Save/Load named presets (localStorage) + Export/Import JSON; Reset now confirms.
- Results gain "List a deck at this price →" (deep-links CreateListingPage with
  ?price&currency, which now reads currency too) and "Get it made →" (Lab Builder).
2026-10-05 10:33:30 +07:00
SkateSpots
ee55669625 Deck cost calculator: convert + a searchable multi-currency selector
- getBtcRates()/useBtcRates() read every BTC→fiat rate from the ONE Coinbase
  exchange-rates call the app already made, plus sats (1e8). No new endpoint.
- The calculator's currency control is now a searchable Popover: sats + every
  fiat Coinbase reports, with a search box. Selecting a currency CONVERTS every
  money field (labour, rent, utilities, subscriptions, equipment cost, material
  order/shipping, packaging materials) by the live ratio; unitless fields
  (overhead factor, board counts, minutes, margins) are untouched. Falls back to
  relabel-only if rates aren't loaded.
- Currency symbols/formats for the common fiats.
2026-10-05 10:16:50 +07:00
SkateSpots
9ecdd966c7 Deck cost calculator: add a currency unit toggle (USD/EUR/GBP/sats)
A chip row at the top switches the whole sheet's unit: $ / € / £ / ₿ sats. Input
labels follow the unit and results reformat (sats are shown rounded, no
decimals). It relabels/reformats only — it does not convert values, since the
app only has a BTC/USD rate (no fiat↔fiat or reliable fiat↔sats conversion);
you enter amounts in the unit you think in. Choice persists with the rest.
2026-10-05 10:12:29 +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
b91224699a Add scripts/transfer-content.mjs (move spots/shops between keys)
Addressable events (30818/30017) are author-signed, so "transferring" them
means re-signing each under the destination key + publishing, and optionally
publishing NIP-09 deletes for the originals. This script does that; it needs
TO_NSEC (destination) and, only for --delete-originals, FROM_NSEC. --dry-run
(default) needs no keys and just lists what would move.
2026-10-04 18:06:07 +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
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
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
122cc871ed docs: record the Lab offer wizard 2026-10-04 12:29:55 +07:00
SkateSpots
6a0b0695ee docs: record Lab offer form polish (drafts, progress, validation, URLs) 2026-10-04 11:39:39 +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
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