242 Commits

Author SHA1 Message Date
418fef1a15 Remove email obfuscation from contact page
The base64 atob() pattern is trivially decoded by scrapers, making it
ineffective. The contact form is the preferred channel anyway.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 19:36:54 -04:00
01cad11472 Fix ALTCHA widget not loading or submitting
Two bugs prevented form submission entirely:
1. `challengeurl` attribute was renamed to `challenge` in altcha v3 — the
   widget silently ignored the old name so it never fetched a challenge.
2. `altchaWidget.value` is not an exposed property on the v3 custom element;
   read the solved payload from the hidden `<input name="altcha">` the widget
   renders in light DOM instead.

Also clears the err-altcha error message at the start of each submit attempt
so it doesn't linger after the user completes verification and retries.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 10:44:05 -04:00
5e6b201336 Add Cache-Control: no-store to ALTCHA challenge endpoint 2026-06-12 09:20:17 -04:00
c503d6dd75 Easter egg: trigger on copyright symbol click only
Previously fired on 5 rapid taps anywhere on the page.
Now triggers with a single click on the © in the footer.
Added id="bpb-copyright" to the symbol span in nav.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 06:03:15 -04:00
2ebbe4fbe0 Fix ALTCHA CDN path for v3 widget
v3 moved the widget from dist/altcha.min.js to dist/main/altcha.min.js;
the old path served a cached v2 widget which expected a 'challenge'
string field and threw split-on-undefined against v3's parameters format.
Pin to @3.1.0 to prevent future surprise upgrades.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 06:01:45 -04:00
c00f2de338 Upgrade main-site to Node 20 for Web Crypto globals
Node 18 requires --experimental-global-webcrypto for crypto.subtle /
crypto.getRandomValues as globals; Node 20 LTS exposes them by default,
which altcha/lib needs for createChallenge.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-12 05:56:40 -04:00
e7af5bca4a Fix store-status route always returning 401
The route had a redundant isAuthed() checking for 'bpb_admin' cookie,
but login sets 'admin_token'. The middleware already guards all
/api/admin/* routes, so the in-route check was just wrong.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:53:39 -04:00
9cac3a8e8a Fix altcha require path for Node 18 compatibility
Node 18 enforces the package exports map; the deep path
'altcha/dist/lib/index.umd.cjs' is not exported, but 'altcha/lib' is.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:43:53 -04:00
0652339539 Add ALTCHA widget to main page contact form
The widget was only on the /contact/ page; main page form was unprotected.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:25:27 -04:00
a89788f531 Add ALTCHA proof-of-work spam protection to contact form
- Server: /api/altcha generates a SHA-256 challenge (v3 API); /api/contact
  verifies the widget payload before processing the submission
- Widget: added <altcha-widget> from CDN above the submit button
- contact-form.js: blocks submission if altcha value is missing and
  appends it to FormData
- docker-compose.yml: passes ALTCHA_HMAC_KEY env var to main-site container
- package.json: added altcha@3.1.0

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-11 14:23:42 -04:00
548c19f3fa Add UX improvements: thumbnails, auto-quote, shareable cart, order status
High impact:
- Cart items now show product thumbnail (52px) so customers can visually
  confirm what they ordered
- Delivery quote auto-fetches 800ms after the address stops changing,
  removing the manual "Check availability" step; also persists across
  sessions and restores when the same address is loaded from localStorage
- Calendar error fallback now shows a clear explanation before the
  booking request form

Medium impact:
- "Copy shareable cart link" button in cart footer encodes the current
  cart as a ?cart= URL param; opening the link re-hydrates the cart from
  the catalog so customers can share or continue on another device
- Order status page at /order/[orderId] shows state, fulfillment time,
  and items; linked from the post-checkout success screen
- Delivery quote is saved to localStorage and restored automatically
  when the same address is loaded in a new session

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 08:26:22 -04:00
9dd4aff35e Remove dead code: unused components, duplicate logic, orphaned route
- Delete Hero, ReviewsSection, TrustedBrands components (never imported)
- Delete /api/admin/orders/[orderId]/complete route (never called; order
  state transitions go through /status instead)
- Extract maxColorsFor() from ProductCard and CartDrawer into
  src/lib/colors.ts to eliminate the duplicated implementation

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-10 08:01:39 -04:00
f0b60f123d Fix store closure message rendering in dark mode
Force explicit light-mode colors with inline styles so Bulma's dark
mode cannot override the closure banner text and background.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-09 21:05:13 -04:00
781f990541 Add store kill switch to admin panel and estore
Admin panel shows a prominent open/closed toggle above the tabs. When
closed, the shop displays a branded closure message and the checkout API
returns 503. The closure state persists in data/store-status.json.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:52:31 -04:00
2e5f253580 Polish: meta/OG tags, JSON-LD, 404 pages, typo fix
- Unique title + meta description on every main-site page
- OpenGraph + Twitter card tags sitewide (hero image on homepage, logo elsewhere)
- LocalBusiness JSON-LD on homepage for Google rich results
- Custom 404 page on main-site (branded, links home + contact)
- Custom not-found page on estore
- Fix typo: "Delivery avalable" → "Delivery available"

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:47:26 -04:00
0d57760df1 Contact form: set min date to today on event date picker 2026-06-07 00:35:32 -04:00
77318fb477 Contact form: split name into first/last, add ntfy notification
- First and last name are now separate required fields
- Server combines them into a full name for emails
- Sends a push notification to NTFY_URL on new inquiry (fire-and-forget)
- NTFY_URL env var wired through docker-compose

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-07 00:32:06 -04:00
cd18bd3937 Contact form: add emoji to subject, show event date instead of type 2026-06-07 00:27:50 -04:00
d026bc8217 Fix balloon animation: use Web Animations API instead of CSS custom properties in keyframes 2026-06-06 21:38:40 -04:00
75b20e6ca2 Add debug logging to easter egg trigger 2026-06-06 21:29:51 -04:00
181195dbbc Fix easter egg trigger: use touchstart on mobile, 5 taps in 3s
click events are unreliable on mobile due to scroll handling. Use
touchstart (fires immediately) for mobile and click for desktop with
deduplication. Lower threshold to 5 taps, widen window to 3 seconds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:23:05 -04:00
066364d2b7 Fix easter egg trigger: 7 quick taps on non-interactive area
Long press on the logo link was unreliable. Switch to detecting 7 rapid
taps on any non-link/button area within 2 seconds — works on mobile and
desktop without conflicting with navigation.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:15:26 -04:00
2002d7f35a Add balloon easter egg — long press logo to trigger
Hold the logo for 0.6s (works on mobile touch and desktop mouse) to launch
22 balloon silhouettes floating up from the bottom. Balloons drift with a
slight sway, have a shine highlight, and naturally disappear under the navbar.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:07:06 -04:00
5900ce817e Fix dark mode: add data-theme=light to all main-site HTML pages
Bulma 1.0 follows system dark mode preference if no theme is set.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 21:01:38 -04:00
6a2bf1f30b Fix nginx port: bind to 3000 instead of 80 for NPM compatibility
NPM proxies beachpartyballoons.com → host:3000. Binding to 80 conflicts
with NPM which owns that port.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:59:04 -04:00
0ec3766447 Wire SMTP env vars into main-site container, document in .env.example
Contact form needs SMTP_HOST/PORT/SECURE/USER/PASS and CONTACT_TO passed
through docker-compose. Added to main-site environment block and documented
in .env.example.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:37:50 -04:00
aee1f10179 Sync native contact form to main-site, replace iframe
Replaces the third-party iframe form on both the homepage and contact page
with the self-hosted form: drag-and-drop photo upload, honeypot, rate
limiting, inline validation, auto-reply email. Adds multer/sharp/nodemailer
dependencies and the /api/contact endpoint to server.js.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:32:05 -04:00
fca6e8da0a Add fulfillment state controls to admin orders panel
Replace single Mark Complete button with contextual In progress / Ready /
Complete buttons based on current fulfillment state. Adds a general
/api/admin/orders/[orderId]/status endpoint that handles all transitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-06 20:22:23 -04:00
ca8773d3c3 Fix order complete: update fulfillment state alongside order state
Square requires all fulfillments to be COMPLETED before the order can be
marked COMPLETED — include fulfillment state in the same updateOrder call.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:08:54 -04:00
ef38c42e17 Add Orders tab to admin panel for managing online orders
Fetches open orders from Square filtered by source=online-shop metadata.
Each order shows customer, fulfillment time/address, items, and total with
a Mark Complete button that updates the order state in Square directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 20:03:52 -04:00
02e49ba41b Fix checkout: block false slots when calendar down, add booking request fallback
- Change fulfillment state from RESERVED to PROPOSED (Square rejects RESERVED)
- Return 503 from slots API when CalDAV is unreachable instead of serving empty
  busy blocks that made all time slots appear falsely available
- Add BookingRequestPanel and /api/booking-request endpoint: when the calendar
  server is down, customers can submit their order and preferred time; server
  emails info@beachpartyballoons.com and sends a confirmation to the customer

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-06-05 19:58:22 -04:00
a49075b167 Fix Apple Pay verification file — serve directly from nginx
With basePath=/shop the Next.js app can't serve /.well-known/ at the
domain root. Mount the file into the nginx container and serve it
directly instead.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-29 06:47:06 -04:00
7d5f74a79e Serve Apple Pay verification file via nginx
Move domain association file to estore/public/.well-known/ so Next.js
serves it, and add a /.well-known/ location block in nginx so Apple's
servers can reach it at the domain root.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:50:17 -04:00
92ab3a5633 Add Google Pay and Apple Pay support; fix catalog category filter
- PaymentForm now initialises Google Pay and Apple Pay via Square's Web
  Payments SDK alongside the existing card form; wallet buttons appear
  above the card with an "or pay with card" divider when available
- Apple Pay domain verification file added to public/.well-known/
- square.ts: fix online-category filter to show all items when the
  category doesn't exist; support multi-category display per item

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-28 17:01:29 -04:00
53a2ca03e7 fix: force Bulma light theme on admin page 2026-05-21 12:07:22 -04:00
55055ae9bc fix: light theme for all admin modals 2026-05-21 12:02:56 -04:00
1435964f6f feat: drag-to-select and full-header click in admin gallery
- Click anywhere on the top bar of a photo card to toggle selection
  (replaces the tiny checkbox)
- Drag across the grid to rubber-band select multiple photos at once
- Selected cards show a blue ring + tinted header + solid checkmark icon
- Cards swept during drag show a green ring preview before releasing
- Fixed innerHTML += perf issue (now builds all cards then sets once)
- Thumbnails used in grid so page loads faster

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:49:31 -04:00
7fce1632be feat: editable tag presets, next/prev modal nav, needs-tagging filter
- Restructured presets as single-tag accumulators (click multiple to build up tags)
- Added 6 new tags: bridal-shower, cocktail, signature, indoor, outdoor, mitzvah
- Fixed organic/garland alias conflict
- Presets stored in data/presets.json with full CRUD API (add, edit, delete from admin)
- Edit modal shows photo thumbnail, prev/next navigation, preset buttons
- Keyboard shortcuts: Ctrl+Enter to save, arrow keys to navigate, Esc to close
- "Needs tagging" filter in manage view shows only uncategorized/low-tag photos

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:42:32 -04:00
0e4461e957 fix: reseed sets createdAt from filename timestamp or file mtime
Photos reseeded from disk now sort by their original upload time
instead of all getting the same insertion timestamp.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-21 11:29:11 -04:00
633f1e2380 fix: reseed script now skips -sm and -md variant files
Previously all .webp files were indexed including thumbnails and medium
variants, causing each photo to appear three times in the gallery.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:48:06 -04:00
2723a6d954 feat: replace text watermark with BPB logo SVG overlay on gallery uploads
Loads bpb-watermark.svg at startup, converts black fills to semi-transparent
white (35% opacity), and composites it centered at 45% image width over every
uploaded photo. Removes the old diagonal "BEACH PARTY BALLOONS" text overlay.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 15:39:03 -04:00
92cf44e5f5 fix: resolve gallery CORS failure and simplify API routing
- gallery backend: replace origin whitelist with wildcard CORS — NPMplus
  was stripping the Allow-Origin header; wildcard passes through reliably
  and is appropriate for a public photo gallery
- gallery.js: hardcode photobackend.beachpartyballoons.com as the API base
  (NPMplus already routes this subdomain) and remove dead port fallbacks
- nginx.conf: add /photos and /uploads proxy routes to gallery-backend
  (kept for direct-nginx access; NPMplus handles external traffic)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:53:13 -04:00
4a135a7919 fix: route gallery API through nginx, send Square receipts, unblock order completion
- nginx: add /photos and /uploads proxy routes to gallery-backend so the
  browser can reach the gallery API without needing direct port access
- gallery.js: drop hardcoded port/subdomain fallbacks; use same-origin path
  via the new nginx routes
- square.ts: pass buyerEmailAddress to createPayment so Square auto-sends
  a payment receipt to the customer on capture
- square.ts: create fulfillments in RESERVED state (was PROPOSED) so staff
  can mark orders complete/filled directly from the Square dashboard
- CartDrawer: merge Custom Vinyl into the Shape Balloon line item (one fewer
  Square line item per vinyl order); show modifier price deltas in cart

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-20 14:32:00 -04:00
bb878c2a8a fix: apply 6.35% CT Sales Tax to all order line items
Tax was $0 on first production order — catalog items don't have tax
configured in Square Dashboard. Apply it programmatically via an
ad-hoc LINE_ITEM scoped tax on every line item. Delivery remains
untaxed (service charge taxable: false).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-11 15:46:36 -04:00
5777788127 fix: point all Plausible scripts to self-hosted metrics instance
Replace plausible.io with metrics.beachpartyballoons.com across all
main-site pages and estore layout.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 13:15:41 -04:00
8799892341 fix: add Plausible window initializer to estore layout
Matches the pattern used on all main-site pages.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:40:54 -04:00
973808088e revert: restore product image background to white
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-10 10:34:24 -04:00
f45a1f807f fix: make product image rounded corners visible
border-radius was already set but invisible (white image on white background).
Changing the image background to a warm off-white makes the 12px rounded
corners show against the surrounding area.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 22:07:12 -04:00
e8240e383a fix: increase Square card form height to show postal code field
89px only fit two rows; postal code (third row) was clipped by the container.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 13:24:23 -04:00
bbf08e4267 fix: hide subtotal line when it equals the total (no delivery/tax)
Subtotal was always shown alongside Total even for pickup orders with
no additional charges, making both lines identical. Now the breakdown
(Subtotal / Delivery / Tax) only appears when there are fees beyond
the item total. Applies to both customer and store alert emails.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-09 13:16:33 -04:00