Commit Graph

235 Commits

Author SHA1 Message Date
Deeman
363f93885d fix: ruff clean + all visual tests passing
- Ruff: auto-fixed 43 errors (unused imports, unsorted imports, bare
  f-strings); manually fixed 6 remaining (unused vars, ambiguous `l`)
- Visual tests: server now builds schema via migrate() instead of the
  deleted schema.sql; fixes ERR_CONNECTION_REFUSED on all tests
- Visual tests: updated assertions for current landing page (text logo
  replacing img, .roi-calc replacing .teaser-calc, intentional dark
  sections hero-dark/cta-card allowed, card count >=6, i18n-prefixed
  logo href, h3 brightness threshold relaxed to 150)
- CSS: remove dead .nav-logo { line-height: 0 } (was for image logo,
  collapsed text logo to zero height) and .nav-logo img rules

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 08:49:47 +01:00
Deeman
b416cd682a feat: i18n URL prefixes + German legal pages
All public-facing blueprints (public, planner, directory, content,
leads, suppliers) now serve under /<lang>/ (e.g. /en/, /de/). Internal
blueprints (auth, dashboard, admin, billing) are unchanged.

URL routing:
- Root / detects lang from cookie → Accept-Language → default 'en'
  and 301-redirects to /<lang>/
- Quart url_value_preprocessor pops <lang> into g.lang; url_defaults
  auto-injects it so existing url_for() calls need no changes
- Unsupported lang prefixes (e.g. /fr/) return 404
- Legacy bare URLs (/terms, /privacy, /imprint, /about, /features,
  /suppliers) redirect 301 to /en/ equivalents
- robots.txt and sitemap.xml moved to app-level root; sitemap now
  includes both en and de variants of every SEO page
- lang cookie persisted 1 year, SameSite=Lax

i18n:
- New i18n.py: SUPPORTED_LANGS, LANG_BLUEPRINTS, flat translation dicts
  for ~20 nav/footer keys in en + de
- lang and t injected into every template context

Templates:
- base.html: <html lang="{{ lang }}">, hreflang tags (en/de/x-default)
  on lang-prefixed pages, nav/footer strings translated via t.*, footer
  language toggle EN | DE, SVG racket logo removed from footer
- 6 legal templates (terms/privacy/imprint × en/de) replacing old 3:
  - English: GDPR sections with correct controller identity (Hendrik
    Dreesmann, Zum Offizierskasino 1, 26127 Oldenburg), real sub-
    processors (Umami self-hosted, Paddle, Resend with SCCs), German-
    law jurisdiction
  - German: DSGVO-konforme Datenschutzerklärung, AGB, Impressum per
    § 5 DDG; Kleinunternehmer § 19 UStG; LfD Niedersachsen reference

Tests updated to use /en/ prefixed routes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 02:29:42 +01:00
Deeman
b19b6b907c fix: use inline SVG data URI for favicon — bypasses browser cache
Browsers were fetching favicon.ico and ignoring the external SVG link.
Inline data URI has no caching layer so the 'p' lettermark shows immediately.
Drops the .ico and .png favicon links (Apple touch icon kept for home screen).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 02:22:29 +01:00
Deeman
9e93df27e9 chore: bump favicon SVG cache-bust to v5
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 02:16:28 +01:00
Deeman
4f83fc1d36 fix: center nav logo, remove footer racket, replace favicon
- Nav: switch nav-inner from flex/space-between to grid (1fr auto 1fr)
  so logo is always geometrically centered regardless of link count per side;
  update input.css source of truth + inline override in base.html
- Footer: remove inline SVG racket, text-only wordmark matching the nav
- favicon.svg: replace padel racket with navy rounded square + white 'p'
  lettermark

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 02:12:51 +01:00
Deeman
8671facd7c chore: polish cookie banner — animation, UX, accessibility
- Slide-up entrance / slide-down exit animations (CSS keyframes)
- Upward box-shadow for visual depth from page content
- Larger toggle (44×24px) for easier tap target on mobile
- Split dual-state Manage/Save into: header button Manage↔Close + panel Save choices button
- ARIA: role=dialog, role=switch, aria-checked on toggle, focus moves to prefs on open
- Use btn-outline / btn component classes for secondary/primary buttons

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 02:06:38 +01:00
Deeman
a7b38339a6 feat: cookie consent banner, defer Paddle.js to checkout pages
- Add cookie consent banner (_cookie_banner.html) — fixed bottom bar with
  "Accept all" and "Manage preferences" (toggles for Essential/Functional);
  consent stored in cookie_consent cookie (1 year); no-JS = only essential
  cookies set (privacy-safe default)
