diff --git a/padelnomics/src/padelnomics/scripts/seed_dev_data.py b/padelnomics/src/padelnomics/scripts/seed_dev_data.py
new file mode 100644
index 0000000..58f8e9d
--- /dev/null
+++ b/padelnomics/src/padelnomics/scripts/seed_dev_data.py
@@ -0,0 +1,437 @@
+"""
+Seed realistic test data for local development.
+
+Creates suppliers, leads, a dev user, credit ledger entries, and lead forwards.
+
+Usage:
+ uv run python -m padelnomics.scripts.seed_dev_data
+"""
+
+import json
+import os
+import sqlite3
+import sys
+from datetime import datetime, timedelta
+from pathlib import Path
+
+from dotenv import load_dotenv
+
+load_dotenv()
+
+DATABASE_PATH = os.getenv("DATABASE_PATH", "data/app.db")
+
+
+def slugify(name: str) -> str:
+ return name.lower().replace(" ", "-").replace("&", "and").replace(",", "")
+
+
+SUPPLIERS = [
+ {
+ "name": "PadelTech GmbH",
+ "slug": "padeltech-gmbh",
+ "country_code": "DE",
+ "city": "Munich",
+ "region": "Europe",
+ "website": "https://padeltech.example.com",
+ "description": "Premium padel court manufacturer with 15+ years experience in Central Europe.",
+ "category": "manufacturer",
+ "tier": "pro",
+ "credit_balance": 80,
+ "monthly_credits": 100,
+ "contact_name": "Hans Weber",
+ "contact_email": "hans@padeltech.example.com",
+ "years_in_business": 15,
+ "project_count": 120,
+ "service_area": "DE,AT,CH",
+ },
+ {
+ "name": "CourtBuild Spain",
+ "slug": "courtbuild-spain",
+ "country_code": "ES",
+ "city": "Madrid",
+ "region": "Europe",
+ "website": "https://courtbuild.example.com",
+ "description": "Turnkey padel facility builder serving Spain and Portugal.",
+ "category": "turnkey",
+ "tier": "growth",
+ "credit_balance": 25,
+ "monthly_credits": 30,
+ "contact_name": "Maria Garcia",
+ "contact_email": "maria@courtbuild.example.com",
+ "years_in_business": 8,
+ "project_count": 45,
+ "service_area": "ES,PT",
+ },
+ {
+ "name": "Nordic Padel Solutions",
+ "slug": "nordic-padel-solutions",
+ "country_code": "SE",
+ "city": "Stockholm",
+ "region": "Europe",
+ "website": "https://nordicpadel.example.com",
+ "description": "Indoor padel specialists for the Nordic climate.",
+ "category": "manufacturer",
+ "tier": "free",
+ "credit_balance": 0,
+ "monthly_credits": 0,
+ "contact_name": "Erik Lindqvist",
+ "contact_email": "erik@nordicpadel.example.com",
+ "years_in_business": 5,
+ "project_count": 20,
+ "service_area": "SE,DK,FI,NO",
+ },
+ {
+ "name": "PadelLux Consulting",
+ "slug": "padellux-consulting",
+ "country_code": "NL",
+ "city": "Amsterdam",
+ "region": "Europe",
+ "website": "https://padellux.example.com",
+ "description": "Independent padel consultants helping investors plan profitable facilities.",
+ "category": "consultant",
+ "tier": "growth",
+ "credit_balance": 18,
+ "monthly_credits": 30,
+ "contact_name": "Jan de Vries",
+ "contact_email": "jan@padellux.example.com",
+ "years_in_business": 3,
+ "project_count": 12,
+ "service_area": "NL,BE,DE",
+ },
+ {
+ "name": "Desert Padel FZE",
+ "slug": "desert-padel-fze",
+ "country_code": "AE",
+ "city": "Dubai",
+ "region": "Middle East",
+ "website": "https://desertpadel.example.com",
+ "description": "Outdoor and covered padel courts designed for extreme heat environments.",
+ "category": "turnkey",
+ "tier": "pro",
+ "credit_balance": 90,
+ "monthly_credits": 100,
+ "contact_name": "Ahmed Al-Rashid",
+ "contact_email": "ahmed@desertpadel.example.com",
+ "years_in_business": 6,
+ "project_count": 30,
+ "service_area": "AE,SA",
+ },
+]
+
+LEADS = [
+ {
+ "facility_type": "indoor",
+ "court_count": 6,
+ "country": "DE",
+ "city": "Berlin",
+ "timeline": "3-6mo",
+ "budget_estimate": 450000,
+ "heat_score": "hot",
+ "status": "new",
+ "contact_name": "Thomas Mueller",
+ "contact_email": "thomas@example.com",
+ "stakeholder_type": "entrepreneur",
+ "financing_status": "loan_approved",
+ "location_status": "lease_signed",
+ "decision_process": "solo",
+ },
+ {
+ "facility_type": "outdoor",
+ "court_count": 4,
+ "country": "ES",
+ "city": "Barcelona",
+ "timeline": "asap",
+ "budget_estimate": 280000,
+ "heat_score": "hot",
+ "status": "new",
+ "contact_name": "Carlos Ruiz",
+ "contact_email": "carlos@example.com",
+ "stakeholder_type": "tennis_club",
+ "financing_status": "self_funded",
+ "location_status": "permit_granted",
+ "decision_process": "partners",
+ },
+ {
+ "facility_type": "both",
+ "court_count": 8,
+ "country": "SE",
+ "city": "Gothenburg",
+ "timeline": "6-12mo",
+ "budget_estimate": 720000,
+ "heat_score": "warm",
+ "status": "new",
+ "contact_name": "Anna Svensson",
+ "contact_email": "anna@example.com",
+ "stakeholder_type": "developer",
+ "financing_status": "seeking",
+ "location_status": "location_found",
+ "decision_process": "committee",
+ },
+ {
+ "facility_type": "indoor",
+ "court_count": 4,
+ "country": "NL",
+ "city": "Rotterdam",
+ "timeline": "3-6mo",
+ "budget_estimate": 350000,
+ "heat_score": "warm",
+ "status": "new",
+ "contact_name": "Pieter van Dijk",
+ "contact_email": "pieter@example.com",
+ "stakeholder_type": "entrepreneur",
+ "financing_status": "self_funded",
+ "location_status": "converting_existing",
+ "decision_process": "solo",
+ },
+ {
+ "facility_type": "indoor",
+ "court_count": 10,
+ "country": "DE",
+ "city": "Hamburg",
+ "timeline": "asap",
+ "budget_estimate": 900000,
+ "heat_score": "hot",
+ "status": "forwarded",
+ "contact_name": "Lisa Braun",
+ "contact_email": "lisa@example.com",
+ "stakeholder_type": "operator",
+ "financing_status": "loan_approved",
+ "location_status": "permit_pending",
+ "decision_process": "partners",
+ },
+ {
+ "facility_type": "outdoor",
+ "court_count": 3,
+ "country": "AE",
+ "city": "Abu Dhabi",
+ "timeline": "12+mo",
+ "budget_estimate": 200000,
+ "heat_score": "cool",
+ "status": "new",
+ "contact_name": "Fatima Hassan",
+ "contact_email": "fatima@example.com",
+ "stakeholder_type": "municipality",
+ "financing_status": "not_started",
+ "location_status": "still_searching",
+ "decision_process": "committee",
+ },
+ {
+ "facility_type": "indoor",
+ "court_count": 6,
+ "country": "FR",
+ "city": "Lyon",
+ "timeline": "6-12mo",
+ "budget_estimate": 500000,
+ "heat_score": "warm",
+ "status": "new",
+ "contact_name": "Jean Dupont",
+ "contact_email": "jean@example.com",
+ "stakeholder_type": "entrepreneur",
+ "financing_status": "seeking",
+ "location_status": "location_found",
+ "decision_process": "solo",
+ },
+ {
+ "facility_type": "both",
+ "court_count": 12,
+ "country": "IT",
+ "city": "Milan",
+ "timeline": "3-6mo",
+ "budget_estimate": 1200000,
+ "heat_score": "hot",
+ "status": "new",
+ "contact_name": "Marco Rossi",
+ "contact_email": "marco@example.com",
+ "stakeholder_type": "developer",
+ "financing_status": "loan_approved",
+ "location_status": "permit_granted",
+ "decision_process": "partners",
+ },
+ {
+ "facility_type": "indoor",
+ "court_count": 4,
+ "country": "GB",
+ "city": "Manchester",
+ "timeline": "6-12mo",
+ "budget_estimate": 400000,
+ "heat_score": "warm",
+ "status": "pending_verification",
+ "contact_name": "James Wilson",
+ "contact_email": "james@example.com",
+ "stakeholder_type": "tennis_club",
+ "financing_status": "seeking",
+ "location_status": "converting_existing",
+ "decision_process": "committee",
+ },
+ {
+ "facility_type": "outdoor",
+ "court_count": 2,
+ "country": "PT",
+ "city": "Lisbon",
+ "timeline": "12+mo",
+ "budget_estimate": 150000,
+ "heat_score": "cool",
+ "status": "new",
+ "contact_name": "Sofia Costa",
+ "contact_email": "sofia@example.com",
+ "stakeholder_type": "entrepreneur",
+ "financing_status": "not_started",
+ "location_status": "still_searching",
+ "decision_process": "solo",
+ },
+]
+
+
+def main():
+ db_path = DATABASE_PATH
+ if not Path(db_path).exists():
+ print(f"ERROR: Database not found at {db_path}. Run migrations first.")
+ sys.exit(1)
+
+ conn = sqlite3.connect(db_path)
+ conn.execute("PRAGMA journal_mode=WAL")
+ conn.execute("PRAGMA foreign_keys=ON")
+ conn.row_factory = sqlite3.Row
+
+ now = datetime.utcnow()
+
+ # 1. Create dev user
+ print("Creating dev user (dev@localhost)...")
+ existing = conn.execute("SELECT id FROM users WHERE email = 'dev@localhost'").fetchone()
+ if existing:
+ dev_user_id = existing["id"]
+ print(f" Already exists (id={dev_user_id})")
+ else:
+ cursor = conn.execute(
+ "INSERT INTO users (email, name, created_at) VALUES (?, ?, ?)",
+ ("dev@localhost", "Dev User", now.isoformat()),
+ )
+ dev_user_id = cursor.lastrowid
+ print(f" Created (id={dev_user_id})")
+
+ # 2. Seed suppliers
+ print(f"\nSeeding {len(SUPPLIERS)} suppliers...")
+ supplier_ids = {}
+ for s in SUPPLIERS:
+ existing = conn.execute("SELECT id FROM suppliers WHERE slug = ?", (s["slug"],)).fetchone()
+ if existing:
+ supplier_ids[s["slug"]] = existing["id"]
+ print(f" {s['name']} already exists (id={existing['id']})")
+ continue
+
+ cursor = conn.execute(
+ """INSERT INTO suppliers
+ (name, slug, country_code, city, region, website, description, category,
+ tier, credit_balance, monthly_credits, contact_name, contact_email,
+ years_in_business, project_count, service_area, created_at)
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
+ (
+ s["name"], s["slug"], s["country_code"], s["city"], s["region"],
+ s["website"], s["description"], s["category"], s["tier"],
+ s["credit_balance"], s["monthly_credits"], s["contact_name"],
+ s["contact_email"], s["years_in_business"], s["project_count"],
+ s["service_area"], now.isoformat(),
+ ),
+ )
+ supplier_ids[s["slug"]] = cursor.lastrowid
+ print(f" {s['name']} -> id={cursor.lastrowid}")
+
+ # 3. Claim 2 suppliers to dev user (growth + pro)
+ print("\nClaiming PadelTech GmbH and CourtBuild Spain to dev@localhost...")
+ for slug in ("padeltech-gmbh", "courtbuild-spain"):
+ sid = supplier_ids.get(slug)
+ if sid:
+ conn.execute(
+ "UPDATE suppliers SET claimed_by = ?, claimed_at = ? WHERE id = ? AND claimed_by IS NULL",
+ (dev_user_id, now.isoformat(), sid),
+ )
+
+ # 4. Seed leads
+ print(f"\nSeeding {len(LEADS)} leads...")
+ lead_ids = []
+ for i, lead in enumerate(LEADS):
+ from padelnomics.credits import HEAT_CREDIT_COSTS
+ credit_cost = HEAT_CREDIT_COSTS.get(lead["heat_score"], 8)
+ verified_at = (now - timedelta(hours=i * 2)).isoformat() if lead["status"] not in ("pending_verification",) else None
+
+ cursor = conn.execute(
+ """INSERT INTO lead_requests
+ (user_id, lead_type, facility_type, court_count, country, location, timeline,
+ budget_estimate, heat_score, status, contact_name, contact_email,
+ stakeholder_type, financing_status, location_status, decision_process,
+ credit_cost, verified_at, created_at)
+ VALUES (?, 'quote', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
+ (
+ dev_user_id, lead["facility_type"], lead["court_count"], lead["country"],
+ lead["city"], lead["timeline"], lead["budget_estimate"],
+ lead["heat_score"], lead["status"], lead["contact_name"],
+ lead["contact_email"], lead["stakeholder_type"],
+ lead["financing_status"], lead["location_status"],
+ lead["decision_process"], credit_cost, verified_at,
+ (now - timedelta(hours=i * 3)).isoformat(),
+ ),
+ )
+ lead_ids.append(cursor.lastrowid)
+ print(f" Lead #{cursor.lastrowid}: {lead['contact_name']} ({lead['heat_score']}, {lead['country']})")
+
+ # 5. Add credit ledger entries for claimed suppliers
+ print("\nAdding credit ledger entries...")
+ for slug in ("padeltech-gmbh", "courtbuild-spain"):
+ sid = supplier_ids.get(slug)
+ if not sid:
+ continue
+ # Monthly allocation
+ conn.execute(
+ """INSERT INTO credit_ledger (supplier_id, delta, balance_after, event_type, note, created_at)
+ VALUES (?, ?, ?, 'monthly_allocation', 'Monthly refill', ?)""",
+ (sid, 100 if slug == "padeltech-gmbh" else 30,
+ 100 if slug == "padeltech-gmbh" else 30,
+ (now - timedelta(days=28)).isoformat()),
+ )
+ # Admin adjustment
+ conn.execute(
+ """INSERT INTO credit_ledger (supplier_id, delta, balance_after, event_type, note, created_at)
+ VALUES (?, ?, ?, 'admin_adjustment', 'Welcome bonus', ?)""",
+ (sid, 10, 110 if slug == "padeltech-gmbh" else 40,
+ (now - timedelta(days=25)).isoformat()),
+ )
+ print(f" {slug}: 2 ledger entries")
+
+ # 6. Add lead forwards for testing
+ print("\nAdding lead forwards...")
+ padeltech_id = supplier_ids.get("padeltech-gmbh")
+ if padeltech_id and len(lead_ids) >= 2:
+ for lead_id in lead_ids[:2]:
+ existing = conn.execute(
+ "SELECT 1 FROM lead_forwards WHERE lead_id = ? AND supplier_id = ?",
+ (lead_id, padeltech_id),
+ ).fetchone()
+ if not existing:
+ conn.execute(
+ """INSERT INTO lead_forwards (lead_id, supplier_id, credit_cost, status, created_at)
+ VALUES (?, ?, ?, 'sent', ?)""",
+ (lead_id, padeltech_id, 35, (now - timedelta(hours=6)).isoformat()),
+ )
+ conn.execute(
+ "UPDATE lead_requests SET unlock_count = unlock_count + 1 WHERE id = ?",
+ (lead_id,),
+ )
+ # Ledger entry for spend
+ conn.execute(
+ """INSERT INTO credit_ledger (supplier_id, delta, balance_after, event_type, reference_id, note, created_at)
+ VALUES (?, -35, ?, 'lead_unlock', ?, ?, ?)""",
+ (padeltech_id, 80, lead_id, f"Unlocked lead #{lead_id}",
+ (now - timedelta(hours=6)).isoformat()),
+ )
+ print(f" PadelTech unlocked lead #{lead_id}")
+
+ conn.commit()
+ conn.close()
+
+ print(f"\nDone! Seed data written to {db_path}")
+ print(" Login: dev@localhost (use magic link or admin impersonation)")
+ print(" Admin: /admin with password 'admin'")
+
+
+if __name__ == "__main__":
+ main()
diff --git a/padelnomics/src/padelnomics/scripts/setup_paddle.py b/padelnomics/src/padelnomics/scripts/setup_paddle.py
index 6091f18..fd2d046 100644
--- a/padelnomics/src/padelnomics/scripts/setup_paddle.py
+++ b/padelnomics/src/padelnomics/scripts/setup_paddle.py
@@ -75,9 +75,9 @@ PRODUCTS = [
"billing_type": "subscription",
},
{
- "key": "boost_newsletter",
- "name": "Boost: Newsletter Feature",
- "price": 9900,
+ "key": "boost_card_color",
+ "name": "Boost: Custom Card Color",
+ "price": 1900,
"currency": CurrencyCode.EUR,
"interval": "month",
"billing_type": "subscription",
diff --git a/padelnomics/src/padelnomics/static/css/input.css b/padelnomics/src/padelnomics/static/css/input.css
index ee6eb03..9e4390b 100644
--- a/padelnomics/src/padelnomics/static/css/input.css
+++ b/padelnomics/src/padelnomics/static/css/input.css
@@ -57,14 +57,12 @@
/* ── Component classes ── */
@layer components {
- /* ── Navigation (Zillow-style: links | logo | links) ── */
+ /* ── Navigation ── */
.nav-bar {
position: sticky;
top: 0;
z-index: 50;
- background: rgba(255,255,255,0.85);
- backdrop-filter: blur(12px);
- -webkit-backdrop-filter: blur(12px);
+ background: #ffffff;
border-bottom: 1px solid #E2E8F0;
}
.nav-inner {
diff --git a/padelnomics/src/padelnomics/suppliers/routes.py b/padelnomics/src/padelnomics/suppliers/routes.py
index b4d17bf..60ffc5b 100644
--- a/padelnomics/src/padelnomics/suppliers/routes.py
+++ b/padelnomics/src/padelnomics/suppliers/routes.py
@@ -60,7 +60,7 @@ BOOST_OPTIONS = [
{"key": "boost_logo", "type": "logo", "name": "Logo", "price": 29, "desc": "Display your company logo"},
{"key": "boost_highlight", "type": "highlight", "name": "Highlight", "price": 39, "desc": "Blue highlighted card border"},
{"key": "boost_verified", "type": "verified", "name": "Verified Badge", "price": 49, "desc": "Verified checkmark badge"},
- {"key": "boost_newsletter", "type": "newsletter", "name": "Newsletter Feature", "price": 99, "desc": "Featured in our monthly newsletter"},
+ {"key": "boost_card_color", "type": "card_color", "name": "Custom Card Color", "price": 19, "desc": "Stand out with a custom border color on your directory listing"},
]
CREDIT_PACK_OPTIONS = [
diff --git a/padelnomics/src/padelnomics/templates/base.html b/padelnomics/src/padelnomics/templates/base.html
index bb67988..2f0651a 100644
--- a/padelnomics/src/padelnomics/templates/base.html
+++ b/padelnomics/src/padelnomics/templates/base.html
@@ -42,7 +42,6 @@
{% block head %}{% endblock %}
-