Replace L.circleMarker with L.divIcon + .pn-marker CSS class (white border, box-shadow, hover scale) matching the beanflows growing conditions map pattern. Dark .map-tooltip CSS override (no arrow, dark navy background). Small venue dots use .pn-venue class. Add _require_maps_flag() to all 4 API endpoints (default=True so dev works without seeding the flag row). Gate /opportunity-map route the same way. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
4.6 KiB
HTML
117 lines
4.6 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}{{ t.markets_page_title }} - {{ config.APP_NAME }}{% endblock %}
|
|
|
|
{% block head %}
|
|
<meta name="description" content="{{ t.markets_page_description }}">
|
|
<meta property="og:title" content="{{ t.markets_page_og_title }} - {{ config.APP_NAME }}">
|
|
<meta property="og:description" content="{{ t.markets_page_og_description }}">
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='vendor/leaflet/leaflet.min.css') }}">
|
|
{% endblock %}
|
|
|
|
{% block content %}
|
|
<main class="container-page py-12">
|
|
<header class="mb-8">
|
|
<h1 class="text-3xl mb-2">{{ t.mkt_heading }}</h1>
|
|
<p class="text-slate">{{ t.mkt_subheading }}</p>
|
|
</header>
|
|
|
|
<div id="markets-map" style="height:420px; border-radius:12px;" class="mb-6"></div>
|
|
|
|
<!-- Filters -->
|
|
<div class="card mb-8">
|
|
<div style="display: grid; grid-template-columns: 1fr auto auto; gap: 1rem; align-items: end;">
|
|
<div>
|
|
<label class="form-label" for="market-q">{{ t.markets_search_label }}</label>
|
|
<input type="text" id="market-q" name="q" value="{{ current_q }}" placeholder="{{ t.mkt_search_placeholder }}"
|
|
class="form-input"
|
|
hx-get="{{ url_for('content.market_results') }}"
|
|
hx-target="#market-results"
|
|
hx-trigger="input changed delay:300ms"
|
|
hx-include="#market-country, #market-region">
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="market-country">{{ t.markets_country_label }}</label>
|
|
<select id="market-country" name="country" class="form-input"
|
|
hx-get="{{ url_for('content.market_results') }}"
|
|
hx-target="#market-results"
|
|
hx-trigger="change"
|
|
hx-include="#market-q, #market-region">
|
|
<option value="">{{ t.mkt_all_countries }}</option>
|
|
{% for c in countries %}
|
|
<option value="{{ c }}" {% if c == current_country %}selected{% endif %}>{{ c }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
<div>
|
|
<label class="form-label" for="market-region">Region</label>
|
|
<select id="market-region" name="region" class="form-input"
|
|
hx-get="{{ url_for('content.market_results') }}"
|
|
hx-target="#market-results"
|
|
hx-trigger="change"
|
|
hx-include="#market-q, #market-country">
|
|
<option value="">{{ t.mkt_all_regions }}</option>
|
|
{% for r in regions %}
|
|
<option value="{{ r }}" {% if r == current_region %}selected{% endif %}>{{ r }}</option>
|
|
{% endfor %}
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Results -->
|
|
<div id="market-results">
|
|
{% include "partials/market_results.html" %}
|
|
</div>
|
|
</main>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script src="{{ url_for('static', filename='vendor/leaflet/leaflet.min.js') }}"></script>
|
|
<script>
|
|
(function() {
|
|
var map = L.map('markets-map', {scrollWheelZoom: false}).setView([48.5, 10], 4);
|
|
L.tileLayer('https://{s}.basemaps.cartocdn.com/light_all/{z}/{x}/{y}{r}.png', {
|
|
attribution: '© <a href="https://www.openstreetmap.org/copyright">OSM</a> © <a href="https://carto.com/">CARTO</a>',
|
|
maxZoom: 18
|
|
}).addTo(map);
|
|
|
|
function scoreColor(score) {
|
|
if (score >= 60) return '#16A34A';
|
|
if (score >= 30) return '#D97706';
|
|
return '#DC2626';
|
|
}
|
|
|
|
function makeIcon(size, color) {
|
|
var s = Math.round(size);
|
|
return L.divIcon({
|
|
className: '',
|
|
html: '<div class="pn-marker" style="width:' + s + 'px;height:' + s + 'px;background:' + color + ';opacity:0.82;"></div>',
|
|
iconSize: [s, s],
|
|
iconAnchor: [s / 2, s / 2],
|
|
});
|
|
}
|
|
|
|
fetch('/api/markets/countries.json')
|
|
.then(function(r) { return r.json(); })
|
|
.then(function(data) {
|
|
if (!data.length) return;
|
|
var maxV = Math.max.apply(null, data.map(function(d) { return d.total_venues; }));
|
|
var lang = document.documentElement.lang || 'en';
|
|
data.forEach(function(c) {
|
|
if (!c.lat || !c.lon) return;
|
|
var size = 12 + 44 * Math.sqrt(c.total_venues / maxV);
|
|
var color = scoreColor(c.avg_market_score);
|
|
var tip = '<strong>' + c.country_name_en + '</strong><br>'
|
|
+ c.total_venues + ' venues · ' + c.city_count + ' cities<br>'
|
|
+ '<span style="color:' + color + ';font-weight:600;">Score ' + c.avg_market_score + '/100</span>';
|
|
L.marker([c.lat, c.lon], { icon: makeIcon(size, color) })
|
|
.bindTooltip(tip, { className: 'map-tooltip', direction: 'top', offset: [0, -Math.round(size / 2)] })
|
|
.on('click', function() { window.location = '/' + lang + '/markets/' + c.country_slug; })
|
|
.addTo(map);
|
|
});
|
|
});
|
|
})();
|
|
</script>
|
|
{% endblock %}
|