- Add "Manage Cookies" link to footer Legal section to re-open the banner
- Extract Paddle.js init into _paddle.html partial; add {% block paddle %}
  to base.html (empty by default); override on export, supplier signup, and
  supplier dashboard pages — Paddle.js no longer loads on every page visit
- Gate ab_test() on functional cookie consent: variant picked per-request
  always, but ab_* cookie only persisted when visitor has consented
- Update privacy policy section 6: full cookie disclosure (essential,
  functional, payment categories + Umami cookieless note); fix "Plausible"
  → "Umami" in service providers list

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-20 01:48:08 +01:00
Deeman
23ca28613e chore: solid white nav — remove frosted glass backdrop 2026-02-20 01:16:14 +01:00
Deeman
6d622e7663 chore: self-host fonts (Bricolage Grotesque, DM Sans) and HTMX — remove Google Fonts, jsDelivr, unpkg dependencies 2026-02-20 01:08:30 +01:00
Deeman
3f8f0a7187 chore: replace nav SVG racket logo with text-only wordmark 2026-02-20 00:59:49 +01:00
Deeman
68cf03c026 refactor: rename ADMIN_EMAIL -> LEADS_EMAIL for clarity 2026-02-20 00:41:05 +01:00
Deeman
bd8976f344 fix: sending domain notification -> notifications.padelnomics.io 2026-02-20 00:37:56 +01:00
Deeman
2e666e84dc chore: add WAITLIST_MODE and RESEND_AUDIENCE_PLANNER to .env.example 2026-02-20 00:25:12 +01:00
Deeman
cb1fc00027 chore: ignore .hypothesis/ test output directory 2026-02-19 23:46:10 +01:00
Deeman
bc7fbcd595 chore: commit pending changes — logo, base template, scratch designs, changelog
- favicon.svg: pending logo tweaks
- base.html: pending template changes
- CHANGELOG.md: add waitlist mode and logo redesign entries missed in prior commits
- scratch/: add design prototype HTML/JSX files, remove old markdown notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 23:45:42 +01:00
Deeman
a1933eb2ba feat: auto-create Resend audiences per blueprint
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 23:42:13 +01:00
Deeman
77d801e326 feat: auto-create Resend audiences per blueprint
Removes RESEND_AUDIENCE_WAITLIST env var. capture_waitlist_email() now
derives audience name from request.blueprints[0] (e.g. waitlist-auth,
waitlist-suppliers), lazily creates it via Resend API on first signup,
and caches the ID in a new resend_audiences table. Zero config beyond
RESEND_API_KEY — adding @waitlist_gate to any new blueprint auto-creates
its audience on first use.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 23:41:45 +01:00
Deeman
05b7397687 Refactor waitlist mode with decorator pattern + helper function
Extract duplicated waitlist logic into two reusable abstractions:

1. @waitlist_gate(template, **context) decorator
   - Intercepts GET requests when WAITLIST_MODE=true
   - Passes through POST requests for form handling
   - Evaluates callable context at request time

2. capture_waitlist_email(email, intent, plan, email_intent) helper
   - Idempotent DB insertion (INSERT OR IGNORE)
   - Enqueues confirmation email only for new signups
   - Adds to Resend audience with silent error handling
   - Supports separate email_intent for supplier messaging

Applied to auth, suppliers, and planner routes, reducing ~80+ lines
of duplicated code. Added comprehensive tests (55 total, all passing)
and documentation (25KB guide in docs/WAITLIST.md).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 23:04:21 +01:00
Deeman
7d39970d50 redesign logo with padel holes and bust favicon cache
- Replace globe lines with 3x2 dot grid (characteristic padel racket holes)
- Makes racket immediately recognizable vs globe/frying pan shape
- Add ?v=3 to all favicon links to force browser cache refresh

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 21:29:28 +01:00
Deeman
95e331769c update logo to padel racket shape and refresh favicons
- Replace circle with rounded rectangle (rx=6, ~30% corner radius)
  matching the typical padel racket head proportions
