diff --git a/CHANGELOG.md b/CHANGELOG.md
index 96a1bd9..71eeb75 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -6,6 +6,121 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
## [Unreleased]
+### Added
+- **Supplier tier system** — Migration 0005 adds `tier` (free/growth/pro),
+ `logo_url`, `is_verified`, `highlight`, `sticky_until`, `sticky_country`
+ columns to suppliers table for paid listing support
+- **HTMX live search** — directory search input and filters update results
+ via `hx-get` with 300ms debounce; new `/directory/results` endpoint returns
+ swappable partial
+- **Directory card tiers** — three-tier card design: Pro (green border, logo,
+ verified badge, website), Growth (description, blue badge), Free (muted,
+ unverified, "Is this your company?" CTA); sticky/featured suppliers pinned
+ to top with blue border
+- **Supplier pricing page** — `/suppliers/` now shows Growth (€149/mo) and
+ Pro (€399/mo) plan cards with feature lists, boost add-ons grid (Logo,
+ Highlight, Verified Badge, Sticky Top, Newsletter Feature), updated FAQ
+- **Mandatory form fields** — country, timeline, and stakeholder_type now
+ required on quote request form with server-side 422 validation
+- **Validation test** — `test_quote_validation_rejects_missing_fields` verifies
+ server returns 422 JSON errors for missing mandatory fields
+
+### Changed
+- **Nav redesign** — Zillow-style sticky nav with backdrop-blur: demand-side
+ links (Planner, Directory) left, supply-side (For Suppliers, Help) after
+ separator, Sign In right; removed "Get Started Free" button
+- **CTA text sweep** — "Get Matched" → "Get Quotes" across planner, landing,
+ and lead forms; removed all "Free" qualifiers from CTAs and badges
+- **ROI calculator fix** — realistic cost model: €35K/court (was €25K),
+ staff costs, €8/sqm rent (was €4); payback and ROI now based on total
+ investment (was equity only); defaults: €40/hr rate, 35% utilization;
+ shows ~3.9yr payback, ~26% ROI (was 0.1yr/1255%)
+- **Directory route refactor** — shared `_build_directory_query()` helper
+ with tier-based SQL ordering (sticky → pro → growth → free → alphabetical)
+
+### Added
+- **Supplier directory** — public searchable directory at `/directory/` with 279
+ padel court suppliers across 31 countries; FTS5 full-text search, country and
+ category filters, pagination, category-colored badges, unclaimed listing model
+- **Supplier landing page** — `/suppliers/` marketing page for suppliers: hero,
+ how-it-works steps, example lead preview, FAQ, "Claim Your Listing" CTAs
+- **Migration 0004** — creates `suppliers` table with FTS5 virtual table,
+ content-sync triggers, and seeds 279 suppliers from PadelDirectory.md
+- **Quick ROI calculator** — landing page now features an interactive 3-slider
+ calculator (courts, rate, utilization) showing investment, monthly cash flow,
+ payback period, and annual ROI in real time
+- **Supplier matching section** — landing page "Find the Right Suppliers" section
+ with 3-step flow and link to directory
+- **FAQ accordion** — landing page FAQ covering planner features, signup
+ requirements, supplier matching, directory pricing, and projection accuracy
+
+### Changed
+- **Visual refresh** — adopted React prototype color palette and aesthetic
+ site-wide: royal blue primary (#1D4ED8), green (#16A34A), gold (#D97706);
+ elevated cards with soft shadows, rounder corners (rounded-2xl cards,
+ rounded-xl buttons/inputs), frosted-glass planner nav, highlighted CTA
+ regions with blue-tinted backgrounds, pill-shaped toggle/filter controls,
+ polished buttons with colored shadows, stronger hover lift on directory cards
+- **Landing hero redesigned** — two-column layout with headline + CTAs on left
+ and interactive Quick ROI calculator on right (matching React prototype);
+ green badge pill, feature check bullets, "Open Full Planner" CTA inside
+ calculator card; responsive single-column on mobile
+- **Landing page redesigned** — replaced screenshot card with Quick ROI
+ calculator; added supplier matching section, FAQ, and live supplier stats;
+ CTAs renamed "Open the Planner — Free"; Build journey card updated with
+ live supplier/country counts
+- **Navbar** — Planner and Directory links now visible for all users (not just
+ logged-in); footer updated with Directory and For Suppliers links
+- **Planner CTAs** — removed sticky "Get Builder Quotes" footer bar; CAPEX and
+ Returns tab CTAs now navigate to wizard step 5 (integrated lead qualifier)
+ instead of redirecting to standalone `/leads/quote` form
+- **Sitemap** — added `/planner/`, `/directory/`, and `/suppliers/` URLs
+
+### Changed
+- **Planner wizard** — Assumptions tab reorganized into 5 guided steps
+ (Your Venue → Pricing → Costs → Finance → Get Matched) with live preview
+ bar and step navigation; reduces cognitive load from 60 sliders to ~6-15
+ per step
+- **Integrated lead qualifier** — Step 5 "Get Matched" embeds the supplier
+ quote form directly in the planner; auto-fills venue, courts, glass,
+ lighting, country, budget from planner state; submits inline via fetch
+- **JSON quote endpoint** — `POST /leads/quote` now accepts `application/json`
+ and returns `{"ok": true, "heat": "..."}` for inline planner submissions;
+ standalone HTML form unchanged
+
+### Added — Phase 0 Round 2: Polish & Country-Specific Calculator
+- **Country-specific calculator** — `country` selector (DE/ES/IT/FR/NL/SE/UK/Other) and `permitsCompliance` CAPEX item for Indoor Rent and Outdoor scenarios; country presets auto-adjust permit costs
+- **Permits & Compliance** — new CAPEX line item for building permits, noise studies, and regulatory compliance (default €12K for Germany); excluded from Indoor Buy where Planning + Permits already covers this
+- **Quote form redesign** — elevated white card on gradient background, green gradient CTA buttons, progress labels (Project/Details/Contact), privacy info box, mandatory consent checkbox
+- **Project phase** (replaces location_status options) — 7-stage progression: still searching → location found → converting existing → lease signed → permit not filed → permit pending → permit granted; updated heat scoring
+- **Stakeholder type** field — "You are..." selector (Entrepreneur, Tennis Club, Municipality, Developer, Operator, Architect) with `stakeholder_type` DB column (migration 0003)
+- **Build context** — added "Need Help Finding a Venue / Land" (`venue_search`) option
+- **Quote submitted page redesign** — "You're matched!" flow with next-steps timeline, email confirmation box, and signup CTA for guests
+- **Migration 0003** — adds `stakeholder_type TEXT` column to `lead_requests`
+
+### Changed
+- **Landing page** — replaced teaser calculator with planner screenshot in browser-frame card + "Start Planning — Free" CTA; all CTAs now point to `/planner/` (no signup gate)
+- **Heat score** — updated `calculate_heat_score()` for new project phase values (`permit_granted` +4, `lease_signed`/`permit_pending` +3, `converting_existing`/`permit_not_filed` +2, `location_found` +1)
+- **Quote URL** — planner now passes `country` parameter to quote form prefill
+- **Admin email** — includes stakeholder type and updated field labels
+
+### Added — Phase 0: Ungate & Validate
+- **Guest mode planner** — removed auth gate from `/planner/` and `/planner/calculate`; scenarios still require login
+- **New calculator variables** — `budgetTarget` (budget vs CAPEX comparison), `glassType` (standard/panoramic, 1.4x multiplier), `lightingType` (LED standard/competition/natural, 1.5x/0x multipliers)
+- **Pill select UI component** — reusable `pillSelect()` helper in planner.js with matching `.pill-btn` CSS for multi-option inputs
+- **Budget indicator card** — shows over/under budget with variance amount and percentage on the Investment tab
+- **3-step "Get Builder Quotes" flow** — `/leads/quote` with project specs, details, and contact steps; no login required
+- **Lead heat scoring** — `calculate_heat_score()` rates leads as hot/warm/cool based on timeline, financing, location readiness, and budget signals
+- **PDF export CTA** — "Export Business Plan (PDF) — €99" wired to Paddle checkout (`business_plan` price in PADDLE_PRICES)
+- **SEO meta tags** — ` ` description, og:title, og:description, og:image on planner page
+- **Migration 0002** — expands `lead_requests` with 17 new columns for quote qualification flow; makes `user_id` nullable for guest leads
+- **Phase 0 test suite** (`tests/test_phase0.py`) — 47 tests covering guest mode, glass/lighting/budget variables, heat scoring, quote submission, schema validation
+- Updated Hypothesis strategy in `test_calculator.py` with `budgetTarget`, `glassType`, `lightingType`
+
+### Changed
+- Planner CTA links now point to `/leads/quote` with pre-filled calculator state params (venue, courts, glass, lighting, budget)
+- Sticky footer bar updated: "Get Builder Quotes" + "Export Business Plan (PDF)" replace old supplier/financing links
+
### Changed
- Landing page journey section: renamed "From Idea to Operating Hall" → "Your Journey", expanded from 4 cards to 5 (Explore → Plan → Finance → Build → Grow) with "Coming Soon" badges on unreleased stages
- Added `.grid-5` CSS helper for 5-column grid layout
diff --git a/PadelDirectory.md b/PadelDirectory.md
new file mode 100644
index 0000000..7219125
--- /dev/null
+++ b/PadelDirectory.md
@@ -0,0 +1,575 @@
+# Global Padel Court Suppliers & Builders Directory
+
+**200+ companies across 30+ countries** — court manufacturers, hall builders, turf suppliers, lighting specialists, booking software, and consultants. The padel court market reached ~$163M in 2023 and is projected to hit $592M by 2031. This directory is organized by region, with Germany as the priority market.
+
+---
+
+## 1. GERMANY
+
+Germany's padel market is growing 60%+ annually, with 100+ facilities now operating. A wave of domestic manufacturers has emerged since 2020.
+
+### 1.1 German-Headquartered Court Manufacturers
+
+| Company | City/Region | Website | Contact | Description |
+|---------|------------|---------|---------|-------------|
+| **artec Sportgeräte GmbH** | Melle, Lower Saxony | artec-sportgeraete.de | Via contact form | 30+ years sports equipment. 12mm laminated safety glass, DIN-standard statics, customizable colors. Indoor & outdoor. |
+| **PADELWERK Court GmbH** | Dortmund | padelwerk.de | +49 172 54 60 150 | "Germany's first padel court manufacturer" (est. 2021). All components made in Germany. Basic and luxury versions, LED lighting, roofing, financing consulting. |
+| **Vindico Sport GmbH** | Germany | vindico-sport.de | Via website | Serie B and Pano models. Carbon-steel S235JRH, 10–12mm tempered glass, 9-stage corrosion coating, WPT-standard entrances, stainless steel A4 connectors. |
+| **Kübler Sport GmbH** | Backnang, Baden-Württemberg (71522) | kuebler-sport.de | info@kuebler-sport.de | Major sports supplier. CLASSIC and PANORAMA courts with steel structure, tempered glass, wire mesh, artificial turf, 8 LED elements. In-house installation teams. |
+| **Padello GmbH** | Troisdorf, NRW | padello.de | Via contact form | Full-service builder with own metalworking shop. Steel construction up to 5mm strength adapted to regional wind loads. DIN-compliant statics, DEKRA-certified noise assessment. |
+| **LOB Sport (BECO Bermüller)** | Nuremberg, Bavaria (90451) | lobsport.de / beco-bermueller.de | +49 911 64200-0 | 40+ years in tennis equipment, now padel. "Edge" and "Infinity" models. Foundation-free option for converting sand courts. Part of BECO Bermüller group. |
+| **Brako Padel GmbH** | Berlin | brakopadel.com | Via contact form | Padel specialist since 2013. 15-year corrosion warranty. Indoor, outdoor, panoramic, and portable courts. No-foundation portable options. |
+| **Padel Concept** | Germany (DACH) | padelconcept.de | Via website | Production in Baltic states, serves DACH/Scandinavia/BeNeLux. Own assembly teams. Innovative mobile floor plate solution. Full statics and noise protection reports. |
+| **Padelsportanlagenbau** | Germany | padelsportanlagenbau.de | Via contact form | Manufacturer & turnkey builder, active in 18+ countries. |
+
+### 1.2 Turnkey Solution Providers & Consultants (Germany)
+
+| Company | City/Region | Website | Contact | Description |
+|---------|------------|---------|---------|-------------|
+| **The Court Company** | Cologne, NRW | courtcompany.de | +49 2237 6034685 | "Germany's most experienced padel construction expert." 40+ courts in 2021. Licensed partner of AFP Courts, RedSport, adidas courts. Full service plus leasing and mobile courts. |
+| **Real Padel GmbH** | Schönebeck, Saxony-Anhalt (39218) | realpadel.de / padel-court.de | Schornsteinfeger Str. 3, 39218 Schönebeck | Premium courts and full-service club consulting. Booking systems, automation, equipment, sponsor connections. Also brokers connecting DACH customers with top manufacturers. |
+| **Padel4U (P4U)** | Germany | padel4u.de | Via website | German distributor for Manzasport. Full range including standard, panoramic, and custom courts. ~1,000 courts/year through partnership. |
+| **Trendsport Rummenigge** | Germany | trendsport-rummenigge.de | Via website | Turnkey solutions from concept to completion. Guidance on German funding programs (Sportstättenförderung). |
+| **PadelCity** | Multiple (21+ locations) | padelcity.de | Via contact form | Germany's largest padel operator (20 facilities, 100+ courts). DTB partner. Proprietary booking app. Franchise model. |
+| **padelBOX** | Multiple locations | padelbox.de | r.stroehl@padelbox.de | Major German padel operator and consultant. |
+| **Padel Solution** | Germany | padelsolution.de | Via website | Supplier, planner, autonomous club solutions. |
+| **Best World Padel** | Denmark/Germany | Via website | Via website | Official MejorSet distributor for Germany and Denmark. |
+
+### 1.3 Padel Hall & Building Constructors (Germany)
+
+| Company | HQ | Website | Contact | Description |
+|---------|------|---------|---------|-------------|
+| **SMC2 Bau** | France/Germany | smc2-bau.de | Via website | Textile membrane sport halls — glass court walls serve as lower facade with membrane roof above. Meets German building norms. 10-year guarantee. |
+| **Padberg Projektbau** | Germany | padberg-projektbau.de | Via website | Turnkey padel hall specialist. Expertise in minimum 6–7m ceiling height, glass wall statics, LED lighting. Assists with permits and grant funding. |
+| **Planeco Building** | Germany | planecobuilding.de | Via website | Building permit consultancy for converting warehouses/halls into padel facilities. Fire safety, structural analysis, change-of-use permits. |
+| **BORGA** | Sweden/Austria | borga.at | Via website | 45+ years building steel halls. 3 standardized padel hall solutions plus custom. Sandwich panels, mezzanine floors, court layout optimization. |
+
+### 1.4 International Manufacturers with Strong German Presence
+
+| Company | HQ | Website | Contact | Description |
+|---------|------|---------|---------|-------------|
+| **AFP Courts / adidas Padel Courts** | Barcelona, Spain | afpcourts.com | Via website | 18+ years, 4,000+ courts in 54+ countries. Official adidas licensee. German distribution via The Court Company. |
+| **Portico Sport** | Spain | porticosport.com/germany | Via website | Leading European builder. Installed at TSG Augsburg, TC Degerloch Stuttgart, and more. Turnkey with canopies. |
+| **Padelcreations** | Spain (DACH-active) | padelcreations.com | +34 965 049 221 / info@padelcreations.com | 24+ courts in DACH region. 10 court models. Certified Spanish assembly teams. |
+| **MejorSet (LeDap Group)** | Alicante, Spain | mejorset.com | +34 966 374 289 / info@mejorset.com | Official court of Premier Padel and FIP. 20+ years, pioneer of panoramic design. Present in Germany via LeDap / Best World Padel. |
+| **Manzasport** | Valencia, Spain | manzasport.com | +34 963 217 472 / info@manzasport.com | Top-4 global builder, 15+ years. German distribution through Padel4U. Multiple court models. |
+| **Courtwall (The Padelcourt Biz)** | Vienna, Austria | padelcourt.biz | Via website | Building courts since 1984 (squash) and padel since 2007. 1,200+ courts worldwide. ISO-certified. |
+| **UnixPadel** | Istanbul, Turkey | unixpadel.com | Via website | 7,800+ courts globally. FIP-standard, KIWA-tested. Up to 12 courts/day production. TÜV-tested components. |
+| **The Padel Lab** | International | thepadellab.com | Via website | HD Vision courts, tournament-grade panoramic, modular steel frame technology. Claims 40% reduction in construction time. |
+
+### 1.5 Turf, Lighting, Software & Accessories (Germany)
+
+| Company | Type | Website | Contact | Description |
+|---------|------|---------|---------|-------------|
+| **Mondo S.p.A.** | Turf | mondoworldwide.com | Via website | World's leading padel turf manufacturer. Official FIP/Premier Padel turf partner. 13,000+ courts globally. |
+| **BECO Bermüller** | Turf | beco-bermueller.de | +49 911 64200-0 | German synthetic turf and sports field materials. Parent of LOB Sport. |
+| **Polytan GmbH** | Turf | polytan.com | Via website | Since 1969. Sports surfaces development and production. Active in padel turf across Europe. |
+| **Primaflor** | Turf | primaflor.de | Via website | Artificial turf supplier for padel courts. |
+| **Playtomic** | Booking software | playtomic.com | Via website | World's largest padel booking platform. 73+ locations in Germany. 6,700+ clubs, 52+ countries. €56M raised. |
+| **bookaball** | Booking software | bookaball.com | Via website | Booking software for racket sport clubs. Used by German market leaders. €30M+ bookings processed. |
+| **Ledkia** | Lighting | ledkia.com | Via website | LED padel court floodlight solutions, 50W–1,250W. |
+| **Lumosa** | Lighting | lumosa.eu | Via website | Dutch LED sports lighting manufacturer. Custom lighting plans, up to 80% energy savings. |
+| **AS LED Lighting** | Lighting | Via website | Via website | Upper Bavaria-based LED specialist for padel. |
+
+### 1.6 German Industry Organizations & Directories
+
+| Organization | Website | Description |
+|-------------|---------|-------------|
+| **Deutscher Padel Verband (DPV)** | dpv-padel.de | Germany's official padel federation. Provides quality criteria for court purchasing. |
+| **mypadel.de (DTB)** | mypadel.de | German Tennis Federation's padel portal. Court finder, leagues, partners. |
+| **padel-test.de** | padel-test.de | Comprehensive German directory with builder comparisons and filterable listings. |
+| **Padelinsider.de** | padelinsider.de | German padel information platform with booking system overviews. |
+| **Sportstättenrechner** | sportstaettenrechner.de | Cost calculators, supplier info, and funding guidance for padel projects. |
+| **Padel Lands** | padellands.com | International directory listing 60+ German clubs. |
+
+---
+
+## 2. SPAIN — World's Largest Padel Market (17,000+ courts)
+
+Spain dominates global padel court manufacturing. Its manufacturers have exported courts to 70+ countries.
+
+### 2.1 Court Manufacturers
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **MejorSet** | Crevillente, Alicante | mejorset.com | +34 966 374 289 / info@mejorset.com | Official court of Premier Padel & FIP. 10,000+ courts in 70+ countries. Part of LeDap group. Pioneer of panoramic design. |
+| **Padel Galis** | Valencia | padelgalis.com | Via website | 10,000+ courts, 75+ countries. Partnership with Wilson and Fernando Belasteguín. Official WPT/Premier Padel supplier. |
+| **Portico Sport** | Villafranca de los Barros, Badajoz | porticosport.com | Via website | 15+ years, 35+ countries, 4,000+ courts. Only manufacturer also building sports canopies. 110,000+ sqft factory. |
+| **Padel10** | Rubí, Barcelona | padel10.com | Calle París 1-7, nave 18, Rubí 08191 | Exclusively padel since 2008. 4,500+ courts. Former WPT official supplier. 15-day production. Mondo and ACT turf systems. |
+| **Manzasport** | Beniparrell, Valencia | manzasport.com | +34 963 217 472 / info@manzasport.com | Top-4 global builder since 2003, 4 factories in Valencia. 100% Spanish manufacturing, FIP and NIDE 2021 compliant. |
+| **Maxpeed** | Viladecans, Barcelona | maxpeed.com | +34 936 593 961 / maxpeed@maxpeed.com | Manufacturer, builder, equipment supplier. |
+| **AFP Courts / RedSport** | Portugal/Spain | afpcourts.com / redsportpadel.com | Via website | Official adidas licensee. 15+ years, 33+ exclusive distribution centers. Official PPL court. 2,150+ courts under RedSport brand. |
+| **Jubo Padel** | Spain | jubopadel.com | Via website | Family-owned, 25+ years, 6,000+ courts. FIP official. Comprehensive alliance partner network. Court configurator. |
+| **Padel Courts Deluxe (PCDLX)** | Alicante | padelcourtsdeluxe.es | info@padelcourtsdeluxe.com | 100% Spanish, premium courts. Robotic welding, carbon fibre courts. Partnership with Greenset for surfaces. |
+| **SportBS** | Extremadura | sportbs.es | Via website | 100% Spanish materials. 360° service including courts, lighting, turf, software. |
+| **Ingode Padel Courts** | Crevillente, Alicante | ingodepadel.com | Via website | ISO 14001 & 9001. Galvanized steel with epoxy paint. 20+ years. |
+| **SkyPadel** | Spain (global) | skypadel.com | Via website | 1,500+ courts in 40+ countries. EUROCODE compliant. Featured at WPT events globally. Subsidiaries in India/Brazil/Mexico. |
+| **PadelMagic** | Valladolid | padelmagic.es | Via website | 400+ courts/year, 80% exported. FIP-certified. |
+| **Padel Hispania** | Spain/Portugal | padelhispania.com | Via website | Federation-approved specialist in Spain and Portugal. Ambassador: Catarina Santos. |
+| **VerdePadel** | Spain | verdepadel.com | Via website | Building since 2004. 2,000+ courts, 40,000+ m² of artificial grass installed. |
+| **J'hayber Padel** | Spain | jhayberinstalaciones.com | Via website | Manufacturer & lighting specialist since 1972. |
+| **PadelFan Valencia** | Alaquàs, Valencia | padelfanvalencia.com | +34 661 320 398 | 10+ years, 800+ customers. Also provides turf replacement. |
+| **TMPadel** | Valencia | tmpadel.com | Via website | Kiwa certified, exports to 6+ EU countries. |
+| **Niberma** | Spain | niberma.es | Via website | 700+ courts. |
+| **Grupo Pineda** | Griñón, Madrid | grupopineda.eu | Via website | Proprietary turf & pavement. |
+| **MR Instalaciones** | Madrid | mrinstalaciones.es | Via website | 20+ years, turnkey. |
+| **X-Treme Group** | Alcalá de Henares, Madrid | x-tremegroup.com | Via website | 18+ years, 6,000 m² facility. |
+| **MTR Padel** | Spain | mtrpadel.com | Via website | Methacrylate glass, turnkey. |
+| **GimPadel** | León | gimpadel.com | Via website | 800+ installations. Built in Netherlands, Belgium, Italy, Kuwait, Portugal, Kenya. |
+| **EE Padel** | Spain/Sweden | eepadel.com | Via website | 2021 merger of Eljoi Padel, EcoNatura, Swedish investors. Aluminum courts. Ships to US/Canada/ME. |
+| **Greencourt** | Spain | greencourt.es | Via website | Galvanized/aluminum/precast options. |
+| **ExtremaAdel** | Extremadura | extremapadel-portugal8.webnode.pt | Via website | Aluminum model innovation. |
+| **Padelgest** | Spain | Via website | Via website | Urban/sustainability focus. |
+| **Iberopadel** | Spain | iberopadel.com | Via website | FEP approved, Joma/Maxpeed partner. |
+| **Padel Alba** | Granja de Rocamora, Valencia | padelalba.com | Via website | 25+ years, FIP-compliant. |
+| **Pistas-Padel.es** | Spain | pistas-padel.es | Via website | All court types. |
+
+### 2.2 Spanish Turf/Surface Specialists
+
+| Company | Website | Contact | Description |
+|---------|---------|---------|-------------|
+| **Mondo** | mondoworldwide.com | Via website | Official FIP/Premier Padel turf. 16,200 m² tufting factory in Borja, Spain. |
+| **Realturf** | realturf.com | Via website | Official USPA sponsor. Drive Pro, Match Play, fibrillated lines. FEP-compliant. |
+| **Act Sports** | act.sport | Via website | Global padel turf leader, 10,000+ fields. |
+| **Eurocesped** | eurocesped.com | Via website | ITF parameters. |
+| **Allgrass** | allgrass.es | Via website | FEP compliant. |
+| **Albergrass** | albergrass.com | Via website | Pilar de la Horadada, Alicante. |
+
+### 2.3 Spanish Lighting Specialists
+
+| Company | Website | Description |
+|---------|---------|-------------|
+| **Led Projects** | ledprojects.es | World leader in padel lighting, 5,000+ courts, WPT/Premier Padel official. |
+| **Óptima LED** | optimaled.es | ProTour padel spotlights, anti-glare. |
+| **PlazaLED** | plazaled.es | Sports LED projectors & scoreboards, Madrid. |
+| **Ellite LED Padel** | ledpadel.com | 30+ years R&D. 360° perimeter system. 10-year warranty. Integration with booking apps. |
+
+### 2.4 Spanish Industry Body
+
+The **International Padel Cluster (CIP)** in Madrid (clusterpadel.com) is the world's largest padel industry association with **132+ member companies**, 165 associated brands, 4,500+ employees, and **€2B+ combined turnover**. It encompasses 57 court construction companies, 38 racquet brands, 20 ball producers, 32 accessory makers, and 25 clothing/textile firms.
+
+---
+
+## 3. ITALY
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Mondo S.p.A.** | Alba (HQ) | mondoworldwide.com | Via website | World's leading padel turf. Official FIP/Premier Padel partner. |
+| **Italgreen** | Italy | italgreen.org | Via website | 40+ years. Patented fiberglass structure. FIP sponsor. Iron, Full Panoramic, V-PRO courts. Own padel turf lines (Padel Pro, Padel Fib). |
+| **Limonta Sport** | Italy | limontasport.com | Via website | Premium padel turf, FEP approved, CONI partner. |
+| **Padel Factory SRL** | Near Rome | padelfactorysrl.com | Via website | 1,000+ courts. Innovative hybrid wood-steel design. 10+ European countries. |
+| **Padel Corporation** | Italy | padelcorporation.com | +39 339 731 2152 | 10+ years, 20+ countries. 100% made in Italy, ITF compliant. Full turnkey. Zambrotta ambassador. |
+| **Italian Padel** | Italy | italianpadel.it | info@italianpadel.it | 3,000+ courts in 28 countries. Up to 180 courts/month. CE certified. |
+| **Italia Team Padel** | Pesaro-Urbino, Marche | italiateampadel.com | +39 0721 571 588 | Supplied first court at Foro Italico. Basic, Vision Pro, Full Vision models. |
+| **Campidapadel.it (Madrid SRL)** | Lesmo (MB) | campidapadel.it | Via website | Design, supply, installation, plus financial and marketing consultancy. |
+| **Favaretti Padel** | Bagnoli di Sopra (PD) | favarettipadel.it | Via website | Turnkey, official Dunlop partner. |
+| **WIP Padel** | Southern Italy | wippadel.it | Via website | CE marked, 40+ years in sports. |
+| **Merli Sport** | Ravenna | merlisport.com | Via website | 500+ courts, "The Wall" showroom. |
+| **NXPadel** | Italy | nxpadel.com | Via website | Patented fiberglass technology. |
+| **Edil Padel S.R.L.** | Italy | edilpadel.it | Via website | Construction/installation, wood/steel. |
+| **Durocem Italia** | Italy | durocem.it/padel/ | Via website | Civil works & court installation, Padel Technologies distributor. |
+| **Top Padel Italia** | Italy | toppadelitalia.it | Via website | Turnkey, from €15,800. |
+| **Toro Padel** | Italy | toro-padel.it | Via website | 100+ courts since 2019, patented lighting. |
+
+---
+
+## 4. FRANCE
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **EPS Concept** | Moutiers, Brittany | eps-concept.com | +33 2 99 96 42 61 / contact@eps-concept.com | French manufacturer, 2,000+ courts, FFT PQP certified. |
+| **Padel 360** | Bischheim (67) | padel360.fr | +33 7 80 91 69 43 / direction@padel360.fr | FFT PQP® certified. Turnkey with 10-year warranty. Automated club solutions and video scoring. |
+| **France Padel** | Paris/Bidart | france-padel.fr | +33 5 35 45 55 00 / contact@france-padel.fr | Only French company 100% padel-dedicated. Premium, innovation-focused. |
+| **100% Padel** | France | centpourcentpadel.fr | +33 6 69 78 18 47 | Family company managed by pro player Jérémy Scatena. French-manufactured. |
+| **Constructeur Padel** | France | constructeur-padel.fr | +33 1 59 30 28 24 / contact@constructeur-padel.fr | 10+ years, 100+ clubs. Turnkey FIP-compliant courts. French/ecological materials. |
+| **Le Padel Français** | France | lepadelfrancais.fr | Via website | 100% made in France, eco-responsible. Qualisteelcoat C5 protection. Hydro'Way permeable flooring. |
+| **Metal Padel (Groupe Metal Laser)** | Rousset (13) | padel.metal-laser.com | Via website | First French padel manufacturer. FFT approved. Installs in France, Sweden, UK, Mauritius. |
+| **SMC2 Construction** | Mornant, near Lyon | smc2-construction.com | +33 4 78 67 60 56 / contact@smc2-construction.com | Covered halls specialist, wood & textile membrane. Largest athletics hall in Southern Europe. |
+| **Univers Construction** | Bouc-Bel-Air | universconstruction.com | +33 6 69 02 08 09 | French manufacturer/installer. Showcase center with 9 courts. 20+ years. |
+| **3S Sport Systems** | Montpellier | sportsystems.fr | Via website | 3mm steel, Saint-Gobain Securit glass. 20-year structure warranty. |
+| **WeOui Padel** | Valence | terrain-padel.com | Via website | High-end courts, FFT compliant. Custom furniture, decoration, accessories, maintenance. |
+| **KIP Sport** | France | kipsport.fr | Via website | FFT Qualisport, 30 years sports infra. |
+| **Storkeo** | France | storkeo.com | Via website | Turnkey including real estate & financing. |
+| **VW Sports Padel** | Noisy-le-Grand (93) | vwsports.fr | +33 1 48 45 04 29 / contact@vwsports.fr | Historic tennis company, now padel, French manufacturing. |
+| **Lauralu Industrie** | South-West France | lauralu.com | Via website | Padel court covers/halls specialist, FFT certified, 20+ years. |
+| **ACS Production** | Near Nantes | Via website | +33 2 40 45 94 94 / contact@acs-production.com | Court construction & coverage, 20+ years, 25-yr membrane warranty. |
+| **Concasport** | France | Via website | Via website | French manufacturer, custom designs. |
+| **FieldTurf (Tarkett)** | France/Global | fieldturf.com | Via website | 25+ years synthetic turf for tennis/padel. 1,000,000+ m² installed. FIFA Preferred Producer. |
+| **Losberger De Boer** | International | losbergerdeboer.com | Via website | Semi-permanent modular padel structures. Aluminum/wood frames with canvas roofing. |
+| **Infinite Padel Courts** | France/North Africa | infinitepadelcourts.com | infinitepadelcourts@gmail.com | Custom courts, height-adjustable, manufactured in Alicante. |
+
+---
+
+## 5. PORTUGAL
+
+| Company | Location | Website | Description |
+|---------|----------|---------|-------------|
+| **inCourts Padel** | Lisbon | incourtspadel.com | Robotic manufacturing, factory in North Portugal. |
+| **Greenpark** | Portugal | greenpark.com.pt | First Portuguese-made padel court manufacturer. |
+| **Sports Evolution** | Portugal | sports-evolution.pt | Builder/installer, also manufactures covers. |
+| **Sports Partner** | Portugal | sportspartner.pt | Equipment supplier, multi-sport. |
+
+---
+
+## 6. UNITED KINGDOM
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **PRO Padel Courts** | UK | propadelcourts.com | Via website | Times 100 Ones to Watch 2025. Italian-engineered, patented anti-noise system. 50-year lifespan. MejorSet master distributor UK. |
+| **Portico Sport UK** | Congleton, Manchester | porticosport.com/uk | Via website | From £15,000. Part of Portico Sport global network. |
+| **Padel Tech** | UK | padeltech.co.uk | Via website | Leading UK supplier/installer. Exclusive AFP Courts/adidas UK distributor. 150+ courts. |
+| **Hexa Padel** | Woodford Green, Essex | hexapadel.co.uk | Via website | One of UK's largest builders. Courts, canopies, booking software, maintenance, academy. |
+| **iPadel Ltd** | UK | ipadel.co.uk | Via website | Independent consultancy. Free quotes from multiple suppliers. Planning and investment matchmaking. |
+| **SG Padel** | UK | sgpadel.co.uk | Via website | Turnkey, MejorSet distributor, SAPCA approved. |
+| **SIS Pitches / SISTurf** | UK | sispitches.com | Via website | 25+ years elite sports surfaces. UK-based turf manufacturer. Design, manufacture, install, maintain. 65 years. |
+| **Padel Magic UK** | Nationwide | padelmagic.co.uk | Via website | Proprietary "Magic Base" for uneven terrain. Custom covers/canopies. Nationwide. |
+| **Padel Build UK** | North Lincolnshire | padelbuilduk.com | Via website | UK manufacturer, hot-dip galvanizing. |
+| **Padel Systems** | UK | padelsystems.co.uk | Via website | Bespoke builder, partners with Italian Padel. Sister company: CopriSystems. |
+| **Red Raven Solutions** | UK | redravensolutions.co.uk | Via website | Exclusive PadelCreations UK distributor, 500+ courts. |
+| **Padel Works** | Whitchurch, Shropshire | padelworks.co.uk | Via website | FIP-approved courts. 10-year warranty on court and surface. |
+| **Padel Galis UK** | Coventry | padelgalis.uk | info@padelgalis.uk / +44 7494 045287 | Exclusive UK supplier of Padel Galis. |
+| **S&C Slatter** | UK | slattersportsconstruction.com | Via website | 30+ years sports construction. Partners with FieldTurf. In-house civil engineering. |
+| **Fordingbridge** | West Sussex | fordingbridge.co.uk | info@fordingbridge.co.uk / +44 1243 554455 | UK's leading padel canopy specialist, 60+ years, 25-yr guarantee. |
+| **Collinson Tensile** | UK | collinsontensile.co.uk | Via website | 20+ years tensile buildings. Exclusive UK partner for Best-Hall Finland. ISO 9001/45001. |
+| **Rubb UK** | Gateshead | rubbuk.com | Via website | Fabric building specialists. Thermohall® insulation. Modular, relocatable. |
+| **J & J Carter** | UK | jjcarter.com | Via website | Tensile sports halls, inflatable halls, frame/fabric structures. |
+| **Padel Consulting** | London | padelconsulting.co.uk | Via website | Advisory firm, SAPCA member. |
+
+---
+
+## 7. NETHERLANDS
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Allesvoorpadel (Padelbouw)** | Biddinghuizen | allesvoorpadel.nl | info@allesvoorpadel.nl / +31 320 331 588 | Leading Dutch builder, 10+ years, NK Padel official rink builder. AFP Courts partner. Philips lighting, Padelkiosk vending. |
+| **Padel Nederland B.V.** | Monster | padelnederland.nl | info@padelnederland.nl / +31 6 83 67 51 47 | Durable aluminum courts, 15-year warranty. KIWA/KNLTB certified. Solar canopy options, acoustic solutions. |
+| **I-Padel** | Netherlands | i-padel.nl | Via website | Dutch manufacturer, in-house production. KIWA ISA Sport / NOC*NSF certified. 15-year warranty. Also offers Ping-Pong Padel. |
+| **SkyPadel NL** | Zuid-Holland | skypadel.nl | info@skypadel.nl / +31 638 448 409 | 1,800+ courts, Babolat official, since 2002. |
+| **Padel.nl** | Netherlands | padel.nl | Via website | Since 2003, KNLTB/KIWA certified, proprietary foundations. |
+| **Orange Padel International** | Netherlands | orangepadel.nl | Via website | Premium Dutch-designed courts. 30 years experience. FEMEPA certification. |
+| **Padel Solution NL** | Netherlands | padelsolution.nl | Via website | 3,000+ installed courts in 30+ countries. Full project support. |
+| **World Padel** | Netherlands | worldpadel.nl | Via website | Court supplier. |
+| **Frisomat** | Belgium (NL-active) | frisomat.com | Via website | Nearly 50 years steel construction. Cold-formed galvanized padel canopies/roofs. Modular, demountable. |
+
+---
+
+## 8. BELGIUM
+
+| Company | Location | Website | Description |
+|---------|----------|---------|-------------|
+| **Padel Projects** | Belgium (Benelux) | padelprojects.eu | Court construction, 10+ years, 3,000+ courts, patented lighting. |
+| **JM Padel** | Province of Liège | jmpadel.be | Installer, consultant, club management, IT/video. |
+| **YoPadel SPRL** | Belgium | yopadel.be | Belgian manufacturer, Belgian materials, Lano Sports turf partner. |
+| **Domo Sports Grass** | Belgium | Via website | Global artificial grass expert. Certified by major sports federations. |
+
+---
+
+## 9. SCANDINAVIA (Sweden, Denmark, Finland, Norway)
+
+| Company | Country | Website | Contact | Description |
+|---------|---------|---------|---------|-------------|
+| **Padeltotal** | Sweden | padeltotal.se | Via website | Largest Nordic supplier. 1,600+ courts since 2013. Galvanized for Nordic conditions, 12mm glass. Duruss partnership. |
+| **Padel Global** | Sweden (Jönköping) | padel-global.com | Via website | Manufacturer, own factory. |
+| **Scandinavian Padel AB** | Sweden (Malmö, factory in Lysekil) | scandinavianpadel.co | info@scandinavianpadel.co / +46 761 309080 | 25+ years in steel/glass for Nordic climate. C3 "oil rig grade" steel. Own ScanTurf turf. |
+| **Sweden Padel Master** | Sweden | swedenpadelmaster.se | Via website | SPM Grass Court Cut technology. |
+| **Acenta Group** | Sweden/Norway | acenta.group | Via website | Major Scandinavian company, courts/service/digital/equipment. Fiberglass courts expanding to AU/NZ. |
+| **Instantpadel (Instant Courts)** | Sweden | instantcourts.com | Via website | World-unique mobile court, setup in <4 hours. 150+ courts, 17 countries. Installations at Gleneagles, Soho Club London. |
+| **Hallgruppen** | Sweden/Nordic | hallgruppen.com | Via website | Padel hall structures. Self-supporting steel frames (50-year lifespan). Rental, leasing, purchase. CE approved. |
+| **Best-Hall** | Finland | Via website | Via website | 5,500+ buildings worldwide. 40+ years. Fabric structures for sports halls. UK partner: Collinson Tensile. |
+| **ViPadel** | Denmark (+ Finland) | vipadel.dk | Via website | Total supplier, official Mondo dealer DK/FI. |
+| **A-Sport** | Denmark | a-sport.dk | Via website | Supplier/installer, 250+ courts. |
+| **Tiebreak International / PadelTotal DK** | Glostrup, Denmark | padeltotal.dk | info@tiebreakinternational.com / +45 36889900 | PadelTotal concept for Denmark, 200+ courts. |
+| **Unisport** | Finland/Denmark | unisport.com | Via website | Court manufacturer, Saltex Tempo turf. |
+
+---
+
+## 10. OTHER EUROPEAN COUNTRIES
+
+| Company | Country | Website | Description |
+|---------|---------|---------|-------------|
+| **Courtwall (Padelcourt Biz)** | Austria | padelcourt.biz | Courts since 1984 (squash) and padel since 2007. 1,200+ courts. ISO certified. |
+| **BORGA** | Sweden/Austria | borga.at | 45+ years building steel halls. Padel hall solutions. |
+| **DUOL** | Slovenia | duol.eu | Air-supported and fabric sports buildings. Nearly 30 years. Online configurator. |
+| **Losberger De Boer** | Germany/International | losbergerdeboer.com | Semi-permanent modular padel structures. |
+
+---
+
+## 11. UNITED STATES
+
+The US padel market reached 688 courts across 180 facilities in 31 states by mid-2025, with 51.5% YoY club growth and a projected trajectory toward 6,800 courts by 2030.
+
+### 11.1 US-Based Manufacturers & Builders
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Absolute Padel** | Mohnton, PA | absolutepadelusa.com | +1 717 445 5036 / info@absolutepadelusa.com | Only North America-based manufacturer. 100+ projects. 50%+ of US courts. Unique Pickleball & Padel combo court. Custom court generator. |
+| **The Padel Box** | Multi-state (CA, FL, NY, NJ, etc.) | thepadelbox.com | info@padelbox.com | US pioneer since 2012, licensed in CA, NV, AZ, UT, NC, NJ. 15+ states. Official MejorSet US/Canada distributor. Hurricane-rated to 180 mph. |
+| **Sportsfield Specialties** | Delhi, NY | sportsfield.com | +1 607 746 8911 / +1 888 975 3343 | USPA-endorsed manufacturer, 100% Made in USA, PaDelhi™ courts. |
+| **USA Padel Center** | Houston, TX | usapadel.com | +1 713 539 3110 / info@usapadelcenter.com | Manufacturer/consultant since 2007. |
+| **Padel One Courts** | Florida (nationwide) | padelonecourts.com | Via website | Premium American-made courts. C5 anti-rust coating. Trusted by Pro Padel League and SVB Mouratoglou Academy. |
+| **Bounce Padel Courts** | Canada (NA-wide) | bouncepadelcourts.com | Via website | Premier North American provider. SGCC/ANSI-certified glass. Hurricane-class anchoring. Converts tennis courts and ice rinks. |
+| **Northeast Padel** | Pocasset, MA | northeastpadel.com | +1 508 759 5636 / info@northeastpadel.com | Division of Cape & Island Tennis & Track, most-awarded US court builder. 50+ facility awards from ASBA. |
+| **Sport Surfaces / Mondo Padel** | West Palm Beach, FL | sportsurfaces.com / mondopadel.com | +1 888 423 1120 / info@mondopadel.com | 150+ years combined team experience. FL licensed. Builds from ground up or converts tennis courts. |
+| **Keystone Sports Construction** | USA | keystonesportsconstruction.com | Via website | Full-service turnkey padel from design to installation. Sportsfield Specialties partner. |
+| **MTJ Sports** | Chicago area | mtjsports.com | Via website | 20+ years sports courts. Padel, pickleball, soccer, tennis. Turnkey for clubs, hotels, municipalities. |
+| **Capas Padel** | USA/Puerto Rico | capaspadel.com | Via website | Builder/consultant, GreenSet surfaces, Smart Padel Club. |
+| **All Racquet Sports** | Sandy, UT / Ft. Myers, FL | allracquetsports.com | info@allracquetsports.com | Official adidas/AFP US distributor, 700+ courts network. |
+
+### 11.2 European Manufacturers with US Operations
+
+| Company | HQ | Website | Contact | Description |
+|---------|------|---------|---------|-------------|
+| **MejorSet USA** | Spain | mejorset.com/us-eng | info@mejorset.com / d.polerecky@mejorset.com | Official FIP 2025–2026 court. Bilingual US team. X-Treme model resists 165 mph winds. FL Building Code certified. |
+| **Portico Sport USA** | Spain | porticosport.com/padel-court-usa | Via website | US since 2021. Projects in NY, TX, FL, CA, IL, MA. AISC-360 and ASCE SEI 7-16 compliant. 10-year guarantee. |
+| **AFP Courts** | Spain/Portugal | afpcourts.com | Via website | Official PPL court. adidas and RedSport manufacturer. |
+| **Padel Galis** | Spain | padelgalis.com | Via website | 10,000+ courts globally. Wilson partnership. |
+| **Italgreen** | Italy | italgreen.org | Via website | Patented fiberglass courts (zero rust). FIP technical sponsor. Up to 10-year warranty. |
+| **SkyPadel** | Spain | skypadel.com | Via website | 1,500+ courts in 40+ countries. |
+
+### 11.3 US Padel Franchises & Operators
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Conquer Padel Club** | Lehi, UT (expanding) | conquerpadel.com | Via website | First US padel franchise, $1.1M–$3M+ investment. |
+| **Park Padel** | San Francisco area | parkpadel.com | hello@parkpadel.com | Franchise, pop-up courts, community-focused. |
+| **Jungle Padel** | USA | junglepadel.com | Via website | Franchise, premium Mondo turf, academy. |
+
+### 11.4 Padel Turf Suppliers (North America)
+
+| Company | HQ | Website | Description |
+|---------|------|---------|-------------|
+| **Realturf** | Spain | realturf.com/us | Official USPA sponsor. Drive Pro, Match Play. FEP-compliant. |
+| **CCGrass** | China | ccgrass.com | Three factories. FEP-compliant. FastPro and YEII products. Also complete court packages. |
+| **FieldTurf (Tarkett)** | France/USA | fieldturf.com | 1,000,000+ m² tennis/padel installed. FIFA Preferred Producer. |
+| **WinterGreen Synthetic Grass** | Dallas, TX | wintergreengrass.com | Pro-grade padel turf in DFW. "Padel Pro" surface (same as WPT). |
+| **JCTurf** | China | jcturf.com | In-house fiber extrusion. FIP/FEP compliant. Also complete court solutions. |
+| **MightyGrass** | China | mightygrass.com | Professional padel turf, FEP-level. Factory direct pricing. |
+| **Laykold / Sport Group** | Global | laykold.com | Padel Turf Pro surface. US Open official surface brand. |
+
+### 11.5 Padel Lighting Specialists (North America)
+
+| Company | Location | Website | Description |
+|---------|----------|---------|-------------|
+| **LED Lighting Supply** | USA | ledlightingsupply.com | 15+ years, 25,000+ projects. 150W LED fixtures. Free photometric plans. 5-year warranty. |
+| **Tweener USA** | USA (French-developed) | tweenerusa.com | Patented LED on existing fencing — no poles needed. Minimal light pollution. Dimmable. |
+| **Brite Court** | USA | britecourt.com | 40+ years racquet sports lighting. 600+ facilities. 18+ fixture designs. Samsung LEDs. 10-year warranty. |
+| **AEON LED Lighting** | USA | aeonledlighting.com | Patented luminaires. UGR below 19 (glare-free). 100,000-hour lifespan. DLC Premium listed. |
+| **AGC Lighting** | China | agcled.com | SP11 linear sports light for padel. Smart controls (DALI 2, DMX). Supports 4K broadcasting. |
+
+### 11.6 US Industry Organizations
+
+The **USPA (United States Padel Association)** at padelusa.org endorses select manufacturers and partnered with the **American Sports Builders Association (ASBA)** on the Padel Courts Installation & Maintenance Manual 2025 — the first US construction standard.
+
+---
+
+## 12. MEXICO
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **American Padel (TCDL)** | Mexico City | americanpadel.com.mx | +52 55 5891 3350 / info@americanpadel.com.mx | FIP-compliant, 25 yrs metalwork. |
+| **Padel Center México** | Aguascalientes | padelcenter.mx | Via website | FIP-certified. Clásica, Semipanorámica, Pro models. 20-30 day delivery. |
+| **MG Canchas** | Monterrey | mgcanchas.com | Via website | Pioneer manufacturer in Monterrey. Also supplies synthetic turf. |
+| **SicaSport** | Mexico | sicasport.com | Via website | Manufacturer/builder/installer. |
+| **Gott Padel** | Mexico | gottpadel.com | Via website | Design, installation, construction. Also sells rackets and balls. |
+| **AFP Courts México** | Mexico | afpcourts.mx | Via website | Official adidas licensee for Mexico. |
+| **CanchasdePadel.com** | Mexico | canchasdepadel.com | Via website | FIP-certified. WPT-certified curly turf. |
+| **Padel Works MX** | Mexico | padelworks.com.mx | Via website | High-quality custom courts. Full support from civil works to club growth. |
+| **PadelStore.mx** | Mexico | padelstore.mx | Via website | Court accessories: fencing, nets, posts, turf, sand, LED, protective pads. Ships nationwide. |
+
+---
+
+## 13. MIDDLE EAST (UAE, Saudi Arabia, GCC)
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Padel Factory (ME)** | Dubai, UAE | padelfactory.me | info@padelfactory.me / +971 56 536 9494 | Top manufacturer/supplier. Super Panoramic, Panoramic, Challenger, Portable. 400+ courts. UAE/KSA/Kuwait/Bahrain/Oman. |
+| **RedLine Padel** | Dubai, UAE | redlinepadel.com | Via website | Spanish manufacturer based in Dubai. 48-hour delivery across ME. UNE EN 1090. Also operates clubs. |
+| **Cypex Group** | UAE | cypex-group.com | Via website | Represents Padel Factory ME. Exclusive LANO GRASS Belgium distributor for GCC. Projects across ME, India, Maldives, South Africa. |
+| **APW Pools** | Dubai | apw-pools.com | +971 50 852 1161 | Padel supplier/installer, smart lighting, advanced materials. |
+| **Mister Shade ME** | Dubai | mistershademe.com | Via website | 20+ years in flooring. Artificial turf and acrylic padel courts. All UAE emirates. |
+| **Gebal Group** | Dubai/Abu Dhabi/Riyadh/Jeddah | gebalgroup.com | Dubai: +971 44519691 / Riyadh: +966 55 977 7146 | Turnkey builder across GCC (6 countries). FIP-compliant. |
+| **Empower Sport Services** | UAE | empowersportservices.com | Via website | 2,500+ installations, FEP certified. |
+| **Shades Galaxy** | Dubai | shadesgalaxy.com | Via website | Manufacturer/supplier, all UAE emirates. |
+| **Fab Floorings** | Dubai | fabfloorings.ae | Via website | Turnkey: flooring/glass/lighting/branding. |
+| **Al Mustaqbal Alsarea** | UAE | almustaqbalalsarea.com | +971 50 247 5749 | Gulf countries leader. |
+| **PFS Gulf** | Saudi Arabia | pfsgulf.com | Via website | Infrastructure company. Padel courts across KSA (Riyadh, Jeddah, Dammam, Mecca, Medina). |
+| **Portico Sport ME** | Spain (Dubai/Saudi) | porticosport.com/middle-east | Via website | Active across Dubai, Abu Dhabi, Riyadh, Jeddah, Kuwait. 10-year guarantee. |
+
+---
+
+## 14. TURKEY
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Mediterra Padel** | Antalya | mediterrapadel.com | info@mediterrapadel.com / +90 554 678 51 40 | Turkey's largest, 35+ countries. Active in Kenya, SA, Sierra Leone, Morocco, Nigeria. |
+| **Integral Grass / Integral Spor** | Istanbul | integralgrass.com | info@integralgrass.com / +90 212 678 13 13 | 11 models, 70+ countries. |
+| **Unix Padel** | Istanbul | unixpadel.com | Via website | 7,800+ courts. Wind/impact tested by Istanbul Technical University. FIP-standard, KIWA-tested. |
+
+---
+
+## 15. CHINA — Global Export Manufacturing Powerhouse
+
+Complete court packages from China typically cost $10,400–$12,700 including shipping and installation.
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **Legend Sports (Qifan)** | Yanshan, Hebei | legendsports.com | Via website | One of China's largest. 220+ employees, 32 engineers. 5,000+ courts in 60+ countries. 66,000 m² factory. |
+| **Fortune Padel** | China (offices in Indonesia, Italy, Spain) | fortunepadel.com | Via website | ISO 9001:2015. 20+ models including electric roof. Ships to 50+ countries within 20 days. 99.99% quality pass rate. |
+| **China Youngman Padel / Young Padel** | Hefei, Anhui | youngpadel.com / premierpadelchina.com | Via website | China's largest since 2010. SGS, CE, ISO9001. 8 models. Also produces roof covers. |
+| **Wanhe Padel** | Huaian, Jiangsu | wanhesport.com | Via website | Follows FIP regulations. Courts, 12mm turf, LED lighting. Also padel rackets. |
+| **Shengshi Sports Tech** | Tianjin | shengshitrade.en.made-in-china.com | Via website | 20+ years sports equipment. 10,000+ m² facility near Tianjin port. |
+| **PadelCourt10** | Hebei | padelcourt10.com | Via website | 1,000 sets/year capacity. 25+ countries. 5-year warranty. DDU door-to-door service. |
+| **Shanghai Super Power** | Shanghai | padelcourtfactory.cn | Via website | 200+ employees. 350 courts/month capacity. Aluminum frames for coastal regions. |
+| **UNIPADEL** | Guangzhou | gzunipadel.com | Via website | 5,000+ courts worldwide. Panoramic, classic, portable, roofed, padbol. Active in Indonesia, ME, Africa, LATAM. |
+| **ArtPadel** | China | artpadel.com | Via website | Panoramic/classic courts, patented technology. |
+| **LDK China** | Shenzhen | ldkchina.com | info@ldkchina.com / +86 755 89896763 | Manufacturer/exporter. |
+| **SANJING Group** | Linqu, Shandong | sanjingcourt.com | Via website | Glass specialist, 27 years, 300+ employees, 40+ countries. |
+| **Luckin Padel** | China | luckinpadel.com | Via website | FIP-certified standards, educational focus. |
+| **Saintyol Sports** | China | Alibaba | Via website | 15+ years. Specializes in padel turf and structures. 10,000+ m² facility. |
+| **Nanjing Padelworker** | Nanjing | Alibaba | Via website | 67% client reorder rate. Courts, squash equipment, glass fittings, turf. |
+| **Hebei Aohe Teaching Equipment** | Hebei | Made-in-China | Via website | Est. 2012. 80% repeat business. Also aluminum frame sports tents. |
+| **Shandong Century Star** | Shandong | Alibaba | Via website | Large facility. Steel structure courts, panoramic models. |
+| **CCGrass** | China (global) | ccgrass.com | Via website | Major turf manufacturer. FEP-compliant. FastPro and YEII. Also complete court packages. |
+| **JCTurf** | China | jcturf.com | Via website | In-house fiber extrusion. FIP/FEP compliant. |
+| **MightyGrass** | China | mightygrass.com | Via website | Professional padel turf, FEP-level. Factory direct. |
+
+---
+
+## 16. INDIA
+
+India is projected to add 12,000 padel courts over the next 15 years.
+
+| Company | Location | Website | Description |
+|---------|----------|---------|-------------|
+| **Asian Flooring India (AFI Padel)** | Thane/Mumbai | asianflooring.in / afipadel.com | India's largest padel manufacturer. FIP-standard. Standard, Panoramic, Ultra Panoramic, Kids. 25-day delivery. |
+| **Apex Sport Surfaces (SMI Padel)** | Mumbai | apexsportsurfaces.in | FIP-compliant manufacturer/exporter. |
+| **Sky Padel India** | Mumbai | skypadel.in | Local manufacturing, subsidiary of Spanish Sky Padel. |
+| **PFS Sport India** | India | pfs.sport | Turf & surface manufacturer. |
+| **PadelHaus India** | India | padelhaus.in | Courts for every budget. |
+| **Portico Sport India** | India | porticosport.com/ind | Targets Mumbai, Delhi, Bengaluru, Hyderabad, Pune. Turnkey. |
+
+---
+
+## 17. ASIA (Non-China, Non-India)
+
+| Company | Country | Website | Description |
+|---------|---------|---------|-------------|
+| **SmartPadel / SEARA Sports** | Southeast Asia | smartpadel.asia | Regional leader in SE Asia. Video recording, automated scoring. |
+| **Olympia Courts** | Asia Pacific | olympiacourts.com | Premium courts. European know-how, Asian manufacturing. Partners with Asia Pacific Padel Tour. |
+| **Indo Padel** | Indonesia (Bali) | indopadel.com | Spanish-Indonesian team. Courts manufactured in Indonesia. Active in Thailand, India. |
+| **Padel Asia** | Thailand | padelasia.org | Courts meeting international standards. Also sells rackets, clothing. Operates courts in Bangkok. |
+
+---
+
+## 18. LATIN AMERICA
+
+### 18.1 Brazil
+
+| Company | Website | Contact | Description |
+|---------|---------|---------|-------------|
+| **Padel Master Brasil** | padelmaster.com.br | Via website | 1,500+ courts in 10+ countries. Official Pala Tour/WPT Americas court. 10-year warranty. One of Brazil's most modern plants. |
+| **Sky Padel Brasil** | skypadel.com.br | Via website | 10+ years. FEP/FIP certified. VP PRO 2.0, SP PRO, Full View, rental, mobile. 15-day production. |
+| **Smart Padel** | smart-padel.com | Via website | IoT-connected courts. Online monitoring, strategic management software. FIP/FEP certified. |
+| **Flores Pádel** | florespadel.com.br | Via website | Community-based manufacturer. 10+ years. Turf certified by Spanish federation. Founded by national team athletes. |
+| **FC Quadras** | fcquadras.com.br | Via website | São Paulo. Distributes Padelgest courts. Turnkey from terrain prep to finishing. |
+| **Padel Prime** | padelprime.com.br | Via website | European-standard manufacturing. Co-founded by ex-footballer Edmílson. |
+| **F4 Quadras** | Instagram: @f4quadras | +55 53 9 8447 8700 | Manufacturer/installer. |
+
+### 18.2 Argentina
+
+| Company | Website | Description |
+|---------|---------|-------------|
+| **Padel Courts Master** | padelcourtsmaster.ar | 1,500+ courts delivered worldwide, 8+ countries. Robotic welding. FIP-standard. 100+ padel experts. |
+| **MS Pádel (Metalser)** | metalurgicametalser.com | +54 2314 407746. Professional panoramic courts. Full package: structure, glass, LED, turf. |
+| **World Padel Court** | worldpadelcourt.com.ar | Led by Visión Deportiva (major event organizer). Top-quality courts. Portable court rental. |
+| **Blue Court** | bluecourt.com.ar | 15+ years manufacturing. Established Argentine brand. |
+| **Slavon Césped Sintético** | slavoncespedsintetico.com | Panoramic and full panoramic courts. Also provides synthetic turf. |
+
+### 18.3 Mexico
+
+| Company | Location | Website | Contact | Description |
+|---------|----------|---------|---------|-------------|
+| **American Padel (TCDL)** | Mexico City | americanpadel.com.mx | +52 55 5891 3350 / info@americanpadel.com.mx | FIP-compliant, 25 yrs metalwork. |
+| **Padel Center México** | Aguascalientes | padelcenter.mx | Via website | FIP-certified. Clásica, Semipanorámica, Pro. |
+| **MG Canchas** | Monterrey | mgcanchas.com | Via website | Pioneer manufacturer. Also synthetic turf. |
+| **SicaSport** | Mexico | sicasport.com | Via website | Manufacturer/builder/installer. |
+| **Gott Padel** | Mexico | gottpadel.com | Via website | Design, installation, construction. |
+| **AFP Courts México** | Mexico | afpcourts.mx | Via website | Official adidas licensee for Mexico. |
+| **CanchasdePadel.com** | Mexico | canchasdepadel.com | Via website | FIP-certified. WPT-certified turf. |
+| **Padel Works MX** | Mexico | padelworks.com.mx | Via website | Custom courts, full civil works support. |
+| **PadelStore.mx** | Mexico | padelstore.mx | Via website | Accessories: fencing, nets, turf, sand, LED, pads. |
+
+---
+
+## 19. AFRICA — South Africa Leads the Continent
+
+South Africa has 150+ courts and multiple domestic manufacturers.
+
+| Company | Country | Website | Description |
+|---------|---------|---------|-------------|
+| **Padel Nation** | South Africa | padelnation.co.za | SA's leading manufacturer. 150+ courts. Local hot-dip galvanized manufacturing. 4-week lead times. Up to 10-year warranty. |
+| **Padel Build** | South Africa | padelbuild.co.za | Premier turnkey builder. Partnered with Spain's Padel Galis. First FlexiPadel Base in Africa. |
+| **Techno Padel** | South Africa | technopadel.co.za | Premier supplier/installer. 5–7 day installation. |
+| **Padel Solutions** | South Africa | padelsolutions.co.za | Proudly SA. Design, manufacture, install. Turnkey. |
+| **Padel Quip** | South Africa | padelquip.co.za | Local manufacturer. Basic to Premium Plus models. Partnership and financial assistance. |
+| **Padel Projects SA** | South Africa | padelprojects.co.za | Designed for local conditions. |
+| **Trompie Sport** | South Africa | trompiesport.co.za | Builder, imported & local courts. |
+| **Belgotex Sport** | South Africa | belgotexsport.co.za | SA-based turf manufacturer (Pietermaritzburg factory). UNE 147301:2018 compliant. 220+ installations. |
+| **Africa Padel** | South Africa | africapadel.com | Largest club group in Africa. 21+ clubs across SA. Founded 2021. Events, corporate leagues. |
+| **Portico Sport SA** | South Africa | porticosport.com/za | Completed Virgin Active Padel Club and Atlantic Padel Cape Town. Force 80 hurricane-resistant courts. |
+| **Padel Africa** | Sub-Saharan | padel.africa | Bringing padel to Ghana and Rwanda. Team has started 100+ companies and sold 2,000 courts. |
+| **Technotrade Sports** | Egypt | technotradesports.com | Contractor, one of the best in Arab world. |
+| **Turkan Company** | Egypt/Saudi Arabia | turkan-eg.com | Manufacturer, one of first in Egypt. |
+
+---
+
+## 20. AUSTRALIA & OCEANIA
+
+| Company | Location | Website | Description |
+|---------|----------|---------|-------------|
+| **Laykold Padel / APT Asia Pacific** | Melbourne | aptasiapacific.com.au | Asia Pacific's largest sports surfaces company. Australian-made turf. Aluminium 6061-T6 frames. AS/NZS certified. Pop-up courts. |
+| **Synthetic Padel Courts / Synthetic Sports Group** | Australia | syntheticpadelcourts.com.au / syntheticsportsgroup.com.au | Built the first padel court in Australia. Preferred installer for Indoor Padel Australia, Sydney Racquet Club. A$65K–$100K per court. |
+| **Padel in One Australia** | Australia | padelinone.com.au | Turnkey specialist. 8+ years. Management, marketing, operations consulting. Mission to consolidate padel in Oceania. |
+| **PadelVolt** | Australia/Pacific | padelvolt.com | End-to-end premium service. MejorSet distributor across Oceania and Pacific Islands. Extreme weather designs. |
+| **NXPadel / Acenta Group** | Sweden → Australia | acenta.group | Swedish Acenta Group expanded fiberglass courts to Australia/NZ (Jan 2026). Positioned for Brisbane 2032 Olympics. |
+| **Padel 360 Australia** | Australia | padel360.com.au | Developer/builder/manager, Gimpadel partner. |
+| **AS Lodge Tennis Courts** | Melbourne, VIC | asltenniscourts.com.au | Builder, $90K–$130K per court. |
+| **All Sport Projects** | QLD & NSW | allsportprojects.com.au | Non-rust aluminum courts, 7–15 yr warranties. |
+| **SPORTENG** | Australia | sporteng.com.au | Consulting/design, developed official Padel Australia Guidelines. |
+
+---
+
+## 21. PAN-EUROPEAN / GLOBAL BOOKING SOFTWARE & TECHNOLOGY
+
+| Company | HQ | Website | Description |
+|---------|------|---------|-------------|
+| **Playtomic** | Spain (Madrid) | playtomic.com | World's largest racket sports platform. 6,700+ clubs, 4M+ players, 52+ countries. €56M raised. Published Global Padel Report 2025. |
+| **MATCHi / Matchpoint** | Sweden | tpcmatchpoint.com | 1,600+ venues. Multi-sport booking, memberships, leagues, coaching. |
+| **Padel Mates** | Europe | Via website | All-in-one platform. Won Rocket Padel (Europe's largest indoor chain). Gamification features. |
+| **SmashClub** | Europe | smashclub.cloud | Padel CRM and club management. Integrates with Playtomic, MATCHi, Padel Mates. |
+| **Taykus** | Spain | Via website | Software specifically for padel clubs. Online booking, payment, communication automation. |
+| **Playbypoint** | USA | playbypoint.com | Official tech partner of 2025 US Open Padel. Custom branded app per club. |
+| **CourtReserve** | USA | Via website | Leading US platform alongside Playbypoint and Playtomic. |
+| **360Player** | International | en-us.360player.com | Club management with website builder, video analysis, player development tools. |
+| **Booklux** | International | booklux.com | Customizable booking system. Stripe payments. Google Analytics integration. |
+| **SetTime** | International | settime.io | Free padel booking software. Google/Apple Calendar sync. Analytics for utilization. |
+| **ProPadelKit** | International | propadelkit.com | Turnkey court solutions, Classic & Fusion models. |
+
+---
+
+## 22. KEY CERTIFICATIONS & INDUSTRY BODIES
+
+| Body | Description |
+|------|-------------|
+| **FIP (International Padel Federation)** | Sets global court standards. Current official suppliers: MejorSet (courts), Mondo (turf). |
+| **International Padel Cluster (CIP)** | Madrid. 132+ member companies, €2B+ combined turnover. clusterpadel.com |
+| **FEP (Spanish Padel Federation)** | Approves manufacturers for world's largest domestic market. |
+| **USPA (US Padel Association)** | Endorses US manufacturers. Partnered with ASBA on first US construction standard (2025). |
+| **FFT PQP (French Tennis Federation)** | Quality certification for French market. |
+| **KIWA / NOC*NSF** | Dutch quality standard for sports equipment. |
+| **DPV (Deutscher Padel Verband)** | German federation quality criteria. |
+| **SAPCA** | UK Sports and Play Construction Association. |
+
+---
+
+*Directory compiled February 2025. The padel industry is evolving rapidly — new entrants appear monthly. The global court count is projected to reach ~84,000 by end of 2026. For the most current information, check the International Padel Cluster directory (clusterpadel.com) and regional federation websites.*
diff --git a/padelnomics/src/padelnomics/app.py b/padelnomics/src/padelnomics/app.py
index 3947f84..7c766fc 100644
--- a/padelnomics/src/padelnomics/app.py
+++ b/padelnomics/src/padelnomics/app.py
@@ -81,6 +81,7 @@ def create_app() -> Quart:
from .auth.routes import bp as auth_bp
from .billing.routes import bp as billing_bp
from .dashboard.routes import bp as dashboard_bp
+ from .directory.routes import bp as directory_bp
from .leads.routes import bp as leads_bp
from .planner.routes import bp as planner_bp
from .public.routes import bp as public_bp
@@ -91,6 +92,7 @@ def create_app() -> Quart:
app.register_blueprint(billing_bp)
app.register_blueprint(planner_bp)
app.register_blueprint(leads_bp)
+ app.register_blueprint(directory_bp)
app.register_blueprint(admin_bp)
# Request ID tracking
diff --git a/padelnomics/src/padelnomics/core.py b/padelnomics/src/padelnomics/core.py
index 303ddb4..a1c12f5 100644
--- a/padelnomics/src/padelnomics/core.py
+++ b/padelnomics/src/padelnomics/core.py
@@ -45,6 +45,7 @@ class Config:
PADDLE_PRICES: dict = {
"starter": os.getenv("PADDLE_PRICE_STARTER", ""),
"pro": os.getenv("PADDLE_PRICE_PRO", ""),
+ "business_plan": os.getenv("PADDLE_PRICE_BUSINESS_PLAN", ""),
}
RESEND_API_KEY: str = os.getenv("RESEND_API_KEY", "")
diff --git a/padelnomics/src/padelnomics/directory/__init__.py b/padelnomics/src/padelnomics/directory/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/padelnomics/src/padelnomics/directory/routes.py b/padelnomics/src/padelnomics/directory/routes.py
new file mode 100644
index 0000000..d3527fd
--- /dev/null
+++ b/padelnomics/src/padelnomics/directory/routes.py
@@ -0,0 +1,165 @@
+"""
+Supplier directory: public, searchable listing of padel court suppliers.
+"""
+from datetime import datetime, timezone
+from pathlib import Path
+
+from quart import Blueprint, render_template, request
+
+from ..core import fetch_all, fetch_one
+
+bp = Blueprint(
+ "directory",
+ __name__,
+ url_prefix="/directory",
+ template_folder=str(Path(__file__).parent / "templates"),
+)
+
+COUNTRY_LABELS = {
+ "DE": "Germany", "ES": "Spain", "IT": "Italy", "FR": "France",
+ "PT": "Portugal", "GB": "United Kingdom", "NL": "Netherlands",
+ "BE": "Belgium", "SE": "Sweden", "DK": "Denmark", "FI": "Finland",
+ "NO": "Norway", "AT": "Austria", "SI": "Slovenia", "IS": "Iceland",
+ "CH": "Switzerland", "EE": "Estonia",
+ "US": "United States", "CA": "Canada",
+ "MX": "Mexico", "BR": "Brazil", "AR": "Argentina",
+ "AE": "UAE", "SA": "Saudi Arabia", "TR": "Turkey",
+ "CN": "China", "IN": "India", "SG": "Singapore",
+ "ID": "Indonesia", "TH": "Thailand", "AU": "Australia",
+ "ZA": "South Africa", "EG": "Egypt",
+}
+
+CATEGORY_LABELS = {
+ "manufacturer": "Manufacturer",
+ "turnkey": "Turnkey Provider",
+ "consultant": "Consultant",
+ "hall_builder": "Hall Builder",
+ "turf": "Turf / Surfaces",
+ "lighting": "Lighting",
+ "software": "Software",
+ "industry_body": "Industry Body",
+ "franchise": "Franchise / Operator",
+}
+
+REGION_LABELS = {
+ "Europe": "Europe",
+ "North America": "North America",
+ "Latin America": "Latin America",
+ "Middle East": "Middle East",
+ "Asia Pacific": "Asia Pacific",
+ "Africa": "Africa",
+}
+
+
+async def _build_directory_query(q, country, category, region, page, per_page=24):
+ """Shared query builder for directory index and HTMX results."""
+ now = datetime.now(timezone.utc).isoformat()
+
+ params: list = []
+ wheres: list[str] = []
+
+ if q:
+ terms = [t for t in q.split() if t]
+ if terms:
+ fts_q = " ".join(t + "*" for t in terms)
+ wheres.append(
+ "s.id IN (SELECT rowid FROM suppliers_fts WHERE suppliers_fts MATCH ?)"
+ )
+ params.append(fts_q)
+
+ if country:
+ wheres.append("s.country_code = ?")
+ params.append(country)
+
+ if category:
+ wheres.append("s.category = ?")
+ params.append(category)
+
+ if region:
+ wheres.append("s.region = ?")
+ params.append(region)
+
+ where = " AND ".join(wheres) if wheres else "1=1"
+
+ count_row = await fetch_one(
+ f"SELECT COUNT(*) as cnt FROM suppliers s WHERE {where}",
+ tuple(params),
+ )
+ total = count_row["cnt"] if count_row else 0
+
+ offset = (page - 1) * per_page
+ # Tier-based ordering: sticky first, then pro > growth > free, then name
+ order_params = [now, country or ""] + params + [per_page, offset]
+ suppliers = await fetch_all(
+ f"""SELECT * FROM suppliers s WHERE {where}
+ ORDER BY
+ CASE WHEN s.sticky_until > ? AND (s.sticky_country IS NULL OR s.sticky_country = '' OR s.sticky_country = ?) THEN 0 ELSE 1 END,
+ CASE s.tier WHEN 'pro' THEN 0 WHEN 'growth' THEN 1 ELSE 2 END,
+ s.name
+ LIMIT ? OFFSET ?""",
+ tuple(order_params),
+ )
+
+ total_pages = max(1, (total + per_page - 1) // per_page)
+
+ return {
+ "suppliers": suppliers,
+ "q": q,
+ "country": country,
+ "category": category,
+ "region": region,
+ "page": page,
+ "total_pages": total_pages,
+ "total": total,
+ "now": now,
+ "country_labels": COUNTRY_LABELS,
+ "category_labels": CATEGORY_LABELS,
+ }
+
+
+@bp.route("/")
+async def index():
+ q = request.args.get("q", "").strip()
+ country = request.args.get("country", "")
+ category = request.args.get("category", "")
+ region = request.args.get("region", "")
+ page = max(1, int(request.args.get("page", "1") or "1"))
+
+ ctx = await _build_directory_query(q, country, category, region, page)
+
+ country_counts = await fetch_all(
+ "SELECT country_code, COUNT(*) as cnt FROM suppliers"
+ " GROUP BY country_code ORDER BY cnt DESC"
+ )
+
+ category_counts = await fetch_all(
+ "SELECT category, COUNT(*) as cnt FROM suppliers"
+ " GROUP BY category ORDER BY cnt DESC"
+ )
+
+ total_suppliers = await fetch_one("SELECT COUNT(*) as cnt FROM suppliers")
+ total_countries = await fetch_one(
+ "SELECT COUNT(DISTINCT country_code) as cnt FROM suppliers"
+ )
+
+ return await render_template(
+ "directory.html",
+ **ctx,
+ country_counts=country_counts,
+ category_counts=category_counts,
+ total_suppliers=total_suppliers["cnt"] if total_suppliers else 0,
+ total_countries=total_countries["cnt"] if total_countries else 0,
+ )
+
+
+@bp.route("/results")
+async def results():
+ """HTMX endpoint — returns only the results partial."""
+ q = request.args.get("q", "").strip()
+ country = request.args.get("country", "")
+ category = request.args.get("category", "")
+ region = request.args.get("region", "")
+ page = max(1, int(request.args.get("page", "1") or "1"))
+
+ ctx = await _build_directory_query(q, country, category, region, page)
+ return await render_template("partials/results.html", **ctx)
diff --git a/padelnomics/src/padelnomics/directory/templates/directory.html b/padelnomics/src/padelnomics/directory/templates/directory.html
new file mode 100644
index 0000000..3f4f20a
--- /dev/null
+++ b/padelnomics/src/padelnomics/directory/templates/directory.html
@@ -0,0 +1,214 @@
+{% extends "base.html" %}
+
+{% block title %}Padel Court Supplier Directory - {{ config.APP_NAME }}{% endblock %}
+
+{% block head %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
Padel Court Supplier Directory
+
Browse {{ total_suppliers }}+ suppliers across {{ total_countries }} countries. Find manufacturers, builders, and specialists for your project.
+
+ {{ total_suppliers }} suppliers
+ {{ total_countries }} countries
+ {{ category_counts | length }} categories
+
+
+
+
+
+
+
+ {% if q or country or category or region %}
+
+ {% if q %}
Search: "{{ q }}" × {% endif %}
+ {% if country %}
{{ country_labels.get(country, country) }} × {% endif %}
+ {% if category %}
{{ category_labels.get(category, category) }} × {% endif %}
+
Clear all
+
+ {% endif %}
+
+
+
+ {% include "partials/results.html" %}
+
+
+
+
+ Are you a padel court supplier?
+ Get listed and connect with entrepreneurs planning padel projects.
+ Get Listed
+
+
+{% endblock %}
diff --git a/padelnomics/src/padelnomics/directory/templates/partials/results.html b/padelnomics/src/padelnomics/directory/templates/partials/results.html
new file mode 100644
index 0000000..db40662
--- /dev/null
+++ b/padelnomics/src/padelnomics/directory/templates/partials/results.html
@@ -0,0 +1,118 @@
+
+ Showing {{ suppliers | length }} of {{ total }} supplier{{ 's' if total != 1 }}
+ {% if page > 1 %} (page {{ page }}){% endif %}
+
+
+{% if suppliers %}
+
+ {# Example card on first page with no active filters #}
+ {% if page == 1 and not q and not country and not category %}
+
+
Example
+
+
Your City, Your Country
+
Verified ✓
+
Full description of your company, services, certifications, and experience. Pro listings get maximum visibility with logo, website, and priority placement.
+
+
+ Your listing could look like this →
+
+
+ {% endif %}
+
+ {% for s in suppliers %}
+ {# --- Pro tier card --- #}
+ {% if s.tier == 'pro' %}
+
+ {% if s.sticky_until and s.sticky_until > now %}
Featured
{% endif %}
+
+
+ {% if s.logo_url %}
{% endif %}
+
{{ s.name }}
+
+
{{ category_labels.get(s.category, s.category) }}
+
+
{{ country_labels.get(s.country_code, s.country_code) }}{% if s.city %}, {{ s.city }}{% endif %}
+
Verified ✓
+ {% if s.description %}
+
{{ s.description }}
+ {% endif %}
+
+
+
+ {# --- Growth tier card --- #}
+ {% elif s.tier == 'growth' %}
+
+ {% if s.sticky_until and s.sticky_until > now %}
Featured
{% endif %}
+
+
{{ s.name }}
+ {{ category_labels.get(s.category, s.category) }}
+
+
{{ country_labels.get(s.country_code, s.country_code) }}{% if s.city %}, {{ s.city }}{% endif %}
+
Growth
+ {% if s.description %}
+
{{ s.description }}
+ {% endif %}
+
+
+
+ {# --- Free / unclaimed tier card --- #}
+ {% else %}
+
+
+
{{ s.name }}
+ {{ category_labels.get(s.category, s.category) }}
+
+
{{ country_labels.get(s.country_code, s.country_code) }}{% if s.city %}, {{ s.city }}{% endif %}
+
Unverified
+
+
+ {% endif %}
+ {% endfor %}
+
+
+
+{% if total_pages > 1 %}
+
+{% endif %}
+
+{% else %}
+
+{% endif %}
diff --git a/padelnomics/src/padelnomics/leads/routes.py b/padelnomics/src/padelnomics/leads/routes.py
index d966dca..6ce6628 100644
--- a/padelnomics/src/padelnomics/leads/routes.py
+++ b/padelnomics/src/padelnomics/leads/routes.py
@@ -1,10 +1,11 @@
"""
Leads domain: capture interest in court suppliers and financing.
"""
+import json
from datetime import datetime
from pathlib import Path
-from quart import Blueprint, flash, g, redirect, render_template, request, url_for
+from quart import Blueprint, flash, g, jsonify, redirect, render_template, request, url_for
from ..auth.routes import login_required
from ..core import config, csrf_protect, execute, fetch_one, send_email
@@ -17,6 +18,52 @@ bp = Blueprint(
)
+# =============================================================================
+# Heat Score Calculation
+# =============================================================================
+
+def calculate_heat_score(form: dict) -> str:
+ """Score lead readiness from form data. Returns 'hot', 'warm', or 'cool'."""
+ score = 0
+ if form.get("timeline") in ("asap", "3-6mo"):
+ score += 3
+ elif form.get("timeline") == "6-12mo":
+ score += 1
+ phase = form.get("location_status", "")
+ if phase in ("permit_granted",):
+ score += 4
+ elif phase in ("lease_signed", "permit_pending"):
+ score += 3
+ elif phase in ("converting_existing", "permit_not_filed"):
+ score += 2
+ elif phase in ("location_found",):
+ score += 1
+ if form.get("financing_status") in ("self_funded", "loan_approved"):
+ score += 3
+ elif form.get("financing_status") == "seeking":
+ score += 1
+ if form.get("decision_process") == "solo":
+ score += 2
+ elif form.get("decision_process") == "partners":
+ score += 1
+ if form.get("previous_supplier_contact") == "received_quotes":
+ score += 2
+ budget = int(form.get("budget_estimate", 0) or 0)
+ if budget >= 250000:
+ score += 2
+ elif budget >= 100000:
+ score += 1
+ if score >= 10:
+ return "hot"
+ if score >= 5:
+ return "warm"
+ return "cool"
+
+
+# =============================================================================
+# Routes
+# =============================================================================
+
@bp.route("/suppliers", methods=["GET", "POST"])
@login_required
@csrf_protect
@@ -52,7 +99,6 @@ async def suppliers():
)
prefill = {}
if scenario:
- import json
try:
state = json.loads(scenario["state_json"])
prefill["court_count"] = state.get("dblCourts", 0) + state.get("sglCourts", 0)
@@ -95,7 +141,6 @@ async def financing():
)
prefill = {}
if scenario:
- import json
try:
state = json.loads(scenario["state_json"])
prefill["court_count"] = state.get("dblCourts", 0) + state.get("sglCourts", 0)
@@ -103,3 +148,122 @@ async def financing():
pass
return await render_template("financing.html", prefill=prefill)
+
+
+@bp.route("/quote", methods=["GET", "POST"])
+@csrf_protect
+async def quote_request():
+ """3-step lead qualification flow. No login required — guests provide contact info."""
+ if request.method == "POST":
+ is_json = request.content_type and "application/json" in request.content_type
+
+ if is_json:
+ form = await request.get_json()
+ # Normalize: get_json returns a dict, wrap list access
+ services = form.get("services_needed", [])
+ if isinstance(services, str):
+ services = [services]
+ else:
+ form = await request.form
+ services = form.getlist("services_needed")
+
+ # Validate mandatory fields
+ errors = []
+ if not form.get("country"):
+ errors.append("Country is required")
+ if not form.get("timeline"):
+ errors.append("Timeline is required")
+ if not form.get("stakeholder_type"):
+ errors.append("Stakeholder type is required")
+ if not form.get("contact_name", "").strip():
+ errors.append("Full name is required")
+ if not form.get("contact_email", "").strip():
+ errors.append("Email is required")
+ if errors:
+ if is_json:
+ return jsonify({"ok": False, "errors": errors}), 422
+ await flash("; ".join(errors), "error")
+ return redirect(url_for("leads.quote_request"))
+
+ heat = calculate_heat_score(form)
+
+ services_json = json.dumps(services) if services else None
+
+ user_id = g.user["id"] if g.user else None
+ contact_email = form.get("contact_email", "")
+
+ await execute(
+ """INSERT INTO lead_requests
+ (user_id, lead_type, court_count, budget_estimate,
+ facility_type, glass_type, lighting_type, build_context,
+ location, country, timeline, location_status,
+ financing_status, wants_financing_help, decision_process,
+ previous_supplier_contact, services_needed, additional_info,
+ contact_name, contact_email, contact_phone, contact_company,
+ stakeholder_type,
+ heat_score, status, created_at)
+ VALUES (?, 'quote', ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, 'new', ?)""",
+ (
+ user_id,
+ form.get("court_count", 0),
+ form.get("budget_estimate", 0),
+ form.get("facility_type", ""),
+ form.get("glass_type", ""),
+ form.get("lighting_type", ""),
+ form.get("build_context", ""),
+ form.get("city", ""),
+ form.get("country", ""),
+ form.get("timeline", ""),
+ form.get("location_status", ""),
+ form.get("financing_status", ""),
+ 1 if form.get("wants_financing_help") else 0,
+ form.get("decision_process", ""),
+ form.get("previous_supplier_contact", ""),
+ services_json,
+ form.get("additional_info", ""),
+ form.get("contact_name", ""),
+ contact_email,
+ form.get("contact_phone", ""),
+ form.get("contact_company", ""),
+ form.get("stakeholder_type", ""),
+ heat,
+ datetime.utcnow().isoformat(),
+ ),
+ )
+
+ # Notify admin
+ await send_email(
+ config.ADMIN_EMAIL,
+ f"[{heat.upper()}] New quote request from {contact_email}",
+ f"Heat: {heat} "
+ f"Contact: {form.get('contact_name')} <{contact_email}> "
+ f"Stakeholder: {form.get('stakeholder_type')} "
+ f"Facility: {form.get('facility_type')} / {form.get('court_count')} courts "
+ f"Glass: {form.get('glass_type')} | Lighting: {form.get('lighting_type')} "
+ f"Phase: {form.get('location_status')} | Timeline: {form.get('timeline')} "
+ f"Financing: {form.get('financing_status')} | Budget: {form.get('budget_estimate')} "
+ f"City: {form.get('city')} | Country: {form.get('country')}
",
+ )
+
+ if is_json:
+ return jsonify({"ok": True, "heat": heat})
+
+ return await render_template(
+ "quote_submitted.html",
+ heat=heat,
+ court_count=form.get("court_count", ""),
+ facility_type=form.get("facility_type", ""),
+ country=form.get("country", ""),
+ contact_email=contact_email,
+ )
+
+ # Pre-fill from query params (planner passes calculator state)
+ prefill = {
+ "facility_type": request.args.get("venue", ""),
+ "court_count": request.args.get("courts", ""),
+ "glass_type": request.args.get("glass", ""),
+ "lighting_type": request.args.get("lighting", ""),
+ "budget": request.args.get("budget", ""),
+ "country": request.args.get("country", ""),
+ }
+ return await render_template("quote_request.html", prefill=prefill)
diff --git a/padelnomics/src/padelnomics/leads/templates/quote_request.html b/padelnomics/src/padelnomics/leads/templates/quote_request.html
new file mode 100644
index 0000000..6df6c5a
--- /dev/null
+++ b/padelnomics/src/padelnomics/leads/templates/quote_request.html
@@ -0,0 +1,339 @@
+{% extends "base.html" %}
+{% block title %}Get Builder Quotes - {{ config.APP_NAME }}{% endblock %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ Project
+ Details
+ Contact
+
+
+
+
+
+
+
+{% endblock %}
+
+{% block scripts %}
+
+{% endblock %}
diff --git a/padelnomics/src/padelnomics/leads/templates/quote_submitted.html b/padelnomics/src/padelnomics/leads/templates/quote_submitted.html
new file mode 100644
index 0000000..ccda767
--- /dev/null
+++ b/padelnomics/src/padelnomics/leads/templates/quote_submitted.html
@@ -0,0 +1,114 @@
+{% extends "base.html" %}
+{% block title %}You're Matched! - {{ config.APP_NAME }}{% endblock %}
+
+{% block head %}
+
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
You're matched!
+
+ We've matched your
+ {% if court_count %}{{ court_count }}-court{% endif %}
+ {% if facility_type %}{{ facility_type }}{% endif %}
+ project
+ {% if country %}in {{ country }}{% endif %}
+ with verified suppliers who'll reach out with tailored proposals.
+
+
+
+
What happens next
+
+
+ 1
+ Suppliers review your project brief and prepare proposals
+ Now
+
+
+ 2
+ Matched suppliers contact you with tailored quotes
+ 1-2 days
+
+
+ 3
+ Compare proposals and ask follow-up questions
+ 1-2 weeks
+
+
+ 4
+ Choose the supplier that fits your project best
+ At your pace
+
+
+
+
+ {% if contact_email %}
+
+ 📧 A confirmation has been sent to {{ contact_email }} . Check your inbox (and spam folder).
+
+ {% endif %}
+
+ {% if not user %}
+
+
Create an account
+
Save scenarios, track your project, and get notified when suppliers respond.
+
Create Account
+
+ {% else %}
+
+ {% endif %}
+
+
+
+{% endblock %}
diff --git a/padelnomics/src/padelnomics/migrations/schema.sql b/padelnomics/src/padelnomics/migrations/schema.sql
index 2dc8b21..7278721 100644
--- a/padelnomics/src/padelnomics/migrations/schema.sql
+++ b/padelnomics/src/padelnomics/migrations/schema.sql
@@ -125,7 +125,7 @@ CREATE INDEX IF NOT EXISTS idx_scenarios_user ON scenarios(user_id);
-- Lead requests (when user wants supplier quotes or financing)
CREATE TABLE IF NOT EXISTS lead_requests (
id INTEGER PRIMARY KEY AUTOINCREMENT,
- user_id INTEGER NOT NULL REFERENCES users(id),
+ user_id INTEGER REFERENCES users(id),
lead_type TEXT NOT NULL,
scenario_id INTEGER REFERENCES scenarios(id),
location TEXT,
@@ -133,7 +133,80 @@ CREATE TABLE IF NOT EXISTS lead_requests (
budget_estimate INTEGER,
message TEXT,
status TEXT DEFAULT 'new',
- created_at TEXT NOT NULL
+ created_at TEXT NOT NULL,
+
+ -- Phase 0: expanded quote qualification fields
+ facility_type TEXT,
+ glass_type TEXT,
+ lighting_type TEXT,
+ build_context TEXT,
+ country TEXT,
+ timeline TEXT,
+ location_status TEXT,
+ financing_status TEXT,
+ wants_financing_help INTEGER DEFAULT 0,
+ decision_process TEXT,
+ previous_supplier_contact TEXT,
+ services_needed TEXT,
+ additional_info TEXT,
+ contact_name TEXT,
+ contact_email TEXT,
+ contact_phone TEXT,
+ contact_company TEXT,
+ stakeholder_type TEXT,
+ heat_score TEXT DEFAULT 'cool'
);
CREATE INDEX IF NOT EXISTS idx_leads_status ON lead_requests(status);
+CREATE INDEX IF NOT EXISTS idx_leads_heat ON lead_requests(heat_score);
+
+-- Suppliers directory (seeded with unclaimed listings)
+CREATE TABLE IF NOT EXISTS suppliers (
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
+ name TEXT NOT NULL,
+ slug TEXT UNIQUE NOT NULL,
+ country_code TEXT NOT NULL,
+ city TEXT,
+ region TEXT NOT NULL,
+ website TEXT,
+ description TEXT,
+ category TEXT NOT NULL,
+ contact TEXT,
+ claimed_at TEXT,
+ claimed_by INTEGER REFERENCES users(id),
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
+ tier TEXT NOT NULL DEFAULT 'free',
+ logo_url TEXT,
+ is_verified INTEGER NOT NULL DEFAULT 0,
+ highlight INTEGER NOT NULL DEFAULT 0,
+ sticky_until TEXT,
+ sticky_country TEXT
+);
+
+CREATE INDEX IF NOT EXISTS idx_suppliers_country ON suppliers(country_code);
+CREATE INDEX IF NOT EXISTS idx_suppliers_category ON suppliers(category);
+CREATE INDEX IF NOT EXISTS idx_suppliers_slug ON suppliers(slug);
+
+-- FTS5 full-text search for suppliers
+CREATE VIRTUAL TABLE IF NOT EXISTS suppliers_fts USING fts5(
+ name, description, city, country_code, category,
+ content='suppliers', content_rowid='id'
+);
+
+-- Keep FTS in sync with suppliers table
+CREATE TRIGGER IF NOT EXISTS suppliers_ai AFTER INSERT ON suppliers BEGIN
+ INSERT INTO suppliers_fts(rowid, name, description, city, country_code, category)
+ VALUES (new.id, new.name, new.description, new.city, new.country_code, new.category);
+END;
+
+CREATE TRIGGER IF NOT EXISTS suppliers_ad AFTER DELETE ON suppliers BEGIN
+ INSERT INTO suppliers_fts(suppliers_fts, rowid, name, description, city, country_code, category)
+ VALUES ('delete', old.id, old.name, old.description, old.city, old.country_code, old.category);
+END;
+
+CREATE TRIGGER IF NOT EXISTS suppliers_au AFTER UPDATE ON suppliers BEGIN
+ INSERT INTO suppliers_fts(suppliers_fts, rowid, name, description, city, country_code, category)
+ VALUES ('delete', old.id, old.name, old.description, old.city, old.country_code, old.category);
+ INSERT INTO suppliers_fts(rowid, name, description, city, country_code, category)
+ VALUES (new.id, new.name, new.description, new.city, new.country_code, new.category);
+END;
diff --git a/padelnomics/src/padelnomics/migrations/versions/0002_expand_lead_requests.py b/padelnomics/src/padelnomics/migrations/versions/0002_expand_lead_requests.py
new file mode 100644
index 0000000..d6da214
--- /dev/null
+++ b/padelnomics/src/padelnomics/migrations/versions/0002_expand_lead_requests.py
@@ -0,0 +1,30 @@
+"""Expand lead_requests for 3-step quote qualification flow."""
+
+
+def up(conn):
+ cols = {r[1] for r in conn.execute("PRAGMA table_info(lead_requests)")}
+ new_cols = {
+ "facility_type": "TEXT",
+ "glass_type": "TEXT",
+ "lighting_type": "TEXT",
+ "build_context": "TEXT",
+ "country": "TEXT",
+ "timeline": "TEXT",
+ "location_status": "TEXT",
+ "financing_status": "TEXT",
+ "wants_financing_help": "INTEGER DEFAULT 0",
+ "decision_process": "TEXT",
+ "previous_supplier_contact": "TEXT",
+ "services_needed": "TEXT",
+ "additional_info": "TEXT",
+ "contact_name": "TEXT",
+ "contact_email": "TEXT",
+ "contact_phone": "TEXT",
+ "contact_company": "TEXT",
+ "heat_score": "TEXT DEFAULT 'cool'",
+ }
+ for col, col_type in new_cols.items():
+ if col not in cols:
+ conn.execute(
+ f"ALTER TABLE lead_requests ADD COLUMN {col} {col_type}"
+ )
diff --git a/padelnomics/src/padelnomics/migrations/versions/0003_add_stakeholder_type.py b/padelnomics/src/padelnomics/migrations/versions/0003_add_stakeholder_type.py
new file mode 100644
index 0000000..5266bdb
--- /dev/null
+++ b/padelnomics/src/padelnomics/migrations/versions/0003_add_stakeholder_type.py
@@ -0,0 +1,7 @@
+"""Add stakeholder_type column to lead_requests."""
+
+
+def up(conn):
+ cols = {r[1] for r in conn.execute("PRAGMA table_info(lead_requests)")}
+ if "stakeholder_type" not in cols:
+ conn.execute("ALTER TABLE lead_requests ADD COLUMN stakeholder_type TEXT")
diff --git a/padelnomics/src/padelnomics/migrations/versions/0004_create_suppliers.py b/padelnomics/src/padelnomics/migrations/versions/0004_create_suppliers.py
new file mode 100644
index 0000000..89fb361
--- /dev/null
+++ b/padelnomics/src/padelnomics/migrations/versions/0004_create_suppliers.py
@@ -0,0 +1,680 @@
+"""Create suppliers table with FTS5 full-text search and seed directory data."""
+
+import re
+import unicodedata
+
+
+def _slugify(text):
+ text = unicodedata.normalize("NFKD", text).encode("ascii", "ignore").decode()
+ text = re.sub(r"[^\w\s-]", "", text.lower())
+ return re.sub(r"[-\s]+", "-", text).strip("-")
+
+
+_REGION = {
+ "DE": "Europe", "ES": "Europe", "IT": "Europe", "FR": "Europe",
+ "PT": "Europe", "GB": "Europe", "NL": "Europe", "BE": "Europe",
+ "SE": "Europe", "DK": "Europe", "FI": "Europe", "NO": "Europe",
+ "AT": "Europe", "SI": "Europe", "IS": "Europe", "CH": "Europe",
+ "US": "North America", "CA": "North America",
+ "MX": "Latin America", "BR": "Latin America", "AR": "Latin America",
+ "AE": "Middle East", "SA": "Middle East", "TR": "Middle East",
+ "CN": "Asia Pacific", "IN": "Asia Pacific", "SG": "Asia Pacific",
+ "ID": "Asia Pacific", "TH": "Asia Pacific", "AU": "Asia Pacific",
+ "ZA": "Africa", "EG": "Africa",
+}
+
+# (name, country_code, city, website, description, category, contact)
+_SUPPLIERS = [
+ # ---- Germany: Court Manufacturers (1.1) ----
+ ("artec Sportgeräte GmbH", "DE", "Melle", "artec-sportgeraete.de",
+ "30+ years sports equipment. 12mm laminated safety glass, DIN-standard statics, customizable colors. Indoor & outdoor.", "manufacturer", None),
+ ("PADELWERK Court GmbH", "DE", "Dortmund", "padelwerk.de",
+ "Germany's first padel court manufacturer (est. 2021). All components made in Germany. LED lighting, roofing, financing consulting.", "manufacturer", "+49 172 54 60 150"),
+ ("Vindico Sport GmbH", "DE", None, "vindico-sport.de",
+ "Serie B and Pano models. Carbon-steel S235JRH, 10-12mm tempered glass, 9-stage corrosion coating, WPT-standard entrances.", "manufacturer", None),
+ ("Kübler Sport GmbH", "DE", "Backnang", "kuebler-sport.de",
+ "Major sports supplier. CLASSIC and PANORAMA courts with steel structure, tempered glass, wire mesh, artificial turf, 8 LED elements.", "manufacturer", "info@kuebler-sport.de"),
+ ("Padello GmbH", "DE", "Troisdorf", "padello.de",
+ "Full-service builder with own metalworking shop. Steel construction up to 5mm adapted to regional wind loads. DIN-compliant, DEKRA-certified.", "manufacturer", None),
+ ("LOB Sport", "DE", "Nuremberg", "lobsport.de",
+ "40+ years in tennis equipment, now padel. Edge and Infinity models. Foundation-free option for converting sand courts. Part of BECO Bermüller group.", "manufacturer", "+49 911 64200-0"),
+ ("Brako Padel GmbH", "DE", "Berlin", "brakopadel.com",
+ "Padel specialist since 2013. 15-year corrosion warranty. Indoor, outdoor, panoramic, and portable courts. No-foundation portable options.", "manufacturer", None),
+ ("Padel Concept", "DE", None, "padelconcept.de",
+ "Production in Baltic states, serves DACH/Scandinavia/BeNeLux. Own assembly teams. Innovative mobile floor plate solution.", "manufacturer", None),
+ ("Padelsportanlagenbau", "DE", None, "padelsportanlagenbau.de",
+ "Manufacturer & turnkey builder, active in 18+ countries.", "manufacturer", None),
+
+ # ---- Germany: Turnkey & Consultants (1.2) ----
+ ("The Court Company", "DE", "Cologne", "courtcompany.de",
+ "Germany's most experienced padel construction expert. 40+ courts in 2021. Licensed partner of AFP Courts, RedSport, adidas courts. Full service plus leasing.", "turnkey", "+49 2237 6034685"),
+ ("Real Padel GmbH", "DE", "Schönebeck", "realpadel.de",
+ "Premium courts and full-service club consulting. Booking systems, automation, equipment, sponsor connections. DACH broker.", "turnkey", None),
+ ("Padel4U", "DE", None, "padel4u.de",
+ "German distributor for Manzasport. Full range including standard, panoramic, and custom courts. ~1,000 courts/year through partnership.", "turnkey", None),
+ ("Trendsport Rummenigge", "DE", None, "trendsport-rummenigge.de",
+ "Turnkey solutions from concept to completion. Guidance on German funding programs (Sportstättenförderung).", "turnkey", None),
+ ("PadelCity", "DE", None, "padelcity.de",
+ "Germany's largest padel operator (20 facilities, 100+ courts). DTB partner. Proprietary booking app. Franchise model.", "franchise", None),
+ ("padelBOX", "DE", None, "padelbox.de",
+ "Major German padel operator and consultant.", "turnkey", "r.stroehl@padelbox.de"),
+ ("Padel Solution", "DE", None, "padelsolution.de",
+ "Supplier, planner, autonomous club solutions.", "turnkey", None),
+ ("Best World Padel", "DK", None, None,
+ "Official MejorSet distributor for Germany and Denmark.", "turnkey", None),
+
+ # ---- Germany: Hall & Building Constructors (1.3) ----
+ ("SMC2 Bau", "DE", None, "smc2-bau.de",
+ "Textile membrane sport halls — glass court walls serve as lower facade with membrane roof above. Meets German building norms. 10-year guarantee.", "hall_builder", None),
+ ("Padberg Projektbau", "DE", None, "padberg-projektbau.de",
+ "Turnkey padel hall specialist. Expertise in minimum 6-7m ceiling height, glass wall statics, LED lighting. Assists with permits and grant funding.", "hall_builder", None),
+ ("Planeco Building", "DE", None, "planecobuilding.de",
+ "Building permit consultancy for converting warehouses/halls into padel facilities. Fire safety, structural analysis, change-of-use permits.", "consultant", None),
+ ("BORGA", "SE", None, "borga.at",
+ "45+ years building steel halls. 3 standardized padel hall solutions plus custom. Sandwich panels, mezzanine floors, court layout optimization.", "hall_builder", None),
+
+ # ---- Germany: International Manufacturers with DE Presence (1.4) — unique entries only ----
+ ("Padelcreations", "ES", None, "padelcreations.com",
+ "24+ courts in DACH region. 10 court models. Certified Spanish assembly teams.", "manufacturer", "+34 965 049 221"),
+ ("Courtwall", "AT", "Vienna", "padelcourt.biz",
+ "Building courts since 1984 (squash) and padel since 2007. 1,200+ courts worldwide. ISO-certified.", "manufacturer", None),
+ ("Unix Padel", "TR", "Istanbul", "unixpadel.com",
+ "7,800+ courts globally. FIP-standard, KIWA-tested. Up to 12 courts/day production. TÜV-tested components.", "manufacturer", None),
+ ("The Padel Lab", "NL", None, "thepadellab.com",
+ "HD Vision courts, tournament-grade panoramic, modular steel frame technology. Claims 40% reduction in construction time.", "manufacturer", None),
+
+ # ---- Germany: Turf, Lighting, Software (1.5) — unique entries only ----
+ ("BECO Bermüller", "DE", "Nuremberg", "beco-bermueller.de",
+ "German synthetic turf and sports field materials. 40+ years. Parent of LOB Sport.", "turf", "+49 911 64200-0"),
+ ("Polytan GmbH", "DE", None, "polytan.com",
+ "Since 1969. Sports surfaces development and production. Active in padel turf across Europe.", "turf", None),
+ ("Primaflor", "DE", None, "primaflor.de",
+ "Artificial turf supplier for padel courts.", "turf", None),
+ ("bookaball", "DE", None, "bookaball.com",
+ "Booking software for racket sport clubs. Used by German market leaders. €30M+ bookings processed.", "software", None),
+ ("AS LED Lighting", "DE", "Upper Bavaria", None,
+ "Upper Bavaria-based LED specialist for padel.", "lighting", None),
+
+ # ---- Germany: Industry Organizations (1.6) ----
+ ("Deutscher Padel Verband", "DE", None, "dpv-padel.de",
+ "Germany's official padel federation. Provides quality criteria for court purchasing.", "industry_body", None),
+ ("mypadel.de", "DE", None, "mypadel.de",
+ "German Tennis Federation's padel portal. Court finder, leagues, partners.", "industry_body", None),
+ ("padel-test.de", "DE", None, "padel-test.de",
+ "Comprehensive German directory with builder comparisons and filterable listings.", "industry_body", None),
+ ("Padelinsider.de", "DE", None, "padelinsider.de",
+ "German padel information platform with booking system overviews.", "industry_body", None),
+ ("Sportstättenrechner", "DE", None, "sportstaettenrechner.de",
+ "Cost calculators, supplier info, and funding guidance for padel projects.", "industry_body", None),
+ ("Padel Lands", "DE", None, "padellands.com",
+ "International directory listing 60+ German clubs.", "industry_body", None),
+
+ # ---- Spain: Court Manufacturers (2.1) ----
+ ("MejorSet", "ES", "Crevillente", "mejorset.com",
+ "Official court of Premier Padel & FIP. 10,000+ courts in 70+ countries. Part of LeDap group. Pioneer of panoramic design.", "manufacturer", "+34 966 374 289"),
+ ("Padel Galis", "ES", "Valencia", "padelgalis.com",
+ "10,000+ courts, 75+ countries. Partnership with Wilson and Fernando Belasteguín. Official WPT/Premier Padel supplier.", "manufacturer", None),
+ ("Portico Sport", "ES", "Villafranca de los Barros", "porticosport.com",
+ "15+ years, 35+ countries, 4,000+ courts. Only manufacturer also building sports canopies. 110,000+ sqft factory.", "manufacturer", None),
+ ("Padel10", "ES", "Rubí", "padel10.com",
+ "Exclusively padel since 2008. 4,500+ courts. Former WPT official supplier. 15-day production. Mondo and ACT turf systems.", "manufacturer", None),
+ ("Manzasport", "ES", "Beniparrell", "manzasport.com",
+ "Top-4 global builder since 2003, 4 factories in Valencia. 100% Spanish manufacturing, FIP and NIDE 2021 compliant.", "manufacturer", "+34 963 217 472"),
+ ("Maxpeed", "ES", "Viladecans", "maxpeed.com",
+ "Manufacturer, builder, equipment supplier.", "manufacturer", "+34 936 593 961"),
+ ("AFP Courts", "ES", None, "afpcourts.com",
+ "Official adidas licensee. 15+ years, 33+ exclusive distribution centers. Official PPL court. 2,150+ courts under RedSport brand.", "manufacturer", None),
+ ("Jubo Padel", "ES", None, "jubopadel.com",
+ "Family-owned, 25+ years, 6,000+ courts. FIP official. Comprehensive alliance partner network. Court configurator.", "manufacturer", None),
+ ("Padel Courts Deluxe", "ES", "Alicante", "padelcourtsdeluxe.es",
+ "100% Spanish, premium courts. Robotic welding, carbon fibre courts. Partnership with Greenset for surfaces.", "manufacturer", "info@padelcourtsdeluxe.com"),
+ ("SportBS", "ES", "Extremadura", "sportbs.es",
+ "100% Spanish materials. 360° service including courts, lighting, turf, software.", "manufacturer", None),
+ ("Ingode Padel Courts", "ES", "Crevillente", "ingodepadel.com",
+ "ISO 14001 & 9001. Galvanized steel with epoxy paint. 20+ years.", "manufacturer", None),
+ ("SkyPadel", "ES", None, "skypadel.com",
+ "1,500+ courts in 40+ countries. EUROCODE compliant. Featured at WPT events globally. Subsidiaries in India/Brazil/Mexico.", "manufacturer", None),
+ ("PadelMagic", "ES", "Valladolid", "padelmagic.es",
+ "400+ courts/year, 80% exported. FIP-certified.", "manufacturer", None),
+ ("Padel Hispania", "ES", None, "padelhispania.com",
+ "Federation-approved specialist in Spain and Portugal.", "manufacturer", None),
+ ("VerdePadel", "ES", None, "verdepadel.com",
+ "Building since 2004. 2,000+ courts, 40,000+ m² of artificial grass installed.", "manufacturer", None),
+ ("J'hayber Padel", "ES", None, "jhayberinstalaciones.com",
+ "Manufacturer & lighting specialist since 1972.", "manufacturer", None),
+ ("PadelFan Valencia", "ES", "Alaquàs", "padelfanvalencia.com",
+ "10+ years, 800+ customers. Also provides turf replacement.", "manufacturer", "+34 661 320 398"),
+ ("TMPadel", "ES", "Valencia", "tmpadel.com",
+ "Kiwa certified, exports to 6+ EU countries.", "manufacturer", None),
+ ("Niberma", "ES", None, "niberma.es",
+ "700+ courts.", "manufacturer", None),
+ ("Grupo Pineda", "ES", "Griñón", "grupopineda.eu",
+ "Proprietary turf & pavement.", "manufacturer", None),
+ ("MR Instalaciones", "ES", "Madrid", "mrinstalaciones.es",
+ "20+ years, turnkey.", "manufacturer", None),
+ ("X-Treme Group", "ES", "Alcalá de Henares", "x-tremegroup.com",
+ "18+ years, 6,000 m² facility.", "manufacturer", None),
+ ("MTR Padel", "ES", None, "mtrpadel.com",
+ "Methacrylate glass, turnkey.", "manufacturer", None),
+ ("GimPadel", "ES", "León", "gimpadel.com",
+ "800+ installations. Built in Netherlands, Belgium, Italy, Kuwait, Portugal, Kenya.", "manufacturer", None),
+ ("EE Padel", "ES", None, "eepadel.com",
+ "2021 merger of Eljoi Padel, EcoNatura, Swedish investors. Aluminum courts. Ships to US/Canada/ME.", "manufacturer", None),
+ ("Greencourt", "ES", None, "greencourt.es",
+ "Galvanized/aluminum/precast options.", "manufacturer", None),
+ ("ExtremaAdel", "ES", "Extremadura", None,
+ "Aluminum model innovation.", "manufacturer", None),
+ ("Padelgest", "ES", None, None,
+ "Urban/sustainability focus.", "manufacturer", None),
+ ("Iberopadel", "ES", None, "iberopadel.com",
+ "FEP approved, Joma/Maxpeed partner.", "manufacturer", None),
+ ("Padel Alba", "ES", "Granja de Rocamora", "padelalba.com",
+ "25+ years, FIP-compliant.", "manufacturer", None),
+ ("Pistas-Padel.es", "ES", None, "pistas-padel.es",
+ "All court types.", "manufacturer", None),
+
+ # ---- Spain: Turf/Surface (2.2) ----
+ ("Realturf", "ES", None, "realturf.com",
+ "Official USPA sponsor. Drive Pro, Match Play, fibrillated lines. FEP-compliant.", "turf", None),
+ ("Act Sports", "ES", None, "act.sport",
+ "Global padel turf leader, 10,000+ fields.", "turf", None),
+ ("Eurocesped", "ES", None, "eurocesped.com",
+ "ITF parameters.", "turf", None),
+ ("Allgrass", "ES", None, "allgrass.es",
+ "FEP compliant.", "turf", None),
+ ("Albergrass", "ES", "Pilar de la Horadada", "albergrass.com",
+ "Artificial turf for padel courts.", "turf", None),
+
+ # ---- Spain: Lighting (2.3) ----
+ ("Led Projects", "ES", None, "ledprojects.es",
+ "World leader in padel lighting, 5,000+ courts, WPT/Premier Padel official.", "lighting", None),
+ ("Óptima LED", "ES", None, "optimaled.es",
+ "ProTour padel spotlights, anti-glare.", "lighting", None),
+ ("PlazaLED", "ES", "Madrid", "plazaled.es",
+ "Sports LED projectors & scoreboards.", "lighting", None),
+ ("Ellite LED Padel", "ES", None, "ledpadel.com",
+ "30+ years R&D. 360° perimeter system. 10-year warranty. Integration with booking apps.", "lighting", None),
+ ("Ledkia", "ES", None, "ledkia.com",
+ "LED padel court floodlight solutions, 50W-1,250W.", "lighting", None),
+
+ # ---- Spain: Industry Body (2.4) ----
+ ("International Padel Cluster", "ES", "Madrid", "clusterpadel.com",
+ "World's largest padel industry association. 132+ member companies, 165 brands, €2B+ combined turnover.", "industry_body", None),
+
+ # ---- Italy (3) ----
+ ("Mondo S.p.A.", "IT", "Alba", "mondoworldwide.com",
+ "World's leading padel turf manufacturer. Official FIP/Premier Padel turf partner. 13,000+ courts globally.", "turf", None),
+ ("Italgreen", "IT", None, "italgreen.org",
+ "40+ years. Patented fiberglass structure. FIP sponsor. Iron, Full Panoramic, V-PRO courts. Own padel turf lines.", "manufacturer", None),
+ ("Limonta Sport", "IT", None, "limontasport.com",
+ "Premium padel turf, FEP approved, CONI partner.", "turf", None),
+ ("Padel Factory SRL", "IT", "Near Rome", "padelfactorysrl.com",
+ "1,000+ courts. Innovative hybrid wood-steel design. 10+ European countries.", "manufacturer", None),
+ ("Padel Corporation", "IT", None, "padelcorporation.com",
+ "10+ years, 20+ countries. 100% made in Italy, ITF compliant. Full turnkey.", "manufacturer", "+39 339 731 2152"),
+ ("Italian Padel", "IT", None, "italianpadel.it",
+ "3,000+ courts in 28 countries. Up to 180 courts/month. CE certified.", "manufacturer", "info@italianpadel.it"),
+ ("Italia Team Padel", "IT", "Pesaro-Urbino", "italiateampadel.com",
+ "Supplied first court at Foro Italico. Basic, Vision Pro, Full Vision models.", "manufacturer", "+39 0721 571 588"),
+ ("Campidapadel.it", "IT", "Lesmo", "campidapadel.it",
+ "Design, supply, installation, plus financial and marketing consultancy.", "manufacturer", None),
+ ("Favaretti Padel", "IT", "Bagnoli di Sopra", "favarettipadel.it",
+ "Turnkey, official Dunlop partner.", "manufacturer", None),
+ ("WIP Padel", "IT", None, "wippadel.it",
+ "CE marked, 40+ years in sports.", "manufacturer", None),
+ ("Merli Sport", "IT", "Ravenna", "merlisport.com",
+ "500+ courts, 'The Wall' showroom.", "manufacturer", None),
+ ("NXPadel", "IT", None, "nxpadel.com",
+ "Patented fiberglass technology.", "manufacturer", None),
+ ("Edil Padel S.R.L.", "IT", None, "edilpadel.it",
+ "Construction/installation, wood/steel.", "manufacturer", None),
+ ("Durocem Italia", "IT", None, "durocem.it",
+ "Civil works & court installation, Padel Technologies distributor.", "manufacturer", None),
+ ("Top Padel Italia", "IT", None, "toppadelitalia.it",
+ "Turnkey, from €15,800.", "manufacturer", None),
+ ("Toro Padel", "IT", None, "toro-padel.it",
+ "100+ courts since 2019, patented lighting.", "manufacturer", None),
+
+ # ---- France (4) ----
+ ("EPS Concept", "FR", "Moutiers", "eps-concept.com",
+ "French manufacturer, 2,000+ courts, FFT PQP certified.", "manufacturer", "+33 2 99 96 42 61"),
+ ("Padel 360", "FR", "Bischheim", "padel360.fr",
+ "FFT PQP certified. Turnkey with 10-year warranty. Automated club solutions and video scoring.", "manufacturer", "+33 7 80 91 69 43"),
+ ("France Padel", "FR", "Paris", "france-padel.fr",
+ "Only French company 100% padel-dedicated. Premium, innovation-focused.", "manufacturer", "+33 5 35 45 55 00"),
+ ("100% Padel", "FR", None, "centpourcentpadel.fr",
+ "Family company managed by pro player Jérémy Scatena. French-manufactured.", "manufacturer", "+33 6 69 78 18 47"),
+ ("Constructeur Padel", "FR", None, "constructeur-padel.fr",
+ "10+ years, 100+ clubs. Turnkey FIP-compliant courts. French/ecological materials.", "manufacturer", "+33 1 59 30 28 24"),
+ ("Le Padel Français", "FR", None, "lepadelfrancais.fr",
+ "100% made in France, eco-responsible. Qualisteelcoat C5 protection. Hydro'Way permeable flooring.", "manufacturer", None),
+ ("Metal Padel", "FR", "Rousset", "padel.metal-laser.com",
+ "First French padel manufacturer. FFT approved. Installs in France, Sweden, UK, Mauritius.", "manufacturer", None),
+ ("SMC2 Construction", "FR", "Mornant", "smc2-construction.com",
+ "Covered halls specialist, wood & textile membrane. Largest athletics hall in Southern Europe.", "hall_builder", "+33 4 78 67 60 56"),
+ ("Univers Construction", "FR", "Bouc-Bel-Air", "universconstruction.com",
+ "French manufacturer/installer. Showcase center with 9 courts. 20+ years.", "manufacturer", "+33 6 69 02 08 09"),
+ ("3S Sport Systems", "FR", "Montpellier", "sportsystems.fr",
+ "3mm steel, Saint-Gobain Securit glass. 20-year structure warranty.", "manufacturer", None),
+ ("WeOui Padel", "FR", "Valence", "terrain-padel.com",
+ "High-end courts, FFT compliant. Custom furniture, decoration, accessories, maintenance.", "manufacturer", None),
+ ("KIP Sport", "FR", None, "kipsport.fr",
+ "FFT Qualisport, 30 years sports infra.", "manufacturer", None),
+ ("Storkeo", "FR", None, "storkeo.com",
+ "Turnkey including real estate & financing.", "turnkey", None),
+ ("VW Sports Padel", "FR", "Noisy-le-Grand", "vwsports.fr",
+ "Historic tennis company, now padel, French manufacturing.", "manufacturer", "+33 1 48 45 04 29"),
+ ("Lauralu Industrie", "FR", None, "lauralu.com",
+ "Padel court covers/halls specialist, FFT certified, 20+ years.", "hall_builder", None),
+ ("ACS Production", "FR", "Near Nantes", None,
+ "Court construction & coverage, 20+ years, 25-yr membrane warranty.", "manufacturer", "+33 2 40 45 94 94"),
+ ("Concasport", "FR", None, None,
+ "French manufacturer, custom designs.", "manufacturer", None),
+ ("FieldTurf", "FR", None, "fieldturf.com",
+ "25+ years synthetic turf for tennis/padel. 1,000,000+ m² installed. FIFA Preferred Producer. Part of Tarkett.", "turf", None),
+ ("Losberger De Boer", "DE", None, "losbergerdeboer.com",
+ "Semi-permanent modular padel structures. Aluminum/wood frames with canvas roofing.", "hall_builder", None),
+ ("Infinite Padel Courts", "FR", None, "infinitepadelcourts.com",
+ "Custom courts, height-adjustable, manufactured in Alicante.", "manufacturer", "infinitepadelcourts@gmail.com"),
+
+ # ---- Portugal (5) ----
+ ("inCourts Padel", "PT", "Lisbon", "incourtspadel.com",
+ "Robotic manufacturing, factory in North Portugal.", "manufacturer", None),
+ ("Greenpark", "PT", None, "greenpark.com.pt",
+ "First Portuguese-made padel court manufacturer.", "manufacturer", None),
+ ("Sports Evolution", "PT", None, "sports-evolution.pt",
+ "Builder/installer, also manufactures covers.", "manufacturer", None),
+ ("Sports Partner", "PT", None, "sportspartner.pt",
+ "Equipment supplier, multi-sport.", "manufacturer", None),
+
+ # ---- United Kingdom (6) ----
+ ("PRO Padel Courts", "GB", None, "propadelcourts.com",
+ "Times 100 Ones to Watch 2025. Italian-engineered, patented anti-noise system. 50-year lifespan. MejorSet master distributor UK.", "manufacturer", None),
+ ("Padel Tech", "GB", None, "padeltech.co.uk",
+ "Leading UK supplier/installer. Exclusive AFP Courts/adidas UK distributor. 150+ courts.", "turnkey", None),
+ ("Hexa Padel", "GB", "Woodford Green", "hexapadel.co.uk",
+ "One of UK's largest builders. Courts, canopies, booking software, maintenance, academy.", "manufacturer", None),
+ ("iPadel Ltd", "GB", None, "ipadel.co.uk",
+ "Independent consultancy. Free quotes from multiple suppliers. Planning and investment matchmaking.", "consultant", None),
+ ("SG Padel", "GB", None, "sgpadel.co.uk",
+ "Turnkey, MejorSet distributor, SAPCA approved.", "turnkey", None),
+ ("SIS Pitches", "GB", None, "sispitches.com",
+ "25+ years elite sports surfaces. UK-based turf manufacturer. Design, manufacture, install, maintain. 65 years.", "turf", None),
+ ("Padel Magic UK", "GB", None, "padelmagic.co.uk",
+ "Proprietary Magic Base for uneven terrain. Custom covers/canopies. Nationwide.", "manufacturer", None),
+ ("Padel Build UK", "GB", "North Lincolnshire", "padelbuilduk.com",
+ "UK manufacturer, hot-dip galvanizing.", "manufacturer", None),
+ ("Padel Systems", "GB", None, "padelsystems.co.uk",
+ "Bespoke builder, partners with Italian Padel. Sister company: CopriSystems.", "manufacturer", None),
+ ("Red Raven Solutions", "GB", None, "redravensolutions.co.uk",
+ "Exclusive PadelCreations UK distributor, 500+ courts.", "turnkey", None),
+ ("Padel Works UK", "GB", "Whitchurch", "padelworks.co.uk",
+ "FIP-approved courts. 10-year warranty on court and surface.", "manufacturer", None),
+ ("Padel Galis UK", "GB", "Coventry", "padelgalis.uk",
+ "Exclusive UK supplier of Padel Galis.", "turnkey", "info@padelgalis.uk"),
+ ("S&C Slatter", "GB", None, "slattersportsconstruction.com",
+ "30+ years sports construction. Partners with FieldTurf. In-house civil engineering.", "turnkey", None),
+ ("Fordingbridge", "GB", "West Sussex", "fordingbridge.co.uk",
+ "UK's leading padel canopy specialist, 60+ years, 25-yr guarantee.", "hall_builder", "info@fordingbridge.co.uk"),
+ ("Collinson Tensile", "GB", None, "collinsontensile.co.uk",
+ "20+ years tensile buildings. Exclusive UK partner for Best-Hall Finland. ISO 9001/45001.", "hall_builder", None),
+ ("Rubb UK", "GB", "Gateshead", "rubbuk.com",
+ "Fabric building specialists. Thermohall insulation. Modular, relocatable.", "hall_builder", None),
+ ("J & J Carter", "GB", None, "jjcarter.com",
+ "Tensile sports halls, inflatable halls, frame/fabric structures.", "hall_builder", None),
+ ("Padel Consulting", "GB", "London", "padelconsulting.co.uk",
+ "Advisory firm, SAPCA member.", "consultant", None),
+
+ # ---- Netherlands (7) ----
+ ("Allesvoorpadel", "NL", "Biddinghuizen", "allesvoorpadel.nl",
+ "Leading Dutch builder, 10+ years, NK Padel official rink builder. AFP Courts partner. Philips lighting.", "manufacturer", "info@allesvoorpadel.nl"),
+ ("Padel Nederland B.V.", "NL", "Monster", "padelnederland.nl",
+ "Durable aluminum courts, 15-year warranty. KIWA/KNLTB certified. Solar canopy options, acoustic solutions.", "manufacturer", "info@padelnederland.nl"),
+ ("I-Padel", "NL", None, "i-padel.nl",
+ "Dutch manufacturer, in-house production. KIWA ISA Sport / NOC*NSF certified. 15-year warranty. Also offers Ping-Pong Padel.", "manufacturer", None),
+ ("SkyPadel NL", "NL", "Zuid-Holland", "skypadel.nl",
+ "1,800+ courts, Babolat official, since 2002.", "turnkey", "info@skypadel.nl"),
+ ("Padel.nl", "NL", None, "padel.nl",
+ "Since 2003, KNLTB/KIWA certified, proprietary foundations.", "manufacturer", None),
+ ("Orange Padel International", "NL", None, "orangepadel.nl",
+ "Premium Dutch-designed courts. 30 years experience. FEMEPA certification.", "manufacturer", None),
+ ("Padel Solution NL", "NL", None, "padelsolution.nl",
+ "3,000+ installed courts in 30+ countries. Full project support.", "turnkey", None),
+ ("World Padel NL", "NL", None, "worldpadel.nl",
+ "Court supplier.", "manufacturer", None),
+ ("Lumosa", "NL", None, "lumosa.eu",
+ "Dutch LED sports lighting manufacturer. Custom lighting plans, up to 80% energy savings.", "lighting", None),
+ ("Frisomat", "BE", None, "frisomat.com",
+ "Nearly 50 years steel construction. Cold-formed galvanized padel canopies/roofs. Modular, demountable.", "hall_builder", None),
+
+ # ---- Belgium (8) ----
+ ("Padel Projects", "BE", None, "padelprojects.eu",
+ "Court construction, 10+ years, 3,000+ courts, patented lighting.", "manufacturer", None),
+ ("JM Padel", "BE", "Province of Liège", "jmpadel.be",
+ "Installer, consultant, club management, IT/video.", "turnkey", None),
+ ("YoPadel SPRL", "BE", None, "yopadel.be",
+ "Belgian manufacturer, Belgian materials, Lano Sports turf partner.", "manufacturer", None),
+ ("Domo Sports Grass", "BE", None, None,
+ "Global artificial grass expert. Certified by major sports federations.", "turf", None),
+
+ # ---- Scandinavia (9) ----
+ ("Padeltotal", "SE", None, "padeltotal.se",
+ "Largest Nordic supplier. 1,600+ courts since 2013. Galvanized for Nordic conditions, 12mm glass. Duruss partnership.", "manufacturer", None),
+ ("Padel Global", "SE", "Jönköping", "padel-global.com",
+ "Manufacturer, own factory.", "manufacturer", None),
+ ("Scandinavian Padel AB", "SE", "Malmö", "scandinavianpadel.co",
+ "25+ years in steel/glass for Nordic climate. C3 oil rig grade steel. Own ScanTurf turf.", "manufacturer", "info@scandinavianpadel.co"),
+ ("Sweden Padel Master", "SE", None, "swedenpadelmaster.se",
+ "SPM Grass Court Cut technology.", "manufacturer", None),
+ ("Acenta Group", "SE", None, "acenta.group",
+ "Major Scandinavian company, courts/service/digital/equipment. Fiberglass courts expanding to AU/NZ.", "manufacturer", None),
+ ("Instantpadel", "SE", None, "instantcourts.com",
+ "World-unique mobile court, setup in <4 hours. 150+ courts, 17 countries. Installations at Gleneagles, Soho Club London.", "manufacturer", None),
+ ("Hallgruppen", "SE", None, "hallgruppen.com",
+ "Padel hall structures. Self-supporting steel frames (50-year lifespan). Rental, leasing, purchase. CE approved.", "hall_builder", None),
+ ("Best-Hall", "FI", None, None,
+ "5,500+ buildings worldwide. 40+ years. Fabric structures for sports halls.", "hall_builder", None),
+ ("ViPadel", "DK", None, "vipadel.dk",
+ "Total supplier, official Mondo dealer DK/FI.", "turnkey", None),
+ ("A-Sport", "DK", None, "a-sport.dk",
+ "Supplier/installer, 250+ courts.", "manufacturer", None),
+ ("Tiebreak International", "DK", "Glostrup", "padeltotal.dk",
+ "PadelTotal concept for Denmark, 200+ courts.", "turnkey", "info@tiebreakinternational.com"),
+ ("Unisport", "FI", None, "unisport.com",
+ "Court manufacturer, Saltex Tempo turf.", "manufacturer", None),
+
+ # ---- Other European (10) — unique entries only ----
+ ("DUOL", "SI", None, "duol.eu",
+ "Air-supported and fabric sports buildings. Nearly 30 years. Online configurator.", "hall_builder", None),
+
+ # ---- USA: Manufacturers & Builders (11.1) ----
+ ("Absolute Padel", "US", "Mohnton, PA", "absolutepadelusa.com",
+ "Only North America-based manufacturer. 100+ projects. 50%+ of US courts. Unique Pickleball & Padel combo court.", "manufacturer", "+1 717 445 5036"),
+ ("The Padel Box", "US", None, "thepadelbox.com",
+ "US pioneer since 2012, licensed in 15+ states. Official MejorSet US/Canada distributor. Hurricane-rated to 180 mph.", "manufacturer", "info@padelbox.com"),
+ ("Sportsfield Specialties", "US", "Delhi, NY", "sportsfield.com",
+ "USPA-endorsed manufacturer, 100% Made in USA, PaDelhi courts.", "manufacturer", "+1 607 746 8911"),
+ ("USA Padel Center", "US", "Houston, TX", "usapadel.com",
+ "Manufacturer/consultant since 2007.", "manufacturer", "+1 713 539 3110"),
+ ("Padel One Courts", "US", "Florida", "padelonecourts.com",
+ "Premium American-made courts. C5 anti-rust coating. Trusted by Pro Padel League and SVB Mouratoglou Academy.", "manufacturer", None),
+ ("Bounce Padel Courts", "CA", None, "bouncepadelcourts.com",
+ "Premier North American provider. SGCC/ANSI-certified glass. Hurricane-class anchoring. Converts tennis courts and ice rinks.", "manufacturer", None),
+ ("Northeast Padel", "US", "Pocasset, MA", "northeastpadel.com",
+ "Division of Cape & Island Tennis & Track, most-awarded US court builder. 50+ facility awards from ASBA.", "manufacturer", "+1 508 759 5636"),
+ ("Mondo Padel US", "US", "West Palm Beach, FL", "mondopadel.com",
+ "150+ years combined team experience. FL licensed. Builds from ground up or converts tennis courts.", "manufacturer", "+1 888 423 1120"),
+ ("Keystone Sports Construction", "US", None, "keystonesportsconstruction.com",
+ "Full-service turnkey padel from design to installation. Sportsfield Specialties partner.", "turnkey", None),
+ ("MTJ Sports", "US", "Chicago", "mtjsports.com",
+ "20+ years sports courts. Padel, pickleball, soccer, tennis. Turnkey for clubs, hotels, municipalities.", "turnkey", None),
+ ("Capas Padel", "US", None, "capaspadel.com",
+ "Builder/consultant, GreenSet surfaces, Smart Padel Club.", "turnkey", None),
+ ("All Racquet Sports", "US", "Sandy, UT", "allracquetsports.com",
+ "Official adidas/AFP US distributor, 700+ courts network.", "turnkey", "info@allracquetsports.com"),
+
+ # ---- USA: Franchises & Operators (11.3) ----
+ ("Conquer Padel Club", "US", "Lehi, UT", "conquerpadel.com",
+ "First US padel franchise, $1.1M-$3M+ investment.", "franchise", None),
+ ("Park Padel", "US", "San Francisco", "parkpadel.com",
+ "Franchise, pop-up courts, community-focused.", "franchise", "hello@parkpadel.com"),
+ ("Jungle Padel", "US", None, "junglepadel.com",
+ "Franchise, premium Mondo turf, academy.", "franchise", None),
+
+ # ---- USA: Turf (11.4) — unique entries only ----
+ ("WinterGreen Synthetic Grass", "US", "Dallas, TX", "wintergreengrass.com",
+ "Pro-grade padel turf in DFW. Padel Pro surface (same as WPT).", "turf", None),
+ ("Laykold", "US", None, "laykold.com",
+ "Padel Turf Pro surface. US Open official surface brand. Part of Sport Group.", "turf", None),
+
+ # ---- USA: Lighting (11.5) ----
+ ("LED Lighting Supply", "US", None, "ledlightingsupply.com",
+ "15+ years, 25,000+ projects. 150W LED fixtures. Free photometric plans. 5-year warranty.", "lighting", None),
+ ("Tweener USA", "US", None, "tweenerusa.com",
+ "Patented LED on existing fencing — no poles needed. Minimal light pollution. Dimmable.", "lighting", None),
+ ("Brite Court", "US", None, "britecourt.com",
+ "40+ years racquet sports lighting. 600+ facilities. 18+ fixture designs. Samsung LEDs. 10-year warranty.", "lighting", None),
+ ("AEON LED Lighting", "US", None, "aeonledlighting.com",
+ "Patented luminaires. UGR below 19 (glare-free). 100,000-hour lifespan. DLC Premium listed.", "lighting", None),
+ ("AGC Lighting", "CN", None, "agcled.com",
+ "SP11 linear sports light for padel. Smart controls (DALI 2, DMX). Supports 4K broadcasting.", "lighting", None),
+
+ # ---- USA: Industry (11.6) ----
+ ("USPA", "US", None, "padelusa.org",
+ "United States Padel Association. Endorses select manufacturers. Partnered with ASBA on first US construction standard.", "industry_body", None),
+
+ # ---- Mexico (12) ----
+ ("American Padel", "MX", "Mexico City", "americanpadel.com.mx",
+ "FIP-compliant, 25 yrs metalwork.", "manufacturer", "+52 55 5891 3350"),
+ ("Padel Center México", "MX", "Aguascalientes", "padelcenter.mx",
+ "FIP-certified. Clásica, Semipanorámica, Pro models. 20-30 day delivery.", "manufacturer", None),
+ ("MG Canchas", "MX", "Monterrey", "mgcanchas.com",
+ "Pioneer manufacturer in Monterrey. Also supplies synthetic turf.", "manufacturer", None),
+ ("SicaSport", "MX", None, "sicasport.com",
+ "Manufacturer/builder/installer.", "manufacturer", None),
+ ("Gott Padel", "MX", None, "gottpadel.com",
+ "Design, installation, construction. Also sells rackets and balls.", "manufacturer", None),
+ ("AFP Courts México", "MX", None, "afpcourts.mx",
+ "Official adidas licensee for Mexico.", "turnkey", None),
+ ("CanchasdePadel.com", "MX", None, "canchasdepadel.com",
+ "FIP-certified. WPT-certified curly turf.", "manufacturer", None),
+ ("Padel Works MX", "MX", None, "padelworks.com.mx",
+ "High-quality custom courts. Full support from civil works to club growth.", "manufacturer", None),
+ ("PadelStore.mx", "MX", None, "padelstore.mx",
+ "Court accessories: fencing, nets, posts, turf, sand, LED, protective pads.", "manufacturer", None),
+
+ # ---- Middle East (13) ----
+ ("Padel Factory ME", "AE", "Dubai", "padelfactory.me",
+ "Top manufacturer/supplier. Super Panoramic, Panoramic, Challenger, Portable. 400+ courts across UAE/KSA/Kuwait/Bahrain/Oman.", "manufacturer", "info@padelfactory.me"),
+ ("RedLine Padel", "AE", "Dubai", "redlinepadel.com",
+ "Spanish manufacturer based in Dubai. 48-hour delivery across ME. UNE EN 1090.", "manufacturer", None),
+ ("Cypex Group", "AE", None, "cypex-group.com",
+ "Represents Padel Factory ME. Exclusive LANO GRASS Belgium distributor for GCC.", "turnkey", None),
+ ("APW Pools", "AE", "Dubai", "apw-pools.com",
+ "Padel supplier/installer, smart lighting, advanced materials.", "manufacturer", "+971 50 852 1161"),
+ ("Mister Shade ME", "AE", "Dubai", "mistershademe.com",
+ "20+ years in flooring. Artificial turf and acrylic padel courts. All UAE emirates.", "manufacturer", None),
+ ("Gebal Group", "AE", "Dubai", "gebalgroup.com",
+ "Turnkey builder across GCC (6 countries). FIP-compliant.", "turnkey", None),
+ ("Empower Sport Services", "AE", None, "empowersportservices.com",
+ "2,500+ installations, FEP certified.", "manufacturer", None),
+ ("Shades Galaxy", "AE", "Dubai", "shadesgalaxy.com",
+ "Manufacturer/supplier, all UAE emirates.", "manufacturer", None),
+ ("Fab Floorings", "AE", "Dubai", "fabfloorings.ae",
+ "Turnkey: flooring/glass/lighting/branding.", "turnkey", None),
+ ("Al Mustaqbal Alsarea", "AE", None, "almustaqbalalsarea.com",
+ "Gulf countries leader.", "manufacturer", "+971 50 247 5749"),
+ ("PFS Gulf", "SA", None, "pfsgulf.com",
+ "Infrastructure company. Padel courts across KSA (Riyadh, Jeddah, Dammam, Mecca, Medina).", "manufacturer", None),
+
+ # ---- Turkey (14) ----
+ ("Mediterra Padel", "TR", "Antalya", "mediterrapadel.com",
+ "Turkey's largest, 35+ countries. Active in Kenya, SA, Sierra Leone, Morocco, Nigeria.", "manufacturer", "info@mediterrapadel.com"),
+ ("Integral Grass", "TR", "Istanbul", "integralgrass.com",
+ "11 models, 70+ countries.", "manufacturer", "info@integralgrass.com"),
+
+ # ---- China (15) ----
+ ("Legend Sports", "CN", "Yanshan", "legendsports.com",
+ "One of China's largest. 220+ employees, 32 engineers. 5,000+ courts in 60+ countries. 66,000 m² factory.", "manufacturer", None),
+ ("Fortune Padel", "CN", None, "fortunepadel.com",
+ "ISO 9001:2015. 20+ models including electric roof. Ships to 50+ countries within 20 days.", "manufacturer", None),
+ ("China Youngman Padel", "CN", "Hefei", "youngpadel.com",
+ "China's largest since 2010. SGS, CE, ISO9001. 8 models. Also produces roof covers.", "manufacturer", None),
+ ("Wanhe Padel", "CN", "Huaian", "wanhesport.com",
+ "Follows FIP regulations. Courts, 12mm turf, LED lighting. Also padel rackets.", "manufacturer", None),
+ ("Shengshi Sports Tech", "CN", "Tianjin", None,
+ "20+ years sports equipment. 10,000+ m² facility near Tianjin port.", "manufacturer", None),
+ ("PadelCourt10", "CN", "Hebei", "padelcourt10.com",
+ "1,000 sets/year capacity. 25+ countries. 5-year warranty. DDU door-to-door service.", "manufacturer", None),
+ ("Shanghai Super Power", "CN", "Shanghai", "padelcourtfactory.cn",
+ "200+ employees. 350 courts/month capacity. Aluminum frames for coastal regions.", "manufacturer", None),
+ ("UNIPADEL", "CN", "Guangzhou", "gzunipadel.com",
+ "5,000+ courts worldwide. Panoramic, classic, portable, roofed. Active in Indonesia, ME, Africa, LATAM.", "manufacturer", None),
+ ("ArtPadel", "CN", None, "artpadel.com",
+ "Panoramic/classic courts, patented technology.", "manufacturer", None),
+ ("LDK China", "CN", "Shenzhen", "ldkchina.com",
+ "Manufacturer/exporter.", "manufacturer", "info@ldkchina.com"),
+ ("SANJING Group", "CN", "Linqu", "sanjingcourt.com",
+ "Glass specialist, 27 years, 300+ employees, 40+ countries.", "manufacturer", None),
+ ("Luckin Padel", "CN", None, "luckinpadel.com",
+ "FIP-certified standards, educational focus.", "manufacturer", None),
+ ("Saintyol Sports", "CN", None, None,
+ "15+ years. Specializes in padel turf and structures. 10,000+ m² facility.", "manufacturer", None),
+ ("Nanjing Padelworker", "CN", "Nanjing", None,
+ "67% client reorder rate. Courts, squash equipment, glass fittings, turf.", "manufacturer", None),
+ ("Hebei Aohe Teaching Equipment", "CN", "Hebei", None,
+ "Est. 2012. 80% repeat business. Also aluminum frame sports tents.", "manufacturer", None),
+ ("Shandong Century Star", "CN", "Shandong", None,
+ "Large facility. Steel structure courts, panoramic models.", "manufacturer", None),
+ ("CCGrass", "CN", None, "ccgrass.com",
+ "Three factories. FEP-compliant. FastPro and YEII products. Also complete court packages.", "turf", None),
+ ("JCTurf", "CN", None, "jcturf.com",
+ "In-house fiber extrusion. FIP/FEP compliant. Also complete court solutions.", "turf", None),
+ ("MightyGrass", "CN", None, "mightygrass.com",
+ "Professional padel turf, FEP-level. Factory direct pricing.", "turf", None),
+
+ # ---- India (16) ----
+ ("Asian Flooring India", "IN", "Mumbai", "afipadel.com",
+ "India's largest padel manufacturer. FIP-standard. Standard, Panoramic, Ultra Panoramic, Kids. 25-day delivery.", "manufacturer", None),
+ ("Apex Sport Surfaces", "IN", "Mumbai", "apexsportsurfaces.in",
+ "FIP-compliant manufacturer/exporter.", "manufacturer", None),
+ ("Sky Padel India", "IN", "Mumbai", "skypadel.in",
+ "Local manufacturing, subsidiary of Spanish Sky Padel.", "manufacturer", None),
+ ("PFS Sport India", "IN", None, "pfs.sport",
+ "Turf & surface manufacturer.", "turf", None),
+ ("PadelHaus India", "IN", None, "padelhaus.in",
+ "Courts for every budget.", "manufacturer", None),
+
+ # ---- Asia Other (17) ----
+ ("SmartPadel", "SG", None, "smartpadel.asia",
+ "Regional leader in SE Asia. Video recording, automated scoring. Part of SEARA Sports.", "manufacturer", None),
+ ("Olympia Courts", "AU", None, "olympiacourts.com",
+ "Premium courts. European know-how, Asian manufacturing. Partners with Asia Pacific Padel Tour.", "manufacturer", None),
+ ("Indo Padel", "ID", "Bali", "indopadel.com",
+ "Spanish-Indonesian team. Courts manufactured in Indonesia. Active in Thailand, India.", "manufacturer", None),
+ ("Padel Asia", "TH", "Bangkok", "padelasia.org",
+ "Courts meeting international standards. Also sells rackets, clothing. Operates courts in Bangkok.", "manufacturer", None),
+
+ # ---- Brazil (18.1) ----
+ ("Padel Master Brasil", "BR", None, "padelmaster.com.br",
+ "1,500+ courts in 10+ countries. Official Pala Tour/WPT Americas court. 10-year warranty.", "manufacturer", None),
+ ("Sky Padel Brasil", "BR", None, "skypadel.com.br",
+ "10+ years. FEP/FIP certified. VP PRO 2.0, SP PRO, Full View, rental, mobile. 15-day production.", "manufacturer", None),
+ ("Smart Padel BR", "BR", None, "smart-padel.com",
+ "IoT-connected courts. Online monitoring, strategic management software. FIP/FEP certified.", "manufacturer", None),
+ ("Flores Pádel", "BR", None, "florespadel.com.br",
+ "Community-based manufacturer. 10+ years. Turf certified by Spanish federation. Founded by national team athletes.", "manufacturer", None),
+ ("FC Quadras", "BR", "São Paulo", "fcquadras.com.br",
+ "Distributes Padelgest courts. Turnkey from terrain prep to finishing.", "turnkey", None),
+ ("Padel Prime", "BR", None, "padelprime.com.br",
+ "European-standard manufacturing. Co-founded by ex-footballer Edmílson.", "manufacturer", None),
+ ("F4 Quadras", "BR", None, None,
+ "Manufacturer/installer.", "manufacturer", None),
+
+ # ---- Argentina (18.2) ----
+ ("Padel Courts Master", "AR", None, "padelcourtsmaster.ar",
+ "1,500+ courts delivered worldwide, 8+ countries. Robotic welding. FIP-standard.", "manufacturer", None),
+ ("MS Pádel", "AR", None, "metalurgicametalser.com",
+ "Professional panoramic courts. Full package: structure, glass, LED, turf.", "manufacturer", "+54 2314 407746"),
+ ("World Padel Court", "AR", None, "worldpadelcourt.com.ar",
+ "Led by Visión Deportiva. Top-quality courts. Portable court rental.", "manufacturer", None),
+ ("Blue Court", "AR", None, "bluecourt.com.ar",
+ "15+ years manufacturing. Established Argentine brand.", "manufacturer", None),
+ ("Slavon Césped Sintético", "AR", None, "slavoncespedsintetico.com",
+ "Panoramic and full panoramic courts. Also provides synthetic turf.", "manufacturer", None),
+
+ # ---- Africa (19) ----
+ ("Padel Nation", "ZA", None, "padelnation.co.za",
+ "SA's leading manufacturer. 150+ courts. Local hot-dip galvanized manufacturing. 4-week lead times. Up to 10-year warranty.", "manufacturer", None),
+ ("Padel Build SA", "ZA", None, "padelbuild.co.za",
+ "Premier turnkey builder. Partnered with Spain's Padel Galis. First FlexiPadel Base in Africa.", "turnkey", None),
+ ("Techno Padel", "ZA", None, "technopadel.co.za",
+ "Premier supplier/installer. 5-7 day installation.", "manufacturer", None),
+ ("Padel Solutions SA", "ZA", None, "padelsolutions.co.za",
+ "Proudly SA. Design, manufacture, install. Turnkey.", "manufacturer", None),
+ ("Padel Quip", "ZA", None, "padelquip.co.za",
+ "Local manufacturer. Basic to Premium Plus models. Partnership and financial assistance.", "manufacturer", None),
+ ("Padel Projects SA", "ZA", None, "padelprojects.co.za",
+ "Designed for local conditions.", "manufacturer", None),
+ ("Trompie Sport", "ZA", None, "trompiesport.co.za",
+ "Builder, imported & local courts.", "manufacturer", None),
+ ("Belgotex Sport", "ZA", "Pietermaritzburg", "belgotexsport.co.za",
+ "SA-based turf manufacturer. UNE 147301:2018 compliant. 220+ installations.", "turf", None),
+ ("Africa Padel", "ZA", None, "africapadel.com",
+ "Largest club group in Africa. 21+ clubs across SA. Founded 2021. Events, corporate leagues.", "franchise", None),
+ ("Padel Africa", "ZA", None, "padel.africa",
+ "Bringing padel to Ghana and Rwanda. Team has started 100+ companies and sold 2,000 courts.", "consultant", None),
+ ("Technotrade Sports", "EG", None, "technotradesports.com",
+ "Contractor, one of the best in Arab world.", "manufacturer", None),
+ ("Turkan Company", "EG", None, "turkan-eg.com",
+ "Manufacturer, one of first in Egypt.", "manufacturer", None),
+
+ # ---- Australia & Oceania (20) ----
+ ("APT Asia Pacific", "AU", "Melbourne", "aptasiapacific.com.au",
+ "Asia Pacific's largest sports surfaces company. Australian-made turf. Aluminium 6061-T6 frames. AS/NZS certified. Pop-up courts.", "manufacturer", None),
+ ("Synthetic Padel Courts", "AU", None, "syntheticpadelcourts.com.au",
+ "Built the first padel court in Australia. Preferred installer for Indoor Padel Australia, Sydney Racquet Club.", "turnkey", None),
+ ("Padel in One Australia", "AU", None, "padelinone.com.au",
+ "Turnkey specialist. 8+ years. Management, marketing, operations consulting.", "turnkey", None),
+ ("PadelVolt", "AU", None, "padelvolt.com",
+ "End-to-end premium service. MejorSet distributor across Oceania and Pacific Islands. Extreme weather designs.", "manufacturer", None),
+ ("Padel 360 Australia", "AU", None, "padel360.com.au",
+ "Developer/builder/manager, Gimpadel partner.", "turnkey", None),
+ ("AS Lodge Tennis Courts", "AU", "Melbourne", "asltenniscourts.com.au",
+ "Builder, $90K-$130K per court.", "manufacturer", None),
+ ("All Sport Projects", "AU", None, "allsportprojects.com.au",
+ "Non-rust aluminum courts, 7-15 yr warranties.", "manufacturer", None),
+ ("SPORTENG", "AU", None, "sporteng.com.au",
+ "Consulting/design, developed official Padel Australia Guidelines.", "consultant", None),
+
+ # ---- Software & Technology (21) ----
+ ("Playtomic", "ES", "Madrid", "playtomic.com",
+ "World's largest racket sports platform. 6,700+ clubs, 4M+ players, 52+ countries. €56M raised.", "software", None),
+ ("MATCHi", "SE", None, "tpcmatchpoint.com",
+ "1,600+ venues. Multi-sport booking, memberships, leagues, coaching.", "software", None),
+ ("Padel Mates", "NL", None, None,
+ "All-in-one platform. Won Rocket Padel (Europe's largest indoor chain). Gamification features.", "software", None),
+ ("SmashClub", "NL", None, "smashclub.cloud",
+ "Padel CRM and club management. Integrates with Playtomic, MATCHi, Padel Mates.", "software", None),
+ ("Taykus", "ES", None, None,
+ "Software specifically for padel clubs. Online booking, payment, communication automation.", "software", None),
+ ("Playbypoint", "US", None, "playbypoint.com",
+ "Official tech partner of 2025 US Open Padel. Custom branded app per club.", "software", None),
+ ("CourtReserve", "US", None, None,
+ "Leading US booking platform alongside Playbypoint and Playtomic.", "software", None),
+ ("360Player", "IS", None, "en-us.360player.com",
+ "Club management with website builder, video analysis, player development tools.", "software", None),
+ ("Booklux", "EE", None, "booklux.com",
+ "Customizable booking system. Stripe payments. Google Analytics integration.", "software", None),
+ ("SetTime", "US", None, "settime.io",
+ "Free padel booking software. Google/Apple Calendar sync. Analytics for utilization.", "software", None),
+ ("ProPadelKit", "GB", None, "propadelkit.com",
+ "Turnkey court solutions, Classic & Fusion models.", "manufacturer", None),
+]
+
+
+def up(conn):
+ # Table, indexes, FTS, and triggers are created by schema.sql (IF NOT EXISTS).
+ # This migration only needs to seed data.
+ row = conn.execute("SELECT COUNT(*) FROM suppliers").fetchone()
+ if row[0] > 0:
+ return
+
+ # Seed suppliers
+ seen_slugs = set()
+ for name, cc, city, website, desc, cat, contact in _SUPPLIERS:
+ slug = _slugify(name)
+ if slug in seen_slugs:
+ i = 2
+ while f"{slug}-{i}" in seen_slugs:
+ i += 1
+ slug = f"{slug}-{i}"
+ seen_slugs.add(slug)
+
+ region = _REGION.get(cc, "Other")
+ conn.execute(
+ "INSERT INTO suppliers"
+ " (name, slug, country_code, city, region, website, description, category, contact)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)",
+ (name, slug, cc, city, region, website, desc, cat, contact),
+ )
diff --git a/padelnomics/src/padelnomics/migrations/versions/0005_add_supplier_tiers.py b/padelnomics/src/padelnomics/migrations/versions/0005_add_supplier_tiers.py
new file mode 100644
index 0000000..f826949
--- /dev/null
+++ b/padelnomics/src/padelnomics/migrations/versions/0005_add_supplier_tiers.py
@@ -0,0 +1,20 @@
+"""Add tier, logo, verified, highlight, and sticky columns to suppliers."""
+
+
+def up(conn):
+ existing = {
+ row[1] for row in conn.execute("PRAGMA table_info(suppliers)").fetchall()
+ }
+
+ columns = [
+ ("tier", "TEXT NOT NULL DEFAULT 'free'"),
+ ("logo_url", "TEXT"),
+ ("is_verified", "INTEGER NOT NULL DEFAULT 0"),
+ ("highlight", "INTEGER NOT NULL DEFAULT 0"),
+ ("sticky_until", "TEXT"),
+ ("sticky_country", "TEXT"),
+ ]
+
+ for name, definition in columns:
+ if name not in existing:
+ conn.execute(f"ALTER TABLE suppliers ADD COLUMN {name} {definition}")
diff --git a/padelnomics/src/padelnomics/planner/calculator.py b/padelnomics/src/padelnomics/planner/calculator.py
index 44d2922..42c1100 100644
--- a/padelnomics/src/padelnomics/planner/calculator.py
+++ b/padelnomics/src/padelnomics/planner/calculator.py
@@ -83,6 +83,11 @@ DEFAULTS = {
"holdYears": 5,
"exitMultiple": 6,
"annualRevGrowth": 2,
+ "budgetTarget": 0,
+ "country": "DE",
+ "permitsCompliance": 12000,
+ "glassType": "standard",
+ "lightingType": "led_standard",
"ramp": [0.25, 0.35, 0.45, 0.55, 0.65, 0.75, 0.82, 0.88, 0.93, 0.96, 0.98, 1],
"season": [0, 0, 0, 0.7, 0.9, 1, 1, 1, 0.8, 0, 0, 0],
}
@@ -166,6 +171,13 @@ def calc(s: dict) -> dict:
)
d["sqm"] = d["hallSqm"] if is_in else d["outdoorLandSqm"]
+ # -- Multipliers for glass and lighting --
+ glass_mult = 1.4 if s["glassType"] == "panoramic" else 1.0
+ light_mult = 1.5 if s["lightingType"] == "led_competition" else 1.0
+ # Natural light zeroes out lighting costs (outdoor only)
+ if s["lightingType"] == "natural" and not is_in:
+ light_mult = 0
+
# -- CAPEX --
capex_items: list[dict] = []
@@ -174,8 +186,9 @@ def calc(s: dict) -> dict:
ci(
"Padel Courts",
- s["dblCourts"] * s["courtCostDbl"] + s["sglCourts"] * s["courtCostSgl"],
- f"{s['dblCourts']}\u00d7dbl + {s['sglCourts']}\u00d7sgl",
+ (s["dblCourts"] * s["courtCostDbl"] + s["sglCourts"] * s["courtCostSgl"]) * glass_mult,
+ f"{s['dblCourts']}\u00d7dbl + {s['sglCourts']}\u00d7sgl"
+ + (" (panoramic)" if s["glassType"] == "panoramic" else ""),
)
ci("Shipping", math.ceil(total_courts / 2) * s["shipping"] if total_courts else 0)
@@ -190,7 +203,7 @@ def calc(s: dict) -> dict:
f"{land_sqm}m\u00b2 \u00d7 \u20ac{s['landPriceSqm']}/m\u00b2")
ci("Transaction Costs", _round(land_sqm * s["landPriceSqm"] * 0.1), "~10% of land")
ci("HVAC System", s["hvac"])
- ci("Electrical + Lighting", s["electrical"])
+ ci("Electrical + Lighting", s["electrical"] * light_mult)
ci("Sanitary / Changing", s["sanitary"])
ci("Parking + Exterior", s["parking"])
ci("Planning + Permits", s["planning"])
@@ -198,13 +211,15 @@ def calc(s: dict) -> dict:
else:
ci("Floor Preparation", s["floorPrep"])
ci("HVAC Upgrade", s["hvacUpgrade"])
- ci("Lighting Upgrade", s["lightingUpgrade"])
+ ci("Lighting Upgrade", s["lightingUpgrade"] * light_mult)
ci("Fit-Out & Reception", s["fitout"])
+ ci("Permits & Compliance", s["permitsCompliance"])
else:
ci("Concrete Foundation", (s["dblCourts"] * 250 + s["sglCourts"] * 150) * s["outdoorFoundation"])
ci("Site Work", s["outdoorSiteWork"])
- ci("Lighting", total_courts * s["outdoorLighting"])
+ ci("Lighting", total_courts * s["outdoorLighting"] * light_mult)
ci("Fencing", s["outdoorFencing"])
+ ci("Permits & Compliance", s["permitsCompliance"])
if is_buy:
ci("Land Purchase", d["outdoorLandSqm"] * s["landPriceSqm"],
f"{d['outdoorLandSqm']}m\u00b2 \u00d7 \u20ac{s['landPriceSqm']}/m\u00b2")
@@ -418,4 +433,9 @@ def calc(s: dict) -> dict:
else 0
)
+ # -- Budget comparison --
+ d["budgetTarget"] = s["budgetTarget"]
+ d["budgetVariance"] = d["capex"] - s["budgetTarget"] if s["budgetTarget"] > 0 else 0
+ d["budgetPct"] = d["capex"] / s["budgetTarget"] * 100 if s["budgetTarget"] > 0 else 0
+
return d
diff --git a/padelnomics/src/padelnomics/planner/routes.py b/padelnomics/src/padelnomics/planner/routes.py
index 163cc21..e4cd61c 100644
--- a/padelnomics/src/padelnomics/planner/routes.py
+++ b/padelnomics/src/padelnomics/planner/routes.py
@@ -50,10 +50,12 @@ async def get_scenarios(user_id: int) -> list[dict]:
# =============================================================================
@bp.route("/")
-@login_required
async def index():
- scenario_count = await count_scenarios(g.user["id"])
- default = await get_default_scenario(g.user["id"])
+ scenario_count = 0
+ default = None
+ if g.user:
+ scenario_count = await count_scenarios(g.user["id"])
+ default = await get_default_scenario(g.user["id"])
initial_state = json.loads(default["state_json"]) if default else {}
state = validate_state(initial_state)
initial_d = calc(state)
@@ -66,7 +68,6 @@ async def index():
@bp.route("/calculate", methods=["POST"])
-@login_required
async def calculate():
data = await request.get_json()
state = validate_state(data.get("state", {}))
diff --git a/padelnomics/src/padelnomics/planner/templates/planner.html b/padelnomics/src/padelnomics/planner/templates/planner.html
index 5bdb0c6..3a66d96 100644
--- a/padelnomics/src/padelnomics/planner/templates/planner.html
+++ b/padelnomics/src/padelnomics/planner/templates/planner.html
@@ -2,6 +2,11 @@
{% block title %}Padel Court Financial Planner - {{ config.APP_NAME }}{% endblock %}
{% block head %}
+
+
+
+
+
{% endblock %}
@@ -29,52 +34,235 @@
-
+
-
-
-
-
-
Environment
-
-
Ownership Model
-
-
-
-
-
+
+
+
+
+
Your Venue
+
Define the type of facility you're planning to build.
+
+
Environment
+
+
Ownership Model
+
-
+
+
+
+
Pricing & Utilization
+
Set your court rates, operating schedule, and ancillary revenue streams.
+
+
+
+
+
+
+
Investment & Build Costs
+
Configure construction costs, glass and lighting options, and your budget target.
+
+
+
+
+
+
Operations & Financing
+
Monthly operating costs, loan terms, and exit assumptions.
+
+
+
+
+
+
+
+
Get Quotes from Suppliers
+
Your project specs are pre-filled from the planner. Complete a few details and we'll match you with verified court builders.
+
+
+
+
+
+
+
+
+
+
+
+
+
+ City / Region
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ I'd like help finding financing options
+
+
+
+
+
+
+
+
+
+
+ Anything else?
+
+
+
+
+
+
+ Full Name *
+
+
+
+ Email *
+
+
+
+ Phone (optional)
+
+
+
+ Company (optional)
+
+
+
+
+
+
Your contact details are shared only with 2-5 pre-vetted suppliers that match your project specs.
+
+
+
+
+
+
+
✓
+
You're matched!
+
We'll connect you with 2-5 verified suppliers within 48 hours. Check your email for confirmation.
+ {% if not user %}
+
+
Save this plan, compare scenarios, and get early access to financing tools.
+
Create Account
+
+ {% endif %}
+
+
+
+
+
+
@@ -87,7 +275,7 @@
@@ -158,7 +346,8 @@
@@ -173,11 +362,13 @@
-
+ {% if not user %}
+
+
Create an account to save scenarios , compare plans , and get early access to financing tools .
+
Sign Up
+
×
+
+ {% endif %}
@@ -194,6 +385,7 @@ window.__PADELNOMICS_INITIAL_D__ = {{ initial_d | safe }};
window.__PADELNOMICS_CALC_URL__ = "{{ url_for('planner.calculate') }}";
window.__PADELNOMICS_SAVE_URL__ = "{{ url_for('planner.save_scenario') }}";
window.__PADELNOMICS_SCENARIO_URL__ = "{{ url_for('planner.index') }}scenarios/";
+window.__PADELNOMICS_QUOTE_URL__ = "{{ url_for('leads.quote_request') }}";
{% endblock %}
diff --git a/padelnomics/src/padelnomics/public/routes.py b/padelnomics/src/padelnomics/public/routes.py
index af86223..de25273 100644
--- a/padelnomics/src/padelnomics/public/routes.py
+++ b/padelnomics/src/padelnomics/public/routes.py
@@ -5,7 +5,7 @@ from pathlib import Path
from quart import Blueprint, Response, render_template
-from ..core import config
+from ..core import config, fetch_one
bp = Blueprint(
"public",
@@ -14,9 +14,26 @@ bp = Blueprint(
)
+async def _supplier_counts():
+ """Fetch aggregate supplier stats for landing/marketing pages."""
+ total = await fetch_one("SELECT COUNT(*) as cnt FROM suppliers")
+ countries = await fetch_one(
+ "SELECT COUNT(DISTINCT country_code) as cnt FROM suppliers"
+ )
+ return (
+ total["cnt"] if total else 0,
+ countries["cnt"] if countries else 0,
+ )
+
+
@bp.route("/")
async def landing():
- return await render_template("landing.html")
+ total_suppliers, total_countries = await _supplier_counts()
+ return await render_template(
+ "landing.html",
+ total_suppliers=total_suppliers,
+ total_countries=total_countries,
+ )
@bp.route("/features")
@@ -39,6 +56,16 @@ async def about():
return await render_template("about.html")
+@bp.route("/suppliers")
+async def suppliers():
+ total_suppliers, total_countries = await _supplier_counts()
+ return await render_template(
+ "suppliers.html",
+ total_suppliers=total_suppliers,
+ total_countries=total_countries,
+ )
+
+
@bp.route("/sitemap.xml")
async def sitemap():
base = config.BASE_URL.rstrip("/")
@@ -46,6 +73,9 @@ async def sitemap():
f"{base}/",
f"{base}/features",
f"{base}/about",
+ f"{base}/planner/",
+ f"{base}/directory/",
+ f"{base}/suppliers",
f"{base}/billing/pricing",
f"{base}/terms",
f"{base}/privacy",
diff --git a/padelnomics/src/padelnomics/public/templates/landing.html b/padelnomics/src/padelnomics/public/templates/landing.html
index b242849..889c755 100644
--- a/padelnomics/src/padelnomics/public/templates/landing.html
+++ b/padelnomics/src/padelnomics/public/templates/landing.html
@@ -10,109 +10,169 @@
{% endblock %}
{% block content %}
-
-
- Plan Your Padel Business in Minutes, Not Months
-
- Model your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections.
-
-
-
-
-
-
-
Quick ROI Estimate
-
-
-
-
Courts
-
-
6
+
+
+
+
+
🎾 Padel court financial planner
+
Plan Your Padel Business in Minutes,Not Months
+
+ Model your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections. Then get matched with verified suppliers.
+
+
-
- Peak Rate
-
- €50/hr
-
-
- Utilization
-
- 40%
-
-
- Build Cost / Court
-
- €30K
-
-
-
Equity %
-
-
25%
+
+ ✓ No signup required
+ ✓ 60+ variables
+ ✓ Unlimited scenarios
-
Assumes €8/m² rent, 5% interest, 10-year loan, 300 m² per court
-
-
-
-
Total Investment
-
—
-
CAPEX (rent model)
+
+
Quick ROI Estimate
+
Drag the sliders to see your projection
+
-
-
Monthly Cash Flow
-
—
-
After debt service
+
+
+
+
Monthly Cash Flow
+
€7K
+
+
+
Payback Period
+
3.9 yr
+
+
-
-
Payback Period
-
—
-
Years to recover equity
-
-
-
Cash-on-Cash
-
—
-
Annual return on equity
-
-
-
-
-
Want the full picture? The planner models 60+ variables with monthly projections, sensitivity analysis, and connects you with court suppliers.
- {% if user %}
-
Start Planning
- {% else %}
-
Create Your Plan
- {% endif %}
+
Assumes indoor rent model, €8/m² rent, staff costs, 5% interest, 10-yr loan. Payback and ROI based on total investment.
+
Open Full Planner →
@@ -134,8 +194,8 @@
Connect with banks and investors experienced in sports facility loans. Your planner data becomes your business case.
-
🏗️ Build Coming Soon
-
Get quotes from verified court suppliers. Compare pricing, quality, and delivery timelines for your specific project.
+
🏗️ Build
+
Browse {{ total_suppliers }}+ court suppliers across {{ total_countries }} countries. Get matched based on your project specs.
📈 Grow Coming Soon
@@ -177,6 +237,59 @@
+
+
+ Find the Right Suppliers for Your Project
+ {{ total_suppliers }}+ verified suppliers across {{ total_countries }} countries. Manufacturers, builders, turf, lighting, and more.
+
+
+
1
+
Plan Your Venue
+
Use the financial planner to model your courts, budget, and timeline.
+
+
+
2
+
Get Quotes
+
Request quotes and we match you with suppliers based on your project specs.
+
+
+
3
+
Compare & Build
+
Receive proposals from 2-5 relevant suppliers. No cold outreach needed.
+
+
+
+
+
+
+
+ Frequently Asked Questions
+
+
+ What does the planner calculate?
+ The planner produces a complete financial model: CAPEX breakdown, monthly operating costs, cash flow projections, debt service, IRR, MOIC, DSCR, payback period, break-even utilization, and sensitivity analysis. It covers indoor/outdoor, rent/buy, and all major cost and revenue variables.
+
+
+ Do I need to sign up?
+ No. The planner works instantly with no signup. Create an account to save scenarios, compare configurations, and export PDF reports.
+
+
+ How does supplier matching work?
+ When you request quotes through the planner, we share your project details (venue type, court count, glass, lighting, country, budget, timeline) with 2-5 relevant suppliers from our directory. They contact you directly with proposals.
+
+
+ Is the supplier directory free?
+ Browsing the directory is free for everyone. Suppliers have a basic listing by default. Paid plans (Growth at €149/mo, Pro at €399/mo) unlock full descriptions, logos, verified badges, and priority placement.
+
+
+ How accurate are the financial projections?
+ The model uses real-world defaults based on European market data. Every assumption is adjustable so you can match your local conditions. The sensitivity analysis shows how results change across different scenarios, helping you understand the range of outcomes.
+
+
+
+
Padel Court Investment Planning
@@ -193,99 +306,77 @@
Start Planning Today
- Start with your plan. Then get quotes from verified court suppliers and connect with financing partners.
- {% if user %}
- Start Planning
- {% else %}
- Create Your Plan
- {% endif %}
+ Model your investment, then get matched with verified court suppliers across {{ total_countries }} countries.
+ Open the Planner
{% endblock %}
{% block scripts %}
{% endblock %}
diff --git a/padelnomics/src/padelnomics/public/templates/suppliers.html b/padelnomics/src/padelnomics/public/templates/suppliers.html
new file mode 100644
index 0000000..4ff2438
--- /dev/null
+++ b/padelnomics/src/padelnomics/public/templates/suppliers.html
@@ -0,0 +1,293 @@
+{% extends "base.html" %}
+
+{% block title %}For Suppliers - Reach Padel Entrepreneurs | {{ config.APP_NAME }}{% endblock %}
+
+{% block head %}
+
+
+{% endblock %}
+
+{% block content %}
+
+
+
Reach Entrepreneurs Actively Planning Padel Projects
+
Padelnomics connects you with qualified leads who've already modeled their investment. No cold outreach. No wasted time.
+
See Plans
+
+
+
+
{{ total_suppliers }}+ suppliers listed
+
{{ total_countries }} countries
+
100% project-qualified leads
+
+
+
+
+ How It Works
+ Three steps to qualified leads.
+
+
+
1
+
Choose Your Plan
+
Your company is already in our directory. Pick a plan to upgrade your listing and start receiving leads.
+
+
+
2
+
Get Quotes
+
When entrepreneurs request quotes, we match them with suppliers based on location, services, and project specs.
+
+
+
3
+
Close Deals
+
You receive pre-qualified leads with full project details: venue type, court count, budget, timeline, and more.
+
+
+
+
+
+
Example Lead You'd Receive
+
+ Facility Indoor (Rent)
+ Courts 6 double + 2 single
+ Glass Panoramic
+ Country Germany
+ Budget €450K
+ Timeline 3-6 months
+ Phase Lease signed
+ Financing Loan approved
+
+
+
+
+
+
+ Plans & Pricing
+ Choose the plan that fits your growth goals.
+
+
+
+
Growth
+
€149 /mo
+
+ Company name & category badge
+ City & country shown
+ Description (3 lines)
+ "Growth" badge
+ Priority over free listings
+
+
Get Started
+
+
+
+
+
Most Popular
+
Pro
+
€399 /mo
+
+ Everything in Growth
+ Company logo displayed
+ Full description
+ Website link shown
+ Verified ✓ badge
+ Priority placement
+ Highlighted card border
+
+
Get Started
+
+
+
+
+ Boost Add-Ons
+ Available with any paid plan.
+
+
+ Logo
+ €29/mo
+
+
+ Highlight
+ €39/mo
+
+
+ Verified Badge
+ €49/mo
+
+
+ Sticky Top
+ €79/wk or €199/mo
+
+
+ Newsletter Feature
+ €99/mo
+
+
+
+
+
+
+ Why Padelnomics Leads Are Different
+ Every lead has already built a financial model for their project.
+
+
+
Pre-Qualified
+
Leads come through our financial planner. They've modeled CAPEX, revenue, and ROI before contacting you.
+
+
+
Full Project Brief
+
You get venue type, court count, glass/lighting specs, budget, timeline, financing status, and contact details.
+
+
+
No Cold Outreach
+
Entrepreneurs come to us. You only hear from people actively planning to build padel facilities.
+
+
+
+
+
+
+ Supplier FAQ
+
+
+ How do I claim my listing?
+ Find your company in our directory and click "Is this your company?" We'll verify your identity and give you access to choose a plan and upgrade your profile.
+
+
+ How much does it cost?
+ We offer two plans: Growth (€149/mo) with description, badge, and priority placement; and Pro (€399/mo) with logo, website, verified badge, and maximum visibility. Optional boost add-ons are available on top.
+
+
+ What information do leads include?
+ Every lead includes: facility type (indoor/outdoor), court count, glass and lighting preferences, country and city, budget estimate, project phase, timeline, financing status, stakeholder type, services needed, and full contact details.
+
+
+ How are leads matched to suppliers?
+ We match based on location, services offered, and project requirements. Each lead is shared with 2-5 relevant suppliers to ensure quality without overwhelming the entrepreneur.
+
+
+ My company isn't listed. How do I get added?
+ Email us at {{ config.ADMIN_EMAIL }} with your company details and we'll add you to the directory within 48 hours.
+
+
+
+
+
+
+ Ready to Receive Qualified Leads?
+ Choose a plan and start getting matched with padel entrepreneurs today.
+ See Plans
+
+
+{% endblock %}
diff --git a/padelnomics/src/padelnomics/static/css/input.css b/padelnomics/src/padelnomics/static/css/input.css
index 76f6845..eca5eee 100644
--- a/padelnomics/src/padelnomics/static/css/input.css
+++ b/padelnomics/src/padelnomics/static/css/input.css
@@ -23,9 +23,9 @@
--color-navy: #0F172A;
--color-charcoal: #1E293B;
- --color-electric: #3B82F6;
- --color-electric-hover: #2563EB;
- --color-accent: #10B981;
+ --color-electric: #1D4ED8;
+ --color-electric-hover: #1E40AF;
+ --color-accent: #16A34A;
--color-soft-white: #F8FAFC;
--color-light-gray: #E2E8F0;
--color-mid-gray: #CBD5E1;
@@ -33,7 +33,7 @@
--color-slate-dark: #475569;
--color-danger: #EF4444;
--color-danger-hover: #DC2626;
- --color-warning: #F59E0B;
+ --color-warning: #D97706;
}
/* ── Base layer ── */
@@ -64,7 +64,7 @@
/* Cards (replace Pico ) */
.card {
- @apply bg-white border border-light-gray rounded-lg p-6 mb-6;
+ @apply bg-white border border-light-gray rounded-2xl p-6 mb-6 shadow-sm;
}
.card-header {
@apply border-b border-light-gray pb-3 mb-4 text-sm text-slate font-medium;
@@ -72,12 +72,12 @@
/* Buttons — shared base */
.btn, .btn-secondary, .btn-danger {
- @apply inline-flex items-center justify-center px-5 py-2.5 rounded-lg
+ @apply inline-flex items-center justify-center px-5 py-2.5 rounded-xl
font-semibold text-sm transition-colors cursor-pointer
focus:outline-none focus:ring-2 focus:ring-electric/50;
}
.btn {
- @apply bg-electric text-white hover:bg-electric-hover;
+ @apply bg-electric text-white hover:bg-electric-hover shadow-[0_2px_10px_rgba(29,78,216,0.25)];
}
.btn-secondary {
@apply bg-slate-dark text-white hover:bg-navy;
@@ -86,7 +86,7 @@
@apply bg-danger text-white hover:bg-danger-hover;
}
.btn-outline {
- @apply inline-flex items-center justify-center px-5 py-2.5 rounded-lg
+ @apply inline-flex items-center justify-center px-5 py-2.5 rounded-xl
font-semibold text-sm transition-colors cursor-pointer
bg-transparent text-slate-dark border border-mid-gray
hover:bg-light-gray hover:text-navy
@@ -101,7 +101,7 @@
@apply block text-sm font-medium text-charcoal mb-1;
}
.form-input {
- @apply w-full px-3 py-2 rounded-lg border border-mid-gray bg-white
+ @apply w-full px-3 py-2 rounded-xl border border-mid-gray bg-white
text-slate-dark placeholder-slate
focus:outline-none focus:ring-2 focus:ring-electric/50 focus:border-electric
transition-colors;
@@ -127,7 +127,7 @@
/* Flash messages */
.flash, .flash-error, .flash-success, .flash-warning {
- @apply px-4 py-3 rounded-lg mb-4 border-l-4 bg-white text-slate-dark text-sm;
+ @apply px-4 py-3 rounded-xl mb-4 border-l-4 bg-white text-slate-dark text-sm;
}
.flash {
@apply border-electric;
diff --git a/padelnomics/src/padelnomics/static/css/planner.css b/padelnomics/src/padelnomics/static/css/planner.css
index 14494ba..3527bad 100644
--- a/padelnomics/src/padelnomics/static/css/planner.css
+++ b/padelnomics/src/padelnomics/static/css/planner.css
@@ -13,14 +13,19 @@
--txt-3: #94A3B8;
--head: #0F172A;
--wht: #0F172A;
- --rd: #3B82F6;
- --rd-bg: rgba(59,130,246,0.06);
- --gn: #10B981;
- --gn-bg: rgba(16,185,129,0.06);
- --bl: #3B82F6;
- --bl-bg: rgba(59,130,246,0.06);
- --am: #F59E0B;
- --am-bg: rgba(245,158,11,0.06);
+ --rd: #1D4ED8;
+ --rd-bg: rgba(29,78,216,0.06);
+ --gn: #16A34A;
+ --gn-bg: rgba(22,163,74,0.06);
+ --bl: #1D4ED8;
+ --bl-bg: rgba(29,78,216,0.06);
+ --am: #D97706;
+ --am-bg: rgba(217,119,6,0.06);
+ --cta: #1D4ED8;
+ --cta-hover: #1E40AF;
+ --cta-shadow: rgba(29,78,216,0.25);
+ --cta-bg: #EFF6FF;
+ --cta-glow: rgba(29,78,216,0.10);
font-family: 'Inter', sans-serif;
font-size: 14px;
@@ -76,7 +81,7 @@
.scenario-controls button {
font-size: 11px;
padding: 4px 12px;
- border-radius: 6px;
+ border-radius: 999px;
border: 1px solid var(--border-2);
background: transparent;
color: var(--txt-2);
@@ -93,8 +98,10 @@
/* ── Tab Navigation ── */
.tab-nav {
display: flex;
- border-bottom: 1px solid var(--border);
- background: var(--bg);
+ border-bottom: 1px solid rgba(226,232,240,0.6);
+ background: rgba(255,255,255,0.92);
+ backdrop-filter: blur(12px);
+ -webkit-backdrop-filter: blur(12px);
overflow-x: auto;
position: sticky;
top: 0;
@@ -154,8 +161,9 @@
.metric-card {
background: var(--bg-2);
border: 1px solid var(--border);
- border-radius: 8px;
- padding: 14px 16px;
+ border-radius: 14px;
+ padding: 18px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.04);
}
.metric-card__label {
font-size: 10px;
@@ -267,7 +275,7 @@
outline: none;
}
.slider-combo input[type=number]:focus {
- border-color: rgba(59,130,246,0.5);
+ border-color: rgba(29,78,216,0.5);
}
/* Hide number spinners */
.slider-combo input[type=number]::-webkit-outer-spin-button,
@@ -285,7 +293,8 @@
padding: 10px 12px;
background: var(--card-bg);
border: 1px solid var(--border);
- border-radius: 6px;
+ border-radius: 14px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.04);
font-size: 11px;
color: var(--txt-3);
}
@@ -317,7 +326,7 @@
border: 1px solid var(--border);
background: transparent;
color: var(--txt-3);
- border-radius: 6px;
+ border-radius: 999px;
cursor: pointer;
font-family: 'Inter', sans-serif;
transition: all 0.15s;
@@ -328,6 +337,71 @@
color: #fff !important;
}
+.btn-reset {
+ padding: 5px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--txt-2);
+ background: var(--bg-3);
+ border: 1px solid var(--border);
+ border-radius: 999px;
+ cursor: pointer;
+ transition: all 0.15s;
+}
+.btn-reset:hover { color: var(--head); border-color: var(--border-2); }
+
+/* ── Pill Select ── */
+.pill-group {
+ margin-bottom: 14px;
+}
+.pill-group label {
+ display: flex;
+ align-items: center;
+ margin-bottom: 4px;
+}
+.pill-options {
+ display: flex;
+ gap: 4px;
+}
+.pill-btn {
+ flex: 1;
+ padding: 6px 10px;
+ font-size: 11px;
+ font-weight: 600;
+ border: 1px solid var(--border);
+ background: transparent;
+ color: var(--txt-3);
+ border-radius: 999px;
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.15s;
+}
+.pill-btn:hover {
+ background: var(--bg-3);
+ color: var(--txt-2);
+}
+.pill-btn--active {
+ background: var(--rd) !important;
+ border-color: var(--rd) !important;
+ color: #fff !important;
+}
+
+/* ── Budget Indicator ── */
+.budget-indicator {
+ background: var(--bg-2);
+ border: 2px solid;
+ border-radius: 14px;
+ padding: 14px 16px;
+}
+.budget-indicator--under {
+ border-color: var(--gn);
+ background: var(--gn-bg);
+}
+.budget-indicator--over {
+ border-color: #EF4444;
+ background: rgba(239,68,68,0.06);
+}
+
/* ── Data Tables ── */
.data-table {
width: 100%;
@@ -372,8 +446,9 @@
.chart-container {
background: var(--bg-2);
border: 1px solid var(--border);
- border-radius: 8px;
+ border-radius: 18px;
padding: 1rem;
+ box-shadow: 0 1px 4px rgba(0,0,0,0.04);
}
.chart-container__label {
font-size: 11px;
@@ -480,7 +555,7 @@
font-size: 12px;
font-weight: 600;
padding: 6px 16px;
- border-radius: 6px;
+ border-radius: 10px;
text-decoration: none;
transition: all 0.15s;
}
@@ -489,7 +564,7 @@
color: #fff;
}
.lead-cta-bar a:first-of-type:hover {
- background: #2563EB;
+ background: #1E40AF;
}
.lead-cta-bar a:last-of-type {
background: transparent;
@@ -503,10 +578,11 @@
/* ── Inline lead CTA ── */
.lead-cta {
- background: var(--bg-3);
- border: 1px solid var(--border-2);
- border-radius: 8px;
- padding: 16px 20px;
+ background: var(--cta-bg);
+ border: 2px solid var(--cta);
+ border-radius: 20px;
+ padding: 20px 24px;
+ box-shadow: 0 4px 24px var(--cta-glow);
margin-top: 1rem;
display: flex;
align-items: center;
@@ -518,18 +594,28 @@
color: var(--txt-2);
}
.lead-cta__btn {
- font-size: 12px;
+ font-size: 13px;
font-weight: 600;
- padding: 8px 20px;
- border-radius: 6px;
- background: var(--rd);
+ padding: 13px 26px;
+ border-radius: 10px;
+ background: var(--cta);
color: #fff;
text-decoration: none;
white-space: nowrap;
transition: background 0.15s;
+ box-shadow: 0 2px 10px var(--cta-shadow);
}
.lead-cta__btn:hover {
- background: #2563EB;
+ background: var(--cta-hover);
+}
+.lead-cta__btn--secondary {
+ background: transparent;
+ border: 1px solid var(--border-2);
+ color: var(--txt-2);
+}
+.lead-cta__btn--secondary:hover {
+ background: var(--bg-3);
+ color: var(--txt);
}
/* ── Exit waterfall ── */
@@ -580,7 +666,7 @@
.scenario-item {
padding: 12px;
border: 1px solid var(--border);
- border-radius: 6px;
+ border-radius: 12px;
margin-bottom: 8px;
cursor: pointer;
transition: background 0.15s;
@@ -610,7 +696,7 @@
background: var(--gn);
color: #fff;
padding: 8px 16px;
- border-radius: 6px;
+ border-radius: 10px;
font-size: 12px;
font-weight: 600;
animation: fadeInOut 2s ease forwards;
@@ -622,6 +708,389 @@
100% { opacity: 0; }
}
+/* ── Wizard ── */
+.wizard-header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 1.25rem;
+ gap: 1rem;
+}
+.wizard-dots {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ flex-wrap: wrap;
+}
+.wiz-dot {
+ display: inline-flex;
+ align-items: center;
+ gap: 6px;
+ padding: 6px 14px;
+ font-size: 11px;
+ font-weight: 600;
+ border-radius: 20px;
+ border: 1px solid var(--border);
+ background: transparent;
+ color: var(--txt-3);
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.15s;
+ white-space: nowrap;
+}
+.wiz-dot:hover {
+ background: var(--bg-3);
+ color: var(--txt-2);
+}
+.wiz-dot--active {
+ background: var(--rd) !important;
+ border-color: var(--rd) !important;
+ color: #fff !important;
+}
+.wiz-dot--done {
+ background: var(--gn-bg);
+ border-color: var(--gn);
+ color: var(--gn);
+}
+.wiz-dot__num {
+ width: 18px;
+ height: 18px;
+ border-radius: 50%;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ font-size: 10px;
+ font-weight: 700;
+ background: rgba(0,0,0,0.06);
+ color: inherit;
+}
+.wiz-dot--active .wiz-dot__num {
+ background: rgba(255,255,255,0.25);
+}
+.wiz-dot--done .wiz-dot__num {
+ background: var(--gn);
+ color: #fff;
+}
+
+.wizard-step {
+ display: none;
+ max-width: 560px;
+}
+.wizard-step.active {
+ display: block;
+}
+.wizard-step__title {
+ font-size: 1.25rem;
+ font-weight: 800;
+ color: var(--head);
+ margin: 0 0 4px;
+}
+.wizard-step__sub {
+ font-size: 13px;
+ color: var(--txt-2);
+ margin: 0 0 1.5rem;
+}
+
+/* Wizard preview bar */
+.wizard-preview {
+ display: flex;
+ gap: 1rem;
+ padding: 12px 16px;
+ background: var(--bg-2);
+ border: 1px solid var(--border);
+ border-radius: 16px;
+ box-shadow: 0 1px 3px rgba(0,0,0,0.04);
+ margin-top: 1.5rem;
+ max-width: 560px;
+}
+.wiz-preview__item {
+ flex: 1;
+ text-align: center;
+}
+.wiz-preview__label {
+ font-size: 10px;
+ color: var(--txt-3);
+ text-transform: uppercase;
+ letter-spacing: 0.05em;
+}
+.wiz-preview__value {
+ font-size: 16px;
+ font-weight: 700;
+ font-family: 'JetBrains Mono', monospace;
+ color: var(--head);
+ line-height: 1.4;
+}
+.wiz-preview__value.c-green { color: var(--gn); }
+.wiz-preview__value.c-red { color: #EF4444; }
+
+/* Wizard navigation */
+.wizard-nav {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-top: 12px;
+ max-width: 560px;
+ gap: 0.75rem;
+}
+.wiz-btn--back {
+ padding: 8px 20px;
+ font-size: 12px;
+ font-weight: 600;
+ border: 1px solid var(--border-2);
+ background: transparent;
+ color: var(--txt-2);
+ border-radius: 10px;
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.15s;
+}
+.wiz-btn--back:hover {
+ background: var(--bg-3);
+ color: var(--txt);
+}
+.wiz-btn--next,
+.wiz-btn--submit {
+ padding: 10px 24px;
+ font-size: 13px;
+ font-weight: 700;
+ border: none;
+ background: var(--cta);
+ color: #fff;
+ border-radius: 10px;
+ cursor: pointer;
+ font-family: 'Inter', sans-serif;
+ transition: all 0.15s;
+ box-shadow: 0 2px 10px var(--cta-shadow);
+}
+.wiz-btn--next:hover,
+.wiz-btn--submit:hover {
+ background: var(--cta-hover);
+}
+.wiz-skip {
+ font-size: 12px;
+ color: var(--txt-3);
+ background: none;
+ border: none;
+ cursor: pointer;
+ text-decoration: underline;
+ font-family: 'Inter', sans-serif;
+ padding: 4px;
+}
+.wiz-skip:hover {
+ color: var(--txt-2);
+}
+.wizard-nav__right {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+}
+
+/* Step 5 form styles */
+.wiz-autofill-summary {
+ background: var(--bg-3);
+ border: 1px solid var(--border);
+ border-radius: 14px;
+ padding: 12px 16px;
+ margin-bottom: 1.5rem;
+ font-size: 12px;
+}
+.wiz-autofill-summary dt {
+ color: var(--txt-3);
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.04em;
+}
+.wiz-autofill-summary dd {
+ color: var(--head);
+ font-weight: 600;
+ margin: 0 0 6px;
+}
+#wizQuoteForm .field-group { margin-bottom: 1.25rem; }
+#wizQuoteForm .field-label {
+ display: block;
+ font-size: 12px;
+ font-weight: 600;
+ color: var(--txt-2);
+ margin-bottom: 4px;
+}
+#wizQuoteForm .field-label .required { color: #EF4444; }
+#wizQuoteForm .pill-grid {
+ display: flex;
+ flex-wrap: wrap;
+ gap: 6px;
+}
+#wizQuoteForm .pill-grid label { cursor: pointer; }
+#wizQuoteForm .pill-grid input[type="radio"],
+#wizQuoteForm .pill-grid input[type="checkbox"] { display: none; }
+#wizQuoteForm .pill-grid .pill {
+ display: inline-block;
+ padding: 7px 14px;
+ border-radius: 999px;
+ border: 1px solid var(--border);
+ font-size: 11px;
+ font-weight: 600;
+ color: var(--txt-3);
+ transition: all 0.15s;
+ background: transparent;
+}
+#wizQuoteForm .pill-grid .pill:hover {
+ background: var(--bg-3);
+ color: var(--txt-2);
+}
+#wizQuoteForm .pill-grid input:checked + .pill {
+ background: var(--rd);
+ border-color: var(--rd);
+ color: #fff;
+}
+.wiz-input {
+ width: 100%;
+ background: var(--bg-3);
+ border: 1px solid var(--border-2);
+ border-radius: 10px;
+ padding: 8px 12px;
+ font-size: 13px;
+ font-family: 'Inter', sans-serif;
+ color: var(--head);
+ outline: none;
+ box-sizing: border-box;
+}
+.wiz-input:focus {
+ border-color: rgba(29,78,216,0.5);
+}
+textarea.wiz-input {
+ resize: vertical;
+}
+.wiz-checkbox-label {
+ display: flex !important;
+ align-items: center;
+ gap: 6px;
+ cursor: pointer;
+}
+.wiz-checkbox-label input[type="checkbox"] {
+ margin: 0;
+ flex-shrink: 0;
+}
+.wiz-privacy-box {
+ background: var(--bl-bg);
+ border: 1px solid rgba(29,78,216,0.2);
+ border-radius: 14px;
+ padding: 12px 14px;
+ margin-bottom: 1rem;
+ font-size: 12px;
+ color: var(--rd);
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+}
+.wiz-privacy-box svg { flex-shrink: 0; margin-top: 1px; }
+#wizQuoteForm .consent-group { margin-bottom: 1.25rem; }
+#wizQuoteForm .consent-group label {
+ display: flex;
+ gap: 8px;
+ align-items: flex-start;
+ font-size: 12px;
+ color: var(--txt-2);
+ cursor: pointer;
+}
+#wizQuoteForm .consent-group input[type="checkbox"] {
+ margin-top: 2px;
+ flex-shrink: 0;
+}
+#wizQuoteForm .consent-group a {
+ color: var(--rd);
+}
+
+/* Success state */
+.wiz-success {
+ text-align: center;
+ padding: 2rem 1rem;
+}
+.wiz-success__icon {
+ width: 48px;
+ height: 48px;
+ border-radius: 50%;
+ background: var(--gn);
+ color: #fff;
+ font-size: 24px;
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ margin-bottom: 1rem;
+}
+.wiz-success h3 {
+ font-size: 1.25rem;
+ font-weight: 800;
+ color: var(--head);
+ margin: 0 0 8px;
+}
+.wiz-success p {
+ font-size: 13px;
+ color: var(--txt-2);
+ max-width: 400px;
+ margin: 0 auto;
+}
+.wiz-signup-nudge {
+ margin-top: 1.5rem;
+ background: var(--cta-bg);
+ border: 2px solid var(--cta);
+ border-radius: 16px;
+ padding: 1.5rem;
+}
+.wiz-signup-nudge p {
+ font-size: 13px;
+ color: var(--txt-2);
+ margin-bottom: 0.75rem;
+}
+
+/* ── Guest signup bar (sticky on results) ── */
+.signup-bar {
+ position: sticky;
+ bottom: 0;
+ display: none; /* shown by JS on results tabs */
+ align-items: center;
+ gap: 12px;
+ padding: 10px 1.5rem;
+ background: var(--cta-bg);
+ border-top: none;
+ box-shadow: 0 -2px 12px rgba(29,78,216,0.08);
+ font-size: 12px;
+ color: var(--txt-2);
+ z-index: 20;
+}
+.signup-bar span { flex: 1; }
+.signup-bar b { color: var(--head); }
+.signup-bar__close {
+ background: none;
+ border: none;
+ font-size: 18px;
+ color: var(--txt-2);
+ cursor: pointer;
+ padding: 0 4px;
+ line-height: 1;
+}
+.signup-bar__close:hover { color: var(--txt); }
+
+/* Mobile wizard */
+@media (max-width: 768px) {
+ .wizard-dots { gap: 4px; }
+ .wiz-dot { padding: 5px 10px; font-size: 10px; }
+ .wiz-dot__num { width: 16px; height: 16px; font-size: 9px; }
+ .wizard-preview {
+ flex-direction: column;
+ gap: 8px;
+ text-align: left;
+ }
+ .wiz-preview__item {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ text-align: left;
+ }
+ .wizard-step { max-width: 100%; }
+ .wizard-preview,
+ .wizard-nav { max-width: 100%; }
+}
+
/* ── Computing indicator ── */
.planner-app--computing .planner-header h1::after {
content: 'computing\2026';
diff --git a/padelnomics/src/padelnomics/static/images/planner-screenshot.png b/padelnomics/src/padelnomics/static/images/planner-screenshot.png
new file mode 100644
index 0000000..7cafa98
Binary files /dev/null and b/padelnomics/src/padelnomics/static/images/planner-screenshot.png differ
diff --git a/padelnomics/src/padelnomics/static/js/planner.js b/padelnomics/src/padelnomics/static/js/planner.js
index af27f61..fd892af 100644
--- a/padelnomics/src/padelnomics/static/js/planner.js
+++ b/padelnomics/src/padelnomics/static/js/planner.js
@@ -15,6 +15,8 @@ const S = {
floorPrep:12000, hvacUpgrade:20000, lightingUpgrade:10000,
outdoorFoundation:35, outdoorSiteWork:8000, outdoorLighting:4000, outdoorFencing:6000,
equipment:2000, workingCapital:15000, contingencyPct:10,
+ country:'DE', permitsCompliance:12000,
+ budgetTarget:0, glassType:'standard', lightingType:'led_standard',
rentSqm:4, outdoorRent:400, insurance:300, electricity:600, heating:400,
maintenance:300, cleaning:300, marketing:350, staff:0, propertyTax:250, water:125,
loanPct:85, interestRate:5, loanTerm:10, constructionMonths:0,
@@ -23,6 +25,9 @@ const S = {
season:[0,0,0,.7,.9,1,1,1,.8,0,0,0],
};
+// Freeze a copy of defaults before any overrides
+const DEFAULTS = Object.freeze(JSON.parse(JSON.stringify(S)));
+
// Restore saved scenario if available
if (window.__PADELNOMICS_INITIAL_STATE__) {
Object.assign(S, window.__PADELNOMICS_INITIAL_STATE__);
@@ -39,6 +44,16 @@ const TABS = [
let activeTab = 'assumptions';
const charts = {};
+// ── Wizard state ──────────────────────────────────────────
+let wizStep = 1;
+const WIZ_STEPS = [
+ {n:1, label:'Venue'},
+ {n:2, label:'Pricing'},
+ {n:3, label:'Costs'},
+ {n:4, label:'Finance'},
+ {n:5, label:'Get Quotes'},
+];
+
// ── Helpers ────────────────────────────────────────────────
const $=s=>document.querySelector(s);
const $$=s=>document.querySelectorAll(s);
@@ -54,6 +69,54 @@ function ti(text){
return ` i${text} `;
}
+function pillSelect(key,label,options,tip){
+ let h=`${label} ${ti(tip)}`;
+ for(const opt of options){
+ h+=`${opt.l} `;
+ }
+ h+='
';
+ return h;
+}
+
+const COUNTRY_PRESETS = {
+ DE: { permitsCompliance: 12000 },
+ ES: { permitsCompliance: 25000 },
+ IT: { permitsCompliance: 18000 },
+ FR: { permitsCompliance: 15000 },
+ NL: { permitsCompliance: 10000 },
+ SE: { permitsCompliance: 8000 },
+ UK: { permitsCompliance: 10000 },
+ OTHER: { permitsCompliance: 12000 },
+};
+// Track which keys the user has manually adjusted
+const _userAdjusted = new Set();
+
+function bindPills(){
+ document.querySelectorAll('.pill-btn').forEach(b=>{
+ b.onclick=()=>{
+ const k=b.dataset.key, v=b.dataset.val;
+ S[k]=v;
+ // Update active state within same group
+ b.closest('.pill-options').querySelectorAll('.pill-btn').forEach(btn=>{
+ btn.classList.toggle('pill-btn--active',btn.dataset.val===v);
+ });
+ // Apply country presets when country changes
+ if(k==='country'){
+ const preset = COUNTRY_PRESETS[v] || COUNTRY_PRESETS.OTHER;
+ for(const [pk,pv] of Object.entries(preset)){
+ if(!_userAdjusted.has(pk)){ S[pk]=pv; }
+ }
+ rebuildCapexInputs(); bindSliders(); bindPills();
+ }
+ // Rebuild inputs if lighting options depend on venue
+ if(k==='glassType'||k==='lightingType'){
+ rebuildCapexInputs(); bindSliders(); bindPills();
+ }
+ render();
+ };
+ });
+}
+
function cardHTML(label,value,sub,cls='',tip=''){
const cc = cls==='green'?'c-green':cls==='red'?'c-red':cls==='blue'?'c-blue':cls==='amber'?'c-amber':'c-head';
return `
@@ -129,6 +192,7 @@ function slider(key,label,min,max,step,fmtFn,tip){
function buildInputs(){
buildToggle('tog-venue',[{v:'indoor',l:'Indoor'},{v:'outdoor',l:'Outdoor'}],'venue');
buildToggle('tog-own',[{v:'rent',l:'Rent / Lease'},{v:'buy',l:'Buy / Build'}],'own');
+ buildCountryPill();
$('#inp-courts').innerHTML =
slider('dblCourts','Double Courts (20\u00D710m)',0,30,1,fN,'Standard padel court for 4 players. Most common format with highest recreational demand.')+
@@ -189,10 +253,26 @@ function rebuildSpaceInputs(){
$('#inp-space').innerHTML = h;
}
+function buildCountryPill(){
+ $('#inp-country').innerHTML = pillSelect('country','Country',[
+ {v:'DE',l:'Germany'},{v:'ES',l:'Spain'},{v:'IT',l:'Italy'},
+ {v:'FR',l:'France'},{v:'NL',l:'Netherlands'},{v:'SE',l:'Sweden'},
+ {v:'UK',l:'United Kingdom'},{v:'OTHER',l:'Other'},
+ ],'Affects regulatory cost defaults');
+}
+
function rebuildCapexInputs(){
const isIn=S.venue==='indoor', isBuy=S.own==='buy';
- let h = slider('courtCostDbl','Court Cost \u2014 Double',0,80000,1000,fE,'Purchase price of one double padel court. Standard glass: \u20AC25\u201330K. Panoramic: \u20AC30\u201345K. WPT-spec: \u20AC40\u201355K.')+
- slider('courtCostSgl','Court Cost \u2014 Single',0,60000,1000,fE,'Purchase price of one single padel court. Generally 60\u201370% of a double court cost.');
+ let glassOpts=[{v:'standard',l:'Standard Glass'},{v:'panoramic',l:'Panoramic Glass'}];
+ let lightOpts=[{v:'led_standard',l:'LED Standard'},{v:'led_competition',l:'LED Competition'}];
+ if(!isIn) lightOpts.push({v:'natural',l:'Natural Light'});
+ // Reset lightingType to led_standard if natural was selected but switched to indoor
+ if(isIn && S.lightingType==='natural') S.lightingType='led_standard';
+
+ let h = pillSelect('glassType','Glass Type',glassOpts,'Standard glass: \u20AC25\u201330K per court. Panoramic glass: \u20AC30\u201345K. Panoramic offers full visibility and premium feel.')+
+ pillSelect('lightingType','Lighting Type',lightOpts,'LED Standard: meets club play requirements. LED Competition: 50% more cost, meets tournament/broadcast standards. Natural: outdoor only, no lighting cost.')+
+ slider('courtCostDbl','Court Cost \u2014 Double',0,80000,1000,fE,'Base price of one double padel court. The glass type multiplier is applied automatically.')+
+ slider('courtCostSgl','Court Cost \u2014 Single',0,60000,1000,fE,'Base price of one single padel court. Generally 60\u201370% of a double court cost.');
if(isIn&&isBuy){
h+=slider('hallCostSqm','Hall Construction (\u20AC/m\u00B2)',0,2000,10,fE,'Construction cost per m\u00B2 for a new hall (Warmhalle). Includes structure, insulation, and cladding. Requires 10\u201312m clear height.')+
slider('foundationSqm','Foundation (\u20AC/m\u00B2)',0,400,5,fE,'Foundation cost per m\u00B2. Depends on soil conditions, load-bearing requirements, and local ground water levels.')+
@@ -206,16 +286,19 @@ function rebuildCapexInputs(){
h+=slider('floorPrep','Floor Preparation',0,100000,1000,fE,'Floor leveling, sealing, and preparation for court installation in an existing rented building.')+
slider('hvacUpgrade','HVAC Upgrade',0,200000,1000,fE,'Upgrading existing HVAC in a rented building to handle sports venue airflow and humidity requirements.')+
slider('lightingUpgrade','Lighting Upgrade',0,100000,1000,fE,'Upgrading existing lighting to meet padel requirements: minimum 500 lux, no glare, even distribution across courts.')+
- slider('fitout','Fit-Out & Reception',0,300000,1000,fE,'Interior fit-out for reception, lounge, viewing areas, and common spaces when renting an existing building.');
+ slider('fitout','Fit-Out & Reception',0,300000,1000,fE,'Interior fit-out for reception, lounge, viewing areas, and common spaces when renting an existing building.')+
+ slider('permitsCompliance','Permits & Compliance',0,50000,1000,fE,'Building permits, change-of-use application, fire safety review, noise assessment');
} else if(!isIn){
h+=slider('outdoorFoundation','Concrete (\u20AC/m\u00B2)',0,150,1,fE,'Concrete pad per m\u00B2 for outdoor courts. Needs proper drainage, level surface, and frost-resistant construction.')+
slider('outdoorSiteWork','Site Work',0,60000,500,fE,'Grading, drainage installation, utilities connection, and site preparation for outdoor courts.')+
slider('outdoorLighting','Lighting per Court',0,20000,500,fE,'Floodlight installation per court. LED recommended for energy efficiency. Must meet competition standards if applicable.')+
- slider('outdoorFencing','Fencing',0,40000,500,fE,'Perimeter fencing around outdoor court area. Includes wind screens, security gates, and ball containment nets.');
+ slider('outdoorFencing','Fencing',0,40000,500,fE,'Perimeter fencing around outdoor court area. Includes wind screens, security gates, and ball containment nets.')+
+ slider('permitsCompliance','Permits & Compliance',0,50000,1000,fE,'Building permits, noise assessment, environmental compliance');
if(isBuy) h+=slider('landPriceSqm','Land Price (\u20AC/m\u00B2)',0,500,5,fE,'Land purchase price per m\u00B2. Varies by location, zoning, and accessibility.');
}
h+=slider('workingCapital','Working Capital',0,200000,1000,fE,'Cash reserve for operating losses during ramp-up phase and seasonal dips. Critical buffer \u2014 underfunding is a common startup failure.')+
- slider('contingencyPct','Contingency',0,30,1,fP,'Percentage buffer on total CAPEX for unexpected costs. 10\u201315% is standard for construction, 15\u201320% for complex projects.');
+ slider('contingencyPct','Contingency',0,30,1,fP,'Percentage buffer on total CAPEX for unexpected costs. 10\u201315% is standard for construction, 15\u201320% for complex projects.')+
+ slider('budgetTarget','Your Budget Target',0,5000000,10000,fE,'Set your total budget to see how your planned CAPEX compares. Leave at 0 to hide the budget indicator.');
$('#inp-capex').innerHTML = h;
}
@@ -248,7 +331,7 @@ function buildToggle(id,opts,key){
el.querySelectorAll('button').forEach(btn=>{
btn.classList.toggle('toggle-btn--active', btn.dataset.val===S[key]);
});
- rebuildSpaceInputs(); rebuildCapexInputs(); rebuildOpexInputs(); bindSliders(); render();
+ rebuildSpaceInputs(); rebuildCapexInputs(); rebuildOpexInputs(); bindSliders(); bindPills(); render();
});
}
@@ -257,6 +340,7 @@ function bindSliders(){
inp.oninput = () => {
const k=inp.dataset.key;
S[k]=parseFloat(inp.value);
+ if(k==='permitsCompliance') _userAdjusted.add(k);
const numInp = document.querySelector(`input[type=number][data-numfor="${k}"]`);
if(numInp) numInp.value = S[k];
render();
@@ -268,6 +352,7 @@ function bindSliders(){
const v = parseFloat(inp.value);
if(isNaN(v)) return;
S[k]=v;
+ if(k==='permitsCompliance') _userAdjusted.add(k);
const rangeInp = document.querySelector(`input[type=range][data-key="${k}"]`);
if(rangeInp) rangeInp.value = v;
render();
@@ -285,6 +370,10 @@ function render(){
t.classList.toggle('active',t.id===`tab-${activeTab}`);
});
+ // Show signup bar on results tabs for guests
+ const sb=$('#signupBar');
+ if(sb) sb.style.display=activeTab!=='assumptions'?'flex':'none';
+
// If we have cached data, render immediately with it
if(_lastD) renderWith(_lastD);
@@ -314,6 +403,8 @@ function renderWith(d){
if(isIn){ sec.classList.remove('visible'); }
else { sec.classList.add('visible'); renderSeasonChart(); }
}
+
+ if(activeTab==='assumptions') renderWizPreview();
}
// ── Table helper ──
@@ -326,6 +417,18 @@ function renderCapex(d){
cardHTML('Per Court',fmt(Math.round(d.capexPerCourt)),d.totalCourts+' courts','','Total investment divided by number of courts. Useful for comparing scenarios and benchmarking.')+
cardHTML('Per m\u00B2',fmt(Math.round(d.capexPerSqm)),fmtN(d.sqm)+' m\u00B2','','Total investment per square meter of venue space. Benchmarks construction efficiency.');
+ // Budget indicator
+ if(d.budgetTarget>0){
+ const over=d.budgetVariance>0;
+ const cls=over?'red':'green';
+ const sign=over?'+':'';
+ $('#capexCards').innerHTML+=`
+
BUDGET ${over?'OVER':'UNDER'}
+
${sign}${fmt(Math.round(d.budgetVariance))}
+
${d.budgetPct.toFixed(0)}% of ${fmtK(d.budgetTarget)} budget
+
`;
+ }
+
let rows = d.capexItems.map(i=>`
${i.name}${i.info?` (${i.info}) `:''} ${fmt(i.amount)} `).join('');
rows += `
TOTAL CAPEX ${fmt(d.capex)} `;
$('#capexTable').innerHTML = `
${TH('Item')}${THR('Amount')} ${rows}
`;
@@ -591,14 +694,28 @@ function loadScenario(id){
Object.assign(S, state);
buildInputs();
bindSliders();
+ bindPills();
render();
document.getElementById('scenario-drawer').classList.remove('open');
}
});
}
+function resetToDefaults(){
+ if(!confirm('Reset all assumptions to defaults?')) return;
+ Object.assign(S, JSON.parse(JSON.stringify(DEFAULTS)));
+ _userAdjusted.clear();
+ buildInputs();
+ bindSliders();
+ bindPills();
+ render();
+}
+
// Wire up save button
document.addEventListener('DOMContentLoaded', () => {
+ const resetBtn = document.getElementById('resetDefaultsBtn');
+ if(resetBtn) resetBtn.onclick = resetToDefaults;
+
const saveBtn = document.getElementById('saveScenarioBtn');
if(saveBtn) saveBtn.onclick = saveScenario;
@@ -610,10 +727,195 @@ document.addEventListener('DOMContentLoaded', () => {
}
});
+// ── Wizard navigation ─────────────────────────────────────
+function buildWizardNav(){
+ const dots = $('#wizardDots');
+ if(!dots) return;
+ dots.innerHTML = WIZ_STEPS.map(s=>{
+ const cls = s.n===wizStep?'wiz-dot wiz-dot--active':s.n
${s.n${s.label}`;
+ }).join('');
+ dots.querySelectorAll('button').forEach(b=>b.onclick=()=>{
+ wizStep=parseInt(b.dataset.wiz);
+ showWizStep();
+ });
+}
+
+function showWizStep(){
+ document.querySelectorAll('.wizard-step').forEach(el=>{
+ el.classList.toggle('active',parseInt(el.dataset.wiz)===wizStep);
+ });
+ buildWizardNav();
+ renderWizNav();
+ if(_lastD) renderWizPreview();
+ // Auto-fill hidden fields when entering step 5
+ if(wizStep===5) populateWizAutoFill();
+}
+
+function renderWizPreview(){
+ const el=$('#wizPreview');
+ if(!el||!_lastD) return;
+ const d=_lastD;
+ const cf = d.ebitdaMonth - d.monthlyPayment;
+ const cfCls = cf>=0?'c-green':'c-red';
+ const irrOk = isFinite(d.irr)&&!isNaN(d.irr);
+ el.innerHTML=`
+
+
CAPEX
+
${fmtK(d.capex)}
+
+
+
Monthly CF
+
${fmtK(cf)}/mo
+
+
+
IRR (${S.holdYears}yr)
+
${irrOk?fmtP(d.irr):'N/A'}
+
`;
+}
+
+function renderWizNav(){
+ const el=$('#wizNav');
+ if(!el) return;
+ let left='', right='';
+
+ if(wizStep>1){
+ left=`← Back `;
+ } else {
+ left='
';
+ }
+
+ if(wizStep<4){
+ right=`Next → `;
+ } else if(wizStep===4){
+ right=`
+ Skip to Results
+ Next →
+
`;
+ } else if(wizStep===5){
+ right=`
+ Skip to Results
+ Submit & Get Quotes →
+
`;
+ }
+
+ el.innerHTML=left+right;
+}
+
+const COUNTRY_NAMES = {
+ DE:'Germany',ES:'Spain',IT:'Italy',FR:'France',NL:'Netherlands',
+ SE:'Sweden',UK:'United Kingdom',OTHER:'Other'
+};
+
+function populateWizAutoFill(){
+ // Set hidden inputs
+ const ct = S.dblCourts+S.sglCourts;
+ const el=v=>document.getElementById(v);
+ if(el('wiz_facility_type')) el('wiz_facility_type').value=S.venue;
+ if(el('wiz_court_count')) el('wiz_court_count').value=ct;
+ if(el('wiz_glass_type')) el('wiz_glass_type').value=S.glassType;
+ if(el('wiz_lighting_type')) el('wiz_lighting_type').value=S.lightingType;
+ if(el('wiz_country')) el('wiz_country').value=S.country;
+ if(el('wiz_budget')) el('wiz_budget').value=S.budgetTarget||'';
+
+ // Render auto-fill summary
+ const summary=$('#wizAutoSummary');
+ if(summary){
+ summary.innerHTML=`
+ Facility ${S.venue==='indoor'?'Indoor':'Outdoor'} (${S.own==='buy'?'Buy':'Rent'})
+ Courts ${ct} (${S.dblCourts} double + ${S.sglCourts} single)
+ Glass ${S.glassType==='panoramic'?'Panoramic':'Standard'}
+ Lighting ${S.lightingType.replace(/_/g,' ')}
+ Country ${COUNTRY_NAMES[S.country]||S.country}
+ ${S.budgetTarget?`Budget ${fmtK(S.budgetTarget)} `:''}
+ `;
+ }
+}
+
+function submitQuote(){
+ const form=document.getElementById('wizQuoteForm');
+ if(!form) return;
+ // Check required fields
+ const name=form.querySelector('[name="contact_name"]');
+ const email=form.querySelector('[name="contact_email"]');
+ const consent=form.querySelector('[name="consent"]');
+ if(!name.value.trim()||!email.value.trim()){
+ name.reportValidity(); email.reportValidity();
+ return;
+ }
+ if(!consent.checked){
+ consent.reportValidity();
+ return;
+ }
+
+ // Collect form data
+ const fd=new FormData(form);
+ const data={};
+ for(const [k,v] of fd.entries()){
+ if(k==='csrf_token') continue;
+ if(k==='services_needed'){
+ if(!data.services_needed) data.services_needed=[];
+ data.services_needed.push(v);
+ } else {
+ data[k]=v;
+ }
+ }
+ // Ensure services_needed is array
+ if(data.services_needed&&!Array.isArray(data.services_needed)) data.services_needed=[data.services_needed];
+
+ const csrf=form.querySelector('[name="csrf_token"]')?.value;
+ const btn=document.querySelector('.wiz-btn--submit');
+ if(btn){ btn.disabled=true; btn.textContent='Submitting\u2026'; }
+
+ fetch(window.__PADELNOMICS_QUOTE_URL__||'/leads/quote',{
+ method:'POST',
+ headers:{
+ 'Content-Type':'application/json',
+ 'X-CSRF-Token':csrf,
+ },
+ body:JSON.stringify(data),
+ })
+ .then(r=>{
+ if(!r.ok&&r.status===422) return r.json().then(d=>{throw d});
+ return r.json();
+ })
+ .then(resp=>{
+ if(resp.ok){
+ // Hide form, show success
+ form.style.display='none';
+ $('#wizAutoSummary').style.display='none';
+ document.getElementById('wizSuccess').style.display='block';
+ // Hide nav buttons
+ $('#wizNav').innerHTML=`
View Results → `;
+ }
+ })
+ .catch(err=>{
+ if(btn){ btn.disabled=false; btn.textContent='Submit & Get Quotes \u2192'; }
+ if(err&&err.errors){
+ alert(err.errors.join('\n'));
+ }
+ });
+}
+
+// ── Quote URL builder ─────────────────────────────────────
+function getQuoteUrl(){
+ const base = window.__PADELNOMICS_QUOTE_URL__ || '/leads/quote';
+ return base+'?'+new URLSearchParams({
+ venue:S.venue,
+ courts:S.dblCourts+S.sglCourts,
+ glass:S.glassType,
+ lighting:S.lightingType,
+ budget:S.budgetTarget||'',
+ country:S.country,
+ }).toString();
+}
+
// ── Init ──────────────────────────────────────────────────
buildNav();
buildInputs();
bindSliders();
+bindPills();
+showWizStep();
// Use server-provided initial data for first render (no API call needed)
if(_lastD){
renderWith(_lastD);
diff --git a/padelnomics/src/padelnomics/templates/base.html b/padelnomics/src/padelnomics/templates/base.html
index 2800c1f..01d2b10 100644
--- a/padelnomics/src/padelnomics/templates/base.html
+++ b/padelnomics/src/padelnomics/templates/base.html
@@ -20,25 +20,34 @@
-
-
-
-
-
- {% if user %}
-
Planner
-
Dashboard
- {% if session.get('is_admin') %}
-
Admin
- {% endif %}
-
-
- Sign Out
-
- {% else %}
-
Sign In
-
Get Started Free
- {% endif %}
+
+
+
+
+ {% if user %}
+
Dashboard
+ {% if session.get('is_admin') %}
+
Admin
+ {% endif %}
+
+
+ Sign Out
+
+ {% else %}
+
Sign In
+ {% endif %}
+
@@ -68,8 +77,8 @@
Product
diff --git a/padelnomics/tests/test_calculator.py b/padelnomics/tests/test_calculator.py
index a313997..866cb48 100644
--- a/padelnomics/tests/test_calculator.py
+++ b/padelnomics/tests/test_calculator.py
@@ -476,6 +476,55 @@ class TestCalcIndoorRent:
assert "Water" in names
assert "Cleaning" in names
+ def test_permits_compliance_in_indoor_rent(self, d):
+ names = [i["name"] for i in d["capexItems"]]
+ assert "Permits & Compliance" in names
+
+ def test_permits_compliance_amount(self, d):
+ permits = next(i for i in d["capexItems"] if i["name"] == "Permits & Compliance")
+ assert permits["amount"] == DEFAULTS["permitsCompliance"]
+
+
+# ════════════════════════════════════════════════════════════
+# calc — permits & compliance across scenarios
+# ════════════════════════════════════════════════════════════
+
+class TestPermitsCompliance:
+ def test_indoor_rent_has_permits(self):
+ d = calc(default_state(venue="indoor", own="rent"))
+ names = [i["name"] for i in d["capexItems"]]
+ assert "Permits & Compliance" in names
+
+ def test_outdoor_rent_has_permits(self):
+ d = calc(default_state(venue="outdoor", own="rent"))
+ names = [i["name"] for i in d["capexItems"]]
+ assert "Permits & Compliance" in names
+
+ def test_outdoor_buy_has_permits(self):
+ d = calc(default_state(venue="outdoor", own="buy"))
+ names = [i["name"] for i in d["capexItems"]]
+ assert "Permits & Compliance" in names
+
+ def test_indoor_buy_no_permits_compliance(self):
+ """Indoor Buy already has Planning + Permits, so no separate Permits & Compliance."""
+ d = calc(default_state(venue="indoor", own="buy"))
+ names = [i["name"] for i in d["capexItems"]]
+ assert "Permits & Compliance" not in names
+ assert "Planning + Permits" in names
+
+ def test_permits_compliance_value_adjustable(self):
+ d = calc(default_state(venue="indoor", own="rent", permitsCompliance=25000))
+ permits = next(i for i in d["capexItems"] if i["name"] == "Permits & Compliance")
+ assert permits["amount"] == 25000
+
+ def test_country_in_defaults(self):
+ assert "country" in DEFAULTS
+ assert DEFAULTS["country"] == "DE"
+
+ def test_permits_compliance_in_defaults(self):
+ assert "permitsCompliance" in DEFAULTS
+ assert DEFAULTS["permitsCompliance"] == 12000
+
# ════════════════════════════════════════════════════════════
# calc — edge cases
@@ -758,7 +807,7 @@ class TestCalcRegression:
return calc(default_state())
def test_capex_value(self, d):
- assert d["capex"] == 270380
+ assert d["capex"] == 283580
def test_total_courts(self, d):
assert d["totalCourts"] == 6
@@ -981,6 +1030,11 @@ plausible_state = st.fixed_dictionaries({
"exitMultiple": st.floats(0, 20, allow_nan=False, allow_infinity=False),
"contingencyPct": st.integers(0, 30),
"staff": st.integers(0, 20000),
+ "budgetTarget": st.integers(0, 5000000),
+ "glassType": st.sampled_from(["standard", "panoramic"]),
+ "lightingType": st.sampled_from(["led_standard", "led_competition", "natural"]),
+ "country": st.sampled_from(["DE", "ES", "IT", "FR", "NL", "SE", "UK", "OTHER"]),
+ "permitsCompliance": st.integers(0, 50000),
})
diff --git a/padelnomics/tests/test_phase0.py b/padelnomics/tests/test_phase0.py
new file mode 100644
index 0000000..9f36a2e
--- /dev/null
+++ b/padelnomics/tests/test_phase0.py
@@ -0,0 +1,505 @@
+"""
+Phase 0 tests: guest mode, new calculator variables, heat score, quote flow, migration.
+"""
+import json
+import math
+
+import pytest
+
+from padelnomics.leads.routes import calculate_heat_score
+from padelnomics.planner.calculator import DEFAULTS, calc, validate_state
+
+ALL_COMBOS = [
+ ("indoor", "rent"),
+ ("indoor", "buy"),
+ ("outdoor", "rent"),
+ ("outdoor", "buy"),
+]
+
+
+def default_state(**overrides):
+ s = {**DEFAULTS, **overrides}
+ return validate_state(s)
+
+
+def _assert_finite(obj, path=""):
+ if isinstance(obj, float):
+ assert math.isfinite(obj), f"Non-finite at {path}: {obj}"
+ elif isinstance(obj, dict):
+ for k, v in obj.items():
+ _assert_finite(v, f"{path}.{k}")
+ elif isinstance(obj, list):
+ for i, v in enumerate(obj):
+ _assert_finite(v, f"{path}[{i}]")
+
+
+# ════════════════════════════════════════════════════════════
+# Guest mode
+# ════════════════════════════════════════════════════════════
+
+class TestGuestMode:
+ async def test_planner_accessible_without_login(self, client):
+ """GET /planner/ returns 200 for unauthenticated user."""
+ resp = await client.get("/planner/")
+ assert resp.status_code == 200
+
+ async def test_calculate_endpoint_works_without_login(self, client):
+ """POST /planner/calculate returns valid JSON for guest."""
+ resp = await client.post(
+ "/planner/calculate",
+ json={"state": {"dblCourts": 4}},
+ )
+ assert resp.status_code == 200
+ data = await resp.get_json()
+ assert "capex" in data
+
+ async def test_scenario_routes_require_login(self, client):
+ """Save/load/delete/list scenarios still require auth."""
+ resp = await client.post(
+ "/planner/scenarios/save",
+ json={"name": "test", "state_json": "{}"},
+ )
+ assert resp.status_code in (302, 401)
+
+ async def test_planner_hides_save_for_guest(self, client):
+ """Planner HTML does not render scenario controls for guests."""
+ resp = await client.get("/planner/")
+ html = (await resp.data).decode()
+ assert "saveScenarioBtn" not in html
+
+ async def test_planner_shows_save_for_auth(self, auth_client):
+ """Planner HTML renders scenario controls for logged-in users."""
+ resp = await auth_client.get("/planner/")
+ html = (await resp.data).decode()
+ assert "saveScenarioBtn" in html
+
+
+# ════════════════════════════════════════════════════════════
+# New calculator variables — defaults
+# ════════════════════════════════════════════════════════════
+
+class TestNewCalculatorVariables:
+ def test_budget_target_in_defaults(self):
+ assert "budgetTarget" in DEFAULTS
+ assert DEFAULTS["budgetTarget"] == 0
+
+ def test_glass_type_in_defaults(self):
+ assert "glassType" in DEFAULTS
+ assert DEFAULTS["glassType"] == "standard"
+
+ def test_lighting_type_in_defaults(self):
+ assert "lightingType" in DEFAULTS
+ assert DEFAULTS["lightingType"] == "led_standard"
+
+
+# ════════════════════════════════════════════════════════════
+# Glass type
+# ════════════════════════════════════════════════════════════
+
+class TestGlassType:
+ def test_panoramic_glass_increases_capex(self):
+ d_std = calc(default_state(glassType="standard"))
+ d_pan = calc(default_state(glassType="panoramic"))
+ assert d_pan["capex"] > d_std["capex"]
+
+ @pytest.mark.parametrize("glass", ["standard", "panoramic"])
+ @pytest.mark.parametrize("venue,own", ALL_COMBOS)
+ def test_glass_type_all_combos(self, venue, own, glass):
+ d = calc(default_state(venue=venue, own=own, glassType=glass))
+ assert d["capex"] > 0
+ _assert_finite(d)
+
+ def test_panoramic_applies_1_4x_multiplier(self):
+ """Panoramic courts cost 1.4x standard courts."""
+ d_std = calc(default_state(glassType="standard"))
+ d_pan = calc(default_state(glassType="panoramic"))
+ std_courts = next(i for i in d_std["capexItems"] if i["name"] == "Padel Courts")
+ pan_courts = next(i for i in d_pan["capexItems"] if i["name"] == "Padel Courts")
+ assert pan_courts["amount"] == pytest.approx(std_courts["amount"] * 1.4, abs=1)
+
+
+# ════════════════════════════════════════════════════════════
+# Lighting type
+# ════════════════════════════════════════════════════════════
+
+class TestLightingType:
+ def test_led_competition_increases_capex(self):
+ d_std = calc(default_state(lightingType="led_standard"))
+ d_comp = calc(default_state(lightingType="led_competition"))
+ assert d_comp["capex"] > d_std["capex"]
+
+ def test_natural_light_outdoor(self):
+ d_led = calc(default_state(venue="outdoor", lightingType="led_standard"))
+ d_nat = calc(default_state(venue="outdoor", lightingType="natural"))
+ assert d_nat["capex"] < d_led["capex"]
+
+ def test_natural_light_zeroes_outdoor_lighting(self):
+ d = calc(default_state(venue="outdoor", lightingType="natural"))
+ lighting_item = next(
+ (i for i in d["capexItems"] if i["name"] == "Lighting"), None
+ )
+ assert lighting_item is not None
+ assert lighting_item["amount"] == 0
+
+ @pytest.mark.parametrize("light", ["led_standard", "led_competition"])
+ @pytest.mark.parametrize("venue,own", ALL_COMBOS)
+ def test_lighting_type_all_combos(self, venue, own, light):
+ d = calc(default_state(venue=venue, own=own, lightingType=light))
+ assert d["capex"] > 0
+ _assert_finite(d)
+
+
+# ════════════════════════════════════════════════════════════
+# Budget target
+# ════════════════════════════════════════════════════════════
+
+class TestBudgetTarget:
+ def test_budget_variance_when_set(self):
+ d = calc(default_state(budgetTarget=300000))
+ assert "budgetVariance" in d
+ assert d["budgetVariance"] == d["capex"] - 300000
+
+ def test_budget_variance_zero_when_no_budget(self):
+ d = calc(default_state(budgetTarget=0))
+ assert d["budgetVariance"] == 0
+ assert d["budgetPct"] == 0
+
+ def test_budget_pct_calculated(self):
+ d = calc(default_state(budgetTarget=200000))
+ assert d["budgetPct"] == pytest.approx(d["capex"] / 200000 * 100)
+
+ def test_budget_target_passthrough(self):
+ d = calc(default_state(budgetTarget=500000))
+ assert d["budgetTarget"] == 500000
+
+
+# ════════════════════════════════════════════════════════════
+# Heat score
+# ════════════════════════════════════════════════════════════
+
+class TestHeatScore:
+ def test_hot_lead(self):
+ """High-readiness signals = hot."""
+ form = {
+ "timeline": "asap",
+ "location_status": "lease_signed",
+ "financing_status": "self_funded",
+ "decision_process": "solo",
+ "previous_supplier_contact": "received_quotes",
+ "budget_estimate": "500000",
+ }
+ assert calculate_heat_score(form) == "hot"
+
+ def test_cool_lead(self):
+ """Low-readiness signals = cool."""
+ form = {
+ "timeline": "12+mo",
+ "location_status": "still_searching",
+ "financing_status": "not_started",
+ "decision_process": "committee",
+ "previous_supplier_contact": "first_time",
+ "budget_estimate": "0",
+ }
+ assert calculate_heat_score(form) == "cool"
+
+ def test_warm_lead(self):
+ """Mid-readiness signals = warm."""
+ form = {
+ "timeline": "3-6mo",
+ "location_status": "location_found",
+ "financing_status": "seeking",
+ "decision_process": "partners",
+ "budget_estimate": "150000",
+ }
+ assert calculate_heat_score(form) == "warm"
+
+ def test_empty_form_is_cool(self):
+ assert calculate_heat_score({}) == "cool"
+
+ def test_timeline_6_12mo_scores_1(self):
+ assert calculate_heat_score({"timeline": "6-12mo"}) == "cool"
+
+ def test_high_budget_alone_not_hot(self):
+ """Budget alone shouldn't make a lead hot."""
+ assert calculate_heat_score({"budget_estimate": "1000000"}) == "cool"
+
+ def test_permit_granted_scores_4(self):
+ assert calculate_heat_score({"location_status": "permit_granted"}) == "cool"
+ # Combined with timeline makes it warm
+ form = {"location_status": "permit_granted", "timeline": "asap"}
+ assert calculate_heat_score(form) == "warm"
+
+ def test_permit_pending_scores_3(self):
+ form = {
+ "location_status": "permit_pending",
+ "timeline": "3-6mo",
+ "financing_status": "self_funded",
+ }
+ assert calculate_heat_score(form) == "warm"
+
+ def test_converting_existing_scores_2(self):
+ assert calculate_heat_score({"location_status": "converting_existing"}) == "cool"
+
+ def test_permit_not_filed_scores_2(self):
+ assert calculate_heat_score({"location_status": "permit_not_filed"}) == "cool"
+
+ def test_location_found_scores_1(self):
+ assert calculate_heat_score({"location_status": "location_found"}) == "cool"
+
+
+# ════════════════════════════════════════════════════════════
+# Quote request route
+# ════════════════════════════════════════════════════════════
+
+class TestQuoteRequest:
+ async def test_quote_form_loads(self, client):
+ """GET /leads/quote returns 200 with form."""
+ resp = await client.get("/leads/quote")
+ assert resp.status_code == 200
+
+ async def test_quote_prefill_from_params(self, client):
+ """Query params pre-fill the form."""
+ resp = await client.get("/leads/quote?venue=outdoor&courts=6")
+ assert resp.status_code == 200
+
+ async def test_quote_submit_creates_lead(self, client, db):
+ """POST /leads/quote creates a lead_requests row."""
+ # Get CSRF token first
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "indoor",
+ "court_count": "4",
+ "glass_type": "panoramic",
+ "lighting_type": "led_standard",
+ "build_context": "new_standalone",
+ "country": "DE",
+ "timeline": "3-6mo",
+ "location_status": "location_found",
+ "financing_status": "self_funded",
+ "decision_process": "solo",
+ "stakeholder_type": "entrepreneur",
+ "contact_name": "Test User",
+ "contact_email": "test@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+
+ async with db.execute("SELECT * FROM lead_requests WHERE lead_type = 'quote'") as cur:
+ rows = await cur.fetchall()
+ assert len(rows) == 1
+ row = dict(rows[0])
+ assert row["heat_score"] in ("hot", "warm", "cool")
+ assert row["contact_email"] == "test@example.com"
+ assert row["facility_type"] == "indoor"
+ assert row["stakeholder_type"] == "entrepreneur"
+
+ async def test_quote_submit_without_login(self, client, db):
+ """Guests can submit quotes (user_id is null)."""
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "indoor",
+ "court_count": "2",
+ "country": "DE",
+ "timeline": "3-6mo",
+ "stakeholder_type": "entrepreneur",
+ "contact_name": "Guest",
+ "contact_email": "guest@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+
+ async with db.execute(
+ "SELECT user_id FROM lead_requests WHERE contact_email = 'guest@example.com'"
+ ) as cur:
+ row = await cur.fetchone()
+ assert row is not None
+ assert row[0] is None # user_id should be NULL for guests
+
+ async def test_quote_submit_with_login(self, auth_client, db, test_user):
+ """Logged-in user gets user_id set on lead."""
+ await auth_client.get("/leads/quote")
+ async with auth_client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await auth_client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "outdoor",
+ "court_count": "6",
+ "country": "DE",
+ "timeline": "asap",
+ "stakeholder_type": "entrepreneur",
+ "contact_name": "Auth User",
+ "contact_email": "auth@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+
+ async with db.execute(
+ "SELECT user_id FROM lead_requests WHERE contact_email = 'auth@example.com'"
+ ) as cur:
+ row = await cur.fetchone()
+ assert row is not None
+ assert row[0] == test_user["id"]
+
+ async def test_venue_search_build_context(self, client, db):
+ """Build context 'venue_search' is stored correctly."""
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "indoor",
+ "court_count": "4",
+ "build_context": "venue_search",
+ "country": "DE",
+ "timeline": "6-12mo",
+ "stakeholder_type": "developer",
+ "contact_name": "Venue Search",
+ "contact_email": "venue@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+
+ async with db.execute(
+ "SELECT build_context FROM lead_requests WHERE contact_email = 'venue@example.com'"
+ ) as cur:
+ row = await cur.fetchone()
+ assert row is not None
+ assert row[0] == "venue_search"
+
+ async def test_stakeholder_type_stored(self, client, db):
+ """stakeholder_type field is stored correctly."""
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "indoor",
+ "court_count": "6",
+ "country": "DE",
+ "timeline": "asap",
+ "stakeholder_type": "tennis_club",
+ "contact_name": "Club Owner",
+ "contact_email": "club@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+
+ async with db.execute(
+ "SELECT stakeholder_type FROM lead_requests WHERE contact_email = 'club@example.com'"
+ ) as cur:
+ row = await cur.fetchone()
+ assert row is not None
+ assert row[0] == "tennis_club"
+
+ async def test_submitted_page_has_context(self, client):
+ """Quote submitted page includes project context."""
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ form={
+ "facility_type": "indoor",
+ "court_count": "6",
+ "country": "DE",
+ "timeline": "3-6mo",
+ "stakeholder_type": "entrepreneur",
+ "contact_name": "Context Test",
+ "contact_email": "ctx@example.com",
+ "csrf_token": csrf,
+ },
+ )
+ assert resp.status_code == 200
+ html = (await resp.data).decode()
+ assert "matched" in html.lower()
+ assert "6-court" in html
+ assert "DE" in html
+
+ async def test_quote_validation_rejects_missing_fields(self, client):
+ """POST /leads/quote returns 422 JSON when mandatory fields missing."""
+ await client.get("/leads/quote")
+ async with client.session_transaction() as sess:
+ csrf = sess.get("csrf_token", "")
+
+ resp = await client.post(
+ "/leads/quote",
+ json={
+ "facility_type": "indoor",
+ "court_count": "4",
+ "contact_name": "",
+ "contact_email": "",
+ },
+ headers={"X-CSRF-Token": csrf},
+ )
+ assert resp.status_code == 422
+ data = await resp.get_json()
+ assert data["ok"] is False
+ assert len(data["errors"]) >= 3 # country, timeline, stakeholder_type + name + email
+
+
+# ════════════════════════════════════════════════════════════
+# Migration / schema
+# ════════════════════════════════════════════════════════════
+
+class TestSchema:
+ async def test_schema_has_new_columns(self, db):
+ """Fresh DB from schema.sql has all expanded lead_requests columns."""
+ async with db.execute("PRAGMA table_info(lead_requests)") as cur:
+ cols = {r[1] for r in await cur.fetchall()}
+ for expected in (
+ "facility_type", "glass_type", "lighting_type",
+ "build_context", "country", "timeline",
+ "location_status", "financing_status",
+ "heat_score", "contact_name", "contact_email",
+ "contact_phone", "contact_company",
+ "wants_financing_help", "decision_process",
+ "previous_supplier_contact", "services_needed",
+ "additional_info", "stakeholder_type",
+ ):
+ assert expected in cols, f"Missing column: {expected}"
+
+ async def test_user_id_nullable(self, db):
+ """lead_requests.user_id should accept NULL for guest leads."""
+ await db.execute(
+ "INSERT INTO lead_requests (lead_type, contact_email, created_at) VALUES (?, ?, datetime('now'))",
+ ("quote", "guest@example.com"),
+ )
+ await db.commit()
+ async with db.execute(
+ "SELECT user_id FROM lead_requests WHERE contact_email = 'guest@example.com'"
+ ) as cur:
+ row = await cur.fetchone()
+ assert row[0] is None
+
+
+# ════════════════════════════════════════════════════════════
+# Business plan price in config
+# ════════════════════════════════════════════════════════════
+
+class TestBusinessPlanConfig:
+ def test_business_plan_in_paddle_prices(self):
+ from padelnomics.core import Config
+ c = Config()
+ assert "business_plan" in c.PADDLE_PRICES
diff --git a/scratch/Kimi_Agent_Padel Landing Proposal.zip b/scratch/Kimi_Agent_Padel Landing Proposal.zip
deleted file mode 100644
index 99656b0..0000000
Binary files a/scratch/Kimi_Agent_Padel Landing Proposal.zip and /dev/null differ
diff --git a/scratch/Kimi_Agent_Padelnomics Brand Proposals.zip b/scratch/Kimi_Agent_Padelnomics Brand Proposals.zip
deleted file mode 100644
index 1555f70..0000000
Binary files a/scratch/Kimi_Agent_Padelnomics Brand Proposals.zip and /dev/null differ
diff --git a/task-backlog.md b/task-backlog.md
index b37c918..6aa7d8e 100644
--- a/task-backlog.md
+++ b/task-backlog.md
@@ -1,2 +1,3 @@
-- Make the calculator or at least parts of it public -> check reasoning in padelnomic lead platform md
-
+- [x] Make the calculator or at least parts of it public -> check reasoning in padelnomic lead platform md (in plan.md)
+- [] Add R2 credentials and setup for litestream to better secure data.
+- [] Check if we have rollback/backup restore functionality for database & deployments