chore: commit pending changes — logo, base template, scratch designs, changelog

- favicon.svg: pending logo tweaks
- base.html: pending template changes
- CHANGELOG.md: add waitlist mode and logo redesign entries missed in prior commits
- scratch/: add design prototype HTML/JSX files, remove old markdown notes

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Deeman
2026-02-19 23:45:42 +01:00
parent a1933eb2ba
commit bc7fbcd595
12 changed files with 3870 additions and 206 deletions

View File

@@ -9,6 +9,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
### Changed ### Changed
- Auto-create Resend audiences per blueprint: `capture_waitlist_email()` now derives the audience name from `request.blueprints[0]` (e.g., `waitlist-auth`, `waitlist-suppliers`) and lazily creates audiences via the Resend API on first use, caching IDs in a new `resend_audiences` table; removes `RESEND_AUDIENCE_WAITLIST` env var — only `RESEND_API_KEY` needed - Auto-create Resend audiences per blueprint: `capture_waitlist_email()` now derives the audience name from `request.blueprints[0]` (e.g., `waitlist-auth`, `waitlist-suppliers`) and lazily creates audiences via the Resend API on first use, caching IDs in a new `resend_audiences` table; removes `RESEND_AUDIENCE_WAITLIST` env var — only `RESEND_API_KEY` needed
### Added
- Waitlist mode (lean startup smoke test): `WAITLIST_MODE` config flag intercepts `/auth/signup`, `/suppliers/signup`, and `/planner/export` to capture emails or show "coming soon" messaging before Paddle billing is ready; confirmation emails sent via Resend; optional `RESEND_AUDIENCE_WAITLIST` for bulk launch blast; flip flag to `false` and all flows revert to normal
### Changed
- Redesign padel racket SVG logo/favicon: filled dark silhouette with white punched-through holes (3×3 grid), proper throat tapering to handle at bottom, grip tape lines — replaces outline-only stroke approach; bump favicon cache to v4
### Added ### Added
- Simple A/B testing with `@ab_test` decorator and Umami `data-tag` integration - Simple A/B testing with `@ab_test` decorator and Umami `data-tag` integration
- SEO defaults in `base.html`: canonical, og:url, og:type, og:image (logo fallback), og:title, og:description, twitter:card — every page gets these automatically, child templates override as needed - SEO defaults in `base.html`: canonical, og:url, og:type, og:image (logo fallback), og:title, og:description, twitter:card — every page gets these automatically, child templates override as needed

View File

@@ -1,13 +1,22 @@
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32" fill="none"> <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
<!-- Padel racket head: rounded rectangle, slightly taller than wide --> <!-- Racket face: solid fill with slightly rounded corners -->
<rect x="4" y="1" width="19" height="21" rx="6" stroke="#0F172A" stroke-width="2.5" fill="none"/> <rect x="4" y="1" width="24" height="18" rx="4" fill="#0F172A"/>
<!-- Characteristic padel holes (3x2 grid) --> <!-- Throat: trapezoid narrowing from face bottom to handle width -->
<circle cx="9.5" cy="7.5" r="1.5" fill="#0F172A" opacity="0.45"/> <path d="M8,19 L24,19 L20,25 L12,25 Z" fill="#0F172A"/>
<circle cx="13.5" cy="7.5" r="1.5" fill="#0F172A" opacity="0.45"/>
<circle cx="17.5" cy="7.5" r="1.5" fill="#0F172A" opacity="0.45"/>
<circle cx="9.5" cy="13.5" r="1.5" fill="#0F172A" opacity="0.45"/>
<circle cx="13.5" cy="13.5" r="1.5" fill="#0F172A" opacity="0.45"/>
<circle cx="17.5" cy="13.5" r="1.5" fill="#0F172A" opacity="0.45"/>
<!-- Handle --> <!-- Handle -->
<line x1="21" y1="22" x2="29" y2="30" stroke="#0F172A" stroke-width="3" stroke-linecap="round"/> <rect x="12" y="24" width="8" height="7" rx="2" fill="#0F172A"/>
<!-- Holes: 3×3 grid punched through face -->
<circle cx="11" cy="6" r="1.6" fill="white"/>
<circle cx="16" cy="6" r="1.6" fill="white"/>
<circle cx="21" cy="6" r="1.6" fill="white"/>
<circle cx="11" cy="11" r="1.6" fill="white"/>
<circle cx="16" cy="11" r="1.6" fill="white"/>
<circle cx="21" cy="11" r="1.6" fill="white"/>
<circle cx="11" cy="16" r="1.6" fill="white"/>
<circle cx="16" cy="16" r="1.6" fill="white"/>
<circle cx="21" cy="16" r="1.6" fill="white"/>
<!-- Grip tape lines -->
<line x1="12.5" y1="26.5" x2="19.5" y2="26.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="28.5" x2="19.5" y2="28.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="30.5" x2="19.5" y2="30.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
</svg> </svg>

Before

Width:  |  Height:  |  Size: 841 B

After

Width:  |  Height:  |  Size: 1.2 KiB

View File