- Handle now connects from bottom edge of rect
- New favicon.svg plus regenerated ico/png/apple-touch-icon from same design
- Add SVG favicon link (modern browsers) with ico/png fallbacks

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 21:19:29 +01:00
Deeman
51e330a57f add Imprint page (§5 DDG) and restructure footer legal column
- Add /imprint route and template stub (fill in company details)
- Move About out of Legal into a new Company column
- Add /imprint to sitemap and RESERVED_PREFIXES

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 21:08:35 +01:00
Deeman
746ba717b3 add research folder with market docs, strategy notes, and KfW files
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 21:00:01 +01:00
Deeman
12e43c23eb add Markets to navbar and fix logo spacing and auth button wrapping
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 20:56:03 +01:00
Deeman
b747de9c8c add .worktrees to gitignore and fix base.html comment
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 20:29:20 +01:00
Deeman
5de83d820c add simple A/B testing with @ab_test decorator and Umami data-tag
Assigns visitors to experiment variants via cookie (30-day), sets g.ab_variant
and g.ab_tag, and conditionally adds data-tag to the Umami script tag so all
pageviews and events are automatically tagged in the Umami dashboard.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 20:07:50 +01:00
Deeman
b108a53ef3 updates 2026-02-19 19:16:23 +01:00
Deeman
781281f9bc fix billing toggle and Most Popular badge consistency
- Fix CSS-only billing toggle: radio inputs were nested inside
  .s-billing-toggle, so ~ siblings couldn't reach plan card price
  elements inside <form>. Moved radios to be direct siblings of both
  .s-billing-toggle and <form>. Updated CSS selectors accordingly.
  Removed .price-yearly{display:block} override that broke monthly view.
- Set Most Popular badge on Growth (not Pro) in signup wizard — Growth
  is the natural entry point for lead-receiving suppliers
