Ready to run the numbers for {{ city_name }}? →
Build your business plan
diff --git a/web/src/padelnomics/content/templates/city-pricing.md.jinja b/web/src/padelnomics/content/templates/city-pricing.md.jinja
index d89fa10..06edd91 100644
--- a/web/src/padelnomics/content/templates/city-pricing.md.jinja
+++ b/web/src/padelnomics/content/templates/city-pricing.md.jinja
@@ -55,7 +55,7 @@ Die Preisspanne von {{ hourly_rate_p25 | round(0) | int }} bis {{ hourly_rate_p7
## Wie steht {{ city_name }} im Vergleich da?
-{{ city_name }} hat {{ padel_venue_count }} Padelanlagen für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner ({{ venues_per_100k | round(1) }} Anlagen pro 100K Einwohner). {% if market_score >= 55 %}Mit einem
padelnomics Market Score von {{ market_score | round(1) }}/100 gehört {{ city_name }} zu den stärksten Padel-Märkten in {{ country_name_en }} — höhere Auslastung und Preise sind typisch für dichte, etablierte Märkte. {% elif market_score >= 35 %}Ein Market Score von {{ market_score | round(1) }}/100 steht für einen Markt im Aufbau: genug Angebot für marktgerechte Preise, aber Raum für neue Anlagen. {% else %}Ein Market Score von {{ market_score | round(1) }}/100 deutet auf einen Markt in der Frühphase hin, in dem sich Preise und Auslastung mit dem Wachstum des Sports noch deutlich entwickeln können. {% endif %}
+{{ city_name }} hat {{ padel_venue_count }} Padelanlagen für {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} Einwohner ({{ venues_per_100k | round(1) }} Anlagen pro 100K Einwohner). {% if opportunity_score >= 65 %}Mit einem
padelnomics Score von {{ opportunity_score | round(1) }}/100 zählt {{ city_name }} zu den vielversprechendsten Standorten in {{ country_name_en }}. {% elif opportunity_score >= 40 %}Ein
padelnomics Score von {{ opportunity_score | round(1) }}/100 steht für solides Investitionspotenzial: genug Markt für faire Preise, aber Raum für neue Anlagen. {% else %}Ein
padelnomics Score von {{ opportunity_score | round(1) }}/100 spricht für einen bereits gut versorgten Markt — Differenzierung über Qualität und Lage wird entscheidend. {% endif %}
Die Anlagendichte von {{ venues_per_100k | round(1) }} pro 100K Einwohner beeinflusst die Preisgestaltung direkt: {% if venues_per_100k >= 3.0 %}Höhere Dichte bedeutet mehr Wettbewerb, was die Preise eher stabilisiert oder senkt.{% elif venues_per_100k >= 1.0 %}Moderate Dichte ermöglicht marktgerechte Preise bei gleichzeitigem Wachstumsspielraum.{% else %}Niedrige Dichte gibt Betreibern mehr Preissetzungsmacht — vorausgesetzt, die Nachfrage ist da.{% endif %}
@@ -168,7 +168,7 @@ The P25–P75 price range of {{ hourly_rate_p25 | round(0) | int }} to {{ hourly
## How Does {{ city_name }} Compare?
-{{ city_name }} has {{ padel_venue_count }} padel venues for a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} ({{ venues_per_100k | round(1) }} venues per 100K residents). {% if market_score >= 55 %}With a
padelnomics Market Score of {{ market_score | round(1) }}/100, {{ city_name }} is one of the stronger padel markets in {{ country_name_en }} — higher occupancy and pricing typically follow dense, competitive markets. {% elif market_score >= 35 %}A market score of {{ market_score | round(1) }}/100 reflects a mid-tier market: enough supply to have competitive pricing, but room for new venues to grow. {% else %}A market score of {{ market_score | round(1) }}/100 indicates an early-stage market where pricing and occupancy benchmarks may shift as the sport grows. {% endif %}
+{{ city_name }} has {{ padel_venue_count }} padel venues for a population of {% if population >= 1000000 %}{{ (population / 1000000) | round(1) }}M{% else %}{{ (population / 1000) | round(0) | int }}K{% endif %} ({{ venues_per_100k | round(1) }} venues per 100K residents). {% if opportunity_score >= 65 %}With a
padelnomics Score of {{ opportunity_score | round(1) }}/100, {{ city_name }} is among the most promising investment locations in {{ country_name_en }}. {% elif opportunity_score >= 40 %}A
padelnomics Score of {{ opportunity_score | round(1) }}/100 reflects solid investment potential: enough market for competitive pricing, but room for new venues. {% else %}A
padelnomics Score of {{ opportunity_score | round(1) }}/100 indicates a well-served market — differentiation through quality and location becomes critical. {% endif %}
Venue density of {{ venues_per_100k | round(1) }} per 100K residents directly influences pricing: {% if venues_per_100k >= 3.0 %}higher density means more competition, which tends to stabilize or compress prices.{% elif venues_per_100k >= 1.0 %}moderate density supports market-rate pricing with room for growth.{% else %}low density gives operators more pricing power — provided demand exists.{% endif %}
diff --git a/web/src/padelnomics/content/templates/country-overview.md.jinja b/web/src/padelnomics/content/templates/country-overview.md.jinja
index d495189..0693789 100644
--- a/web/src/padelnomics/content/templates/country-overview.md.jinja
+++ b/web/src/padelnomics/content/templates/country-overview.md.jinja
@@ -7,7 +7,7 @@ natural_key: country_slug
languages: [en, de]
url_pattern: "/markets/{{ country_slug }}"
title_pattern: "{% if language == 'de' %}Padel in {{ country_name_en }} — Marktüberblick {{ 'now' | datetimeformat('%Y') }}{% else %}Padel in {{ country_name_en }} — Market Overview {{ 'now' | datetimeformat('%Y') }}{% endif %}"
-meta_description_pattern: "{% if language == 'de' %}{{ total_venues }} Padelanlagen in {{ city_count }} Städten in {{ country_name_en }}. padelnomics Market Score, Preisdaten und Investmentanalysen für jede Stadt.{% else %}{{ total_venues }} padel venues across {{ city_count }} cities in {{ country_name_en }}. padelnomics Market Score, pricing data, and investment analysis for each city.{% endif %}"
+meta_description_pattern: "{% if language == 'de' %}{{ total_venues }} Padelanlagen in {{ city_count }} Städten in {{ country_name_en }}. Padelnomics Score, Preisdaten und Investmentanalysen für jede Stadt.{% else %}{{ total_venues }} padel venues across {{ city_count }} cities in {{ country_name_en }}. Padelnomics Score, pricing data, and investment analysis for each city.{% endif %}"
schema_type: [Article, FAQPage]
priority_column: total_venues
---
@@ -25,15 +25,9 @@ priority_column: total_venues
{{ city_count }}
Median Spitzenpreis
{% if median_peak_rate %}{{ median_peak_rate | int }}{% else %}—{% endif %}{% if median_peak_rate and price_currency %}{{ price_currency }}/Std{% endif %}
@@ -42,19 +36,17 @@ priority_column: total_venues
-In {{ country_name_en }} erfassen wir aktuell **{{ total_venues }} Padelanlagen** in **{{ city_count }} Städten**. Der durchschnittliche
padelnomics Market Score liegt bei **{{ avg_market_score }}/100**{% if avg_market_score >= 55 %} — ein starker Markt mit breiter Infrastruktur und belastbaren Preisdaten{% elif avg_market_score >= 35 %} — ein wachsender Markt mit guter Abdeckung{% else %} — ein aufstrebender Markt, in dem Früheinsteiger noch Premiumstandorte sichern können{% endif %}.
+In {{ country_name_en }} erfassen wir aktuell **{{ total_venues }} Padelanlagen** in **{{ city_count }} Städten**. Der durchschnittliche
padelnomics Score liegt bei **{{ avg_opportunity_score }}/100** — {% if avg_opportunity_score >= 65 %}hohes Investitionspotenzial mit relevanten Versorgungslücken{% elif avg_opportunity_score >= 40 %}solides Potenzial, der Markt bietet noch Raum für neue Standorte{% else %}ein bereits gut versorgter Markt, der sorgfältige Standortwahl erfordert{% endif %}.
## Marktlandschaft
-Padel wächst in {{ country_name_en }} mit bemerkenswertem Tempo. Unsere Daten zeigen {{ total_venues }} erfasste Anlagen — eine Zahl, die angesichts nicht auf Buchungsplattformen gelisteter Clubs vermutlich noch höher liegt. Der durchschnittliche
padelnomics Market Score von {{ avg_market_score }}/100 über {{ city_count }} Städte spiegelt sowohl die Marktreife als auch die Datenverfügbarkeit wider.
+Padel wächst in {{ country_name_en }} mit bemerkenswertem Tempo. Unsere Daten zeigen {{ total_venues }} erfasste Anlagen — eine Zahl, die angesichts nicht auf Buchungsplattformen gelisteter Clubs vermutlich noch höher liegt. Der durchschnittliche
padelnomics Score von {{ avg_opportunity_score }}/100 über {{ city_count }} Städte bewertet das Investitionspotenzial anhand von Versorgungslücken, Einzugsgebiet, Marktreife und Sportaffinität.
-{% if avg_market_score >= 55 %}Märkte mit Scores über 55 weisen in der Regel eine etablierte Spielerbasis, belastbare Preisdaten und berechenbare Nachfragemuster auf — entscheidend für eine solide Finanzplanung. Dennoch bleiben viele Städte unterversorgt: Selbst in reifen Märkten variiert die Anlagendichte pro 100.000 Einwohner erheblich.{% elif avg_market_score >= 35 %}Ein Score im mittleren Bereich deutet auf eine Wachstumsphase hin: Die Nachfrage ist nachweisbar, die Anlageninfrastruktur baut sich auf, und Preise haben sich noch nicht vollständig auf Wettbewerbsniveau eingependelt. Das eröffnet Chancen für gut positionierte Neueintritte.{% else %}Aufstrebende Märkte bieten First-Mover-Vorteile — weniger direkte Konkurrenz, potenziell attraktivere Mietkonditionen und die Möglichkeit, eine loyale Spielerbasis aufzubauen, bevor sich der Markt verdichtet.{% endif %}
-
-{% if avg_opportunity_score %}Der durchschnittliche **
padelnomics Opportunity Score von {{ avg_opportunity_score }}/100** zeigt, wie viel Investitionspotenzial in {{ country_name_en }} noch unerschlossen ist. {% if avg_opportunity_score >= 60 and avg_market_score < 40 %}Die Kombination aus hohem Opportunity Score und moderatem Market Score macht {{ country_name_en }} besonders interessant: Nachfragepotenzial und Sportaffinität sind vorhanden, die Infrastruktur noch im Aufbau — First-Mover-Konditionen für gut gewählte Standorte.{% elif avg_opportunity_score >= 60 %}Trotz eines bereits aktiven Markts gibt es noch Standorte mit erheblichem Potenzial — vor allem in mittelgroßen Städten und an der Peripherie großer Ballungsräume.{% else %}Viele Standorte in {{ country_name_en }} sind bereits gut versorgt. Neue Projekte brauchen eine sorgfältige Standortanalyse und ein klares Differenzierungsprofil.{% endif %}{% endif %}
+{% if avg_opportunity_score >= 65 %}Ein Durchschnittsscore über 65 signalisiert relevante Versorgungslücken bei gleichzeitig vorhandener Nachfrage. Selbst in Regionen mit etablierter Padel-Infrastruktur variiert die Anlagendichte pro 100.000 Einwohner erheblich — lokale Analyse lohnt sich.{% elif avg_opportunity_score >= 40 %}Ein Score im mittleren Bereich deutet auf eine Wachstumsphase hin: Die Nachfrage ist nachweisbar, die Anlageninfrastruktur baut sich auf, und gut positionierte Standorte bieten noch Chancen für Neueintritte.{% else %}Viele Standorte in {{ country_name_en }} sind bereits gut versorgt. Neue Projekte brauchen eine sorgfältige Standortanalyse und ein klares Differenzierungsprofil.{% endif %}
## Top-Städte in {{ country_name_en }}
-Die Rangfolge basiert auf dem
padelnomics Market Score — einem Komposit aus Bevölkerungsgröße, Anlagendichte und Datenqualität. Städte mit höherem Score bieten in der Regel größere adressierbare Märkte und belastbarere Benchmarks für die Finanzplanung.
+Die Rangfolge basiert auf dem
padelnomics Score — einer Bewertung des Investitionspotenzials anhand von Versorgungslücken, Einzugsgebiet, Marktreife und Sportaffinität. Städte mit höherem Score bieten in der Regel die besten Standortbedingungen für neue Anlagen.
| Stadt | Marktanalyse |
|-------|-------------|
@@ -63,17 +55,6 @@ Die Rangfolge basiert auf dem
padelnomics Market Score ist **{{ top_city_names[0] }}** (Score: {{ top_city_market_score }}/100). Der Score kombiniert Bevölkerungsgröße, Anlagendichte und Datenqualität. Eine hohe Punktzahl deutet auf einen großen adressierbaren Markt mit validierten Preisdaten hin. Die beste Stadt für *Dein* Vorhaben hängt aber von Faktoren wie Flächenverfügbarkeit, lokalem Wettbewerb und Deiner Zielgruppe ab. Nutze den
Finanzplaner, um verschiedene Standorte durchzurechnen.
+Unsere Spitzenstadt nach
padelnomics Score ist **{{ top_city_names[0] }}**. Der Score bewertet Investitionspotenzial anhand von Versorgungslücken, Einzugsgebiet, Marktreife und Sportaffinität. Die beste Stadt für *Dein* Vorhaben hängt aber von Faktoren wie Flächenverfügbarkeit, lokalem Wettbewerb und Deiner Zielgruppe ab. Nutze den
Finanzplaner, um verschiedene Standorte durchzurechnen.
@@ -124,23 +105,15 @@ Unsere Spitzenstadt nach padelnomics Market Score (wie {{ top_city_names[0] }}) haben in der Regel die umfassendsten Preisdaten, weil dort mehr Anlagen auf Playtomic gelistet sind. In unserem {{ country_name_en }}-Marktüberblick findest Du alle Städte nach Market Score sortiert.
+Städte mit höherem padelnomics Score (wie {{ top_city_names[0] }}) haben in der Regel die umfassendsten Preisdaten, weil dort mehr Anlagen auf Playtomic gelistet sind. In unserem {{ country_name_en }}-Marktüberblick findest Du alle Städte nach Score sortiert.
-{% if avg_opportunity_score %}
-
-Was ist der Unterschied zwischen Market Score und Opportunity Score?
-
-Der **Market Score (Ø {{ avg_market_score }}/100)** bewertet die Marktreife: Bevölkerungsgröße, bestehende Anlagendichte und Datenqualität. Der **Opportunity Score (Ø {{ avg_opportunity_score }}/100)** dreht die Logik um: Er misst Investitionspotenzial — Versorgungslücken, Einzugsgebiet und Tennisinfrastruktur als Proxy für Racket-Sport-Affinität. Für Standortentscheidungen ist die Kombination beider Scores am aussagekräftigsten: Hoher Opportunity Score bei niedrigem Market Score signalisiert White-Space-Chancen. Hoher Wert in beiden zeigt Märkte, in denen Nachfrage belegt ist und trotzdem noch offene Standorte existieren.
-
-{% endif %}
-
Du überlegst, eine Padelhalle in {{ country_name_en }} zu eröffnen? Rechne Dein Vorhaben mit echten Marktdaten durch →
Zum Finanzplaner
@@ -159,15 +132,9 @@ Der **Market Score (Ø {{ avg_market_score }}/100)** bewertet die Marktreife: Be
{{ city_count }}
-
padelnomics Market Score
-
{{ avg_market_score }}/100
-
- {% if avg_opportunity_score %}
-
-
padelnomics Opportunity Score
+
padelnomics Score
{{ avg_opportunity_score }}/100
- {% endif %}
Median Peak Rate
{% if median_peak_rate %}{{ median_peak_rate | int }}{% else %}—{% endif %}{% if median_peak_rate and price_currency %}{{ price_currency }}/hr{% endif %}
@@ -176,19 +143,17 @@ Der **Market Score (Ø {{ avg_market_score }}/100)** bewertet die Marktreife: Be
-{{ country_name_en }} has **{{ total_venues }} padel venues** tracked across **{{ city_count }} cities**. The average
padelnomics Market Score across tracked cities is **{{ avg_market_score }}/100**{% if avg_market_score >= 55 %} — a strong market with widespread venue penetration and solid pricing data{% elif avg_market_score >= 35 %} — a growing market with healthy city coverage{% else %} — an emerging market where early entrants can still capture prime locations{% endif %}.
+{{ country_name_en }} has **{{ total_venues }} padel venues** tracked across **{{ city_count }} cities**. The average
padelnomics Score is **{{ avg_opportunity_score }}/100** — {% if avg_opportunity_score >= 65 %}strong investment potential with meaningful supply gaps{% elif avg_opportunity_score >= 40 %}solid potential with room for new locations{% else %}a well-served market requiring careful site selection{% endif %}.
## Market Landscape
-Padel is growing rapidly across {{ country_name_en }}. Our data tracks {{ total_venues }} venues — a figure that likely understates the true count given independent clubs not listed on booking platforms. The average
padelnomics Market Score of {{ avg_market_score }}/100 across {{ city_count }} cities reflects both market maturity and data availability.
+Padel is growing rapidly across {{ country_name_en }}. Our data tracks {{ total_venues }} venues — a figure that likely understates the true count given independent clubs not listed on booking platforms. The average
padelnomics Score of {{ avg_opportunity_score }}/100 across {{ city_count }} cities evaluates investment potential based on supply gaps, catchment reach, market maturity, and sports culture.
-{% if avg_market_score >= 55 %}Markets scoring above 55 typically show an established player base, reliable pricing data, and predictable demand patterns — all critical for sound financial planning. Yet even in mature markets, venue density per 100,000 residents varies significantly between cities, leaving genuine supply gaps even in established markets.{% elif avg_market_score >= 35 %}A mid-range score signals a growth phase: demand is proven, venue infrastructure is building, and pricing hasn't fully settled to competitive levels. This creates opportunities for well-positioned new entrants who can secure good locations before the market matures.{% else %}Emerging markets offer first-mover advantages — less direct competition, potentially better lease terms, and the opportunity to build a loyal player base before the market fills out. The trade-off is less pricing data and more uncertainty in demand projections.{% endif %}
-
-{% if avg_opportunity_score %}The average **
padelnomics Opportunity Score of {{ avg_opportunity_score }}/100** shows how much investment potential remains untapped in {{ country_name_en }}. {% if avg_opportunity_score >= 60 and avg_market_score < 40 %}The combination of a high Opportunity Score and a moderate Market Score makes {{ country_name_en }} particularly attractive for new entrants: demand potential and sports culture are there, infrastructure is still building — first-mover conditions for well-chosen locations.{% elif avg_opportunity_score >= 60 %}Despite an already active market, locations with significant potential remain — particularly in mid-size cities and at the periphery of major metro areas.{% else %}Many locations in {{ country_name_en }} are already well-served. New projects need careful site selection and a clear differentiation strategy to compete.{% endif %}{% endif %}
+{% if avg_opportunity_score >= 65 %}A score above 65 signals meaningful supply gaps alongside existing demand. Even in regions with established padel infrastructure, venue density per 100,000 residents varies significantly between cities — local analysis pays off.{% elif avg_opportunity_score >= 40 %}A mid-range score signals a growth phase: demand is proven, venue infrastructure is building, and well-positioned locations still offer opportunities for new entrants.{% else %}Many locations in {{ country_name_en }} are already well-served. New projects need careful site selection and a clear differentiation strategy to compete.{% endif %}
## Top Cities in {{ country_name_en }}
-Cities are ranked by
padelnomics Market Score — a composite of population size, venue density, and data quality. Higher-scoring cities generally offer larger addressable markets and more reliable benchmarks for financial planning.
+Cities are ranked by
padelnomics Score — evaluating investment potential based on supply gaps, catchment reach, market maturity, and sports culture. Higher-scoring cities generally offer the strongest conditions for new facilities.
| City | Market Analysis |
|------|----------------|
@@ -197,17 +162,6 @@ Cities are ranked by
padelnomics Market Score is **{{ top_city_names[0] }}** (score: {{ top_city_market_score }}/100). The
padelnomics Market Score combines population size, existing venue density, and data quality — a high score indicates a large addressable market with validated pricing data. However, the best city for *you* depends on land availability, local competition, and your target customer profile. Use the
financial planner to model different locations side by side.
+Our top-ranked city by
padelnomics Score is **{{ top_city_names[0] }}**. The score evaluates investment potential based on supply gaps, catchment reach, market maturity, and sports culture. However, the best city for *you* depends on land availability, local competition, and your target customer profile. Use the
financial planner to model different locations side by side.
@@ -258,23 +212,15 @@ Our top-ranked city by padelnomics Market Scores (like {{ top_city_names[0] }}) typically have the most comprehensive pricing data, because more venues are listed on Playtomic. Browse our {{ country_name_en }} market overview to see all cities ranked by padelnomics Market Score.
+Cities with higher padelnomics Scores (like {{ top_city_names[0] }}) typically have the most comprehensive pricing data, because more venues are listed on Playtomic. Browse our {{ country_name_en }} market overview to see all cities ranked by score.
-{% if avg_opportunity_score %}
-
-What is the difference between Market Score and Opportunity Score?
-
-The **Market Score (avg. {{ avg_market_score }}/100)** measures market maturity: population size, existing venue density, and data quality. The **Opportunity Score (avg. {{ avg_opportunity_score }}/100)** inverts that logic: it scores investment potential based on supply gaps, catchment reach, and tennis infrastructure as a proxy for racket sport demand. For site selection, the combination of both scores is the most informative signal. A high Opportunity Score with a low Market Score points to white-space locations. High on both means proven demand with open sites still available.
-
-{% endif %}
-
Considering a padel center in {{ country_name_en }}? Model your investment with real market data →
Open the Planner
diff --git a/web/src/padelnomics/content/templates/markets.html b/web/src/padelnomics/content/templates/markets.html
index b62a09b..f140a02 100644
--- a/web/src/padelnomics/content/templates/markets.html
+++ b/web/src/padelnomics/content/templates/markets.html
@@ -20,10 +20,6 @@
-
- ◉ inner = Market Score
- ○ ring = Opportunity Score
-
≥80
≥60
@@ -90,20 +86,16 @@
-{% endblock %}
-
-{% block content %}
-
-
-
-
-
-
- padelnomics
- Market Score
-
- {{ t.mscore_subtitle }}
-
-
-
-
- {{ t.mscore_dual_h2 }}
- {{ t.mscore_dual_intro }}
-
-
-
{{ t.mscore_reife_chip }}
-
{{ t.mscore_reife_question }}
-
{{ t.mscore_reife_desc }}
-
-
-
{{ t.mscore_potenzial_chip }}
-
{{ t.mscore_potenzial_question }}
-
{{ t.mscore_potenzial_desc }}
-
-
-
-
-
-
- padelnomics {{ t.mscore_what_h2 }}
- {{ t.mscore_what_intro }}
-
-
-
-
👥
-
{{ t.mscore_cat_demo_h3 }}
-
{{ t.mscore_cat_demo_p }}
-
-
-
💶
-
{{ t.mscore_cat_econ_h3 }}
-
{{ t.mscore_cat_econ_p }}
-
-
-
📈
-
{{ t.mscore_cat_demand_h3 }}
-
{{ t.mscore_cat_demand_p }}
-
-
-
🔍
-
{{ t.mscore_cat_data_h3 }}
-
{{ t.mscore_cat_data_p }}
-
-
-
-
-
-
- padelnomics {{ t.mscore_read_h2 }}
-
-
-
-
- {{ t.mscore_band_high_label }}
-
-
{{ t.mscore_band_high_p }}
-
-
-
-
- {{ t.mscore_band_mid_label }}
-
-
{{ t.mscore_band_mid_p }}
-
-
-
-
- {{ t.mscore_band_low_label }}
-
-
{{ t.mscore_band_low_p }}
-
-
- {{ t.mscore_read_note }}
-
-
-
-
- padelnomics {{ t.mscore_pot_what_h2 }}
- {{ t.mscore_pot_what_intro }}
-
-
-
-
📊
-
{{ t.mscore_pot_cat_market_h3 }}
-
{{ t.mscore_pot_cat_market_p }}
-
-
-
💶
-
{{ t.mscore_pot_cat_econ_h3 }}
-
{{ t.mscore_pot_cat_econ_p }}
-
-
-
🎯
-
{{ t.mscore_pot_cat_gap_h3 }}
-
{{ t.mscore_pot_cat_gap_p }}
-
-
-
📍
-
{{ t.mscore_pot_cat_catchment_h3 }}
-
{{ t.mscore_pot_cat_catchment_p }}
-
-
-
🎾
-
{{ t.mscore_pot_cat_tennis_h3 }}
-
{{ t.mscore_pot_cat_tennis_p }}
-
-
-
-
-
-
- padelnomics {{ t.mscore_pot_read_h2 }}
-
-
-
-
- {{ t.mscore_pot_band_high_label }}
-
-
{{ t.mscore_pot_band_high_p }}
-
-
-
-
- {{ t.mscore_pot_band_mid_label }}
-
-
{{ t.mscore_pot_band_mid_p }}
-
-
-
-
- {{ t.mscore_pot_band_low_label }}
-
-
{{ t.mscore_pot_band_low_p }}
-
-
-
-
-
-
- {{ t.mscore_sources_h2 }}
- {{ t.mscore_sources_p }}
-
-
-
-
- {{ t.mscore_limits_h2 }}
-
-
{{ t.mscore_limits_p1 }}
-
{{ t.mscore_limits_p2 }}
-
{{ t.mscore_limits_p3 }}
-
-
-
-
-
-
-
-
- {{ t.mscore_faq_h2 }}
-
- {% for i in range(1, 8) %}
-
- {{ t['mscore_faq_q' ~ i] }}
- {{ t['mscore_faq_a' ~ i] }}
-
- {% endfor %}
-
-
-
-
-
-{% endblock %}
diff --git a/web/src/padelnomics/public/templates/opportunity_map.html b/web/src/padelnomics/public/templates/opportunity_map.html
index 0fbe294..8801036 100644
--- a/web/src/padelnomics/public/templates/opportunity_map.html
+++ b/web/src/padelnomics/public/templates/opportunity_map.html
@@ -39,7 +39,6 @@
-
◉ inner = Opportunity Score ○ ring = Market Score
≥80
≥60
@@ -56,8 +55,10 @@
+{% endblock %}
+
+{% block content %}
+
+
+
+
+
+
+ padelnomics
+ Score
+
+ {{ t.pnscore_subtitle }}
+
+
+
+
+ {{ t.pnscore_what_h2 }}
+ {{ t.pnscore_what_intro }}
+
+
+
+
+ {{ t.pnscore_components_h2 }}
+ {{ t.pnscore_components_intro }}
+
+
+
+
👥
+
{{ t.pnscore_cat_market_h3 }}
+
{{ t.pnscore_cat_market_p }}
+
+
+
💶
+
{{ t.pnscore_cat_econ_h3 }}
+
{{ t.pnscore_cat_econ_p }}
+
+
+
🎯
+
{{ t.pnscore_cat_gap_h3 }}
+
{{ t.pnscore_cat_gap_p }}
+
+
+
🎾
+
{{ t.pnscore_cat_sports_h3 }}
+
{{ t.pnscore_cat_sports_p }}
+
+
+
📍
+
{{ t.pnscore_cat_catchment_h3 }}
+
{{ t.pnscore_cat_catchment_p }}
+
+
+
📊
+
{{ t.pnscore_cat_maturity_h3 }}
+
{{ t.pnscore_cat_maturity_p }}
+
+
+
+
+
+
+ {{ t.pnscore_read_h2 }}
+
+
+
+
+ {{ t.pnscore_band_high_label }}
+
+
{{ t.pnscore_band_high_p }}
+
+
+
+
+ {{ t.pnscore_band_good_label }}
+
+
{{ t.pnscore_band_good_p }}
+
+
+
+
+ {{ t.pnscore_band_mid_label }}
+
+
{{ t.pnscore_band_mid_p }}
+
+
+
+
+ {{ t.pnscore_band_low_label }}
+
+
{{ t.pnscore_band_low_p }}
+
+
+
+
+
+
+ {{ t.pnscore_sources_h2 }}
+ {{ t.pnscore_sources_p }}
+
+
+
+
+ {{ t.pnscore_limits_h2 }}
+
+
{{ t.pnscore_limits_p1 }}
+
{{ t.pnscore_limits_p2 }}
+
+
+
+
+
+
+
+
+ {{ t.pnscore_faq_h2 }}
+
+ {% for i in range(1, 6) %}
+
+ {{ t['pnscore_faq_q' ~ i] }}
+ {{ t['pnscore_faq_a' ~ i] }}
+
+ {% endfor %}
+
+
+
+
+
+{% endblock %}
diff --git a/web/src/padelnomics/static/css/input.css b/web/src/padelnomics/static/css/input.css
index c09932d..d50c017 100644
--- a/web/src/padelnomics/static/css/input.css
+++ b/web/src/padelnomics/static/css/input.css
@@ -879,10 +879,9 @@
.leaflet-tooltip.map-tooltip::before { display: none; }
.leaflet-tooltip.map-tooltip strong { color: white; }
-/* ── Dual-ring map markers ── */
-/* Container: sets size, white border, shadow, hover */
+/* ── Single-color map markers ── */
+/* Circle: score-colored background, white border, shadow, hover */
.pn-marker {
- position: relative;
border-radius: 50%;
border: 2.5px solid white;
box-shadow: 0 2px 8px rgba(0,0,0,0.28);
@@ -893,25 +892,6 @@
box-shadow: 0 3px 12px rgba(0,0,0,0.38);
transform: scale(1.1);
}
-/* Outer ring: secondary score color */
-.pn-marker__ring {
- position: absolute;
- inset: 0;
- border-radius: 50%;
- opacity: 0.65;
-}
-/* Inner core: primary score color, inset 5px from container edges */
-.pn-marker__core {
- position: absolute;
- inset: 5px;
- border-radius: 50%;
- border: 1.5px solid rgba(255,255,255,0.5);
-}
-/* Compact fallback: marker < 18px, no ring — core fills container */
-.pn-marker--compact .pn-marker__core {
- inset: 0;
- border: none;
-}
/* User's city highlight — blue outer glow */
.pn-marker--highlight {
@@ -919,29 +899,24 @@
box-shadow: 0 0 0 3px rgba(59,130,246,0.25), 0 2px 8px rgba(0,0,0,0.28);
}
-/* Non-article city markers: faded, dashed ring outline, no click */
+/* Non-article city markers: faded, no click */
.pn-marker--muted {
opacity: 0.4;
cursor: default;
filter: saturate(0.6) brightness(1.1);
}
-.pn-marker--muted .pn-marker__ring {
- background: transparent !important;
- border: 1.5px dashed rgba(255,255,255,0.6);
- opacity: 1;
-}
.pn-marker--muted:hover {
transform: none;
box-shadow: 0 2px 8px rgba(0,0,0,0.28);
}
-/* Pulse animation — opportunity map only, score >= 75 */
-.pn-marker--pulse .pn-marker__ring {
+/* Pulse animation — high-score locations */
+.pn-marker--pulse {
animation: marker-pulse 2.5s ease-in-out infinite;
}
@keyframes marker-pulse {
- 0%, 100% { transform: scale(1); opacity: 0.65; }
- 50% { transform: scale(1.35); opacity: 0.25; }
+ 0%, 100% { transform: scale(1); opacity: 1; }
+ 50% { transform: scale(1.2); opacity: 0.7; }
}
/* Small fixed venue dot */
diff --git a/web/src/padelnomics/static/js/article-maps.js b/web/src/padelnomics/static/js/article-maps.js
index a767c70..01200c8 100644
--- a/web/src/padelnomics/static/js/article-maps.js
+++ b/web/src/padelnomics/static/js/article-maps.js
@@ -15,12 +15,12 @@
var TILES = 'https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png';
var TILES_ATTR = '© OSM © CARTO';
var sc = PNMarkers.scoreColor;
+ var T = window.__MAP_T || {};
- function tooltipDot(hex, filled) {
- var style = filled
- ? 'display:inline-block;width:8px;height:8px;border-radius:50%;background:' + hex + ';vertical-align:middle;margin-right:4px;'
- : 'display:inline-block;width:8px;height:8px;border-radius:50%;border:2px solid ' + hex + ';vertical-align:middle;margin-right:4px;';
- return '';
+ function fmtPop(p) {
+ return p >= 1000000 ? (p / 1000000).toFixed(1) + 'M'
+ : p >= 1000 ? Math.round(p / 1000) + 'K'
+ : (p || '');
}
function initCountryMap(el) {
@@ -38,26 +38,23 @@
if (!c.lat || !c.lon) return;
var size = 10 + 36 * Math.sqrt((c.padel_venue_count || 1) / maxV);
var hasArticle = c.has_article !== false;
- var coreHex = sc(c.market_score);
- var ringHex = sc(c.opportunity_score || 0);
- var pop = c.population >= 1000000
- ? (c.population / 1000000).toFixed(1) + 'M'
- : (c.population >= 1000 ? Math.round(c.population / 1000) + 'K' : (c.population || ''));
+ var score = c.opportunity_score || 0;
+ var hex = sc(score);
+ var pop = fmtPop(c.population);
var tip = '' + c.city_name + '
'
- + tooltipDot(coreHex, true) + 'Market Score: ' + Math.round(c.market_score) + '/100
'
- + tooltipDot(ringHex, false) + 'Opportunity Score: ' + Math.round(c.opportunity_score || 0) + '/100
'
+ + ''
+ + '' + (T.score_label || 'Padelnomics Score') + ': ' + Math.round(score) + '/100
'
+ ''
- + (c.padel_venue_count || 0) + ' venues'
- + (pop ? ' · ' + pop : '') + '';
+ + (c.padel_venue_count || 0) + ' ' + (T.venues || 'venues')
+ + (pop ? ' · ' + pop + ' ' + (T.pop || 'pop') : '') + '';
if (hasArticle) {
- tip += '
Click to explore →';
+ tip += '
' + (T.click_explore || 'Click to explore →') + '';
} else {
- tip += '
Coming soon';
+ tip += '
' + (T.coming_soon || 'Coming soon') + '';
}
var icon = PNMarkers.makeIcon({
size: size,
- coreColor: coreHex,
- ringColor: ringHex,
+ color: hex,
muted: !hasArticle,
});
var marker = L.marker([c.lat, c.lon], { icon: icon })
@@ -80,8 +77,7 @@
var hSize = 10 + 36 * Math.sqrt((match.padel_venue_count || 1) / maxV);
var hIcon = PNMarkers.makeIcon({
size: hSize,
- coreColor: sc(match.market_score),
- ringColor: sc(match.opportunity_score || 0),
+ color: sc(match.opportunity_score || 0),
highlight: true,
});
L.marker([match.lat, match.lon], { icon: hIcon }).addTo(map);
@@ -107,9 +103,9 @@
var outdoor = v.outdoor_court_count || 0;
var total = v.court_count || (indoor + outdoor);
var courtLine = total
- ? total + ' court' + (total > 1 ? 's' : '')
+ ? total + ' ' + (T.courts || 'court' + (total > 1 ? 's' : ''))
+ (indoor || outdoor
- ? ' (' + [indoor ? indoor + ' indoor' : '', outdoor ? outdoor + ' outdoor' : ''].filter(Boolean).join(', ') + ')'
+ ? ' (' + [indoor ? indoor + ' ' + (T.indoor || 'indoor') : '', outdoor ? outdoor + ' ' + (T.outdoor || 'outdoor') : ''].filter(Boolean).join(', ') + ')'
: '')
: '';
var tip = '
' + v.name + '' + (courtLine ? '
' + courtLine : '');
diff --git a/web/src/padelnomics/static/js/map-markers.js b/web/src/padelnomics/static/js/map-markers.js
index e0e3996..59ffbc8 100644
--- a/web/src/padelnomics/static/js/map-markers.js
+++ b/web/src/padelnomics/static/js/map-markers.js
@@ -1,18 +1,17 @@
/**
- * Shared map marker utilities — dual-ring design with 5-tier color scale.
+ * Shared map marker utilities — single-color markers with 5-tier color scale.
*
* Exposes window.PNMarkers = { scoreColor, makeIcon }
*
* scoreColor(score) → hex color string (5 tiers, colorblind-safe luminance steps)
- * makeIcon(opts) → L.divIcon with dual-ring HTML
+ * makeIcon(opts) → L.divIcon with single-color circle
*
* opts = {
* size: number, // marker diameter in px
- * coreColor: string, // hex for inner core (primary score)
- * ringColor: string, // hex for outer ring (secondary score)
- * muted: boolean, // dashed ring, no click affordance
+ * color: string, // hex color (from scoreColor)
+ * muted: boolean, // faded, no click affordance
* highlight: boolean, // blue outer glow (user's geo city)
- * pulse: boolean, // gentle ring pulse (high opportunity)
+ * pulse: boolean, // gentle pulse (high score)
* }
*/
(function() {
@@ -27,30 +26,15 @@
return '#DC2626'; // red — poor
}
- var COMPACT_THRESHOLD_PX = 18;
-
function makeIcon(opts) {
var s = Math.round(opts.size);
- var compact = s < COMPACT_THRESHOLD_PX;
var cls = 'pn-marker';
- if (compact) cls += ' pn-marker--compact';
if (opts.muted) cls += ' pn-marker--muted';
if (opts.highlight) cls += ' pn-marker--highlight';
if (opts.pulse && !opts.muted) cls += ' pn-marker--pulse';
- var html;
- if (compact || !opts.ringColor) {
- // Single-layer fallback: core fills entire marker
- html = '
';
- } else {
- html = '
';
- }
+ var html = '
';
return L.divIcon({
className: '',
diff --git a/web/src/padelnomics/templates/base.html b/web/src/padelnomics/templates/base.html
index 144f455..3b8b542 100644
--- a/web/src/padelnomics/templates/base.html
+++ b/web/src/padelnomics/templates/base.html
@@ -172,7 +172,7 @@
{{ t.nav_planner }}
{{ t.nav_directory }}
{{ t.nav_markets }}
-
{{ t.footer_market_score }}
+
{{ t.footer_padelnomics_score }}
{{ t.nav_suppliers }}
diff --git a/web/src/padelnomics/templates/businessplan/plan.html b/web/src/padelnomics/templates/businessplan/plan.html
index f2bfff5..d85ffa6 100644
--- a/web/src/padelnomics/templates/businessplan/plan.html
+++ b/web/src/padelnomics/templates/businessplan/plan.html
@@ -219,10 +219,10 @@
{{ s.labels.median_offpeak_rate }}
{% endif %}
- {% if s.market_data.market_score %}
+ {% if s.market_data.opportunity_score %}
-
{{ "%.0f"|format(s.market_data.market_score) }}/100
-
{{ s.labels.market_score }}
+
{{ "%.0f"|format(s.market_data.opportunity_score) }}/100
+
{{ s.labels.padelnomics_score }}
{% endif %}
diff --git a/web/tests/test_i18n_parity.py b/web/tests/test_i18n_parity.py
index ba844cf..9b866cf 100644
--- a/web/tests/test_i18n_parity.py
+++ b/web/tests/test_i18n_parity.py
@@ -57,10 +57,10 @@ _IDENTICAL_VALUE_ALLOWLIST = {
# Business plan — Indoor/Outdoor same in DE, financial abbreviations
"bp_indoor", "bp_outdoor",
"bp_lbl_ebitda", "bp_lbl_irr", "bp_lbl_moic", "bp_lbl_opex",
- # Market Score — branded term kept in English in DE
- "footer_market_score",
- # Market Score chip labels — branded product names, same in DE
- "mscore_reife_chip", "mscore_potenzial_chip",
+ # Padelnomics Score — branded term kept in English in DE
+ "footer_padelnomics_score", "bp_lbl_padelnomics_score",
+ # Map tooltip keys — some are identical in both languages
+ "map_score_label", "map_indoor", "map_outdoor",
# Brand name "Padelnomics" — same in DE
"landing_vs_col_us",
}
diff --git a/web/tests/test_market_score.py b/web/tests/test_padelnomics_score.py
similarity index 54%
rename from web/tests/test_market_score.py
rename to web/tests/test_padelnomics_score.py
index 2adf335..3642323 100644
--- a/web/tests/test_market_score.py
+++ b/web/tests/test_padelnomics_score.py
@@ -1,30 +1,36 @@
-"""Tests for the Market Score methodology page."""
+"""Tests for the Padelnomics Score methodology page."""
async def test_en_returns_200(client):
- resp = await client.get("/en/market-score")
+ resp = await client.get("/en/padelnomics-score")
assert resp.status_code == 200
text = await resp.get_data(as_text=True)
- assert "Market Score" in text
+ assert "Padelnomics Score" in text
assert "padelnomics" in text
async def test_de_returns_200(client):
- resp = await client.get("/de/market-score")
+ resp = await client.get("/de/padelnomics-score")
assert resp.status_code == 200
text = await resp.get_data(as_text=True)
- assert "Market Score" in text
+ assert "Padelnomics Score" in text
assert "padelnomics" in text
-async def test_legacy_redirect(client):
- resp = await client.get("/market-score")
+async def test_old_market_score_redirects(client):
+ resp = await client.get("/en/market-score")
assert resp.status_code == 301
- assert resp.headers["Location"].endswith("/en/market-score")
+ assert "/padelnomics-score" in resp.headers["Location"]
+
+
+async def test_de_old_market_score_redirects(client):
+ resp = await client.get("/de/market-score")
+ assert resp.status_code == 301
+ assert "/padelnomics-score" in resp.headers["Location"]
async def test_contains_jsonld(client):
- resp = await client.get("/en/market-score")
+ resp = await client.get("/en/padelnomics-score")
text = await resp.get_data(as_text=True)
assert '"@type": "WebPage"' in text
assert '"@type": "FAQPage"' in text
@@ -32,28 +38,27 @@ async def test_contains_jsonld(client):
async def test_contains_faq_section(client):
- resp = await client.get("/en/market-score")
+ resp = await client.get("/en/padelnomics-score")
text = await resp.get_data(as_text=True)
assert "Frequently Asked Questions" in text
assert "