@@ -6,10 +6,10 @@
<title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title> <title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
<!-- Favicon --> <!-- Favicon -->
<link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='images/favicon.svg') }}?v=3"> <link rel="icon" type="image/svg+xml" href="{{ url_for('static', filename='images/favicon.svg') }}?v=4">
<link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}?v=3" sizes="any"> <link rel="icon" href="{{ url_for('static', filename='images/favicon.ico') }}?v=4" sizes="any">
<link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/favicon-32.png') }}?v=3"> <link rel="icon" type="image/png" sizes="32x32" href="{{ url_for('static', filename='images/favicon-32.png') }}?v=4">
<link rel="apple-touch-icon" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}?v=3"> <link rel="apple-touch-icon" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}?v=4">
<!-- Fonts --> <!-- Fonts -->
<link rel="preconnect" href="https://fonts.googleapis.com"> <link rel="preconnect" href="https://fonts.googleapis.com">
@@ -72,15 +72,22 @@
<!-- Center: logo --> <!-- Center: logo -->
<a href="{{ url_for('public.landing') }}" class="nav-logo" style="display:inline-flex;align-items:center;gap:6px;text-decoration:none"> <a href="{{ url_for('public.landing') }}" class="nav-logo" style="display:inline-flex;align-items:center;gap:6px;text-decoration:none">
<svg width="28" height="28" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <svg width="28" height="28" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="4" y="1" width="19" height="21" rx="6" stroke="#0F172A" stroke-width="2.2" fill="none"/> <rect x="4" y="1" width="24" height="18" rx="4" fill="#0F172A"/>
<circle cx="9.5" cy="7.5" r="1.4" fill="#0F172A" opacity="0.35"/> <path d="M8,19 L24,19 L20,25 L12,25 Z" fill="#0F172A"/>
<circle cx="13.5" cy="7.5" r="1.4" fill="#0F172A" opacity="0.35"/> <rect x="12" y="24" width="8" height="7" rx="2" fill="#0F172A"/>
<circle cx="17.5" cy="7.5" r="1.4" fill="#0F172A" opacity="0.35"/> <circle cx="11" cy="6" r="1.6" fill="white"/>
<circle cx="9.5" cy="13.5" r="1.4" fill="#0F172A" opacity="0.35"/> <circle cx="16" cy="6" r="1.6" fill="white"/>
<circle cx="13.5" cy="13.5" r="1.4" fill="#0F172A" opacity="0.35"/> <circle cx="21" cy="6" r="1.6" fill="white"/>
<circle cx="17.5" cy="13.5" r="1.4" fill="#0F172A" opacity="0.35"/> <circle cx="11" cy="11" r="1.6" fill="white"/>
<line x1="21" y1="22" x2="29" y2="30" stroke="#0F172A" stroke-width="2.5" stroke-linecap="round"/> <circle cx="16" cy="11" r="1.6" fill="white"/>
<circle cx="21" cy="11" r="1.6" fill="white"/>
<circle cx="11" cy="16" r="1.6" fill="white"/>
<circle cx="16" cy="16" r="1.6" fill="white"/>
<circle cx="21" cy="16" r="1.6" fill="white"/>
<line x1="12.5" y1="26.5" x2="19.5" y2="26.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="28.5" x2="19.5" y2="28.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="30.5" x2="19.5" y2="30.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
</svg> </svg>
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:1.125rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> <span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:1.125rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
</a> </a>
@@ -146,13 +153,22 @@
<div class="grid-3 mb-8"> <div class="grid-3 mb-8">
<div> <div>
<div class="mb-1" style="display:inline-flex;align-items:center;gap:5px"> <div class="mb-1" style="display:inline-flex;align-items:center;gap:5px">
<svg width="22" height="22" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true"> <svg width="22" height="22" viewBox="0 0 32 32" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
<rect x="4" y="2" width="20" height="20" rx="6" stroke="#0F172A" stroke-width="2.2" fill="none"/> <rect x="4" y="1" width="24" height="18" rx="4" fill="#0F172A"/>
<line x1="14" y1="2" x2="14" y2="22" stroke="#0F172A" stroke-width="1.2" opacity="0.35"/> <path d="M8,19 L24,19 L20,25 L12,25 Z" fill="#0F172A"/>
<line x1="4" y1="12" x2="24" y2="12" stroke="#0F172A" stroke-width="1.2" opacity="0.35"/> <rect x="12" y="24" width="8" height="7" rx="2" fill="#0F172A"/>
<line x1="6.5" y1="4.5" x2="21.5" y2="19.5" stroke="#0F172A" stroke-width="1" opacity="0.2"/> <circle cx="11" cy="6" r="1.6" fill="white"/>
<line x1="21.5" y1="4.5" x2="6.5" y2="19.5" stroke="#0F172A" stroke-width="1" opacity="0.2"/> <circle cx="16" cy="6" r="1.6" fill="white"/>
<line x1="20" y1="22" x2="28" y2="30" stroke="#0F172A" stroke-width="2.5" stroke-linecap="round"/> <circle cx="21" cy="6" r="1.6" fill="white"/>
<circle cx="11" cy="11" r="1.6" fill="white"/>
<circle cx="16" cy="11" r="1.6" fill="white"/>
<circle cx="21" cy="11" r="1.6" fill="white"/>
<circle cx="11" cy="16" r="1.6" fill="white"/>
<circle cx="16" cy="16" r="1.6" fill="white"/>
<circle cx="21" cy="16" r="1.6" fill="white"/>
<line x1="12.5" y1="26.5" x2="19.5" y2="26.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="28.5" x2="19.5" y2="28.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
<line x1="12.5" y1="30.5" x2="19.5" y2="30.5" stroke="white" stroke-width="0.7" opacity="0.45"/>
</svg> </svg>
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:0.9375rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span> <span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:0.9375rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
</div> </div>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,709 @@
<!DOCTYPE html>
<!--
PADELNOMICS — Basic Listing Tier: Supplier Detail Page Prototype
================================================================
Design prototype — adapt field names and URLs to Jinja2 / actual routes.
Dummy data: "CourtCraft GmbH", fictional manufacturer based in Munich.
================================================================
-->
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>CourtCraft GmbH — Padel Court Manufacturer · padelnomics</title>
<link href="https://fonts.googleapis.com/css2?family=Bricolage+Grotesque:opsz,wght@12..96,400;12..96,600;12..96,700;12..96,800&family=DM+Sans:ital,opsz,wght@0,9..40,400;0,9..40,500;0,9..40,600;0,9..40,700;1,9..40,400&display=swap" rel="stylesheet">
<style>
*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
:root {
--navy: #0F172A;
--navy-2: #1E293B;
--slate: #64748B;
--slate-2: #94A3B8;
--slate-3: #CBD5E1;
--border: #E2E8F0;
--bg: #F8FAFC;
--bg-2: #F1F5F9;
--white: #FFFFFF;
--blue: #1D4ED8;
--blue-2: #1E40AF;
--blue-bg: #EFF6FF;
--green: #16A34A;
--green-bg:#DCFCE7;
--font-display: 'Bricolage Grotesque', sans-serif;
--font-body: 'DM Sans', sans-serif;
--radius-sm: 8px;
--radius: 14px;
--radius-lg: 20px;
--shadow-sm: 0 1px 3px rgba(15,23,42,0.06), 0 1px 2px rgba(15,23,42,0.04);
--shadow: 0 4px 16px rgba(15,23,42,0.07), 0 1px 4px rgba(15,23,42,0.04);
--shadow-lg: 0 12px 40px rgba(15,23,42,0.10), 0 2px 8px rgba(15,23,42,0.05);
}
html { font-size: 16px; scroll-behavior: smooth; }
body {
font-family: var(--font-body);
color: var(--navy);
background: var(--bg);
line-height: 1.6;
-webkit-font-smoothing: antialiased;
}
a { color: var(--blue); text-decoration: none; }
a:hover { text-decoration: underline; }
/* ── Navbar (glass) ─────────────────────────────────── */
.nav {
position: sticky; top: 0; z-index: 100;
background: rgba(255,255,255,0.85);
backdrop-filter: blur(14px);
-webkit-backdrop-filter: blur(14px);
border-bottom: 1px solid var(--border);
padding: 0 2rem;
display: flex; align-items: center; justify-content: space-between;
height: 60px;
}
.nav-logo {
display: flex; align-items: center; gap: 7px;
font-family: var(--font-display); font-weight: 800;
font-size: 1.125rem; color: var(--navy); letter-spacing: -0.02em;
}
.nav-logo svg { flex-shrink: 0; }
.nav-back {
font-size: 0.8125rem; color: var(--slate); display: flex;
align-items: center; gap: 5px; padding: 6px 12px; border-radius: 8px;
transition: background 0.15s;
}
.nav-back:hover { background: var(--bg-2); color: var(--navy); text-decoration: none; }
/* ── Page wrapper ───────────────────────────────────── */
.page { max-width: 1120px; margin: 0 auto; padding: 0 2rem; }
/* ── Hero ───────────────────────────────────────────── */
.hero {
position: relative; overflow: hidden;
background: var(--navy);
border-radius: 0 0 var(--radius-lg) var(--radius-lg);
margin-bottom: 2.5rem;
padding: 3rem 2rem 2.5rem;
}
/* Padel court grid — CSS-only geometric background */
.hero::before {
content: '';
position: absolute; inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.04) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.04) 1px, transparent 1px);
background-size: 48px 48px;
pointer-events: none;
}
/* Centre service line */
.hero::after {
content: '';
position: absolute; left: 50%; top: 0; bottom: 0;
width: 1px; background: rgba(255,255,255,0.06);
pointer-events: none;
}
.hero-inner {
position: relative; z-index: 1;
max-width: 1120px; margin: 0 auto;
display: flex; align-items: flex-start;
justify-content: space-between; gap: 2rem;
}
.hero-left { flex: 1; }
/* Logo + name row */
.hero-identity { display: flex; align-items: center; gap: 1rem; margin-bottom: 1.25rem; }
.hero-logo {
width: 72px; height: 72px; border-radius: 14px; object-fit: cover;
background: rgba(255,255,255,0.12); border: 2px solid rgba(255,255,255,0.15);
display: flex; align-items: center; justify-content: center;
font-family: var(--font-display); font-weight: 800;
font-size: 1.75rem; color: rgba(255,255,255,0.5);
flex-shrink: 0;
}
.hero-title-group { min-width: 0; }
.hero-badges { display: flex; align-items: center; gap: 6px; margin-bottom: 6px; }
.hero-badge {
font-size: 0.625rem; font-weight: 700; padding: 3px 9px;
border-radius: 999px; letter-spacing: 0.06em; text-transform: uppercase;
}
.hero-badge--category {
background: rgba(29,78,216,0.5); color: #93C5FD;
border: 1px solid rgba(147,197,253,0.3);
}
.hero-badge--verified {
background: rgba(22,163,74,0.4); color: #86EFAC;
border: 1px solid rgba(134,239,172,0.3);
display: flex; align-items: center; gap: 4px;
}
.hero-badge--verified svg { width: 10px; height: 10px; }
.hero-name {
font-family: var(--font-display); font-weight: 800;
font-size: clamp(1.75rem, 4vw, 2.75rem);
color: #fff; line-height: 1.1; letter-spacing: -0.025em;
}
.hero-meta {
margin-top: 6px; font-size: 0.875rem; color: rgba(255,255,255,0.5);
display: flex; align-items: center; gap: 10px;
}
.hero-meta-dot { width: 3px; height: 3px; border-radius: 50%; background: rgba(255,255,255,0.25); }
.hero-tagline {
margin-top: 1.25rem; font-size: 1rem; color: rgba(255,255,255,0.7);
max-width: 520px; line-height: 1.6;
font-style: italic;
}
/* Hero actions */
.hero-actions { display: flex; flex-direction: column; gap: 10px; flex-shrink: 0; padding-top: 4px; }
.btn-hero-primary {
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px; border-radius: 10px; font-size: 0.875rem; font-weight: 600;
font-family: var(--font-body); background: #fff; color: var(--navy);
border: none; cursor: pointer; white-space: nowrap;
box-shadow: 0 2px 12px rgba(0,0,0,0.2); transition: all 0.15s;
text-decoration: none;
}
.btn-hero-primary:hover { background: var(--blue-bg); color: var(--blue); text-decoration: none; }
.btn-hero-secondary {
display: inline-flex; align-items: center; gap: 7px;
padding: 9px 18px; border-radius: 10px; font-size: 0.8125rem; font-weight: 500;
font-family: var(--font-body); cursor: pointer; white-space: nowrap;
background: rgba(255,255,255,0.08); color: rgba(255,255,255,0.75);
border: 1px solid rgba(255,255,255,0.15); transition: all 0.15s;
text-decoration: none;
}
.btn-hero-secondary:hover { background: rgba(255,255,255,0.14); color: #fff; text-decoration: none; }
.btn-hero-secondary svg { width: 15px; height: 15px; flex-shrink: 0; }
/* ── Body grid ──────────────────────────────────────── */
.body-grid {
display: grid;
grid-template-columns: 1fr 320px;
gap: 1.75rem;
align-items: start;
}
.body-main > * + * { margin-top: 1.5rem; }
.body-sidebar { display: flex; flex-direction: column; gap: 1.25rem; }
/* ── Card ───────────────────────────────────────────── */
.card {
background: var(--white); border: 1px solid var(--border);
border-radius: var(--radius); padding: 1.5rem;
box-shadow: var(--shadow-sm);
}
.card-title {
font-size: 0.625rem; font-weight: 700; letter-spacing: 0.08em;
text-transform: uppercase; color: var(--slate-2); margin-bottom: 1rem;
display: flex; align-items: center; gap: 8px;
}
.card-title::after {
content: ''; flex: 1; height: 1px; background: var(--border);
}
/* Description card */
.desc-body {
font-size: 0.9375rem; color: var(--slate); line-height: 1.75;
margin-bottom: 1.25rem;
}
.cat-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.cat-pill {
font-size: 0.6875rem; font-weight: 600; padding: 4px 12px;
border-radius: 999px; background: var(--blue-bg);
color: var(--blue); border: 1px solid #BFDBFE;
letter-spacing: 0.01em;
}
/* Services list */
.service-list { display: grid; grid-template-columns: 1fr 1fr; gap: 0.5rem; }
.service-item {
display: flex; align-items: flex-start; gap: 8px;
font-size: 0.875rem; color: var(--navy-2); padding: 6px 0;
}
.service-check {
width: 18px; height: 18px; border-radius: 5px;
background: var(--green-bg); color: var(--green);
display: flex; align-items: center; justify-content: center;
flex-shrink: 0; margin-top: 1px;
}
.service-check svg { width: 10px; height: 10px; }
/* Service area */
.area-pills { display: flex; flex-wrap: wrap; gap: 6px; }
.area-pill {
font-size: 0.75rem; font-weight: 500; padding: 4px 10px;
border-radius: 6px; background: var(--bg-2);
color: var(--slate); border: 1px solid var(--border);
}
/* ── Sidebar cards ──────────────────────────────────── */
.sidebar-sticky { position: sticky; top: 80px; }
/* Contact card */
.contact-avatar {
width: 44px; height: 44px; border-radius: 50%;
background: linear-gradient(135deg, #BFDBFE, #EFF6FF);
display: flex; align-items: center; justify-content: center;
font-family: var(--font-display); font-weight: 800;
font-size: 1.125rem; color: var(--blue); flex-shrink: 0;
}
.contact-name { font-weight: 700; font-size: 0.9375rem; color: var(--navy); }
.contact-role { font-size: 0.75rem; color: var(--slate-2); margin-top: 1px; }
.contact-row {
display: flex; align-items: center; gap: 8px;
font-size: 0.8125rem; color: var(--slate); padding: 4px 0;
}
.contact-row a { color: var(--blue); }
.contact-row svg { width: 14px; height: 14px; color: var(--slate-2); flex-shrink: 0; }
.contact-divider { border: none; border-top: 1px solid var(--border); margin: 0.75rem 0; }
/* Stats */
.stat-grid { display: grid; grid-template-columns: 1fr 1fr; gap: 0.75rem; }
.stat-box {
background: var(--bg); border: 1px solid var(--border);
border-radius: 10px; padding: 12px 14px; text-align: center;
}
.stat-val {
font-family: var(--font-display); font-weight: 800;
font-size: 1.625rem; color: var(--navy); line-height: 1;
}
.stat-label { font-size: 0.6875rem; color: var(--slate-2); margin-top: 3px; text-transform: uppercase; letter-spacing: 0.04em; }
/* Social links */
.social-row { display: flex; gap: 8px; }
.social-btn {
display: inline-flex; align-items: center; gap: 6px;
padding: 6px 12px; border-radius: 8px; font-size: 0.75rem; font-weight: 600;
border: 1px solid var(--border); color: var(--slate);
background: var(--white); transition: all 0.15s; text-decoration: none;
}
.social-btn:hover { border-color: var(--blue); color: var(--blue); text-decoration: none; }
.social-btn svg { width: 14px; height: 14px; }
/* Verified trust note */
.trust-note {
background: var(--green-bg); border: 1px solid #BBF7D0;
border-radius: 10px; padding: 10px 14px;
display: flex; align-items: flex-start; gap: 8px;
}
.trust-note svg { width: 15px; height: 15px; color: var(--green); flex-shrink: 0; margin-top: 1px; }
.trust-note p { font-size: 0.75rem; color: #14532D; line-height: 1.5; }
/* ── Contact form ───────────────────────────────────── */
.enquiry-section { margin-top: 2.5rem; }
.enquiry-section .card { max-width: 720px; }
.enquiry-intro { font-size: 0.875rem; color: var(--slate); margin-bottom: 1.25rem; line-height: 1.6; }
.form-row { display: grid; grid-template-columns: 1fr 1fr; gap: 1rem; margin-bottom: 1rem; }
.form-field { margin-bottom: 1rem; }
.form-label { display: block; font-size: 0.75rem; font-weight: 600; color: var(--navy-2); margin-bottom: 5px; }
.form-label span { color: #EF4444; }
.form-input, .form-textarea {
width: 100%; padding: 9px 12px; border-radius: var(--radius-sm);
border: 1px solid var(--border); font-family: var(--font-body);
font-size: 0.875rem; color: var(--navy); background: var(--white);
transition: border-color 0.15s, box-shadow 0.15s; outline: none;
}
.form-input:focus, .form-textarea:focus {
border-color: var(--blue); box-shadow: 0 0 0 3px rgba(29,78,216,0.08);
}
.form-textarea { min-height: 110px; resize: vertical; }
.form-privacy {
font-size: 0.6875rem; color: var(--slate-2); line-height: 1.6;
padding: 10px 12px; background: var(--bg-2); border-radius: 8px;
margin-bottom: 1rem;
}
.form-privacy a { color: var(--slate); text-decoration: underline; }
.btn-submit {
display: inline-flex; align-items: center; gap: 8px;
padding: 11px 24px; border-radius: 10px; font-size: 0.9375rem; font-weight: 600;
font-family: var(--font-body); background: var(--blue); color: #fff;
border: none; cursor: pointer; transition: all 0.15s;
box-shadow: 0 2px 10px rgba(29,78,216,0.25);
}
.btn-submit:hover { background: var(--blue-2); box-shadow: 0 4px 16px rgba(29,78,216,0.3); }
.btn-submit svg { width: 16px; height: 16px; }
/* ── CTA strip ──────────────────────────────────────── */
.cta-strip {
margin: 2.5rem 0 3rem;
background: var(--navy);
border-radius: var(--radius-lg);
padding: 1.5rem 2rem;
display: flex; align-items: center; justify-content: space-between; gap: 1.5rem;
position: relative; overflow: hidden;
}
.cta-strip::before {
content: '';
position: absolute; inset: 0;
background-image:
linear-gradient(rgba(255,255,255,0.03) 1px, transparent 1px),
linear-gradient(90deg, rgba(255,255,255,0.03) 1px, transparent 1px);
background-size: 32px 32px;
pointer-events: none;
}
.cta-strip-text { position: relative; z-index: 1; }
.cta-strip-label { font-size: 0.6875rem; font-weight: 700; letter-spacing: 0.08em; text-transform: uppercase; color: rgba(255,255,255,0.4); margin-bottom: 4px; }
.cta-strip-title { font-family: var(--font-display); font-weight: 700; font-size: 1.125rem; color: #fff; }
.cta-strip-sub { font-size: 0.8125rem; color: rgba(255,255,255,0.5); margin-top: 2px; }
.cta-strip-btn {
position: relative; z-index: 1; flex-shrink: 0;
display: inline-flex; align-items: center; gap: 7px;
padding: 10px 20px; border-radius: 10px; font-size: 0.875rem; font-weight: 600;
font-family: var(--font-body); background: #fff; color: var(--navy);
border: none; cursor: pointer; transition: all 0.15s; text-decoration: none;
white-space: nowrap;
}
.cta-strip-btn:hover { background: var(--blue-bg); color: var(--blue); text-decoration: none; }
/* ── Animations ─────────────────────────────────────── */
@keyframes fadeUp {
from { opacity: 0; transform: translateY(16px); }
to { opacity: 1; transform: translateY(0); }
}
.hero-left { animation: fadeUp 0.45s ease both; }
.hero-actions { animation: fadeUp 0.45s 0.1s ease both; }
.body-main { animation: fadeUp 0.45s 0.15s ease both; }
.body-sidebar { animation: fadeUp 0.45s 0.2s ease both; }
/* ── Responsive ─────────────────────────────────────── */
@media (max-width: 900px) {
.body-grid { grid-template-columns: 1fr; }
.body-sidebar { display: contents; }
.sidebar-sticky { position: static; }
.service-list { grid-template-columns: 1fr; }
}
@media (max-width: 640px) {
.hero { padding: 2rem 1.25rem 2rem; border-radius: 0; }
.hero-inner { flex-direction: column; }
.hero-actions { flex-direction: row; flex-wrap: wrap; width: 100%; }
.form-row { grid-template-columns: 1fr; }
.page { padding: 0 1rem; }
.cta-strip { flex-direction: column; text-align: center; }
}
</style>
</head>
<body>
<!-- Navbar -->
<nav class="nav">
<a href="/" class="nav-logo">
<svg width="26" height="26" viewBox="0 0 32 32" fill="none">
<circle cx="14" cy="12" r="10" stroke="#0F172A" stroke-width="2.2" fill="none"/>
<line x1="14" y1="2" x2="14" y2="22" stroke="#0F172A" stroke-width="1.2" opacity="0.35"/>
<line x1="4" y1="12" x2="24" y2="12" stroke="#0F172A" stroke-width="1.2" opacity="0.35"/>
<line x1="6.5" y1="4.5" x2="21.5" y2="19.5" stroke="#0F172A" stroke-width="1" opacity="0.2"/>
<line x1="21.5" y1="4.5" x2="6.5" y2="19.5" stroke="#0F172A" stroke-width="1" opacity="0.2"/>
<line x1="20" y1="20" x2="28" y2="30" stroke="#0F172A" stroke-width="2.5" stroke-linecap="round"/>
</svg>
padelnomics
</a>
<a href="/directory" class="nav-back">
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="m15 18-6-6 6-6"/></svg>
Supplier Directory
</a>
</nav>
<!-- Hero -->
<div class="hero">
<div class="hero-inner">
<div class="hero-left">
<div class="hero-identity">
<!-- {# supplier.logo_file or supplier.logo_url #} -->
<div class="hero-logo">C</div>
<div class="hero-title-group">
<div class="hero-badges">
<!-- {# category_labels.get(supplier.category, supplier.category) #} -->
<span class="hero-badge hero-badge--category">Manufacturer</span>
<!-- {# if supplier.is_verified or supplier.tier != 'free' #} -->
<span class="hero-badge hero-badge--verified">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
Verified
</span>
</div>
<!-- {# supplier.name #} -->
<h1 class="hero-name">CourtCraft GmbH</h1>
<div class="hero-meta">
<!-- {# supplier.city, country_labels.get(supplier.country_code) #} -->
<span>Munich</span>
<span class="hero-meta-dot"></span>
<span>Germany</span>
<!-- {# if supplier.years_in_business #} -->
<span class="hero-meta-dot"></span>
<span>Est. 2009</span>
</div>
</div>
</div>
<!-- {# supplier.tagline #} -->
<p class="hero-tagline">"Engineering courts that last a generation — precision manufacturing for professional padel."</p>
</div>
<div class="hero-actions">
<!-- {# if supplier.website #} -->
<a href="https://courtcraft.de" target="_blank" rel="noopener" class="btn-hero-primary">
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M18 13v6a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V8a2 2 0 0 1 2-2h6"/><polyline points="15 3 21 3 21 9"/><line x1="10" y1="14" x2="21" y2="3"/></svg>
Visit Website
</a>
<!-- {# if supplier.contact_phone #} -->
<a href="tel:+4989123456789" class="btn-hero-secondary">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13.6a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 3h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 10.6a16 16 0 0 0 6.29 6.29l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 18.92Z"/></svg>
+49 89 123 456 789
</a>
<button type="button" class="btn-hero-secondary" onclick="navigator.share ? navigator.share({title:'CourtCraft GmbH',url:location.href}) : navigator.clipboard.writeText(location.href)">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="18" cy="5" r="3"/><circle cx="6" cy="12" r="3"/><circle cx="18" cy="19" r="3"/><line x1="8.59" y1="13.51" x2="15.42" y2="17.49"/><line x1="15.41" y1="6.51" x2="8.59" y2="10.49"/></svg>
Share
</button>
</div>
</div>
</div><!-- /hero -->
<!-- Body -->
<div class="page">
<div class="body-grid">
<!-- LEFT: main content -->
<div class="body-main">
<!-- About -->
<div class="card">
<div class="card-title">About</div>
<!-- {# supplier.long_description or supplier.description #} -->
<p class="desc-body">
CourtCraft GmbH designs and manufactures professional padel courts at our 12,000 m² production facility in Munich, Germany. Founded in 2009 by former professional players, we combine German engineering precision with deep understanding of the game's technical demands.
</p>
<p class="desc-body">
Our courts are installed across 34 countries, from private clubs and hotels to national federation training centres. Every CourtCraft court ships with a 15-year structural warranty and full documentation for planning authorities. We handle logistics and installation coordination in all major European markets.
</p>
<div class="cat-pills">
<!-- {# for cat in (supplier.service_categories or '').split(',') #} -->
<span class="cat-pill">Manufacturer</span>
<span class="cat-pill">Installer</span>
<span class="cat-pill">Turnkey</span>
<span class="cat-pill">Indoor</span>
<span class="cat-pill">Outdoor</span>
<span class="cat-pill">Panoramic</span>
</div>
</div>
<!-- Services -->
<div class="card">
<div class="card-title">Services Offered</div>
<div class="service-list">
<!-- {# for service in services_list #} -->
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Court design &amp; layout planning
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Manufacturing &amp; supply
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Installation &amp; commissioning
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Lighting systems
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Flooring &amp; surfaces
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
After-sales &amp; warranty
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Permitting documentation
</div>
<div class="service-item">
<span class="service-check">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="3" stroke-linecap="round" stroke-linejoin="round"><polyline points="20 6 9 17 4 12"/></svg>
</span>
Project management
</div>
</div>
</div>
<!-- Service Area -->
<div class="card">
<div class="card-title">Service Area</div>
<div class="area-pills">
<!-- {# for country in (supplier.service_area or '').split(',') #} -->
<span class="area-pill">🇩🇪 Germany</span>
<span class="area-pill">🇦🇹 Austria</span>
<span class="area-pill">🇨🇭 Switzerland</span>
<span class="area-pill">🇳🇱 Netherlands</span>
<span class="area-pill">🇧🇪 Belgium</span>
<span class="area-pill">🇩🇰 Denmark</span>
<span class="area-pill">🇸🇪 Sweden</span>
<span class="area-pill">🇳🇴 Norway</span>
<span class="area-pill">🇫🇮 Finland</span>
<span class="area-pill">🇵🇱 Poland</span>
<span class="area-pill">🇨🇿 Czech Republic</span>
<span class="area-pill">🇸🇮 Slovenia</span>
</div>
</div>
</div><!-- /body-main -->
<!-- RIGHT: sidebar -->
<div class="body-sidebar">
<div class="sidebar-sticky">
<!-- Contact card -->
<div class="card">
<div class="card-title">Contact</div>
<div style="display:flex;align-items:center;gap:10px;margin-bottom:1rem">
<!-- {# supplier.contact_name #} -->
<div class="contact-avatar">M</div>
<div>
<div class="contact-name">Markus Berger</div>
<div class="contact-role">International Sales</div>
</div>
</div>
<div class="contact-row">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="16" x="2" y="4" rx="2"/><path d="m22 7-8.97 5.7a1.94 1.94 0 0 1-2.06 0L2 7"/></svg>
<!-- {# supplier.contact_email #} -->
<a href="mailto:m.berger@courtcraft.de">m.berger@courtcraft.de</a>
</div>
<div class="contact-row">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 16.92v3a2 2 0 0 1-2.18 2 19.79 19.79 0 0 1-8.63-3.07A19.5 19.5 0 0 1 4.69 13.6a19.79 19.79 0 0 1-3.07-8.67A2 2 0 0 1 3.6 3h3a2 2 0 0 1 2 1.72c.127.96.361 1.903.7 2.81a2 2 0 0 1-.45 2.11L7.91 10.6a16 16 0 0 0 6.29 6.29l.96-.96a2 2 0 0 1 2.11-.45c.907.339 1.85.573 2.81.7A2 2 0 0 1 22 18.92Z"/></svg>
<!-- {# supplier.contact_phone #} -->
<a href="tel:+4989123456789">+49 89 123 456 789</a>
</div>
<div class="contact-row">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a14.5 14.5 0 0 0 0 20 14.5 14.5 0 0 0 0-20"/><path d="M2 12h20"/></svg>
<!-- {# supplier.website #} -->
<a href="https://courtcraft.de" target="_blank" rel="noopener">courtcraft.de</a>
</div>
<hr class="contact-divider">
<!-- Company stats -->
<div class="stat-grid">
<!-- {# if supplier.years_in_business #} -->
<div class="stat-box">
<div class="stat-val">16</div>
<div class="stat-label">Years active</div>
</div>
<!-- {# if supplier.project_count #} -->
<div class="stat-box">
<div class="stat-val">340+</div>
<div class="stat-label">Courts built</div>
</div>
</div>
<hr class="contact-divider">
<!-- Social links -->
<div class="social-row">
<a href="#" class="social-btn" title="LinkedIn">
<svg viewBox="0 0 24 24" fill="currentColor"><path d="M16 8a6 6 0 0 1 6 6v7h-4v-7a2 2 0 0 0-2-2 2 2 0 0 0-2 2v7h-4v-7a6 6 0 0 1 6-6zM2 9h4v12H2z"/><circle cx="4" cy="4" r="2"/></svg>
LinkedIn
</a>
<a href="#" class="social-btn" title="Instagram">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect width="20" height="20" x="2" y="2" rx="5" ry="5"/><path d="M16 11.37A4 4 0 1 1 12.63 8 4 4 0 0 1 16 11.37z"/><line x1="17.5" y1="6.5" x2="17.51" y2="6.5"/></svg>
Instagram
</a>
<a href="#" class="social-btn" title="YouTube">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M2.5 17a24.12 24.12 0 0 1 0-10 2 2 0 0 1 1.4-1.4 49.56 49.56 0 0 1 16.2 0A2 2 0 0 1 21.5 7a24.12 24.12 0 0 1 0 10 2 2 0 0 1-1.4 1.4 49.55 49.55 0 0 1-16.2 0A2 2 0 0 1 2.5 17"/><polygon points="10 15 15 12 10 9 10 15"/></svg>
YouTube
</a>
</div>
</div>
<!-- Trust note -->
<div class="trust-note">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><polyline points="9 12 11 14 15 10"/></svg>
<p>Verified listing. Company details confirmed by padelnomics.</p>
</div>
</div><!-- /sidebar-sticky -->
</div><!-- /body-sidebar -->
</div><!-- /body-grid -->
<!-- Enquiry form -->
<div class="enquiry-section">
<div class="card">
<div class="card-title">Send an Enquiry</div>
<!-- {# action="{{ url_for('directory.supplier_enquiry', slug=supplier.slug) }}" #} -->
<form method="post" action="/directory/courtcraft-gmbh/enquiry">
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
<p class="enquiry-intro">
Questions about this supplier? Send a message via padelnomics and we'll relay it directly to CourtCraft GmbH. Your contact details will only be shared with this supplier.
</p>
<div class="form-row">
<div class="form-field" style="margin-bottom:0">
<label class="form-label" for="eq-name">Your Name <span>*</span></label>
<input type="text" id="eq-name" name="contact_name" class="form-input" required placeholder="Maria Schmidt">
</div>
<div class="form-field" style="margin-bottom:0">
<label class="form-label" for="eq-email">Email <span>*</span></label>
<input type="email" id="eq-email" name="contact_email" class="form-input" required placeholder="maria@example.com">
</div>
</div>
<div class="form-field">
<label class="form-label" for="eq-msg">Message <span>*</span></label>
<textarea id="eq-msg" name="message" class="form-textarea" required
placeholder="Briefly describe your project — number of courts, location, timeline, and any specific requirements."></textarea>
</div>
<p class="form-privacy">
Your message is relayed by padelnomics to this supplier. We do not sell your data. By submitting you agree to our
<a href="/privacy">Privacy Policy</a> and <a href="/terms">Terms of Use</a>.
</p>
<button type="submit" class="btn-submit">
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22 2 15 22 11 13 2 9 22 2"/></svg>
Send Enquiry
</button>
</form>
</div>
</div>
<!-- Footer CTA strip -->
<!--
{# if supplier.tier == 'basic' and supplier.claimed_by #}
Show "Upgrade to Growth" strip
{# elif not supplier.claimed_by #}
Show "Claim this listing" strip
{# endif #}
-->
<div class="cta-strip">
<div class="cta-strip-text">
<!-- Variant A: unclaimed -->
<!-- <div class="cta-strip-label">Is this your company?</div>
<div class="cta-strip-title">Claim this listing for free</div>
<div class="cta-strip-sub">Take control of your profile and respond to enquiries.</div> -->
<!-- Variant B: claimed Basic → upsell Growth -->
<div class="cta-strip-label">Ready to grow?</div>
<div class="cta-strip-title">Start receiving project leads</div>
<div class="cta-strip-sub">Upgrade to Growth — get matched with buyers actively requesting quotes in your region.</div>
</div>
<!-- Variant A: -->
<!-- <a href="/suppliers/claim/courtcraft-gmbh" class="cta-strip-btn">Claim Listing &rarr;</a> -->
<!-- Variant B: -->
<a href="/suppliers/upgrade" class="cta-strip-btn">See Growth Plans &rarr;</a>
</div>
</div><!-- /page -->
</body>
</html>

View File

@@ -0,0 +1,256 @@
import { useState, useEffect } from "react";
const COURT_TYPES = ["Indoor", "Outdoor", "Both indoor & outdoor"];
const SURFACES = ["Artificial turf", "Panoramic glass", "Standard glass", "No preference yet"];
const LIGHTING = ["LED competition", "LED standard", "Natural light only", "Not sure yet"];
const TIMELINES = ["ASAP (< 3 months)", "36 months", "612 months", "12+ months / exploring"];
const BUDGETS = ["< €100K", "€100K €250K", "€250K €500K", "€500K €1M", "> €1M", "Not sure yet"];
const SERVICES = ["Court construction (turnkey)", "Court surfaces only", "Steel structures / canopy", "Glass walls", "Lighting systems", "Flooring / foundation", "Architecture & planning", "Financing / investment", "Consulting / feasibility"];
const CALC = { courts: 6, type: "Indoor (rented hall)", totalInvestment: "€285,000", monthlyRevenue: "€18,750", monthlyCosts: "€12,400", monthlyCashFlow: "€6,350", paybackYears: "3.7", irr: "22.4%", cashOnCash: "26.8%" };
export default function App() {
const [view, setView] = useState("results");
const [bStep, setBStep] = useState(1);
const [anim, setAnim] = useState(true);
const [brief, setBrief] = useState({ courtType: "", courts: 6, surface: "", lighting: "", location: "", country: "Germany", timeline: "", budget: "", services: [], info: "", name: "", email: "", phone: "", company: "" });
useEffect(() => { setAnim(false); const t = setTimeout(() => setAnim(true), 50); return () => clearTimeout(t); }, [view, bStep]);
const $ = {
bg: "#FFFFFF", soft: "#F8FAFC", muted: "#F1F5F9",
border: "#E2E8F0", borderDark: "#CBD5E1",
text: "#0F172A", sub: "#334155", dim: "#64748B", faint: "#94A3B8",
blue: "#1D4ED8", blueLight: "#3B82F6", bluePale: "#DBEAFE", blueGhost: "#EFF6FF", blueDark: "#1E40AF",
green: "#16A34A", greenPale: "#DCFCE7",
gold: "#D97706", goldPale: "#FEF3C7",
};
const font = "'Inter', -apple-system, BlinkMacSystemFont, sans-serif";
const mono = "'JetBrains Mono', 'SF Mono', monospace";
const Pill = ({ options, value, onChange, multi }) => (
<div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
{options.map(o => {
const a = multi ? value.includes(o) : value === o;
return <button key={o} onClick={() => onChange(o)} style={{ background: a ? $.blueGhost : $.bg, border: `1.5px solid ${a ? $.blue : $.border}`, borderRadius: "999px", padding: "8px 16px", fontSize: "13px", fontWeight: a ? 600 : 400, color: a ? $.blue : $.dim, cursor: "pointer", fontFamily: font }}>{o}</button>;
})}
</div>
);
const Stat = ({ label, value, highlight, sub }) => (
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "18px", flex: "1 1 140px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<div style={{ fontSize: "11px", color: $.faint, marginBottom: "5px", textTransform: "uppercase", letterSpacing: "0.05em", fontWeight: 600 }}>{label}</div>
<div style={{ fontSize: "22px", fontWeight: 700, fontFamily: mono, color: highlight ? $.blue : $.text, letterSpacing: "-0.02em" }}>{value}</div>
{sub && <div style={{ fontSize: "12px", color: $.dim, marginTop: "3px" }}>{sub}</div>}
</div>
);
return (
<div style={{ minHeight: "100vh", background: $.soft, color: $.text, fontFamily: font }}>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<div style={{ maxWidth: "800px", margin: "0 auto", padding: "32px 20px 80px" }}>
{/* Header */}
<div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: "32px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "32px", height: "32px", borderRadius: "9px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "16px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "18px", fontWeight: 700, letterSpacing: "-0.02em" }}>padelnomics</span>
</div>
{view !== "results" && <button onClick={() => { setView("results"); setBStep(1); }} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "8px", padding: "6px 14px", fontSize: "13px", color: $.dim, cursor: "pointer", fontFamily: font }}> Back to calculator</button>}
</div>
<div style={{ opacity: anim ? 1 : 0, transform: anim ? "translateY(0)" : "translateY(10px)", transition: "all 0.3s cubic-bezier(0.16,1,0.3,1)" }}>
{/* ─── RESULTS ─── */}
{view === "results" && (<div>
<div style={{ marginBottom: "24px" }}>
<div style={{ fontSize: "12px", color: $.blue, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "5px" }}>Your Projection</div>
<h1 style={{ fontSize: "clamp(22px, 4vw, 30px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "0 0 4px" }}>{CALC.courts}-Court Indoor Padel Hall</h1>
<p style={{ color: $.dim, fontSize: "14px", margin: 0 }}>Based on your inputs {CALC.type}</p>
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: "12px", marginBottom: "12px" }}>
<Stat label="Total Investment" value={CALC.totalInvestment} sub="CAPEX (rent model)" />
<Stat label="Monthly Revenue" value={CALC.monthlyRevenue} sub="At 40% utilization" />
<Stat label="Monthly Cash Flow" value={CALC.monthlyCashFlow} highlight sub="After debt service" />
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: "12px", marginBottom: "32px" }}>
<Stat label="Payback Period" value={`${CALC.paybackYears} yrs`} sub="Equity recovery" />
<Stat label="IRR" value={CALC.irr} highlight sub="10-year horizon" />
<Stat label="Cash-on-Cash" value={CALC.cashOnCash} sub="Annual return on equity" />
</div>
{/* CTA */}
<div style={{ background: $.blueGhost, border: `2px solid ${$.blue}`, borderRadius: "20px", padding: "30px", position: "relative", overflow: "hidden", boxShadow: "0 4px 24px rgba(29,78,216,0.10)" }}>
<div style={{ position: "relative" }}>
<div style={{ fontSize: "12px", color: $.blue, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "8px" }}>Next Step</div>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 8px" }}>Get quotes from verified court suppliers</h2>
<p style={{ color: $.dim, fontSize: "14px", lineHeight: 1.6, margin: "0 0 18px", maxWidth: "540px" }}>Share your project specs and we'll connect you with pre-vetted suppliers who match your requirements. They'll reach out directly with tailored proposals typically within 48 hours.</p>
<div style={{ display: "flex", flexWrap: "wrap", gap: "12px", marginBottom: "20px" }}>
{["25 matched suppliers", "Direct contact, no middleman", "Free, no commitment", "Your data stays private"].map(t => (
<div key={t} style={{ display: "flex", alignItems: "center", gap: "5px", fontSize: "13px" }}><span style={{ color: $.green, fontWeight: 700 }}></span><span style={{ color: $.dim }}>{t}</span></div>
))}
</div>
<div style={{ display: "flex", gap: "12px", alignItems: "center" }}>
<button onClick={() => setView("brief")} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "13px 26px", fontSize: "15px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 10px rgba(29,78,216,0.25)" }}>Get Supplier Quotes </button>
<span style={{ fontSize: "12px", color: $.faint }}>Takes ~2 minutes</span>
</div>
</div>
</div>
<div style={{ marginTop: "20px", display: "flex", gap: "24px", justifyContent: "center" }}>
{[["540+", "projects matched"], ["48h", "avg. response"], ["35+", "verified suppliers"]].map(([n, l]) => (
<div key={l} style={{ textAlign: "center" }}><div style={{ fontSize: "18px", fontWeight: 700, fontFamily: mono, color: $.blue }}>{n}</div><div style={{ fontSize: "12px", color: $.faint }}>{l}</div></div>
))}
</div>
</div>)}
{/* ─── BRIEF ─── */}
{view === "brief" && (<div>
<div style={{ display: "flex", gap: "8px", marginBottom: "28px" }}>
{["Project", "Details", "Contact"].map((l, i) => {
const n = i + 1, on = bStep === n, done = bStep > n;
return (<div key={l} style={{ flex: 1 }}><div style={{ height: "3px", borderRadius: "999px", background: done || on ? $.blue : $.border, marginBottom: "7px" }} /><div style={{ fontSize: "12px", fontWeight: on ? 600 : 400, color: on ? $.text : done ? $.dim : $.faint }}>{l}</div></div>);
})}
</div>
{/* Brief 1 */}
{bStep === 1 && (<div>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 5px" }}>Tell us about your project</h2>
<p style={{ color: $.dim, fontSize: "14px", marginBottom: "24px" }}>This helps us match you with the right suppliers. All fields optional.</p>
<div style={{ display: "flex", flexDirection: "column", gap: "22px" }}>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Facility type</label><Pill options={COURT_TYPES} value={brief.courtType} onChange={v => setBrief(p => ({ ...p, courtType: v }))} /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Number of courts</label>
<div style={{ display: "flex", alignItems: "center", gap: "16px" }}><input type="range" min={1} max={16} value={brief.courts} onChange={e => setBrief(p => ({ ...p, courts: +e.target.value }))} style={{ flex: 1, accentColor: $.blue }} /><div style={{ fontSize: "24px", fontWeight: 700, fontFamily: mono, color: $.blue, minWidth: "36px", textAlign: "center" }}>{brief.courts}</div></div>
</div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Court surface preference</label><Pill options={SURFACES} value={brief.surface} onChange={v => setBrief(p => ({ ...p, surface: v }))} /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Lighting</label><Pill options={LIGHTING} value={brief.lighting} onChange={v => setBrief(p => ({ ...p, lighting: v }))} /></div>
</div>
<div style={{ display: "flex", justifyContent: "flex-end", marginTop: "28px" }}>
<button onClick={() => setBStep(2)} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 8px rgba(29,78,216,0.25)" }}>Continue </button>
</div>
</div>)}
{/* Brief 2 */}
{bStep === 2 && (<div>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 5px" }}>Project details</h2>
<p style={{ color: $.dim, fontSize: "14px", marginBottom: "24px" }}>Help suppliers understand your timeline and scope.</p>
<div style={{ display: "flex", flexDirection: "column", gap: "22px" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "14px" }}>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>City / Region</label><input placeholder="e.g. Göttingen" value={brief.location} onChange={e => setBrief(p => ({ ...p, location: e.target.value }))} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: $.text, fontFamily: font, outline: "none", boxSizing: "border-box" }} onFocus={e => e.target.style.borderColor = $.blue} onBlur={e => e.target.style.borderColor = $.border} /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>Country</label><select value={brief.country} onChange={e => setBrief(p => ({ ...p, country: e.target.value }))} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: $.text, fontFamily: font, outline: "none", boxSizing: "border-box" }}>{["Germany", "Netherlands", "France", "Italy", "Spain", "UK", "Sweden", "Belgium", "Austria", "Switzerland", "Portugal", "UAE", "USA", "Other"].map(c => <option key={c}>{c}</option>)}</select></div>
</div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Timeline</label><Pill options={TIMELINES} value={brief.timeline} onChange={v => setBrief(p => ({ ...p, timeline: v }))} /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Estimated budget</label><Pill options={BUDGETS} value={brief.budget} onChange={v => setBrief(p => ({ ...p, budget: v }))} /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>What do you need? (select all)</label><Pill options={SERVICES} value={brief.services} onChange={s => setBrief(p => ({ ...p, services: p.services.includes(s) ? p.services.filter(x => x !== s) : [...p.services, s] }))} multi /></div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>Anything else?</label><textarea placeholder="E.g. converting a warehouse, need planning support..." value={brief.info} onChange={e => setBrief(p => ({ ...p, info: e.target.value }))} rows={3} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: $.text, fontFamily: font, outline: "none", resize: "vertical", boxSizing: "border-box" }} /></div>
</div>
<div style={{ display: "flex", justifyContent: "space-between", marginTop: "28px" }}>
<button onClick={() => setBStep(1)} style={{ background: $.bg, color: $.dim, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "12px 22px", fontSize: "14px", cursor: "pointer", fontFamily: font }}> Back</button>
<button onClick={() => setBStep(3)} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 8px rgba(29,78,216,0.25)" }}>Continue </button>
</div>
</div>)}
{/* Brief 3 */}
{bStep === 3 && (<div>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 5px" }}>How should suppliers reach you?</h2>
<p style={{ color: $.dim, fontSize: "14px", marginBottom: "24px" }}>Your details are only shared with matched suppliers never published publicly.</p>
<div style={{ background: $.blueGhost, border: `1px solid ${$.bluePale}`, borderRadius: "12px", padding: "14px 18px", marginBottom: "22px", display: "flex", gap: "12px", alignItems: "flex-start" }}>
<span style={{ fontSize: "18px" }}>🔒</span>
<div><div style={{ fontSize: "13px", fontWeight: 600, color: $.blue, marginBottom: "3px" }}>Your privacy matters</div><div style={{ fontSize: "12px", color: $.dim, lineHeight: 1.5 }}>Contact details go to 25 pre-vetted suppliers only. They pay to access your brief so only serious companies reach out.</div></div>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "14px", maxWidth: "460px" }}>
{[["name", "Your name *", "Full name"], ["email", "Email *", "you@company.com"], ["phone", "Phone (optional)", "+49 170 1234567"], ["company", "Company (optional)", "e.g. Padel Club Göttingen"]].map(([k, l, p]) => (
<div key={k}><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>{l}</label><input placeholder={p} value={brief[k]} onChange={e => setBrief(f => ({ ...f, [k]: e.target.value }))} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: $.text, fontFamily: font, outline: "none", boxSizing: "border-box" }} onFocus={e => e.target.style.borderColor = $.blue} onBlur={e => e.target.style.borderColor = $.border} /></div>
))}
</div>
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "18px 20px", marginTop: "24px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<div style={{ fontSize: "12px", fontWeight: 700, color: $.faint, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "10px" }}>Your project brief</div>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "8px 20px" }}>
{[["Type", brief.courtType || "—"], ["Courts", brief.courts], ["Surface", brief.surface || "—"], ["Lighting", brief.lighting || "—"], ["Location", brief.location ? `${brief.location}, ${brief.country}` : brief.country], ["Timeline", brief.timeline || "—"], ["Budget", brief.budget || "—"], ["Services", brief.services.length ? `${brief.services.length} selected` : "—"]].map(([l, v]) => (
<div key={l} style={{ display: "flex", justifyContent: "space-between", padding: "5px 0", borderBottom: `1px solid ${$.muted}` }}><span style={{ fontSize: "12px", color: $.faint }}>{l}</span><span style={{ fontSize: "13px", fontWeight: 500, color: $.dim }}>{v}</span></div>
))}
</div>
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: "24px" }}>
<button onClick={() => setBStep(2)} style={{ background: $.bg, color: $.dim, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "12px 22px", fontSize: "14px", cursor: "pointer", fontFamily: font }}> Back</button>
<button onClick={() => setView("submitted")} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "13px 26px", fontSize: "15px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 10px rgba(29,78,216,0.25)" }}>Submit & Get Matched </button>
</div>
<div style={{ textAlign: "center", fontSize: "11px", color: $.faint, marginTop: "10px" }}>By submitting you agree to our Terms & Privacy Policy. Free, no obligation.</div>
</div>)}
</div>)}
{/* ─── SUBMITTED ─── */}
{view === "submitted" && (<div style={{ textAlign: "center", paddingTop: "36px" }}>
<div style={{ width: "68px", height: "68px", borderRadius: "50%", background: $.blueGhost, border: `2px solid ${$.blue}`, display: "flex", alignItems: "center", justifyContent: "center", margin: "0 auto 20px", fontSize: "28px", color: $.blue }}></div>
<h2 style={{ fontSize: "26px", fontWeight: 700, marginBottom: "8px" }}>You're matched!</h2>
<p style={{ color: $.dim, fontSize: "15px", maxWidth: "440px", margin: "0 auto 28px", lineHeight: 1.6 }}>We've matched your {brief.courts}-court {brief.courtType.toLowerCase() || "padel"} project with <strong style={{ color: $.blue }}>3 verified suppliers</strong> in {brief.country}. They'll reach out within 48 hours.</p>
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "26px", maxWidth: "500px", margin: "0 auto 28px", textAlign: "left", boxShadow: "0 1px 6px rgba(0,0,0,0.06)" }}>
<div style={{ fontSize: "14px", fontWeight: 700, marginBottom: "18px" }}>What happens next</div>
{[["1", "Suppliers review your brief", "Matched suppliers receive your project specs and assess fit.", "Now"], ["2", "They contact you directly", "Expect calls or emails within 2448 hours.", "12 days"], ["3", "Compare proposals", "Review quotes, ask questions, visit references.", "12 weeks"], ["4", "Choose your supplier", "Pick the best fit. We're here if you need help.", "Your pace"]].map(([n, t, d, time], i) => (
<div key={n} style={{ display: "flex", gap: "14px", marginBottom: i < 3 ? "18px" : 0 }}>
<div style={{ width: "26px", height: "26px", borderRadius: "50%", background: $.blueGhost, border: `1.5px solid ${$.bluePale}`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "12px", fontWeight: 700, fontFamily: mono, color: $.blue, flexShrink: 0 }}>{n}</div>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "2px" }}><span style={{ fontSize: "14px", fontWeight: 600 }}>{t}</span><span style={{ fontSize: "11px", fontFamily: mono, color: $.faint }}>{time}</span></div>
<div style={{ fontSize: "13px", color: $.dim, lineHeight: 1.4 }}>{d}</div>
</div>
</div>
))}
</div>
<div style={{ background: $.blueGhost, border: `1px solid ${$.bluePale}`, borderRadius: "12px", padding: "14px 18px", maxWidth: "500px", margin: "0 auto 20px", display: "flex", gap: "12px", alignItems: "center" }}>
<span style={{ fontSize: "20px" }}>📧</span>
<div style={{ textAlign: "left" }}><div style={{ fontSize: "13px", fontWeight: 600, color: $.blue }}>Check your email</div><div style={{ fontSize: "12px", color: $.dim }}>Confirmation sent to <strong style={{ color: $.text }}>{brief.email || "your email"}</strong></div></div>
</div>
<button onClick={() => setView("dashboard")} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "10px 22px", fontSize: "14px", color: $.dim, cursor: "pointer", fontFamily: font, marginTop: "4px" }}>View your project dashboard →</button>
</div>)}
{/* ─── DASHBOARD ─── */}
{view === "dashboard" && (<div>
<div style={{ marginBottom: "24px" }}>
<div style={{ fontSize: "12px", color: $.blue, fontWeight: 700, textTransform: "uppercase", letterSpacing: "0.06em", marginBottom: "3px" }}>Your Project</div>
<h2 style={{ fontSize: "24px", fontWeight: 700, margin: "0 0 4px" }}>{brief.courts}-Court {brief.courtType || "Padel"} Facility</h2>
<p style={{ color: $.dim, fontSize: "14px", margin: 0 }}>{brief.location ? `${brief.location}, ` : ""}{brief.country} · Submitted just now</p>
</div>
<div style={{ background: $.blueGhost, border: `1px solid ${$.bluePale}`, borderRadius: "14px", padding: "18px 22px", marginBottom: "22px", display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "12px" }}>
<div><div style={{ fontSize: "15px", fontWeight: 600, marginBottom: "3px" }}>3 suppliers matched</div><div style={{ fontSize: "13px", color: $.dim }}>Waiting for their first response — typically 2448 hours</div></div>
<div style={{ display: "flex" }}>
{["P", "M", "C"].map((l, i) => <div key={i} style={{ width: "34px", height: "34px", borderRadius: "50%", background: [$.blue, $.blueLight, $.gold][i], display: "flex", alignItems: "center", justifyContent: "center", fontSize: "14px", fontWeight: 700, color: "#fff", border: `2px solid ${$.bg}`, marginLeft: i > 0 ? "-8px" : 0, position: "relative", zIndex: 3 - i }}>{l}</div>)}
</div>
</div>
<div style={{ fontSize: "14px", fontWeight: 700, marginBottom: "14px" }}>Matched Suppliers</div>
{[
{ name: "PadelCourt Europe", loc: "Cologne, Germany", spec: "Full turnkey", rating: 4.9, projects: "150+", avatar: "P", color: $.blue },
{ name: "Mondo Padel", loc: "Milan, Italy", spec: "Court surfaces & glass", rating: 4.8, projects: "200+", avatar: "M", color: $.blueLight },
{ name: "CourtTech Solutions", loc: "Amsterdam, Netherlands", spec: "Steel & lighting", rating: 4.7, projects: "85+", avatar: "C", color: $.gold },
].map((sup, i) => (
<div key={i} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "18px", marginBottom: "10px", display: "flex", gap: "14px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<div style={{ width: "42px", height: "42px", borderRadius: "12px", background: sup.color + "14", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "17px", fontWeight: 700, color: sup.color, flexShrink: 0 }}>{sup.avatar}</div>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "5px", flexWrap: "wrap", gap: "8px" }}>
<div>
<div style={{ fontSize: "15px", fontWeight: 600, marginBottom: "1px" }}>{sup.name}</div>
<div style={{ fontSize: "12px", color: $.dim }}>{sup.loc} · {sup.spec}</div>
</div>
<div style={{ background: $.goldPale, borderRadius: "999px", padding: "3px 10px", fontSize: "11px", fontWeight: 600, color: $.gold }}>⏳ Reviewing your brief</div>
</div>
<div style={{ display: "flex", gap: "14px", fontSize: "12px", color: $.faint, marginTop: "6px" }}><span>⭐ {sup.rating}</span><span>{sup.projects} projects</span><span style={{ color: $.green }}>✓ Verified</span></div>
</div>
</div>
))}
<div style={{ textAlign: "center", fontSize: "12px", color: $.faint, marginTop: "18px", lineHeight: 1.6 }}>Suppliers contact you directly by email or phone once they've reviewed your brief.<br />Reply to the confirmation email to update your specs.</div>
</div>)}
</div>
</div>
</div>
);
}

View File

@@ -0,0 +1,337 @@
import { useState, useEffect, useRef } from "react";
export default function App() {
const [openFaq, setOpenFaq] = useState(null);
const [vis, setVis] = useState(new Set());
const refs = useRef({});
const [courts, setCourts] = useState(6);
const [rate, setRate] = useState(50);
const [util, setUtil] = useState(40);
useEffect(() => {
const obs = new IntersectionObserver((entries) => {
entries.forEach((e) => { if (e.isIntersecting) setVis((p) => new Set([...p, e.target.dataset.s])); });
}, { threshold: 0.12 });
Object.values(refs.current).forEach((el) => el && obs.observe(el));
return () => obs.disconnect();
}, []);
const r = (id) => (el) => { if (el) { el.dataset.s = id; refs.current[id] = el; } };
const v = (id) => vis.has(id);
const fade = (id, d = 0) => ({ opacity: v(id) ? 1 : 0, transform: v(id) ? "translateY(0)" : "translateY(24px)", transition: `all 0.6s cubic-bezier(0.16,1,0.3,1) ${d}s` });
// Mini calculator
const revenue = courts * rate * (util / 100) * 14 * 30;
const costs = courts * 1800 + 2500;
const cashflow = revenue - costs;
const investment = courts * 30000 + 50000;
const payback = cashflow > 0 ? (investment / (cashflow * 12)).toFixed(1) : "—";
const $ = {
bg: "#FFFFFF", soft: "#F8FAFC", muted: "#F1F5F9", warm: "#FFFBEB",
border: "#E2E8F0", borderDark: "#CBD5E1",
text: "#0F172A", sub: "#334155", dim: "#64748B", faint: "#94A3B8",
blue: "#1D4ED8", blueLight: "#3B82F6", bluePale: "#DBEAFE", blueGhost: "#EFF6FF", blueDark: "#1E40AF",
green: "#16A34A", greenPale: "#DCFCE7", greenGhost: "#F0FDF4",
gold: "#D97706", goldPale: "#FEF3C7",
};
const font = "'Inter', -apple-system, BlinkMacSystemFont, sans-serif";
const mono = "'JetBrains Mono', 'SF Mono', monospace";
const JOURNEY = [
{ icon: "📊", title: "Plan", desc: "Model your investment with 60+ variables. CAPEX, operating costs, cash flow, returns, sensitivity analysis. Know your numbers before you commit.", tag: "Free" },
{ icon: "💰", title: "Finance", desc: "Your planner output becomes your business case. Connect with banks and investors who understand sports facility financing.", tag: "Coming soon" },
{ icon: "🏗️", title: "Build", desc: "Get quotes from verified court suppliers matched to your specs. Compare pricing, quality, and timelines. Direct contact, no middleman.", tag: "Live" },
{ icon: "🎾", title: "Operate", desc: "Analytics powered by real booking data. Benchmark against similar venues. Pricing optimization and demand forecasting.", tag: "Coming soon" },
];
const FEATURES = [
{ icon: "🔧", title: "60+ Variables", desc: "Every assumption adjustable — court costs, rent, pricing, utilization, financing terms, exit scenarios." },
{ icon: "📋", title: "6 Analysis Tabs", desc: "Assumptions, CAPEX, Operating Model, Cash Flow, Returns & Exit, Key Metrics. Each with interactive charts." },
{ icon: "☀️", title: "Indoor & Outdoor", desc: "Model indoor halls (rent or build) and outdoor courts with seasonality. Compare scenarios side by side." },
{ icon: "📉", title: "Sensitivity Analysis", desc: "See how returns change with different utilization and pricing. Find your break-even instantly." },
{ icon: "🎯", title: "Professional Metrics", desc: "IRR, MOIC, DSCR, cash-on-cash, break-even utilization, RevPAH. The metrics banks want to see." },
{ icon: "💾", title: "Save & Compare", desc: "Unlimited scenarios. Test locations, court counts, financing structures. Find your optimal plan." },
];
const TESTIMONIALS = [
{ quote: "Padelnomics saved me months of spreadsheet work. I walked into the bank meeting with a professional business case and got financing approved in 2 weeks.", name: "Stefan M.", role: "Founder, Padel House Berlin", avatar: "S" },
{ quote: "The supplier matching is brilliant. I submitted my specs and had 3 detailed proposals within a week. Ended up saving €40K versus the first quote I'd gotten on my own.", name: "Anna L.", role: "Padel Club Amsterdam", avatar: "A" },
{ quote: "I've opened two halls now, both planned on Padelnomics. The sensitivity analysis alone is worth it — it showed me the exact utilization I needed to break even.", name: "Marco P.", role: "CEO, Padel Nation", avatar: "M" },
];
const FAQS = [
{ q: "Is the planner really free?", a: "Yes. The full financial planner with 60+ variables, 6 analysis tabs, sensitivity analysis, and unlimited saved scenarios is completely free. No credit card required. We make money on the supplier side — suppliers pay to access project briefs from entrepreneurs like you." },
{ q: "How does supplier matching work?", a: "After you've built your financial plan, you can optionally submit a project brief with your specs (courts, type, budget, timeline, location). We match you with 25 pre-vetted suppliers who fit your requirements. They contact you directly with tailored proposals. It's free for you — suppliers pay for the introduction." },
{ q: "Do I have to share my contact details?", a: "Only if you choose to request supplier quotes. Your planner data is 100% private. If you submit a project brief, your contact details are shared only with the 25 matched suppliers — never published publicly, never sold to third parties." },
{ q: "How accurate are the projections?", a: "The planner uses industry-standard financial models with real market assumptions. However, every market is different — the projections are as good as your inputs. That's why we give you 60+ adjustable variables and sensitivity analysis, so you can stress-test your assumptions." },
{ q: "Which countries/markets do you cover?", a: "The planner works globally — just input your local costs and pricing. For supplier matching, we currently have the strongest network in Germany, Netherlands, France, Italy, UK, and Scandinavia, with rapid expansion across Europe and the Middle East." },
{ q: "Can I use it for my bank or investor presentation?", a: "Absolutely — that's one of the main use cases. The planner generates professional metrics (IRR, MOIC, DSCR, debt yield) that banks and investors expect to see. Many users export their scenario data for business plan documents." },
];
const LOGOS_SEEN = ["200+ padel halls planned", "15 countries", "€85M+ in planned investments"];
return (
<div style={{ minHeight: "100vh", background: $.bg, color: $.text, fontFamily: font, overflowX: "hidden" }}>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
{/* NAV */}
<nav style={{ position: "sticky", top: 0, zIndex: 50, background: "rgba(255,255,255,0.92)", backdropFilter: "blur(12px)", borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "14px 24px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "32px", height: "32px", borderRadius: "9px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "16px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "18px", fontWeight: 700, letterSpacing: "-0.02em" }}>padelnomics</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "24px" }}>
<a href="#planner" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>Planner</a>
<a href="#suppliers" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>Suppliers</a>
<a href="#faq" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>FAQ</a>
<button style={{ background: "transparent", color: $.sub, border: `1.5px solid ${$.border}`, borderRadius: "8px", padding: "8px 16px", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: font }}>Sign In</button>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "8px", padding: "9px 18px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>Get Started Free</button>
</div>
</div>
</nav>
{/* HERO */}
<section ref={r("hero")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "72px 24px 56px" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "48px", alignItems: "center" }}>
<div>
<div style={{ ...fade("hero"), display: "inline-flex", alignItems: "center", gap: "8px", background: $.greenGhost, border: `1px solid ${$.greenPale}`, borderRadius: "999px", padding: "5px 14px", marginBottom: "20px" }}>
<span style={{ fontSize: "12px" }}>🎾</span>
<span style={{ fontSize: "12px", fontWeight: 600, color: $.green }}>Free padel court financial planner</span>
</div>
<h1 style={{ ...fade("hero", 0.08), fontSize: "clamp(30px, 4.5vw, 48px)", fontWeight: 800, letterSpacing: "-0.035em", lineHeight: 1.1, margin: "0 0 18px" }}>
Plan Your Padel<br />Business in Minutes,<br /><span style={{ color: $.blue }}>Not Months</span>
</h1>
<p style={{ ...fade("hero", 0.16), fontSize: "17px", color: $.dim, lineHeight: 1.65, margin: "0 0 28px", maxWidth: "460px" }}>
Model your padel court investment with 60+ variables, sensitivity analysis, and professional-grade projections. Then get matched with verified suppliers.
</p>
<div style={{ ...fade("hero", 0.24), display: "flex", gap: "12px", flexWrap: "wrap", marginBottom: "18px" }}>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "14px 26px", fontSize: "16px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 4px 14px rgba(29,78,216,0.25)" }}>Create Your Plan Free </button>
<button style={{ background: $.bg, color: $.sub, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "14px 22px", fontSize: "16px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>See Demo</button>
</div>
<div style={{ ...fade("hero", 0.3), display: "flex", gap: "20px", fontSize: "13px", color: $.faint }}>
{["No credit card", "60+ variables", "Unlimited scenarios"].map((t) => (
<span key={t} style={{ display: "flex", alignItems: "center", gap: "5px" }}><span style={{ color: $.green, fontWeight: 700 }}></span>{t}</span>
))}
</div>
</div>
{/* Mini calculator */}
<div style={{ ...fade("hero", 0.2) }}>
<div style={{ background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "20px", padding: "28px", boxShadow: "0 8px 40px rgba(0,0,0,0.06)" }}>
<div style={{ fontSize: "14px", fontWeight: 700, marginBottom: "4px" }}>Quick ROI Estimate</div>
<div style={{ fontSize: "12px", color: $.faint, marginBottom: "22px" }}>Drag the sliders to see your projection</div>
{[["Courts", courts, setCourts, 2, 16, ""], ["Peak Rate", rate, setRate, 30, 80, "€/hr"], ["Utilization", util, setUtil, 20, 70, "%"]].map(([label, val, setter, min, max, unit]) => (
<div key={label} style={{ marginBottom: "18px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "6px" }}>
<span style={{ fontSize: "13px", color: $.dim, fontWeight: 500 }}>{label}</span>
<span style={{ fontSize: "14px", fontWeight: 700, fontFamily: mono, color: $.blue }}>{val}{unit}</span>
</div>
<input type="range" min={min} max={max} value={val} onChange={(e) => setter(+e.target.value)} style={{ width: "100%", accentColor: $.blue, height: "6px" }} />
</div>
))}
<div style={{ borderTop: `1px solid ${$.border}`, paddingTop: "18px", marginTop: "6px", display: "grid", gridTemplateColumns: "1fr 1fr", gap: "14px" }}>
{[
["Total Investment", `${(investment / 1000).toFixed(0)}K`, false],
["Monthly Cash Flow", `${cashflow > 0 ? cashflow.toLocaleString("de") : 0}`, true],
["Payback Period", `${payback} yrs`, false],
["Cash-on-Cash", `${cashflow > 0 ? ((cashflow * 12 / (investment * 0.25)) * 100).toFixed(0) : 0}%`, true],
].map(([label, val, hl]) => (
<div key={label}>
<div style={{ fontSize: "11px", color: $.faint, textTransform: "uppercase", letterSpacing: "0.04em", marginBottom: "3px" }}>{label}</div>
<div style={{ fontSize: "18px", fontWeight: 700, fontFamily: mono, color: hl ? $.blue : $.text }}>{val}</div>
</div>
))}
</div>
<div style={{ fontSize: "11px", color: $.faint, marginTop: "14px", lineHeight: 1.5 }}>Assumes 8/ rent, 5% interest, 10-yr loan, 300 /court</div>
<button style={{ width: "100%", background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px", fontSize: "14px", fontWeight: 700, cursor: "pointer", fontFamily: font, marginTop: "16px", boxShadow: "0 2px 8px rgba(29,78,216,0.2)" }}>Open Full Planner </button>
</div>
</div>
</div>
</section>
{/* SOCIAL PROOF BAR */}
<section style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "28px 24px", display: "flex", justifyContent: "center", gap: "48px", flexWrap: "wrap" }}>
{LOGOS_SEEN.map((t, i) => (
<div key={i} style={{ fontSize: "14px", color: $.dim, fontWeight: 600, display: "flex", alignItems: "center", gap: "8px" }}>
<span style={{ color: $.blue, fontSize: "16px" }}></span>{t}
</div>
))}
</div>
</section>
{/* JOURNEY: Plan → Finance → Build → Operate */}
<section ref={r("journey")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "48px" }}>
<div style={fade("journey")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>From Idea to Operating Hall</span></div>
<h2 style={{ ...fade("journey", 0.08), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 6px" }}>Everything you need, in order</h2>
<p style={{ ...fade("journey", 0.14), color: $.dim, fontSize: "15px", maxWidth: "520px", margin: "0 auto" }}>Padelnomics walks you through the entire journey from first napkin math to your operating padel hall.</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(230px, 1fr))", gap: "18px" }}>
{JOURNEY.map((step, i) => (
<div key={i} style={{ ...fade("journey", 0.18 + i * 0.08), background: $.bg, border: `1px solid ${$.border}`, borderRadius: "18px", padding: "28px 22px", position: "relative", boxShadow: "0 1px 4px rgba(0,0,0,0.04)" }}>
<div style={{ position: "absolute", top: "16px", right: "16px", background: step.tag === "Live" ? $.greenPale : step.tag === "Free" ? $.bluePale : $.muted, color: step.tag === "Live" ? $.green : step.tag === "Free" ? $.blue : $.faint, fontSize: "10px", fontWeight: 700, padding: "3px 8px", borderRadius: "999px", textTransform: "uppercase", letterSpacing: "0.04em" }}>{step.tag}</div>
<div style={{ fontSize: "32px", marginBottom: "14px" }}>{step.icon}</div>
<h3 style={{ fontSize: "18px", fontWeight: 700, margin: "0 0 8px" }}>{step.title}</h3>
<p style={{ fontSize: "14px", color: $.dim, lineHeight: 1.6, margin: 0 }}>{step.desc}</p>
</div>
))}
</div>
</section>
{/* PLANNER FEATURES */}
<section id="planner" ref={r("features")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "48px" }}>
<div style={fade("features")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Financial Planner</span></div>
<h2 style={{ ...fade("features", 0.08), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 6px" }}>Built for serious padel entrepreneurs</h2>
<p style={{ ...fade("features", 0.14), color: $.dim, fontSize: "15px" }}>Not a toy calculator. A professional-grade financial model.</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: "16px" }}>
{FEATURES.map((f, i) => (
<div key={i} style={{ ...fade("features", 0.18 + i * 0.06), background: $.bg, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "24px 22px", display: "flex", gap: "14px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<span style={{ fontSize: "24px", flexShrink: 0 }}>{f.icon}</span>
<div>
<div style={{ fontSize: "15px", fontWeight: 700, marginBottom: "4px" }}>{f.title}</div>
<div style={{ fontSize: "13px", color: $.dim, lineHeight: 1.5 }}>{f.desc}</div>
</div>
</div>
))}
</div>
<div style={{ ...fade("features", 0.6), textAlign: "center", marginTop: "36px" }}>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "14px 28px", fontSize: "15px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 4px 14px rgba(29,78,216,0.25)" }}>Create Your Plan Free </button>
</div>
</div>
</section>
{/* SUPPLIER MATCHING */}
<section id="suppliers" ref={r("suppliers")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "48px", alignItems: "center" }}>
<div>
<div style={fade("suppliers")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Supplier Matching</span></div>
<h2 style={{ ...fade("suppliers", 0.08), fontSize: "clamp(24px, 4vw, 34px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 12px" }}>From plan to quotes<br />in 48 hours</h2>
<p style={{ ...fade("suppliers", 0.14), fontSize: "15px", color: $.dim, lineHeight: 1.65, margin: "0 0 24px" }}>When your numbers work, take the next step. Submit your project specs and we'll match you with 25 verified court suppliers. They reach out directly with proposals tailored to your project.</p>
<div style={{ ...fade("suppliers", 0.2), display: "flex", flexDirection: "column", gap: "14px", marginBottom: "28px" }}>
{[
["Verified suppliers only", "Every supplier is vetted for quality, reliability, and track record."],
["Direct contact", "You get real proposals from real people. No chatbots, no middlemen."],
["100% free for you", "Suppliers pay for the introduction. You pay nothing — ever."],
["Your data stays private", "Contact details shared only with your matched suppliers."],
].map(([title, desc]) => (
<div key={title} style={{ display: "flex", gap: "12px" }}>
<div style={{ width: "22px", height: "22px", borderRadius: "6px", background: $.greenPale, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, marginTop: "1px" }}>
<span style={{ fontSize: "12px", color: $.green, fontWeight: 700 }}>✓</span>
</div>
<div>
<div style={{ fontSize: "14px", fontWeight: 600, marginBottom: "1px" }}>{title}</div>
<div style={{ fontSize: "13px", color: $.dim, lineHeight: 1.4 }}>{desc}</div>
</div>
</div>
))}
</div>
<button style={{ ...fade("suppliers", 0.28), background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "13px 24px", fontSize: "15px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 10px rgba(29,78,216,0.2)" }}>Get Supplier Quotes →</button>
</div>
{/* Mock supplier cards */}
<div style={{ ...fade("suppliers", 0.2), display: "flex", flexDirection: "column", gap: "12px" }}>
{[
{ name: "PadelCourt Europe", loc: "Cologne, Germany", spec: "Full turnkey solutions", rating: 4.9, projects: "150+ projects", color: $.blue, avatar: "P" },
{ name: "Mondo Padel", loc: "Milan, Italy", spec: "Court surfaces & glass walls", rating: 4.8, projects: "200+ projects", color: $.blueLight, avatar: "M" },
{ name: "CourtTech Solutions", loc: "Amsterdam, NL", spec: "Steel structures & lighting", rating: 4.7, projects: "85+ projects", color: $.gold, avatar: "C" },
].map((s, i) => (
<div key={i} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "18px", display: "flex", gap: "14px", boxShadow: "0 1px 4px rgba(0,0,0,0.04)", transition: "box-shadow 0.2s", position: "relative" }}>
<div style={{ width: "42px", height: "42px", borderRadius: "12px", background: s.color + "18", display: "flex", alignItems: "center", justifyContent: "center", fontSize: "17px", fontWeight: 700, color: s.color, flexShrink: 0 }}>{s.avatar}</div>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", flexWrap: "wrap", gap: "6px" }}>
<div>
<div style={{ fontSize: "15px", fontWeight: 600, marginBottom: "1px" }}>{s.name}</div>
<div style={{ fontSize: "12px", color: $.dim }}>{s.loc} · {s.spec}</div>
</div>
<div style={{ background: $.greenPale, borderRadius: "999px", padding: "3px 10px", fontSize: "11px", fontWeight: 600, color: $.green }}>✓ Verified</div>
</div>
<div style={{ display: "flex", gap: "14px", fontSize: "12px", color: $.faint, marginTop: "8px" }}>
<span>⭐ {s.rating}</span><span>{s.projects}</span>
</div>
</div>
</div>
))}
<div style={{ textAlign: "center", fontSize: "12px", color: $.faint, marginTop: "4px" }}>35+ verified suppliers across Europe</div>
</div>
</div>
</section>
{/* TESTIMONIALS */}
<section ref={r("test")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={fade("test")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Success Stories</span></div>
<h2 style={{ ...fade("test", 0.08), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0" }}>From plans to padel halls</h2>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "20px" }}>
{TESTIMONIALS.map((t, i) => (
<div key={i} style={{ ...fade("test", 0.12 + i * 0.08), background: $.bg, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "26px" }}>
<div style={{ fontSize: "22px", color: $.bluePale, marginBottom: "12px" }}>★★★★★</div>
<p style={{ fontSize: "14px", color: $.sub, lineHeight: 1.65, margin: "0 0 18px", fontStyle: "italic" }}>"{t.quote}"</p>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "36px", height: "36px", borderRadius: "50%", background: $.bluePale, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "15px", fontWeight: 700, color: $.blue }}>{t.avatar}</div>
<div><div style={{ fontSize: "14px", fontWeight: 600 }}>{t.name}</div><div style={{ fontSize: "12px", color: $.dim }}>{t.role}</div></div>
</div>
</div>
))}
</div>
</div>
</section>
{/* FAQ */}
<section id="faq" ref={r("faq")} style={{ maxWidth: "700px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={fade("faq")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>FAQ</span></div>
<h2 style={{ ...fade("faq", 0.08), fontSize: "clamp(24px, 4vw, 32px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0" }}>Common questions</h2>
</div>
<div style={{ ...fade("faq", 0.14), display: "flex", flexDirection: "column", gap: "8px" }}>
{FAQS.map((f, i) => (
<div key={i} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "12px", overflow: "hidden" }}>
<button onClick={() => setOpenFaq(openFaq === i ? null : i)} style={{ width: "100%", background: "transparent", border: "none", padding: "18px 22px", display: "flex", justifyContent: "space-between", alignItems: "center", cursor: "pointer", fontFamily: font }}>
<span style={{ fontSize: "15px", fontWeight: 600, color: $.text, textAlign: "left" }}>{f.q}</span>
<span style={{ fontSize: "18px", color: $.faint, transform: openFaq === i ? "rotate(45deg)" : "none", transition: "transform 0.2s", flexShrink: 0, marginLeft: "12px" }}>+</span>
</button>
{openFaq === i && <div style={{ padding: "0 22px 18px", fontSize: "14px", color: $.dim, lineHeight: 1.6 }}>{f.a}</div>}
</div>
))}
</div>
</section>
{/* FINAL CTA */}
<section ref={r("cta")} style={{ background: $.blueGhost, borderTop: `1px solid ${$.bluePale}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "72px 24px", textAlign: "center" }}>
<h2 style={{ ...fade("cta"), fontSize: "clamp(26px, 4.5vw, 40px)", fontWeight: 800, letterSpacing: "-0.03em", margin: "0 0 12px" }}>Start planning your padel business today</h2>
<p style={{ ...fade("cta", 0.08), color: $.dim, fontSize: "17px", maxWidth: "500px", margin: "0 auto 28px", lineHeight: 1.6 }}>Join 200+ entrepreneurs who've planned their padel hall on Padelnomics. Free, no credit card, unlimited scenarios.</p>
<div style={{ ...fade("cta", 0.16), display: "flex", gap: "14px", justifyContent: "center", flexWrap: "wrap" }}>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "12px", padding: "16px 36px", fontSize: "17px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 4px 16px rgba(29,78,216,0.25)" }}>Create Your Plan Free </button>
<button style={{ background: $.bg, color: $.sub, border: `1.5px solid ${$.border}`, borderRadius: "12px", padding: "16px 28px", fontSize: "17px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>I'm a Supplier </button>
</div>
</div>
</section>
{/* FOOTER */}
<footer style={{ borderTop: `1px solid ${$.border}`, background: $.soft }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "36px 24px", display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "16px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "28px", height: "28px", borderRadius: "8px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "14px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "16px", fontWeight: 700 }}>padelnomics</span>
<span style={{ fontSize: "13px", color: $.faint, marginLeft: "4px" }}>Plan, finance, and build your padel business.</span>
</div>
<div style={{ display: "flex", gap: "24px", fontSize: "13px", color: $.dim }}>
<span>Planner</span><span>Suppliers</span><span>About</span><span>Terms</span><span>Privacy</span>
</div>
<div style={{ fontSize: "12px", color: $.faint }}>© 2026 Padelnomics. All rights reserved.</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,328 @@
import { useState, useEffect, useRef } from "react";
export default function App() {
const [openFaq, setOpenFaq] = useState(null);
const [visibleSections, setVisibleSections] = useState(new Set());
const sectionRefs = useRef({});
useEffect(() => {
const observer = new IntersectionObserver(
(entries) => {
entries.forEach((entry) => {
if (entry.isIntersecting) {
setVisibleSections((prev) => new Set([...prev, entry.target.dataset.section]));
}
});
},
{ threshold: 0.15 }
);
Object.values(sectionRefs.current).forEach((el) => el && observer.observe(el));
return () => observer.disconnect();
}, []);
const ref = (id) => (el) => {
if (el) { el.dataset.section = id; sectionRefs.current[id] = el; }
};
const visible = (id) => visibleSections.has(id);
const fadeUp = (id, delay = 0) => ({
opacity: visible(id) ? 1 : 0,
transform: visible(id) ? "translateY(0)" : "translateY(24px)",
transition: `all 0.6s cubic-bezier(0.16,1,0.3,1) ${delay}s`,
});
const $ = {
bg: "#FFFFFF", soft: "#F8FAFC", muted: "#F1F5F9", warm: "#FFFBEB",
border: "#E2E8F0", borderDark: "#CBD5E1",
text: "#0F172A", sub: "#334155", dim: "#64748B", faint: "#94A3B8",
blue: "#1D4ED8", blueLight: "#3B82F6", bluePale: "#DBEAFE", blueGhost: "#EFF6FF", blueDark: "#1E40AF",
green: "#16A34A", greenPale: "#DCFCE7",
gold: "#D97706", goldPale: "#FEF3C7",
red: "#DC2626", redPale: "#FEE2E2",
};
const font = "'Inter', -apple-system, BlinkMacSystemFont, sans-serif";
const mono = "'JetBrains Mono', 'SF Mono', monospace";
const STATS = [
{ value: "1,000+", label: "Calculator requests / month", icon: "📊" },
{ value: "€285K", label: "Average project value", icon: "💰" },
{ value: "48h", label: "Average response time", icon: "⚡" },
{ value: "35+", label: "Verified suppliers", icon: "✓" },
];
const STEPS = [
{ num: "01", title: "We capture the lead", desc: "Padel entrepreneurs use our free ROI calculator and planner. When they're ready to build, they submit a project brief with specs, budget, timeline, and location.", icon: "📋" },
{ num: "02", title: "You browse & choose", desc: "Anonymized project briefs appear in your lead feed. See project type, court count, budget range, and timeline — before spending a single credit.", icon: "👁" },
{ num: "03", title: "Unlock & connect", desc: "Spend credits to unlock full contact details: name, email, phone. Reach out directly with a tailored proposal. No middleman, no platform fee on the deal.", icon: "🔓" },
];
const LEADS = [
{ type: "Indoor", courts: 8, region: "Munich, Bavaria", budget: "€400K €600K", timeline: "36 months", services: ["Turnkey", "Glass walls", "LED lighting"], heat: "hot", bidders: 1 },
{ type: "Outdoor", courts: 4, region: "Amsterdam, Netherlands", budget: "€150K €250K", timeline: "ASAP", services: ["Court surfaces", "Steel canopy"], heat: "hot", bidders: 0 },
{ type: "Indoor + Outdoor", courts: 12, region: "Lyon, France", budget: "€800K €1.2M", timeline: "612 months", services: ["Architecture", "Turnkey", "Financing"], heat: "warm", bidders: 2 },
];
const PLANS = [
{ name: "Starter", price: "Free", period: "", credits: "10 credits", sub: "one-time", features: ["Basic supplier profile", "Browse anonymized leads", "Unlock up to 10 leads", "Email notifications"], cta: "Get Started Free", outline: true },
{ name: "Growth", price: "€149", period: "/mo", credits: "30 credits/mo", sub: "included", features: ["Everything in Starter", "Directory listing + logo", "Priority search placement", "Monthly market report", "30 lead unlocks/month"], cta: "Start Growing", popular: true },
{ name: "Pro", price: "€399", period: "/mo", credits: "100 credits/mo", sub: "included", features: ["Everything in Growth", "Verified supplier badge", "Newsletter feature", "Custom brand color", "Auto-unlock hot leads", "Quarterly strategy call"], cta: "Go Pro" },
];
const TESTIMONIALS = [
{ quote: "We've closed 3 projects worth over €1.2M total from Padelnomics leads in the first 6 months. The ROI is insane compared to trade shows.", name: "Thomas K.", role: "CEO, PadelBau GmbH", avatar: "T" },
{ quote: "The lead quality is exceptional — these are real entrepreneurs with budgets and timelines, not tire-kickers. Worth every credit.", name: "Maria S.", role: "Sales Director, CourtTech", avatar: "M" },
{ quote: "Finally a lead source where we can see the project specs before committing. The credit system is fair and transparent.", name: "Luca R.", role: "Founder, Padel Italia", avatar: "L" },
];
const FAQS = [
{ q: "How do credits work?", a: "Each credit unlocks one lead's full contact details (name, email, phone). Credit cost varies by project size: 58 for small residential, 1525 for mid-size clubs, 3040 for large complexes. You browse anonymized briefs first, then decide which leads to unlock." },
{ q: "Where do the leads come from?", a: "Padel entrepreneurs use our free ROI calculator and financial planner at padelnomics.io. When they're ready to move from planning to building, they submit a project brief requesting supplier quotes. These are high-intent leads actively planning a facility." },
{ q: "Do I get the actual contact details?", a: "Yes. When you unlock a lead, you get their full name, email, and phone number. You contact them directly — we don't sit in the middle of the conversation. The deal is between you and the client." },
{ q: "What if a lead doesn't respond?", a: "We guarantee lead quality. If contact information is invalid or the lead was submitted in error, we refund the credits to your account within 48 hours. We also limit each lead to a maximum of 5 supplier unlocks to avoid overwhelming the client." },
{ q: "Can I cancel anytime?", a: "Yes. All paid plans are month-to-month with no lock-in. Cancel anytime from your dashboard. Unused credits from your plan expire at the end of each billing cycle, but purchased credit packs never expire." },
{ q: "Which countries do you cover?", a: "We currently have the strongest lead flow in Germany, Netherlands, France, Italy, and the UK. We're expanding rapidly across Europe, Scandinavia, and the Middle East. You'll only see leads in regions you've selected in your profile." },
];
const BOOSTS = [
{ name: "Directory Listing", price: "€49/mo", boost: "+120%", desc: "discoverability" },
{ name: "Highlighted", price: "€39/mo", boost: "+65%", desc: "more views" },
{ name: "Verified Badge", price: "€49/mo", boost: "+55%", desc: "more inquiries" },
{ name: "Newsletter", price: "€99/mo", boost: "+120", desc: "impressions/wk" },
{ name: "Sticky Top", price: "€199", boost: "6×", desc: "more views" },
{ name: "Brand Color", price: "€59/mo", boost: "+80%", desc: "brand recall" },
];
return (
<div style={{ minHeight: "100vh", background: $.bg, color: $.text, fontFamily: font, overflowX: "hidden" }}>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700;800&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
{/* ─── NAV ─── */}
<nav style={{ position: "sticky", top: 0, zIndex: 50, background: "rgba(255,255,255,0.92)", backdropFilter: "blur(12px)", borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "14px 24px", display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "32px", height: "32px", borderRadius: "9px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "16px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "18px", fontWeight: 700, letterSpacing: "-0.02em" }}>padelnomics</span>
<span style={{ fontSize: "12px", color: $.faint, marginLeft: "4px", fontWeight: 500 }}>for Suppliers</span>
</div>
<div style={{ display: "flex", alignItems: "center", gap: "24px" }}>
<a href="#how" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>How it works</a>
<a href="#pricing" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>Pricing</a>
<a href="#faq" style={{ fontSize: "14px", color: $.dim, textDecoration: "none", fontWeight: 500 }}>FAQ</a>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "8px", padding: "9px 18px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>Get Started</button>
</div>
</div>
</nav>
{/* ─── HERO ─── */}
<section ref={ref("hero")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px 60px", textAlign: "center" }}>
<div style={fadeUp("hero")}>
<div style={{ display: "inline-flex", alignItems: "center", gap: "8px", background: $.blueGhost, border: `1px solid ${$.bluePale}`, borderRadius: "999px", padding: "6px 16px", marginBottom: "24px" }}>
<span style={{ fontSize: "13px" }}>🎾</span>
<span style={{ fontSize: "13px", fontWeight: 600, color: $.blue }}>The #1 lead source for padel court suppliers</span>
</div>
</div>
<h1 style={{ ...fadeUp("hero", 0.1), fontSize: "clamp(32px, 5.5vw, 56px)", fontWeight: 800, letterSpacing: "-0.035em", lineHeight: 1.08, margin: "0 0 20px", maxWidth: "750px", marginLeft: "auto", marginRight: "auto" }}>
Stop chasing leads.<br />
<span style={{ color: $.blue }}>Let them come to you.</span>
</h1>
<p style={{ ...fadeUp("hero", 0.2), fontSize: "18px", color: $.dim, lineHeight: 1.6, maxWidth: "560px", margin: "0 auto 32px" }}>
Padel entrepreneurs plan their facility on Padelnomics. When they're ready to build, you get first access to their project brief — with real budgets, timelines, and contact details.
</p>
<div style={{ ...fadeUp("hero", 0.3), display: "flex", gap: "14px", justifyContent: "center", flexWrap: "wrap", marginBottom: "20px" }}>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "14px 28px", fontSize: "16px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 4px 14px rgba(29,78,216,0.25)" }}>Start Free — 10 Credits Included →</button>
<button style={{ background: $.bg, color: $.sub, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "14px 24px", fontSize: "16px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>See Example Leads</button>
</div>
<p style={{ ...fadeUp("hero", 0.35), fontSize: "13px", color: $.faint }}>No credit card required · Cancel anytime</p>
</section>
{/* ─── STATS BAR ─── */}
<section ref={ref("stats")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "36px 24px", display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(200px, 1fr))", gap: "24px" }}>
{STATS.map((s, i) => (
<div key={i} style={{ ...fadeUp("stats", i * 0.08), textAlign: "center" }}>
<div style={{ fontSize: "28px", fontWeight: 700, fontFamily: mono, color: $.text, letterSpacing: "-0.02em" }}>{s.value}</div>
<div style={{ fontSize: "13px", color: $.dim, marginTop: "4px" }}>{s.label}</div>
</div>
))}
</div>
</section>
{/* ─── HOW IT WORKS ─── */}
<section id="how" ref={ref("how")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "48px" }}>
<div style={fadeUp("how")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>How it works</span></div>
<h2 style={{ ...fadeUp("how", 0.1), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 0" }}>Three steps to your next project</h2>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "24px" }}>
{STEPS.map((step, i) => (
<div key={i} style={{ ...fadeUp("how", 0.15 + i * 0.1), background: $.bg, border: `1px solid ${$.border}`, borderRadius: "18px", padding: "32px 26px", position: "relative", boxShadow: "0 1px 4px rgba(0,0,0,0.04)" }}>
<div style={{ display: "flex", alignItems: "center", gap: "12px", marginBottom: "16px" }}>
<div style={{ fontSize: "28px" }}>{step.icon}</div>
<span style={{ fontSize: "32px", fontWeight: 800, fontFamily: mono, color: $.bluePale }}>{step.num}</span>
</div>
<h3 style={{ fontSize: "18px", fontWeight: 700, margin: "0 0 8px" }}>{step.title}</h3>
<p style={{ fontSize: "14px", color: $.dim, lineHeight: 1.6, margin: 0 }}>{step.desc}</p>
</div>
))}
</div>
</section>
{/* ─── LEAD FEED PREVIEW ─── */}
<section ref={ref("leads")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={fadeUp("leads")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Live Lead Feed</span></div>
<h2 style={{ ...fadeUp("leads", 0.1), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 6px" }}>Real projects, real budgets</h2>
<p style={{ ...fadeUp("leads", 0.15), color: $.dim, fontSize: "15px" }}>Here's what your lead feed looks like. Browse anonymized briefs, then unlock the ones that fit.</p>
</div>
<div style={{ maxWidth: "680px", margin: "0 auto", display: "flex", flexDirection: "column", gap: "14px" }}>
{LEADS.map((lead, i) => (
<div key={i} style={{ ...fadeUp("leads", 0.2 + i * 0.1), background: $.bg, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "22px", boxShadow: "0 1px 4px rgba(0,0,0,0.04)", borderLeft: `4px solid ${lead.heat === "hot" ? $.red : $.gold}` }}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "12px", flexWrap: "wrap", gap: "8px" }}>
<div>
<div style={{ display: "flex", alignItems: "center", gap: "8px", marginBottom: "4px" }}>
<span style={{ background: lead.heat === "hot" ? $.redPale : $.goldPale, color: lead.heat === "hot" ? $.red : $.gold, fontSize: "11px", fontWeight: 700, padding: "2px 8px", borderRadius: "999px" }}>{lead.heat === "hot" ? "🔥 Hot" : "◐ Warm"}</span>
<span style={{ fontSize: "15px", fontWeight: 600 }}>{lead.type} · {lead.courts} courts</span>
</div>
<div style={{ fontSize: "13px", color: $.dim }}>{lead.region}</div>
</div>
<div style={{ textAlign: "right" }}>
<div style={{ fontSize: "14px", fontWeight: 600, fontFamily: mono, color: $.text }}>{lead.budget}</div>
<div style={{ fontSize: "12px", color: $.faint }}>{lead.timeline}</div>
</div>
</div>
<div style={{ display: "flex", flexWrap: "wrap", gap: "6px", marginBottom: "14px" }}>
{lead.services.map(s => <span key={s} style={{ background: $.muted, borderRadius: "6px", padding: "3px 10px", fontSize: "12px", color: $.sub, fontWeight: 500 }}>{s}</span>)}
</div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
<span style={{ fontSize: "12px", color: lead.bidders === 0 ? $.green : $.faint }}>{lead.bidders === 0 ? "✨ No suppliers yet — be first!" : `${lead.bidders} supplier${lead.bidders > 1 ? "s" : ""} unlocked`}</span>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "8px", padding: "8px 16px", fontSize: "13px", fontWeight: 600, cursor: "pointer", fontFamily: font, display: "flex", alignItems: "center", gap: "6px" }}>
🔓 Unlock · {lead.courts > 8 ? "35" : lead.courts > 5 ? "20" : "10"} credits
</button>
</div>
</div>
))}
</div>
<div style={{ ...fadeUp("leads", 0.6), textAlign: "center", marginTop: "28px" }}>
<button style={{ background: $.bg, color: $.blue, border: `1.5px solid ${$.blue}`, borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font }}>Start Free See All Leads </button>
</div>
</div>
</section>
{/* ─── BOOSTS STRIP ─── */}
<section ref={ref("boosts")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "60px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "32px" }}>
<div style={fadeUp("boosts")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Visibility Boosts</span></div>
<h2 style={{ ...fadeUp("boosts", 0.1), fontSize: "clamp(20px, 3vw, 28px)", fontWeight: 700, letterSpacing: "-0.02em", margin: "10px 0 6px" }}>Stack add-ons to stand out</h2>
<p style={{ ...fadeUp("boosts", 0.15), color: $.dim, fontSize: "14px" }}>Every plan supports optional boosts. Mix and match to maximize your reach.</p>
</div>
<div style={{ ...fadeUp("boosts", 0.2), display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(160px, 1fr))", gap: "12px" }}>
{BOOSTS.map((b, i) => (
<div key={i} style={{ background: $.soft, border: `1px solid ${$.border}`, borderRadius: "12px", padding: "16px", textAlign: "center" }}>
<div style={{ fontSize: "13px", fontWeight: 600, marginBottom: "6px" }}>{b.name}</div>
<div style={{ fontSize: "20px", fontWeight: 700, color: $.blue, fontFamily: mono }}>{b.boost}</div>
<div style={{ fontSize: "11px", color: $.dim, marginBottom: "8px" }}>{b.desc}</div>
<div style={{ fontSize: "12px", color: $.faint, fontFamily: mono }}>{b.price}</div>
</div>
))}
</div>
</section>
{/* ─── PRICING ─── */}
<section id="pricing" ref={ref("pricing")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "44px" }}>
<div style={fadeUp("pricing")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Pricing</span></div>
<h2 style={{ ...fadeUp("pricing", 0.1), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0 6px" }}>Simple, transparent pricing</h2>
<p style={{ ...fadeUp("pricing", 0.15), color: $.dim, fontSize: "15px" }}>Start free. Upgrade when the leads start flowing.</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(270px, 1fr))", gap: "20px", maxWidth: "900px", margin: "0 auto" }}>
{PLANS.map((p, i) => (
<div key={i} style={{ ...fadeUp("pricing", 0.2 + i * 0.08), position: "relative", background: $.bg, border: `2px solid ${p.popular ? $.blue : $.border}`, borderRadius: "18px", padding: "30px 24px", boxShadow: p.popular ? "0 8px 30px rgba(29,78,216,0.12)" : "0 1px 4px rgba(0,0,0,0.04)" }}>
{p.popular && <div style={{ position: "absolute", top: "-1px", left: "50%", transform: "translateX(-50%)", background: $.blue, color: "#fff", fontSize: "11px", fontWeight: 700, padding: "4px 16px", borderRadius: "0 0 10px 10px", textTransform: "uppercase", letterSpacing: "0.04em" }}>Most Popular</div>}
<div style={{ fontSize: "20px", fontWeight: 700, marginBottom: "4px" }}>{p.name}</div>
<div style={{ display: "flex", alignItems: "baseline", gap: "3px", marginBottom: "4px" }}>
<span style={{ fontSize: "36px", fontWeight: 800, letterSpacing: "-0.03em" }}>{p.price}</span>
{p.period && <span style={{ fontSize: "15px", color: $.dim }}>{p.period}</span>}
</div>
<div style={{ display: "inline-flex", alignItems: "center", gap: "5px", background: $.bluePale, borderRadius: "999px", padding: "4px 12px", marginBottom: "20px" }}>
<span style={{ fontSize: "13px" }}></span>
<span style={{ fontSize: "13px", fontWeight: 600, color: $.blue, fontFamily: mono }}>{p.credits}</span>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "9px", marginBottom: "24px" }}>
{p.features.map((f, j) => <div key={j} style={{ display: "flex", gap: "7px", fontSize: "14px" }}><span style={{ color: $.green, fontWeight: 700 }}></span><span style={{ color: $.dim, lineHeight: 1.4 }}>{f}</span></div>)}
</div>
<button style={{ width: "100%", background: p.popular ? $.blue : $.bg, color: p.popular ? "#fff" : $.sub, border: p.popular ? "none" : `1.5px solid ${$.border}`, borderRadius: "10px", padding: "12px", fontSize: "15px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: p.popular ? "0 2px 10px rgba(29,78,216,0.25)" : "none" }}>{p.cta}</button>
</div>
))}
</div>
</div>
</section>
{/* ─── TESTIMONIALS ─── */}
<section ref={ref("test")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={fadeUp("test")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>Trusted By Suppliers</span></div>
<h2 style={{ ...fadeUp("test", 0.1), fontSize: "clamp(24px, 4vw, 36px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0" }}>Suppliers love the lead quality</h2>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))", gap: "20px" }}>
{TESTIMONIALS.map((t, i) => (
<div key={i} style={{ ...fadeUp("test", 0.15 + i * 0.1), background: $.soft, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "26px" }}>
<div style={{ fontSize: "22px", color: $.bluePale, marginBottom: "12px" }}></div>
<p style={{ fontSize: "14px", color: $.sub, lineHeight: 1.65, margin: "0 0 18px", fontStyle: "italic" }}>"{t.quote}"</p>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "36px", height: "36px", borderRadius: "50%", background: $.bluePale, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "15px", fontWeight: 700, color: $.blue }}>{t.avatar}</div>
<div><div style={{ fontSize: "14px", fontWeight: 600 }}>{t.name}</div><div style={{ fontSize: "12px", color: $.dim }}>{t.role}</div></div>
</div>
</div>
))}
</div>
</section>
{/* ─── FAQ ─── */}
<section id="faq" ref={ref("faq")} style={{ background: $.soft, borderTop: `1px solid ${$.border}`, borderBottom: `1px solid ${$.border}` }}>
<div style={{ maxWidth: "700px", margin: "0 auto", padding: "80px 24px" }}>
<div style={{ textAlign: "center", marginBottom: "40px" }}>
<div style={fadeUp("faq")}><span style={{ fontSize: "12px", fontWeight: 700, color: $.blue, textTransform: "uppercase", letterSpacing: "0.08em" }}>FAQ</span></div>
<h2 style={{ ...fadeUp("faq", 0.1), fontSize: "clamp(24px, 4vw, 32px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "10px 0" }}>Common questions</h2>
</div>
<div style={{ ...fadeUp("faq", 0.15), display: "flex", flexDirection: "column", gap: "8px" }}>
{FAQS.map((faq, i) => (
<div key={i} style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "12px", overflow: "hidden" }}>
<button onClick={() => setOpenFaq(openFaq === i ? null : i)} style={{ width: "100%", background: "transparent", border: "none", padding: "18px 22px", display: "flex", justifyContent: "space-between", alignItems: "center", cursor: "pointer", fontFamily: font }}>
<span style={{ fontSize: "15px", fontWeight: 600, color: $.text, textAlign: "left" }}>{faq.q}</span>
<span style={{ fontSize: "18px", color: $.faint, transform: openFaq === i ? "rotate(45deg)" : "none", transition: "transform 0.2s" }}>+</span>
</button>
{openFaq === i && <div style={{ padding: "0 22px 18px", fontSize: "14px", color: $.dim, lineHeight: 1.6 }}>{faq.a}</div>}
</div>
))}
</div>
</div>
</section>
{/* ─── FINAL CTA ─── */}
<section ref={ref("cta")} style={{ maxWidth: "1100px", margin: "0 auto", padding: "80px 24px", textAlign: "center" }}>
<div style={fadeUp("cta")}>
<h2 style={{ fontSize: "clamp(24px, 4vw, 38px)", fontWeight: 800, letterSpacing: "-0.03em", margin: "0 0 12px" }}>Ready to grow your pipeline?</h2>
<p style={{ color: $.dim, fontSize: "16px", maxWidth: "480px", margin: "0 auto 28px", lineHeight: 1.6 }}>Start with 10 free credits. Browse real project briefs. Unlock your first lead today.</p>
<button style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "12px", padding: "16px 36px", fontSize: "17px", fontWeight: 700, cursor: "pointer", fontFamily: font, boxShadow: "0 4px 16px rgba(29,78,216,0.25)" }}>Get Started Free </button>
<p style={{ fontSize: "13px", color: $.faint, marginTop: "14px" }}>No credit card · 10 free lead credits · Cancel anytime</p>
</div>
</section>
{/* ─── FOOTER ─── */}
<footer style={{ borderTop: `1px solid ${$.border}`, background: $.soft }}>
<div style={{ maxWidth: "1100px", margin: "0 auto", padding: "36px 24px", display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: "16px" }}>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<div style={{ width: "28px", height: "28px", borderRadius: "8px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "14px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "16px", fontWeight: 700 }}>padelnomics</span>
</div>
<div style={{ display: "flex", gap: "24px", fontSize: "13px", color: $.dim }}>
<a href="https://padelnomics.io" style={{ color: $.dim, textDecoration: "none" }}>padelnomics.io</a>
<span>Terms</span><span>Privacy</span><span>Contact</span>
</div>
<div style={{ fontSize: "12px", color: $.faint }}>© 2026 Padelnomics. All rights reserved.</div>
</div>
</footer>
</div>
);
}

View File

@@ -0,0 +1,245 @@
import { useState, useEffect } from "react";
const PLANS = [
{ id: "starter", name: "Starter", price: 0, period: "forever", credits: 10, tagline: "Test the waters", features: ["Basic supplier profile", "10 lead credits included", "Browse anonymized lead feed", "Email notifications for matching leads"] },
{ id: "growth", name: "Growth", price: 149, period: "/mo", credits: 30, tagline: "For active suppliers", popular: true, features: ["Everything in Starter", "30 lead credits/month", "Directory listing included", "Company logo on profile", "Priority in search results", "Monthly market report"] },
{ id: "pro", name: "Pro", price: 399, period: "/mo", credits: 100, tagline: "Dominate your market", features: ["Everything in Growth", "100 lead credits/month", "Verified supplier badge", "Featured in newsletter", "Custom brand color listing", "Quarterly strategy call", "Auto-unlock hot leads"] },
];
const ADDONS = [
{ id: "directory", name: "Directory Listing", description: "Appear in the Padelnomics supplier directory", price: 49, period: "/mo", boost: "+120% discoverability", icon: "📍", includedIn: ["growth", "pro"] },
{ id: "logo", name: "Logo Display", description: "Show your company logo on your listing and lead responses", price: 29, period: "/mo", boost: "+40% more clicks", icon: "🏷", includedIn: ["growth", "pro"] },
{ id: "highlight", name: "Highlighted Listing", description: "Stand out with a colored background in search results", price: 39, period: "/mo", boost: "+65% more views", icon: "✦", includedIn: [] },
{ id: "verified", name: "Verified Badge", description: "Earn trust with a verified supplier checkmark", price: 49, period: "/mo", boost: "+55% more inquiries", icon: "✓", includedIn: ["pro"] },
{ id: "sticky_week", name: "Sticky Top — 1 Week", description: "Pin your listing to the top of the directory for 7 days", price: 79, period: "one-time", boost: "2× more views", icon: "📌", includedIn: [] },
{ id: "sticky_month", name: "Sticky Top — 1 Month", description: "Pin to the top for 30 days", price: 199, period: "one-time", boost: "6× more views", icon: "🔝", includedIn: [] },
{ id: "newsletter", name: "Newsletter Feature", description: "Featured in our weekly newsletter to 2,400+ padel entrepreneurs", price: 99, period: "/mo", boost: "+120 impressions/wk", icon: "📨", includedIn: ["pro"] },
{ id: "brand_color", name: "Custom Brand Color", description: "Replace default highlight with your brand color", price: 59, period: "/mo", boost: "+80% brand recall", icon: "🎨", includedIn: ["pro"] },
];
const CREDIT_PACKS = [
{ amount: 25, price: 99, perCredit: "€3.96" },
{ amount: 50, price: 179, perCredit: "€3.58", popular: true },
{ amount: 100, price: 329, perCredit: "€3.29" },
{ amount: 250, price: 749, perCredit: "€3.00" },
];
const CATEGORIES = ["Court Construction", "Court Surfaces", "Lighting Systems", "Steel Structures", "Glass Walls", "Flooring", "Full Turnkey", "Consulting", "Architecture", "Financing"];
export default function App() {
const [step, setStep] = useState(1);
const [plan, setPlan] = useState("growth");
const [addons, setAddons] = useState(new Set());
const [credits, setCredits] = useState(null);
const [form, setForm] = useState({ company: "", name: "", email: "", country: "", categories: [] });
const [anim, setAnim] = useState(true);
useEffect(() => { setAnim(false); const t = setTimeout(() => setAnim(true), 50); return () => clearTimeout(t); }, [step]);
const toggle = (id) => { if (ADDONS.find(a => a.id === id).includedIn.includes(plan)) return; setAddons(p => { const n = new Set(p); n.has(id) ? n.delete(id) : n.add(id); return n; }); };
const incl = (id) => ADDONS.find(a => a.id === id).includedIn.includes(plan);
const active = (id) => incl(id) || addons.has(id);
const monthly = () => { let t = PLANS.find(p => p.id === plan).price; addons.forEach(id => { const a = ADDONS.find(x => x.id === id); if (a?.period === "/mo" && !a.includedIn.includes(plan)) t += a.price; }); return t; };
const oneTime = () => { let t = 0; addons.forEach(id => { const a = ADDONS.find(x => x.id === id); if (a?.period === "one-time" && !a.includedIn.includes(plan)) t += a.price; }); if (credits) t += CREDIT_PACKS.find(p => p.amount === credits)?.price || 0; return t; };
const totalCr = () => { let c = PLANS.find(p => p.id === plan).credits; if (credits) c += credits; return c; };
const vis = () => { let m = 1; ["directory","logo","highlight","verified","newsletter","brand_color","sticky_week","sticky_month"].forEach((id, i) => { if (active(id)) m += [0.5,0.3,0.6,0.4,0.8,0.5,0.8,2.0][i]; }); return m; };
const $ = {
bg: "#FFFFFF", soft: "#F8FAFC", muted: "#F1F5F9",
border: "#E2E8F0", borderDark: "#CBD5E1",
text: "#0F172A", sub: "#334155", dim: "#64748B", faint: "#94A3B8",
blue: "#1D4ED8", blueLight: "#3B82F6", bluePale: "#DBEAFE", blueGhost: "#EFF6FF", blueDark: "#1E40AF",
green: "#16A34A", greenPale: "#DCFCE7",
gold: "#D97706", goldPale: "#FEF3C7",
};
const font = "'Inter', -apple-system, BlinkMacSystemFont, sans-serif";
const mono = "'JetBrains Mono', 'SF Mono', monospace";
return (
<div style={{ minHeight: "100vh", background: $.soft, color: $.text, fontFamily: font }}>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500&display=swap" rel="stylesheet" />
<div style={{ maxWidth: "960px", margin: "0 auto", padding: "32px 20px 80px" }}>
{/* Header */}
<div style={{ textAlign: "center", marginBottom: "36px" }}>
<div style={{ display: "inline-flex", alignItems: "center", gap: "10px", marginBottom: "14px" }}>
<div style={{ width: "34px", height: "34px", borderRadius: "10px", background: $.blue, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "18px", fontWeight: 700, color: "#fff" }}>P</div>
<span style={{ fontSize: "20px", fontWeight: 700, letterSpacing: "-0.02em" }}>padelnomics</span>
</div>
<h1 style={{ fontSize: "clamp(24px, 4vw, 34px)", fontWeight: 700, letterSpacing: "-0.03em", margin: "12px 0 8px" }}>Supplier Platform</h1>
<p style={{ color: $.dim, fontSize: "16px", maxWidth: "440px", margin: "0 auto" }}>Get discovered by padel entrepreneurs. Unlock verified leads. Grow your business.</p>
</div>
{/* Progress */}
<div style={{ display: "flex", justifyContent: "center", marginBottom: "36px", alignItems: "center" }}>
{["Plan", "Boost", "Credits", "Account"].map((label, i) => {
const n = i + 1, on = step === n, done = step > n;
return (
<div key={label} style={{ display: "flex", alignItems: "center" }}>
<div onClick={() => done && setStep(n)} style={{ display: "flex", alignItems: "center", gap: "7px", cursor: done ? "pointer" : "default", padding: "6px 14px", borderRadius: "999px", background: on ? $.blueGhost : "transparent", border: `1.5px solid ${on ? $.blue : done ? $.blueLight : $.border}` }}>
<div style={{ width: "22px", height: "22px", borderRadius: "50%", background: done || on ? $.blue : "transparent", border: `2px solid ${done || on ? $.blue : $.borderDark}`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: "11px", fontWeight: 700, color: done || on ? "#fff" : $.faint }}>{done ? "✓" : n}</div>
<span style={{ fontSize: "13px", fontWeight: on ? 600 : 400, color: on ? $.blue : done ? $.sub : $.faint }}>{label}</span>
</div>
{i < 3 && <div style={{ width: "28px", height: "2px", background: step > n ? $.blue : $.border }} />}
</div>
);
})}
</div>
<div style={{ opacity: anim ? 1 : 0, transform: anim ? "translateY(0)" : "translateY(10px)", transition: "all 0.3s cubic-bezier(0.16,1,0.3,1)" }}>
{/* STEP 1 */}
{step === 1 && (<div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(260px, 1fr))", gap: "16px", marginBottom: "28px" }}>
{PLANS.map(p => {
const sel = plan === p.id;
return (
<div key={p.id} onClick={() => setPlan(p.id)} style={{ position: "relative", background: sel ? $.blueGhost : $.bg, border: `2px solid ${sel ? $.blue : $.border}`, borderRadius: "16px", padding: "26px 22px", cursor: "pointer", boxShadow: sel ? "0 4px 20px rgba(29,78,216,0.10)" : "0 1px 3px rgba(0,0,0,0.04)" }}>
{p.popular && <div style={{ position: "absolute", top: "-1px", left: "50%", transform: "translateX(-50%)", background: $.blue, color: "#fff", fontSize: "10px", fontWeight: 700, padding: "3px 14px", borderRadius: "0 0 8px 8px", textTransform: "uppercase", letterSpacing: "0.04em" }}>Most Popular</div>}
<div style={{ fontSize: "12px", color: $.dim, marginBottom: "4px" }}>{p.tagline}</div>
<div style={{ fontSize: "20px", fontWeight: 700, marginBottom: "14px" }}>{p.name}</div>
<div style={{ display: "flex", alignItems: "baseline", gap: "3px", marginBottom: "6px" }}>
<span style={{ fontSize: "34px", fontWeight: 700, letterSpacing: "-0.03em" }}>{p.price === 0 ? "Free" : `${p.price}`}</span>
{p.price > 0 && <span style={{ fontSize: "14px", color: $.dim }}>{p.period}</span>}
</div>
<div style={{ display: "inline-flex", alignItems: "center", gap: "5px", background: $.bluePale, borderRadius: "999px", padding: "4px 12px", marginBottom: "18px" }}>
<span style={{ fontSize: "13px" }}></span>
<span style={{ fontSize: "13px", fontWeight: 600, color: $.blue, fontFamily: mono }}>{p.credits} credits{p.id !== "starter" ? "/mo" : ""}</span>
</div>
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
{p.features.map((f, i) => <div key={i} style={{ display: "flex", gap: "7px", fontSize: "13px" }}><span style={{ color: $.green, fontWeight: 700 }}></span><span style={{ color: $.dim, lineHeight: 1.4 }}>{f}</span></div>)}
</div>
<div style={{ position: "absolute", top: "22px", right: "22px", width: "20px", height: "20px", borderRadius: "50%", border: `2px solid ${sel ? $.blue : $.borderDark}`, background: sel ? $.blue : "transparent", display: "flex", alignItems: "center", justifyContent: "center" }}>{sel && <span style={{ fontSize: "11px", color: "#fff", fontWeight: 700 }}></span>}</div>
</div>
);
})}
</div>
<div style={{ display: "flex", justifyContent: "flex-end" }}>
<button onClick={() => setStep(2)} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 8px rgba(29,78,216,0.25)" }}>Continue with {PLANS.find(p => p.id === plan).name} </button>
</div>
</div>)}
{/* STEP 2 */}
{step === 2 && (<div>
<div style={{ textAlign: "center", marginBottom: "24px" }}>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 6px" }}>Boost your visibility</h2>
<p style={{ color: $.dim, fontSize: "14px" }}>Add-ons to maximize your reach. Items in your plan show as included.</p>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(280px, 1fr))", gap: "12px", marginBottom: "20px" }}>
{ADDONS.map(a => {
const inc = incl(a.id), act = active(a.id);
return (
<div key={a.id} onClick={() => toggle(a.id)} style={{ background: act ? $.blueGhost : $.bg, border: `1.5px solid ${act ? (inc ? $.blueLight : $.blue) : $.border}`, borderRadius: "14px", padding: "16px", cursor: inc ? "default" : "pointer", opacity: inc ? 0.6 : 1, position: "relative", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
{inc && <div style={{ position: "absolute", top: "10px", right: "10px", background: $.bluePale, color: $.blue, fontSize: "10px", fontWeight: 700, padding: "2px 8px", borderRadius: "999px", textTransform: "uppercase" }}>Included</div>}
<div style={{ display: "flex", gap: "12px" }}>
<div style={{ width: "20px", height: "20px", borderRadius: "6px", border: `2px solid ${act ? $.blue : $.borderDark}`, background: act ? $.blue : "transparent", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, marginTop: "2px" }}>{act && <span style={{ fontSize: "11px", color: "#fff", fontWeight: 700 }}></span>}</div>
<div style={{ flex: 1 }}>
<div style={{ display: "flex", alignItems: "center", gap: "6px", marginBottom: "3px" }}><span style={{ fontSize: "14px" }}>{a.icon}</span><span style={{ fontSize: "14px", fontWeight: 600 }}>{a.name}</span></div>
<p style={{ fontSize: "12px", color: $.dim, lineHeight: 1.4, margin: "0 0 8px" }}>{a.description}</p>
<div style={{ display: "flex", alignItems: "center", gap: "10px" }}>
<span style={{ fontSize: "13px", fontWeight: 600, fontFamily: mono, color: inc ? $.faint : $.text, textDecoration: inc ? "line-through" : "none" }}>{a.price}<span style={{ fontSize: "11px", color: $.faint, marginLeft: "2px" }}>{a.period}</span></span>
<span style={{ fontSize: "11px", fontWeight: 600, color: $.blue, background: $.bluePale, padding: "2px 8px", borderRadius: "999px" }}>{a.boost}</span>
</div>
</div>
</div>
</div>
);
})}
</div>
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "16px 20px", marginBottom: "24px", display: "flex", alignItems: "center", justifyContent: "space-between", flexWrap: "wrap", gap: "12px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<div>
<div style={{ fontSize: "12px", color: $.dim, marginBottom: "3px" }}>Your visibility multiplier</div>
<div style={{ display: "flex", alignItems: "baseline", gap: "5px" }}><span style={{ fontSize: "26px", fontWeight: 700, fontFamily: mono, color: vis() > 3 ? $.gold : $.blue }}>{vis().toFixed(1)}×</span><span style={{ fontSize: "12px", color: $.faint }}>vs. basic profile</span></div>
</div>
<div style={{ height: "8px", flex: "1 1 200px", maxWidth: "260px", background: $.muted, borderRadius: "999px", overflow: "hidden" }}>
<div style={{ height: "100%", width: `${Math.min((vis() / 7) * 100, 100)}%`, background: vis() > 3 ? `linear-gradient(90deg, ${$.blue}, ${$.gold})` : $.blue, borderRadius: "999px", transition: "width 0.4s ease-out" }} />
</div>
</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button onClick={() => setStep(1)} style={{ background: $.bg, color: $.dim, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "12px 22px", fontSize: "14px", fontWeight: 500, cursor: "pointer", fontFamily: font }}> Back</button>
<button onClick={() => setStep(3)} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 8px rgba(29,78,216,0.25)" }}>Continue </button>
</div>
</div>)}
{/* STEP 3 */}
{step === 3 && (<div>
<div style={{ textAlign: "center", marginBottom: "24px" }}>
<h2 style={{ fontSize: "22px", fontWeight: 700, margin: "0 0 6px" }}>Need more lead credits?</h2>
<p style={{ color: $.dim, fontSize: "14px", maxWidth: "440px", margin: "0 auto" }}>Your plan includes <strong style={{ color: $.blue }}>{PLANS.find(p => p.id === plan).credits} credits{plan !== "starter" ? "/mo" : ""}</strong>. Buy extra at volume discount.</p>
</div>
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "14px", padding: "16px 20px", marginBottom: "20px", boxShadow: "0 1px 3px rgba(0,0,0,0.04)" }}>
<div style={{ fontSize: "11px", fontWeight: 700, color: $.faint, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "12px" }}>How credits work</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(150px, 1fr))", gap: "14px" }}>
{[["🏠", "Small project", "2-court residential", "58 credits"], ["🏢", "Medium project", "46 court club", "1525 credits"], ["🏟", "Large project", "8+ court complex", "3040 credits"]].map(([e, l, d, c]) => (
<div key={l} style={{ display: "flex", gap: "10px" }}><span style={{ fontSize: "20px" }}>{e}</span><div><div style={{ fontSize: "13px", fontWeight: 600 }}>{l}</div><div style={{ fontSize: "12px", color: $.dim }}>{d}</div><div style={{ fontSize: "12px", fontFamily: mono, color: $.blue, marginTop: "3px" }}>{c}</div></div></div>
))}
</div>
</div>
<div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(190px, 1fr))", gap: "12px", marginBottom: "24px" }}>
{CREDIT_PACKS.map(pk => {
const sel = credits === pk.amount;
return (
<div key={pk.amount} onClick={() => setCredits(sel ? null : pk.amount)} style={{ position: "relative", background: sel ? $.blueGhost : $.bg, border: `2px solid ${sel ? $.blue : $.border}`, borderRadius: "14px", padding: "20px", cursor: "pointer", textAlign: "center", boxShadow: sel ? "0 4px 16px rgba(29,78,216,0.10)" : "0 1px 3px rgba(0,0,0,0.04)" }}>
{pk.popular && <div style={{ position: "absolute", top: "-1px", left: "50%", transform: "translateX(-50%)", background: $.gold, color: "#fff", fontSize: "10px", fontWeight: 700, padding: "2px 10px", borderRadius: "0 0 6px 6px", textTransform: "uppercase" }}>Best Value</div>}
<div style={{ fontSize: "28px", fontWeight: 700, fontFamily: mono }}>{pk.amount}</div>
<div style={{ fontSize: "12px", color: $.dim, marginBottom: "10px" }}>credits</div>
<div style={{ fontSize: "20px", fontWeight: 700 }}>{pk.price}</div>
<div style={{ fontSize: "12px", color: $.dim }}>{pk.perCredit}/credit</div>
</div>
);
})}
</div>
<div style={{ textAlign: "center", marginBottom: "24px", fontSize: "13px", color: $.faint }}>You can always buy more later. Skip to start with plan credits only.</div>
<div style={{ display: "flex", justifyContent: "space-between" }}>
<button onClick={() => setStep(2)} style={{ background: $.bg, color: $.dim, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "12px 22px", fontSize: "14px", cursor: "pointer", fontFamily: font }}> Back</button>
<button onClick={() => setStep(4)} style={{ background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "12px 24px", fontSize: "14px", fontWeight: 600, cursor: "pointer", fontFamily: font, boxShadow: "0 2px 8px rgba(29,78,216,0.25)" }}>{credits ? "Continue →" : "Skip, continue →"}</button>
</div>
</div>)}
{/* STEP 4 */}
{step === 4 && (<div style={{ display: "grid", gridTemplateColumns: "1fr 320px", gap: "24px", alignItems: "start" }}>
<div>
<h2 style={{ fontSize: "22px", fontWeight: 700, marginBottom: "22px" }}>Create your account</h2>
<div style={{ display: "flex", flexDirection: "column", gap: "16px" }}>
{[["company", "Company name", "e.g. PadelCourt GmbH"], ["name", "Your name", "Full name"], ["email", "Email", "you@company.com"]].map(([k, l, p]) => (
<div key={k}><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>{l}</label><input placeholder={p} value={form[k]} onChange={e => setForm(f => ({ ...f, [k]: e.target.value }))} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: $.text, fontFamily: font, outline: "none", boxSizing: "border-box" }} onFocus={e => e.target.style.borderColor = $.blue} onBlur={e => e.target.style.borderColor = $.border} /></div>
))}
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "6px" }}>Country</label>
<select value={form.country} onChange={e => setForm(f => ({ ...f, country: e.target.value }))} style={{ width: "100%", background: $.bg, border: `1.5px solid ${$.border}`, borderRadius: "10px", padding: "11px 14px", fontSize: "14px", color: form.country ? $.text : $.faint, fontFamily: font, outline: "none", boxSizing: "border-box" }}>
<option value="">Select country</option>{["Germany", "Netherlands", "France", "Italy", "Spain", "UK", "Sweden", "Belgium", "Austria", "Switzerland", "Portugal", "UAE", "USA", "Other"].map(c => <option key={c}>{c}</option>)}
</select>
</div>
<div><label style={{ display: "block", fontSize: "13px", fontWeight: 500, color: $.dim, marginBottom: "8px" }}>Service categories</label>
<div style={{ display: "flex", flexWrap: "wrap", gap: "8px" }}>
{CATEGORIES.map(cat => { const a = form.categories.includes(cat); return <button key={cat} onClick={() => setForm(f => ({ ...f, categories: a ? f.categories.filter(c => c !== cat) : [...f.categories, cat] }))} style={{ background: a ? $.blueGhost : $.bg, border: `1.5px solid ${a ? $.blue : $.border}`, borderRadius: "999px", padding: "6px 14px", fontSize: "13px", fontWeight: a ? 600 : 400, color: a ? $.blue : $.dim, cursor: "pointer", fontFamily: font }}>{cat}</button>; })}
</div>
</div>
</div>
<button onClick={() => setStep(3)} style={{ background: $.bg, color: $.dim, border: `1px solid ${$.border}`, borderRadius: "10px", padding: "10px 20px", fontSize: "14px", cursor: "pointer", fontFamily: font, marginTop: "20px" }}> Back</button>
</div>
{/* Summary */}
<div style={{ background: $.bg, border: `1px solid ${$.border}`, borderRadius: "16px", padding: "22px", position: "sticky", top: "24px", boxShadow: "0 1px 6px rgba(0,0,0,0.06)" }}>
<div style={{ fontSize: "15px", fontWeight: 700, marginBottom: "16px", paddingBottom: "12px", borderBottom: `1px solid ${$.border}` }}>Order Summary</div>
<div style={{ marginBottom: "14px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "2px" }}><span style={{ fontSize: "14px", fontWeight: 600 }}>{PLANS.find(p => p.id === plan).name}</span><span style={{ fontSize: "14px", fontFamily: mono }}>{PLANS.find(p => p.id === plan).price === 0 ? "Free" : `${PLANS.find(p => p.id === plan).price}/mo`}</span></div>
<div style={{ fontSize: "12px", color: $.dim }}>Includes {PLANS.find(p => p.id === plan).credits} credits{plan !== "starter" ? "/mo" : ""}</div>
</div>
{addons.size > 0 && <div style={{ marginBottom: "14px" }}><div style={{ fontSize: "10px", fontWeight: 700, color: $.faint, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "6px" }}>Add-ons</div>{Array.from(addons).map(id => { const a = ADDONS.find(x => x.id === id); return <div key={id} style={{ display: "flex", justifyContent: "space-between", fontSize: "13px", marginBottom: "4px" }}><span style={{ color: $.dim }}>{a.icon} {a.name}</span><span style={{ fontFamily: mono, fontSize: "12px" }}>{a.price}<span style={{ color: $.faint }}>{a.period}</span></span></div>; })}</div>}
{credits && <div style={{ marginBottom: "14px" }}><div style={{ fontSize: "10px", fontWeight: 700, color: $.faint, textTransform: "uppercase", letterSpacing: "0.05em", marginBottom: "6px" }}>Extra Credits</div><div style={{ display: "flex", justifyContent: "space-between", fontSize: "13px" }}><span style={{ color: $.dim }}> {credits} credits</span><span style={{ fontFamily: mono, fontSize: "12px" }}>{CREDIT_PACKS.find(p => p.amount === credits)?.price}</span></div></div>}
<div style={{ borderTop: `1px solid ${$.border}`, paddingTop: "12px", marginTop: "4px" }}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "8px", fontSize: "13px" }}><span style={{ color: $.dim }}>Total credits</span><span style={{ fontFamily: mono, fontWeight: 600, color: $.blue }}> {totalCr()}</span></div>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "12px", fontSize: "13px" }}><span style={{ color: $.dim }}>Visibility boost</span><span style={{ fontFamily: mono, fontWeight: 600, color: vis() > 3 ? $.gold : $.blue }}>{vis().toFixed(1)}×</span></div>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: "3px" }}><span style={{ fontSize: "14px", fontWeight: 600 }}>Monthly</span><span style={{ fontSize: "18px", fontWeight: 700, fontFamily: mono }}>{monthly()}</span></div>
{oneTime() > 0 && <div style={{ display: "flex", justifyContent: "space-between" }}><span style={{ fontSize: "13px", color: $.dim }}>Due today (one-time)</span><span style={{ fontSize: "14px", fontWeight: 600, fontFamily: mono, color: $.dim }}>{oneTime()}</span></div>}
</div>
<button style={{ width: "100%", background: $.blue, color: "#fff", border: "none", borderRadius: "10px", padding: "13px", fontSize: "15px", fontWeight: 700, cursor: "pointer", fontFamily: font, marginTop: "14px", boxShadow: "0 2px 10px rgba(29,78,216,0.25)" }}>{monthly() === 0 && oneTime() === 0 ? "Create Free Account" : "Start Subscription →"}</button>
<div style={{ textAlign: "center", fontSize: "11px", color: $.faint, marginTop: "10px" }}>{monthly() > 0 ? "Cancel anytime. No lock-in." : "No credit card required."}</div>
</div>
</div>)}
</div>
</div>
</div>
);
}

View File

@@ -1 +0,0 @@
<table class="min-w-full border-collapse text-sm leading-[1.7] whitespace-normal"><thead class="text-left"><tr><th class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Stage</th><th class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Question They Ask</th><th class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Your Product</th><th class="text-text-100 border-b-0.5 border-border-300/60 py-2 pr-4 align-top font-bold">Monetization</th></tr></thead><tbody><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Explore</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"Is padel viable in my area?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Market demand calculator, whitespace analysis</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Free → lead capture</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Plan</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"How many courts? Indoor/outdoor? What will it cost?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">ROI calculator, construction cost estimator</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Builder lead gen (€500-2K/lead)</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Finance</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"How do I fund this?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Financing marketplace, business plan generator</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Financing lead gen / broker fees</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Build</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"Who builds it? What equipment?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Vetted builder marketplace, supplier directory</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Referral commissions</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Launch</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"How do I price? What's my marketing plan?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Pricing benchmarks, launch playbook</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Subscription (€100-300/mo)</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Run</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"How am I performing vs. market?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Benchmarking dashboard, operational KPIs</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Subscription</td></tr><tr><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top"><strong>Scale</strong></td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">"Should I add courts? Open location #2?"</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Expansion analytics, new site scoring</td><td class="border-b-0.5 border-border-300/30 py-2 pr-4 align-top">Premium subscription / advisory</td></tr></tbody></table>

View File

@@ -1,174 +0,0 @@
# Padelnomics — Platform Vision & Research Summary
*February 16, 2026*
---
## What We Set Out To Do
We researched the entire landscape of padel-related online businesses, then zoomed deep into the "calculator → lead gen → supplier revenue" model to understand exactly how it works, who's doing it, what they earn, and how we can build a better, more automated version.
---
## Part 1: The Padel Online Business Landscape
We mapped 35+ international padel digital businesses across 11 categories:
**Dominant players:** Playtomic (booking marketplace, €147M raised, 10M+ users), Padel Nuestro (e-commerce, ~€60M revenue), GameCam (AI video analytics). The market is booming — Playtomic's 2025 report cites 26% increase in club openings, 7,000 new courts built worldwide, and the sport is expected to continue growing at 17%+ annually.
**Key gap identified:** Nobody has built the *intelligence and lead generation layer* for people planning and building padel facilities. Playtomic serves players. Nobody serves the facility builders, planners, and suppliers in a structured, data-driven way.
---
## Part 2: The Calculator Lead Gen Model — Deep Dive
### Two sites doing this today
We identified only two independent platforms worldwide running the "free calculator → email capture → supplier connection" playbook:
**Sportstättenrechner (sportstaettenrechner.de)** — Germany, founded August 2016 by Jan Prümper in Cologne. 18,400 unique users/month, 90+ calculators, 140+ SEO articles, covers all sports facility types. Revenue from tiered supplier directory (Basic €600/yr, Business €1,400/yr, Premium on request) plus manual lead forwarding ("Angebotsservice") to paying suppliers.
**Sports Venue Calculator (sportsvenuecalculator.com)** — USA, founded May 2021 by the same team (Jan + co-founders Antti and Tommi). 40,000+ users/month, 1,000+ calculator requests/month, 300,000+ project owners reached in past 12 months. Higher pricing: Premium Listing from $500/yr, Marketing Booster from $14,800/yr, Unfair Advantage from $24,000/yr (full lead forwarding with contact details).
### Same operation, confirmed
Both sites are run by the same people. Jan Prümper is the sole owner listed in every Impressum. US entity: Calculator Group LLC, Sheridan, Wyoming. The co-founders (Antti: marketing/sales; Tommi: former pro athlete/fighter pilot, sales) handle the international side.
### Jan's full empire — 8 sites, same WordPress template
We found every site in the network, all running the identical playbook:
| Site | Niche | Founded |
|---|---|---|
| sportstaettenrechner.de | Sports facility construction (DE) | Aug 2016 |
| sportsvenuecalculator.com | Sports facility construction (US) | May 2021 |
| event-rechner.de | Event planning costs (DE) | Jul 2018 |
| sportstaetten-finanzierung.de | Sports facility crowdfunding (DE) | ~2019 |
| spielplatzrechner.de | Playground construction costs (DE) | ~2020 |
| swimmingpool-rechner.de | Swimming pool costs (DE) | ~2021 |
| sportnetzwerk-fsb.de | FSB trade show networking (DE) | ~2019 |
| directory.sportsvenuecalculator.com | US supplier directory | ~2023 |
All share the same Impressum, same address, same tax ID (USt-ID: DE305156863), same WordPress CMS (confirmed via `wp-content` in source code). He literally copy-pastes the same WP site into new niches, swaps the content, and lets SEO do the work.
### Revenue estimate for the entire operation
| Revenue stream | Estimated annual |
|---|---|
| SVC US — Unfair Advantage tier (~10 clients × $24K+) | $240K$290K |
| SVC US — Marketing Booster (~15 × $14.8K) | ~$220K |
| SVC US — Premium Listings (~15 × $500) | ~$8K |
| Sportstättenrechner DE — supplier directory (~25 × ~€5K avg) | ~€125K |
| Event-rechner DE | €20K€40K |
| Spielplatzrechner DE (JV) | €10K€30K |
| Swimmingpool-rechner DE | €5K€15K |
| SPORTNETZWERK.FSB trade show sponsorships | €30K€60K |
| **Total estimated** | **~€500K€750K/yr (€40K€60K MRR)** |
Almost pure margin — WordPress hosting costs nothing, content is written once and compounds via SEO, team is only 3 founders.
### The current lead process (manual)
1. Someone googles "padel court cost" → lands on calculator page
2. Fills in project specs (courts, indoor/outdoor, surface type, location)
3. Email gate — must enter email to see results
4. Gets cost estimate by email + option to "request real quotes"
5. If they opt in: provide phone + address for supplier contact
6. Jan's team manually reviews the inquiry, matches it to 2-3 Premium suppliers, forwards the lead via email
7. Supplier (paying €1,400+/yr) receives warm lead with full project specs
Key stat: Sportstättenrechner forwards 492 inquiries/month to Premium partners. SVC processes 1,000+ calculator requests/month.
### Massive geographic gaps
The sportstaettenrechner model has NOT been replicated in: France, Italy, Netherlands, Sweden, Belgium, Austria, Portugal, UAE/Middle East, Mexico/Latin America, or the UK (properly). These are all major or fast-growing padel markets with zero equivalent platform.
---
## Part 3: How We Make It Better — The Automated Platform
### The problem with Jan's model
It works, but it's manual. One guy in Cologne reading forms and CC'ing suppliers. It doesn't scale, can't handle real-time lead matching, and leaves money on the table because there's no self-serve mechanism for suppliers to buy leads.
### Three proven automated lead models we studied
**Model A — Pay-per-lead (Angi/HomeAdvisor):** Suppliers pay $15$100 per lead, which gets blasted to 3-5 contractors simultaneously. Massive scale ($1.47B revenue at Angi) but terrible reputation for lead quality. FTC fined HomeAdvisor $7.2M for deceptive lead marketing. Contractors hate paying for shared, often low-quality leads.
**Model B — Credit system (Bark):** Suppliers buy credits upfront, then browse anonymized project briefs and spend credits to unlock contact details. A lead costs 3-20 credits ($5$36) depending on job value. Suppliers self-select which leads they want — feels fairer. But still complaints about lead quality and credit pricing opacity.
**Model C — Dynamic marketplace (Thumbtack):** Pay-per-lead with no annual fees. Lead prices are dynamic based on job size, location, and competition. More automated matching but suppliers report costs of $50$75/lead with low conversion rates.
### The RemoteOK upsell playbook (Pieter Levels)
RemoteOK (job board, $3.4M revenue in 2024) pioneered stacking micro-upsells on directory listings. When Levels added upsells like logo display (+$49), yellow highlight (+$49), custom brand color (+$89), sticky post for 1 week (+$99) or 1 month (+$299), average price paid jumped from $310 to $484 overnight — a 25% revenue increase.
Key psychological tricks: options come pre-selected (loss aversion — unselecting feels like losing an advantage), each upsell shows a "X% more views" counter to make the ROI tangible, and pricing is dynamic based on demand.
---
## Part 4: Our Platform Design — Padelnomics
### The hybrid model (four layers)
**Layer 1 — Free directory (supplier acquisition top-of-funnel)**
Any supplier can create a free basic listing: name, description, link. Seeds the directory with content and makes it look active. Zero barrier to entry.
**Layer 2 — RemoteOK-style upsells (predictable MRR)**
Self-serve, stackable visibility upgrades:
| Upsell | Price | Impact |
|---|---|---|
| Show company logo | €29/mo | +40% more clicks |
| Highlight listing (yellow) | €39/mo | +65% more views |
| Custom brand color | €59/mo | +80% brand recognition |
| Verified badge ✓ | €49/mo | +55% more inquiries |
| Sticky top 1 week | €79 one-time | 2× more views |
| Sticky top 1 month | €199 one-time | 6× more views |
| Featured in newsletter | €99/mo | +120 targeted impressions/wk |
| Lead feed access | €199/mo | Direct customer contact |
Pre-select the "recommended" package (logo + highlight + verified + lead access = €316/mo) so suppliers have to actively uncheck items to go cheaper.
**Layer 3 — Lead marketplace (credit-based, self-serve)**
When someone completes a padel court calculator, their project specs become an anonymized brief in a "lead feed" visible to paying suppliers. Example brief: *"Indoor padel center, 6 courts, Lower Saxony, budget €400K€600K, timeline Q3 2026."*
Suppliers browse the feed, see project specs, heat ratings (hot/warm/cool based on timeline and completeness), and how many other suppliers have already unlocked each lead. They spend credits to unlock contact details. Credit cost scales dynamically by project value — a 2-court residential project costs 8 credits, a 10-court commercial complex costs 40 credits.
This is dramatically better than Jan's manual email forwarding because: suppliers self-select relevant leads (no wasted matches), the credit system creates a self-serve revenue engine, the anonymized feed creates urgency (seeing "0 other suppliers unlocked this" or "3 already on this"), and it scales infinitely without manual intervention.
**Layer 4 — Premium tier (enterprise, "Unfair Advantage")**
For large suppliers (court manufacturers, national installers) who want everything: all leads auto-forwarded (no credit spending), co-branded calculator pages, quarterly market intelligence reports, newsletter sponsorship included. Price on request: €500€2,000/mo depending on category coverage.
### The prototype we built
We designed an interactive React prototype showing the supplier-facing dashboard with four views:
**Dashboard** — Welcome screen with key stats (profile views, leads unlocked, credit balance, directory rank), a notification banner for new matching leads, and a recent activity feed showing lead alerts, listing views, and newsletter features.
**Lead Feed** — The core innovation. An anonymized project brief feed with heat badges (🔥 Hot / ◐ Warm / ○ Cool), project specs (type, courts, region, budget, timeline), technical requirements as tags, a count of how many other suppliers already unlocked each lead, and a prominent "Unlock Contact — X credits" button. Clicking unlock reveals name, organization, email, and phone. A credit balance is always visible in the top-right with a "Buy More" button.
**My Listing** — Preview of how the supplier's company card appears in the directory, showing active boosts (logo, highlight, verified badge), and performance metrics for the last 30 days (views, clicks, website clicks, contact requests).
**Boost & Upsells** — The RemoteOK-style self-serve upgrade page. All upsells listed as toggle cards with checkboxes, descriptions, prices, and "+X% more views" impact badges. Pre-selected recommended items. A sticky summary sidebar shows the selected plan, monthly cost, and a real-time "view multiplier" that updates as you toggle options (e.g., "4.2× more visibility vs basic listing").
### Technical approach
The whole thing can run lean. WordPress + WooCommerce (for payments/subscriptions) + a directory plugin like ListingPro or a custom post type for the lead feed. The calculator forms capture project specs into a database, anonymize them, and display them in the lead feed. Credit purchases and unlocks are WooCommerce transactions. No fancy CRM, no complex API integrations — just like Levels runs RemoteOK on a single PHP file.
### Why this beats the competition
Sportstättenrechner/SVC proved the model works (1,000+ leads/month, €500K+ revenue). But they're manual, not internationalized, and haven't touched padel-specific intelligence. We take their proven playbook and add: automated self-serve lead matching (no manual forwarding), the RemoteOK upsell stack for predictable MRR, credit-based lead purchases that scale without human intervention, and eventually layer the DaaS market intelligence vision (occupancy data, demand analytics, pricing benchmarks) on top as a premium differentiator nobody else has.
The geographic whitespace is enormous — there's no equivalent platform in France, Italy, Netherlands, Sweden, or Latin America. First mover in any of these markets with a localized calculator + supplier directory captures the entire funnel.
---
## Files Created
| File | Contents |
|---|---|
| `Padel_Online_Business_Landscape.md` | 35+ businesses across 11 categories, detailed analysis |
| `Padel_Lead_Gen_Sites_Analysis.md` | Country-by-country breakdown of calculator/lead gen sites, business models, geographic gaps |
| `supplier-dashboard.jsx` | Interactive React prototype of the supplier portal (dashboard, lead feed, listing preview, upsell page) |
| `Padelnomics_Platform_Summary.md` | This document |

View File

@@ -0,0 +1,855 @@
import { useState } from "react";
const COLORS = {
bg: "#0A0F1C",
surface: "#111827",
surfaceLight: "#1A2236",
surfaceHover: "#1F2A40",
border: "#2A3550",
borderLight: "#354263",
accent: "#22D3A7",
accentDim: "rgba(34,211,167,0.12)",
accentGlow: "rgba(34,211,167,0.25)",
warning: "#F59E0B",
warningDim: "rgba(245,158,11,0.12)",
hot: "#EF4444",
hotDim: "rgba(239,68,68,0.12)",
blue: "#3B82F6",
blueDim: "rgba(59,130,246,0.12)",
purple: "#A78BFA",
purpleDim: "rgba(167,139,250,0.12)",
text: "#F1F5F9",
textMuted: "#94A3B8",
textDim: "#64748B",
};
const tabs = [
{ id: "dashboard", label: "Dashboard", icon: "◐" },
{ id: "leads", label: "Lead Feed", icon: "◉" },
{ id: "listing", label: "My Listing", icon: "◧" },
{ id: "boost", label: "Boost & Upsells", icon: "△" },
];
const mockLeads = [
{
id: 1,
type: "Indoor Padel Center",
courts: 6,
region: "Lower Saxony, Germany",
budget: "€400K €600K",
timeline: "Q3 2026",
specs: ["Indoor steel hall", "LED lighting", "Panoramic glass walls", "Pro-grade turf"],
posted: "2 hours ago",
heat: "hot",
credits: 25,
unlocked: false,
bidders: 1,
},
{
id: 2,
type: "Outdoor Padel Courts",
courts: 4,
region: "Bavaria, Germany",
budget: "€180K €280K",
timeline: "Q2 2026",
specs: ["Outdoor installation", "Drainage system", "Windbreak fencing", "Night lighting"],
posted: "5 hours ago",
heat: "warm",
credits: 15,
unlocked: false,
bidders: 3,
},
{
id: 3,
type: "Indoor Padel & Fitness Complex",
courts: 10,
region: "North Rhine-Westphalia, Germany",
budget: "€800K €1.2M",
timeline: "Q1 2027",
specs: ["Mixed-use facility", "Padel + gym + café", "Climate control", "Spectator area"],
posted: "1 day ago",
heat: "hot",
credits: 40,
unlocked: false,
bidders: 0,
},
{
id: 4,
type: "Padel Court Addition",
courts: 2,
region: "Hesse, Germany",
budget: "€80K €140K",
timeline: "Q4 2026",
specs: ["Tennis club conversion", "2 courts", "Basic lighting", "Standard glass"],
posted: "2 days ago",
heat: "cool",
credits: 8,
unlocked: true,
bidders: 5,
contact: { name: "Thomas M.", org: "TC Wiesbaden e.V.", email: "t.m***@tc-wiesbaden.de", phone: "+49 611 ***" },
},
{
id: 5,
type: "Commercial Padel Venue",
courts: 8,
region: "Hamburg, Germany",
budget: "€500K €750K",
timeline: "Q2 2026",
specs: ["Standalone commercial", "Bar/lounge area", "Premium courts", "Booking system integration"],
posted: "3 days ago",
heat: "warm",
credits: 30,
unlocked: false,
bidders: 2,
},
];
const upsells = [
{
id: "logo",
label: "Show Company Logo",
desc: "Display your logo next to your listing name",
price: 29,
period: "/mo",
boost: "+40% more clicks",
preselected: true,
active: true,
},
{
id: "highlight",
label: "Highlight Listing",
desc: "Yellow background to stand out in search results",
price: 39,
period: "/mo",
boost: "+65% more views",
preselected: true,
active: true,
},
{
id: "brandcolor",
label: "Custom Brand Color",
desc: "Use your brand color instead of yellow",
price: 59,
period: "/mo",
boost: "+80% brand recognition",
preselected: false,
active: false,
},
{
id: "verified",
label: "Verified Badge ✓",
desc: "Trusted supplier badge on your profile",
price: 49,
period: "/mo",
boost: "+55% more inquiries",
preselected: true,
active: true,
},
{
id: "sticky_week",
label: "Sticky Top 1 Week",
desc: "Pinned to the top of your category for 7 days",
price: 79,
period: "one-time",
boost: "2× more views",
preselected: false,
active: false,
},
{
id: "sticky_month",
label: "Sticky Top 1 Month",
desc: "Pinned to the top of your category for 30 days",
price: 199,
period: "one-time",
boost: "6× more views",
preselected: false,
active: false,
},
{
id: "newsletter",
label: "Featured in Newsletter",
desc: "Included in weekly email to 2,400+ project planners",
price: 99,
period: "/mo",
boost: "+120 targeted impressions/week",
preselected: false,
active: false,
},
{
id: "leadaccess",
label: "Lead Feed Access",
desc: "Browse & unlock leads in your categories",
price: 199,
period: "/mo",
boost: "Direct customer contact",
preselected: true,
active: true,
},
];
function HeatBadge({ heat }) {
const conf = {
hot: { bg: COLORS.hotDim, color: COLORS.hot, label: "🔥 Hot", border: "rgba(239,68,68,0.3)" },
warm: { bg: COLORS.warningDim, color: COLORS.warning, label: "◐ Warm", border: "rgba(245,158,11,0.3)" },
cool: { bg: COLORS.blueDim, color: COLORS.blue, label: "○ Cool", border: "rgba(59,130,246,0.3)" },
}[heat];
return (
<span style={{
background: conf.bg,
color: conf.color,
border: `1px solid ${conf.border}`,
padding: "3px 10px",
borderRadius: 20,
fontSize: 12,
fontWeight: 600,
letterSpacing: 0.3,
}}>{conf.label}</span>
);
}
function StatCard({ label, value, sub, color }) {
return (
<div style={{
background: COLORS.surface,
border: `1px solid ${COLORS.border}`,
borderRadius: 12,
padding: "20px 22px",
flex: 1,
minWidth: 160,
}}>
<div style={{ fontSize: 12, color: COLORS.textDim, textTransform: "uppercase", letterSpacing: 1.2, marginBottom: 8, fontWeight: 600 }}>{label}</div>
<div style={{ fontSize: 28, fontWeight: 700, color: color || COLORS.text, lineHeight: 1.1 }}>{value}</div>
{sub && <div style={{ fontSize: 12, color: COLORS.textMuted, marginTop: 6 }}>{sub}</div>}
</div>
);
}
function DashboardView() {
return (
<div>
<div style={{ marginBottom: 28 }}>
<h2 style={{ fontSize: 22, fontWeight: 700, color: COLORS.text, margin: 0 }}>Welcome back, PadelBau GmbH</h2>
<p style={{ color: COLORS.textMuted, margin: "6px 0 0", fontSize: 14 }}>Your supplier dashboard February 2026</p>
</div>
<div style={{ display: "flex", gap: 14, flexWrap: "wrap", marginBottom: 28 }}>
<StatCard label="Profile Views" value="1,247" sub="↑ 23% vs last month" color={COLORS.accent} />
<StatCard label="Leads Unlocked" value="14" sub="This month" color={COLORS.blue} />
<StatCard label="Credits Balance" value="85" sub="~3 premium leads" color={COLORS.warning} />
<StatCard label="Directory Rank" value="#3" sub="Padel Courts category" color={COLORS.purple} />
</div>
<div style={{
background: `linear-gradient(135deg, ${COLORS.accentDim}, ${COLORS.purpleDim})`,
border: `1px solid ${COLORS.border}`,
borderRadius: 12,
padding: "22px 26px",
marginBottom: 28,
}}>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 12 }}>
<div>
<div style={{ fontSize: 15, fontWeight: 600, color: COLORS.text, marginBottom: 4 }}>🔥 3 new leads match your profile</div>
<div style={{ fontSize: 13, color: COLORS.textMuted }}>Indoor facilities in Lower Saxony, NRW, and Hamburg total project value 1.7M+</div>
</div>
<button style={{
background: COLORS.accent,
color: COLORS.bg,
border: "none",
borderRadius: 8,
padding: "10px 20px",
fontWeight: 700,
fontSize: 13,
cursor: "pointer",
whiteSpace: "nowrap",
}}>View Lead Feed </button>
</div>
</div>
<h3 style={{ fontSize: 15, fontWeight: 600, color: COLORS.text, marginBottom: 14 }}>Recent Activity</h3>
{[
{ time: "2h ago", text: "New lead posted: 6-court indoor center in Lower Saxony (€400K€600K)", dot: COLORS.hot },
{ time: "1d ago", text: "Your listing was viewed 47 times today — 12 from the Padel Courts category", dot: COLORS.accent },
{ time: "2d ago", text: "Lead unlocked: TC Wiesbaden — 2-court addition project. Contact details available.", dot: COLORS.blue },
{ time: "3d ago", text: "Your 'Sticky Top' placement in Padel Courts expired. Renew to stay on top.", dot: COLORS.warning },
{ time: "5d ago", text: "Newsletter sent to 2,400 subscribers — your company was featured", dot: COLORS.purple },
].map((item, i) => (
<div key={i} style={{
display: "flex",
gap: 14,
padding: "12px 0",
borderBottom: i < 4 ? `1px solid ${COLORS.border}` : "none",
alignItems: "flex-start",
}}>
<div style={{ width: 8, height: 8, borderRadius: 4, background: item.dot, marginTop: 6, flexShrink: 0 }} />
<div style={{ flex: 1 }}>
<div style={{ fontSize: 13, color: COLORS.text, lineHeight: 1.5 }}>{item.text}</div>
</div>
<div style={{ fontSize: 12, color: COLORS.textDim, whiteSpace: "nowrap", flexShrink: 0 }}>{item.time}</div>
</div>
))}
</div>
);
}
function LeadFeedView() {
const [leads, setLeads] = useState(mockLeads);
const [filter, setFilter] = useState("all");
const unlock = (id) => {
setLeads(leads.map(l => l.id === id ? {
...l,
unlocked: true,
contact: { name: "Max S.", org: "Padel Invest Nord GmbH", email: "m.s***@padel-invest.de", phone: "+49 40 ***" }
} : l));
};
const filtered = filter === "all" ? leads : leads.filter(l => l.heat === filter);
return (
<div>
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 24, flexWrap: "wrap", gap: 12 }}>
<div>
<h2 style={{ fontSize: 22, fontWeight: 700, color: COLORS.text, margin: 0 }}>Lead Feed</h2>
<p style={{ color: COLORS.textMuted, margin: "6px 0 0", fontSize: 14 }}>Browse anonymized project briefs · Spend credits to unlock contact details</p>
</div>
<div style={{
background: COLORS.surface,
border: `1px solid ${COLORS.border}`,
borderRadius: 8,
padding: "8px 14px",
display: "flex",
alignItems: "center",
gap: 8,
}}>
<span style={{ fontSize: 12, color: COLORS.textDim }}>Balance:</span>
<span style={{ fontSize: 16, fontWeight: 700, color: COLORS.warning }}>85 credits</span>
<button style={{
background: COLORS.warningDim,
color: COLORS.warning,
border: `1px solid rgba(245,158,11,0.3)`,
borderRadius: 6,
padding: "4px 10px",
fontSize: 11,
fontWeight: 600,
cursor: "pointer",
marginLeft: 4,
}}>Buy More</button>
</div>
</div>
<div style={{ display: "flex", gap: 8, marginBottom: 20 }}>
{[
{ id: "all", label: "All Leads" },
{ id: "hot", label: "🔥 Hot" },
{ id: "warm", label: "◐ Warm" },
{ id: "cool", label: "○ Cool" },
].map(f => (
<button key={f.id} onClick={() => setFilter(f.id)} style={{
background: filter === f.id ? COLORS.accentDim : "transparent",
color: filter === f.id ? COLORS.accent : COLORS.textMuted,
border: `1px solid ${filter === f.id ? "rgba(34,211,167,0.3)" : COLORS.border}`,
borderRadius: 20,
padding: "6px 16px",
fontSize: 12,
fontWeight: 600,
cursor: "pointer",
}}>{f.label}</button>
))}
</div>
<div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
{filtered.map(lead => (
<div key={lead.id} style={{
background: lead.unlocked ? COLORS.surface : COLORS.surfaceLight,
border: `1px solid ${lead.heat === "hot" ? "rgba(239,68,68,0.2)" : COLORS.border}`,
borderRadius: 12,
padding: "20px 22px",
position: "relative",
overflow: "hidden",
}}>
{lead.heat === "hot" && !lead.unlocked && (
<div style={{
position: "absolute",
top: 0,
left: 0,
right: 0,
height: 2,
background: `linear-gradient(90deg, ${COLORS.hot}, ${COLORS.warning})`,
}} />
)}
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: 12, flexWrap: "wrap", gap: 8 }}>
<div>
<div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 6 }}>
<span style={{ fontSize: 16, fontWeight: 700, color: COLORS.text }}>{lead.type}</span>
<HeatBadge heat={lead.heat} />
</div>
<div style={{ display: "flex", gap: 16, flexWrap: "wrap" }}>
<span style={{ fontSize: 13, color: COLORS.textMuted }}>📍 {lead.region}</span>
<span style={{ fontSize: 13, color: COLORS.textMuted }}>🏟 {lead.courts} courts</span>
<span style={{ fontSize: 13, color: COLORS.textMuted }}>💰 {lead.budget}</span>
<span style={{ fontSize: 13, color: COLORS.textMuted }}>📅 {lead.timeline}</span>
</div>
</div>
<div style={{ fontSize: 12, color: COLORS.textDim }}>{lead.posted}</div>
</div>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap", marginBottom: 14 }}>
{lead.specs.map((s, i) => (
<span key={i} style={{
background: COLORS.bg,
color: COLORS.textMuted,
border: `1px solid ${COLORS.border}`,
borderRadius: 6,
padding: "3px 10px",
fontSize: 11,
fontWeight: 500,
}}>{s}</span>
))}
</div>
{lead.unlocked ? (
<div style={{
background: COLORS.accentDim,
border: `1px solid rgba(34,211,167,0.2)`,
borderRadius: 8,
padding: "14px 18px",
}}>
<div style={{ fontSize: 12, color: COLORS.accent, fontWeight: 600, marginBottom: 8, textTransform: "uppercase", letterSpacing: 0.8 }}> Contact Unlocked</div>
<div style={{ display: "flex", gap: 24, flexWrap: "wrap" }}>
<div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>Contact</div>
<div style={{ fontSize: 13, color: COLORS.text, fontWeight: 600 }}>{lead.contact.name}</div>
</div>
<div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>Organization</div>
<div style={{ fontSize: 13, color: COLORS.text, fontWeight: 600 }}>{lead.contact.org}</div>
</div>
<div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>Email</div>
<div style={{ fontSize: 13, color: COLORS.text, fontWeight: 600 }}>{lead.contact.email}</div>
</div>
<div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>Phone</div>
<div style={{ fontSize: 13, color: COLORS.text, fontWeight: 600 }}>{lead.contact.phone}</div>
</div>
</div>
</div>
) : (
<div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", flexWrap: "wrap", gap: 10 }}>
<div style={{ fontSize: 12, color: COLORS.textDim }}>
{lead.bidders === 0 ? (
<span style={{ color: COLORS.accent, fontWeight: 600 }}> No other suppliers yet be first!</span>
) : (
<span>{lead.bidders} supplier{lead.bidders > 1 ? "s" : ""} already unlocked this lead</span>
)}
</div>
<button onClick={() => unlock(lead.id)} style={{
background: `linear-gradient(135deg, ${COLORS.accent}, #1AAB8A)`,
color: COLORS.bg,
border: "none",
borderRadius: 8,
padding: "10px 20px",
fontWeight: 700,
fontSize: 13,
cursor: "pointer",
display: "flex",
alignItems: "center",
gap: 8,
}}>
<span>Unlock Contact</span>
<span style={{
background: "rgba(0,0,0,0.2)",
borderRadius: 4,
padding: "2px 8px",
fontSize: 12,
}}>{lead.credits} credits</span>
</button>
</div>
)}
</div>
))}
</div>
</div>
);
}
function ListingView() {
return (
<div>
<h2 style={{ fontSize: 22, fontWeight: 700, color: COLORS.text, margin: "0 0 6px" }}>Your Listing Preview</h2>
<p style={{ color: COLORS.textMuted, margin: "0 0 24px", fontSize: 14 }}>This is how your company appears in the directory</p>
{/* Listing card preview */}
<div style={{
background: "linear-gradient(135deg, rgba(245,158,11,0.06), rgba(245,158,11,0.02))",
border: `1px solid rgba(245,158,11,0.25)`,
borderRadius: 14,
padding: "24px 26px",
marginBottom: 28,
position: "relative",
}}>
<div style={{
position: "absolute",
top: 12,
right: 14,
background: COLORS.warningDim,
color: COLORS.warning,
border: `1px solid rgba(245,158,11,0.3)`,
padding: "3px 10px",
borderRadius: 20,
fontSize: 11,
fontWeight: 700,
letterSpacing: 0.5,
}}> PROMOTED</div>
<div style={{ display: "flex", gap: 18, alignItems: "flex-start", marginBottom: 16 }}>
<div style={{
width: 56,
height: 56,
borderRadius: 10,
background: `linear-gradient(135deg, ${COLORS.accent}, ${COLORS.blue})`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 22,
fontWeight: 800,
color: COLORS.bg,
flexShrink: 0,
}}>PB</div>
<div>
<div style={{ display: "flex", alignItems: "center", gap: 8, marginBottom: 4 }}>
<span style={{ fontSize: 18, fontWeight: 700, color: COLORS.text }}>PadelBau GmbH</span>
<span style={{
background: COLORS.accentDim,
color: COLORS.accent,
border: `1px solid rgba(34,211,167,0.3)`,
padding: "2px 8px",
borderRadius: 4,
fontSize: 10,
fontWeight: 700,
}}> VERIFIED</span>
</div>
<div style={{ fontSize: 13, color: COLORS.textMuted, marginBottom: 8 }}>Premium padel court manufacturer & installer. 150+ courts built across DACH region. Full-service from planning to turnkey delivery.</div>
<div style={{ display: "flex", gap: 12, flexWrap: "wrap" }}>
<span style={{ fontSize: 12, color: COLORS.textDim }}>📍 Cologne, Germany</span>
<span style={{ fontSize: 12, color: COLORS.textDim }}>🏟 150+ courts installed</span>
<span style={{ fontSize: 12, color: COLORS.textDim }}> 4.8/5 (23 reviews)</span>
</div>
</div>
</div>
<div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
{["Padel Courts", "Court Construction", "LED Lighting", "Artificial Turf", "Planning & Design"].map((tag, i) => (
<span key={i} style={{
background: COLORS.bg,
color: COLORS.textMuted,
border: `1px solid ${COLORS.border}`,
borderRadius: 6,
padding: "4px 10px",
fontSize: 11,
fontWeight: 500,
}}>{tag}</span>
))}
</div>
</div>
<h3 style={{ fontSize: 15, fontWeight: 600, color: COLORS.text, marginBottom: 14 }}>Active Boosts on Your Listing</h3>
<div style={{ display: "flex", gap: 10, flexWrap: "wrap", marginBottom: 28 }}>
{[
{ label: "Logo Visible", color: COLORS.accent },
{ label: "Yellow Highlight", color: COLORS.warning },
{ label: "Verified Badge", color: COLORS.accent },
{ label: "Lead Feed Access", color: COLORS.blue },
].map((b, i) => (
<span key={i} style={{
background: `${b.color}15`,
color: b.color,
border: `1px solid ${b.color}30`,
padding: "6px 14px",
borderRadius: 8,
fontSize: 12,
fontWeight: 600,
}}> {b.label}</span>
))}
</div>
<h3 style={{ fontSize: 15, fontWeight: 600, color: COLORS.text, marginBottom: 14 }}>Listing Performance (Last 30 Days)</h3>
<div style={{ display: "flex", gap: 14, flexWrap: "wrap" }}>
<StatCard label="Views" value="1,247" sub="From directory search" color={COLORS.accent} />
<StatCard label="Profile Clicks" value="183" sub="14.7% click rate" color={COLORS.blue} />
<StatCard label="Website Clicks" value="67" sub="From your listing" color={COLORS.purple} />
<StatCard label="Contact Requests" value="12" sub="Direct inquiries" color={COLORS.warning} />
</div>
</div>
);
}
function BoostView() {
const [selected, setSelected] = useState(
Object.fromEntries(upsells.map(u => [u.id, u.preselected]))
);
const toggle = (id) => setSelected({ ...selected, [id]: !selected[id] });
const total = upsells.reduce((sum, u) => {
if (!selected[u.id]) return sum;
return sum + u.price;
}, 0);
const monthlyTotal = upsells.reduce((sum, u) => {
if (!selected[u.id] || u.period !== "/mo") return sum;
return sum + u.price;
}, 0);
const viewMultiplier = upsells.reduce((mult, u) => {
if (!selected[u.id]) return mult;
const m = parseFloat(u.boost.match(/[\d.]+/)?.[0] || 0);
if (u.boost.includes("×")) return mult * m;
if (u.boost.includes("%")) return mult * (1 + m / 100);
return mult;
}, 1);
return (
<div>
<h2 style={{ fontSize: 22, fontWeight: 700, color: COLORS.text, margin: "0 0 6px" }}>Boost Your Visibility</h2>
<p style={{ color: COLORS.textMuted, margin: "0 0 24px", fontSize: 14 }}>Select upgrades to stand out · Pre-selected options are our recommendation</p>
<div style={{ display: "flex", gap: 20, flexWrap: "wrap" }}>
{/* Upsell list */}
<div style={{ flex: "1 1 400px", display: "flex", flexDirection: "column", gap: 10 }}>
{upsells.map(u => {
const isOn = selected[u.id];
return (
<div key={u.id} onClick={() => toggle(u.id)} style={{
background: isOn ? COLORS.surfaceLight : COLORS.surface,
border: `1px solid ${isOn ? COLORS.accent + "40" : COLORS.border}`,
borderRadius: 10,
padding: "16px 18px",
cursor: "pointer",
transition: "all 0.15s",
display: "flex",
justifyContent: "space-between",
alignItems: "center",
gap: 14,
}}>
<div style={{ display: "flex", gap: 14, alignItems: "center", flex: 1 }}>
<div style={{
width: 22,
height: 22,
borderRadius: 6,
border: `2px solid ${isOn ? COLORS.accent : COLORS.textDim}`,
background: isOn ? COLORS.accent : "transparent",
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 13,
color: COLORS.bg,
fontWeight: 800,
flexShrink: 0,
}}>{isOn ? "✓" : ""}</div>
<div>
<div style={{ fontSize: 14, fontWeight: 600, color: COLORS.text, marginBottom: 2 }}>{u.label}</div>
<div style={{ fontSize: 12, color: COLORS.textDim }}>{u.desc}</div>
</div>
</div>
<div style={{ textAlign: "right", flexShrink: 0 }}>
<div style={{ fontSize: 16, fontWeight: 700, color: isOn ? COLORS.accent : COLORS.textMuted }}>{u.price}<span style={{ fontSize: 11, fontWeight: 400, color: COLORS.textDim }}>{u.period}</span></div>
<div style={{
fontSize: 11,
color: COLORS.accent,
background: COLORS.accentDim,
borderRadius: 4,
padding: "2px 8px",
marginTop: 4,
display: "inline-block",
fontWeight: 600,
}}>{u.boost}</div>
</div>
</div>
);
})}
</div>
{/* Summary sidebar */}
<div style={{
flex: "0 0 260px",
position: "sticky",
top: 20,
alignSelf: "flex-start",
}}>
<div style={{
background: COLORS.surface,
border: `1px solid ${COLORS.border}`,
borderRadius: 12,
padding: "22px 20px",
}}>
<div style={{ fontSize: 13, color: COLORS.textDim, textTransform: "uppercase", letterSpacing: 1, marginBottom: 16, fontWeight: 600 }}>Your Plan</div>
{upsells.filter(u => selected[u.id]).map(u => (
<div key={u.id} style={{
display: "flex",
justifyContent: "space-between",
fontSize: 13,
color: COLORS.text,
marginBottom: 8,
}}>
<span>{u.label}</span>
<span style={{ color: COLORS.textMuted }}>{u.price}</span>
</div>
))}
<div style={{
borderTop: `1px solid ${COLORS.border}`,
marginTop: 14,
paddingTop: 14,
}}>
<div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}>
<span style={{ fontSize: 14, fontWeight: 700, color: COLORS.text }}>Monthly</span>
<span style={{ fontSize: 18, fontWeight: 700, color: COLORS.accent }}>{monthlyTotal}/mo</span>
</div>
</div>
<div style={{
background: COLORS.accentDim,
borderRadius: 8,
padding: "12px 14px",
marginTop: 16,
textAlign: "center",
}}>
<div style={{ fontSize: 12, color: COLORS.textMuted, marginBottom: 2 }}>Estimated view boost</div>
<div style={{ fontSize: 22, fontWeight: 800, color: COLORS.accent }}>{viewMultiplier.toFixed(1)}×</div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>more visibility vs basic listing</div>
</div>
<button style={{
width: "100%",
background: `linear-gradient(135deg, ${COLORS.accent}, #1AAB8A)`,
color: COLORS.bg,
border: "none",
borderRadius: 8,
padding: "12px 0",
fontWeight: 700,
fontSize: 14,
cursor: "pointer",
marginTop: 16,
}}>Update Plan </button>
<div style={{ fontSize: 11, color: COLORS.textDim, textAlign: "center", marginTop: 10 }}>
Cancel anytime · No lock-in contracts
</div>
</div>
</div>
</div>
</div>
);
}
export default function SupplierDashboard() {
const [activeTab, setActiveTab] = useState("dashboard");
return (
<div style={{
fontFamily: "'DM Sans', 'Manrope', system-ui, sans-serif",
background: COLORS.bg,
color: COLORS.text,
minHeight: "100vh",
display: "flex",
}}>
{/* Sidebar */}
<div style={{
width: 220,
background: COLORS.surface,
borderRight: `1px solid ${COLORS.border}`,
padding: "20px 0",
display: "flex",
flexDirection: "column",
flexShrink: 0,
}}>
<div style={{
padding: "0 20px 20px",
borderBottom: `1px solid ${COLORS.border}`,
marginBottom: 14,
}}>
<div style={{
fontSize: 17,
fontWeight: 800,
color: COLORS.accent,
letterSpacing: -0.5,
}}> PadelConnect</div>
<div style={{ fontSize: 11, color: COLORS.textDim, marginTop: 2 }}>Supplier Portal</div>
</div>
<div style={{ flex: 1 }}>
{tabs.map(tab => (
<button key={tab.id} onClick={() => setActiveTab(tab.id)} style={{
width: "100%",
display: "flex",
alignItems: "center",
gap: 10,
padding: "10px 20px",
border: "none",
background: activeTab === tab.id ? COLORS.accentDim : "transparent",
color: activeTab === tab.id ? COLORS.accent : COLORS.textMuted,
fontSize: 13,
fontWeight: activeTab === tab.id ? 600 : 400,
cursor: "pointer",
textAlign: "left",
borderRight: activeTab === tab.id ? `2px solid ${COLORS.accent}` : "2px solid transparent",
}}>
<span style={{ fontSize: 16 }}>{tab.icon}</span>
{tab.label}
{tab.id === "leads" && (
<span style={{
marginLeft: "auto",
background: COLORS.hotDim,
color: COLORS.hot,
borderRadius: 10,
padding: "1px 7px",
fontSize: 11,
fontWeight: 700,
}}>3</span>
)}
</button>
))}
</div>
<div style={{
padding: "14px 20px",
borderTop: `1px solid ${COLORS.border}`,
}}>
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
<div style={{
width: 32,
height: 32,
borderRadius: 8,
background: `linear-gradient(135deg, ${COLORS.accent}, ${COLORS.blue})`,
display: "flex",
alignItems: "center",
justifyContent: "center",
fontSize: 13,
fontWeight: 700,
color: COLORS.bg,
}}>PB</div>
<div>
<div style={{ fontSize: 12, fontWeight: 600, color: COLORS.text }}>PadelBau GmbH</div>
<div style={{ fontSize: 11, color: COLORS.textDim }}>Premium Plan</div>
</div>
</div>
</div>
</div>
{/* Main content */}
<div style={{
flex: 1,
padding: "28px 32px",
overflowY: "auto",
maxHeight: "100vh",
}}>
{activeTab === "dashboard" && <DashboardView />}
{activeTab === "leads" && <LeadFeedView />}
{activeTab === "listing" && <ListingView />}
{activeTab === "boost" && <BoostView />}
</div>
</div>
);
}