- Update public pricing page: Growth €149→€199, Pro €399→€499
- Refresh Pro feature list: logo+cover photo, full stats, verified badge
  (no longer described as add-ons since they're built into Pro)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 18:36:12 +01:00
Deeman
321d321ba9 add image-first directory card redesign and cover image upload
- 4-tier visual ladder: Free (muted grey) → Basic (verified presence) →
  Growth (stats + quotes) → Pro (court media + full stats + green glow)
- New card layout: 16:9 cover media, frosted category badge, logo avatar
  straddling media/body border (body-relative to avoid overflow:hidden clip)
- Pro default: CSS court visualization placeholder; Growth/Basic: dark-green
  grid placeholder; Free: grey/desaturated placeholder
- Cover image upload in supplier dashboard (saves to static/uploads/covers/)
- Migration 0013: cover_image TEXT column on suppliers table
- Updated prototype (scratch/design_directory_cards.html) with Basic tier
  cards, fixed logo-wrap positioning across all tiers

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 18:17:54 +01:00
Deeman
536eefffdb add Basic tier, monthly/yearly billing, and supplier detail redesign
- New Basic tier (€39/mo or €349/yr): verified directory listing with
  enquiry form, contact sidebar, services checklist, social links, no leads
- Monthly + yearly billing for all paid tiers; yearly defaults selected
  in signup wizard with CSS-only price toggle (no JS state)
- Redesigned supplier_detail.html: navy hero with court-grid pattern,
  two-column body+sidebar for Basic+, tier-adaptive CTA strips
- Supplier enquiry form: HTMX-powered, rate-limited 5/24h, email relayed
  via worker task; supplier_enquiries table tracks all submissions
- New supplier columns: services_offered, contact_role, linkedin_url,
  instagram_url, youtube_url (migration 0012)
- _lead_tier_required decorator restricts lead feed to growth/pro;
  Basic users see overview + listing tabs only
- Admin: basic tier in dropdown, new fields in form/detail + enquiry count
- setup_paddle.py: adds 4 new products with yearly interval support
- Webhook handler strips _monthly/_yearly suffixes, Basic gets 0 credits
  and is_verified=1; existing growth/pro webhooks unchanged
- Sort order: pro > growth > basic > free
- 572 tests pass (+2 new for basic tier + yearly webhook variants)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-02-19 15:03:21 +01:00
Deeman
07c7e61049 refactor migration system: single source of truth via replay
Eliminated dual-maintenance of schema.sql + versioned migrations.
All databases (fresh and existing) now replay migrations in order
starting from 0000_initial_schema.py. Removed _is_fresh_db() and
the fresh-DB fast-path that skipped migration execution.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-19 00:23:28 +01:00
Deeman
0b8350c770 fix webhook crashes on null custom_data, migrate to SDK Verifier
Paddle sandbox sends lifecycle events (subscription.updated, etc.) with
"custom_data": null. The .get("custom_data", {}) default only applies
when the key is missing, not when the value is explicitly null, causing
AttributeError on the next .get() call. Also guarded subscription.activated
to skip when user_id is absent (was inserting user_id=0 → FK violation).

Replaced manual HMAC verification with paddle_billing.Notifications.Verifier
via a lightweight _WebhookRequest wrapper satisfying the SDK's Request Protocol.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:43:40 +01:00
Deeman
df8a747463 add credit system and supplier webhook test suites, remove dead test
24 tests for credits.py (balance, spend, unlock, refill, ledger) and
10 integration tests for supplier webhook handlers (credit packs, sticky
boosts, subscription activation, business plan purchase). Removed
test_mobile_nav_no_overflow which never asserted its JS result.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 22:02:09 +01:00
Deeman
0fe5ab1259 fix supplier dashboard UX, Paddle integration, and dev tooling
Supplier dashboard: impersonate redirects to supplier dashboard when user
owns a supplier, sidenav active tab updates on click, listing preview
capped at 420px, insufficient-credits error shows balance and buy CTA,
boost buy buttons resolve Paddle price IDs server-side.

Dev tooling: dev_run.sh kills child processes on Ctrl-C (process
substitution fix), syncs Paddle products to DB on each run,
setup_paddle.py gains --sync mode and fixes Duration/Interval SDK change.

Seed data: each claimed supplier gets its own owner user and subscription.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 19:40:06 +01:00
Deeman
891b875cd1 fix dev_run.sh not stopping child processes on Ctrl-C
run_with_label piped output through a while loop, so $! tracked the
formatter PID instead of the actual command. Ctrl-C killed the formatters
but left app/worker/css-watch running as orphans. Switch to process
substitution so $! is the real command PID, and pkill -P children before
killing tracked PIDs.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:38:26 +01:00
Deeman
4c14b14bef add clickable admin list rows and supplier owner impersonation
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 17:25:42 +01:00
Deeman
4e61e9b1ab fix broken webhook signature verification and stale billing tests
Webhook handler called Verifier().verify() with raw bytes instead of a
request object, so signature verification always failed. Replaced with
manual HMAC check matching Paddle's ts=...;h1=... format. Updated tests
to produce correct signature format, mock the SDK instead of httpx for
manage/cancel routes, and expect JSON for overlay checkout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 16:49:23 +01:00
Deeman
61bf855103 add programmatic SEO content engine with article generation pipeline and tests
Adds a complete content generation system for producing SEO articles
at scale with embedded financial scenario widgets. Includes DB schema
(published_scenarios, article_templates, template_data, articles with
FTS5), bulk generation pipeline with staggered publish dates, admin CRUD
for templates/scenarios/articles, public markets hub with HTMX filtering,
catch-all article serving from pre-rendered static HTML, sitemap
integration, and 94 pytest tests covering the full stack.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 16:40:11 +01:00
Deeman
0b218f35ca add Paddle webhook auto-setup, ngrok tunnel, and clean DB on each dev run
setup_paddle.py creates a notification destination in Paddle and writes
the webhook secret + setting ID to .env. dev_run.sh resets the DB, seeds
data, and starts an ngrok tunnel to update the webhook URL automatically
for end-to-end checkout testing.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 14:01:02 +01:00
Deeman
77da44f3c8 add dev setup/run scripts and Resend test email docs
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 12:16:37 +01:00
Deeman
b99cd3c7d8 fix quote form state loss, admin errors, UI polish; add seed data and playwright tests
Quote wizard _accumulated hidden inputs used double-quote attribute delimiters
which broke on tojson output containing literal " characters — all step data was
lost by step 9. Admin dashboard crashed on credit_ledger queries referencing
wrong column names (amount/entry_type vs delta/event_type). Also: opaque nav bar,
pricing card button alignment, newsletter boost replaced with card color boost,
admin CRUD for suppliers/leads, dev seed data script, and playwright quote wizard
tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-18 09:37:13 +01:00
Deeman
7d3aa3141d update README with local testing guide, fix feedback placeholder, sync .env.example
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 22:29:40 +01:00
Deeman
f29c56cbaa add Phase 2: supplier dashboard, business plan PDF, Paddle.js checkout, admin tools
Migrate all checkouts to Paddle.js overlay (no redirect), move Paddle price
IDs from env vars to DB table, add 4-tab supplier dashboard (overview, leads,
listing, boosts), business plan PDF export with WeasyPrint, enhanced supplier
landing page with live stats, admin supplier management + feedback widget.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 22:23:43 +01:00
Deeman
6a10f82b5d add double opt-in email verification for quote requests
Guest quote submissions now require email verification before the lead
goes live. The verification click also creates a user account and logs
them in. Logged-in users submitting with their own email skip verification.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 17:02:32 +01:00
Deeman
e0563d62ff polish nav, planner UX, country pills, and dev magic link
- Nav: Zillow-style centered logo, solid blue Sign In button
- Planner: center app at 72rem, center wizard steps/header/preview
- Country pills: UK/USA labels, remove Other, show permits slider
  inline under country so the effect is transparent and adjustable
- Reset button: inline confirm (red "Sure? Reset") instead of alert
- Worker: print magic link to console when DEBUG=true for local dev

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:36:26 +01:00
Deeman
cefdb7ce3a fix directory search: SQL param order and HTMX include selector
Parameter bindings for ORDER BY (now, country) were placed before WHERE
params, causing mismatched bindings when any filter was active. Also
switched hx-include from .dir-search class (shared by form + inputs)
to #dir-search-form ID to prevent duplicate parameters.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:16:21 +01:00
Deeman
fc410920d8 add supplier tiers, directory redesign, CTA cleanup, and ROI fix
Phase 0 features: ungate planner, lead qualifier with heat scoring,
quote form (migrations 0002-0003), supplier directory with FTS5 search
(migration 0004), landing page redesign with ROI calculator and FAQ.

Phase 1 improvements: supplier tier system with Growth/Pro paid plans
(migration 0005), HTMX live directory search, three-tier card design,
Zillow-style sticky nav, "Get Matched" → "Get Quotes" CTA rename,
remove "Free" messaging site-wide, realistic ROI calculator defaults
(~3.9yr payback / ~26% ROI), mandatory form validation with 422 errors,
supplier pricing page with boost add-ons.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-17 14:11:35 +01:00
Deeman
02d216bc94 update landing journey: 5-stage funnel with Coming Soon badges
Explore → Plan → Finance → Build → Grow, with .grid-5 layout.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 18:30:16 +01:00
Deeman
7cb41d91f2 fix planner toggle active state, improve space defaults
- Toggle buttons (Indoor/Outdoor, Rent/Buy) now visually update
  their active state on click
- Space requirement sliders start from minimum court size
  (200m² double, 120m² single) instead of 0
- Defaults updated to court + 2m buffer (336/240/312/216 m²)
- Reference dimensions panel shows standard court sizes
- Chart.js font updated from JetBrains Mono to Commit Mono

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 18:06:03 +01:00
Deeman
11999bdc5d add scratch stuff 2026-02-16 18:03:00 +01:00
Deeman
1d744bbf6d style transactional emails with branded layout
Wrap magic link and welcome emails in a proper HTML email template
with navy header, white card body, CTA button, and muted footer.
Fallback plain-text URL included below the button.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-02-16 16:29:13 +01:00