298 lines
15 KiB
HTML
298 lines
15 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="{{ lang }}">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<meta name="google-site-verification" content="tUwq9NLbumk01h3sNzpCPWNn_-LLIWaKQOaIVUn4JaU">
|
|
<title>{% block title %}{{ config.APP_NAME }}{% endblock %}</title>
|
|
|
|
<!-- Favicon (inline SVG data URI — no caching issues) -->
|
|
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 32 32'><rect width='32' height='32' rx='6' fill='%230F172A'/><text x='16' y='24' text-anchor='middle' font-family='system-ui' font-weight='800' font-size='23' fill='white'>p</text></svg>">
|
|
<link rel="apple-touch-icon" href="{{ url_for('static', filename='images/apple-touch-icon.png') }}?v=4">
|
|
|
|
<!-- Fonts (self-hosted, @font-face in output.css) -->
|
|
|
|
<!-- Tailwind (compiled) -->
|
|
<link rel="stylesheet" href="{{ url_for('static', filename='css/output.css') }}?v={{ v }}">
|
|
|
|
<!-- Umami Analytics (self-hosted script; re-download from umami.padelnomics.io/Z.js on Umami upgrade) -->
|
|
<script defer src="{{ url_for('static', filename='js/Z.js') }}?v={{ v }}" data-website-id="4474414b-58d6-4c6e-89a1-df5ea1f49d70" data-host-url="https://umami.padelnomics.io"{% if ab_tag %} data-tag="{{ ab_tag }}"{% endif %}></script>
|
|
|
|
<!-- Microsoft Clarity (consent-gated) -->
|
|
{% if config.CLARITY_PROJECT_ID %}
|
|
<script type="text/javascript">
|
|
(function(){
|
|
if (!/cookie_consent=[^;]*functional/.test(document.cookie)) return;
|
|
(function(c,l,a,r,i,t,y){
|
|
c[a]=c[a]||function(){(c[a].q=c[a].q||[]).push(arguments)};
|
|
t=l.createElement(r);t.async=1;t.src="https://www.clarity.ms/tag/"+i;
|
|
y=l.getElementsByTagName(r)[0];y.parentNode.insertBefore(t,y);
|
|
})(window, document, "clarity", "script", "{{ config.CLARITY_PROJECT_ID }}");
|
|
})();
|
|
</script>
|
|
{% endif %}
|
|
|
|
<!-- Paddle.js (only on checkout pages via block override) -->
|
|
{% block paddle %}{% endblock %}
|
|
|
|
<!-- SEO defaults (child templates may override via block head) -->
|
|
<link rel="canonical" href="{{ config.BASE_URL }}{{ request.path }}">
|
|
{% if request.path.startswith('/en/') or request.path.startswith('/de/') %}
|
|
{% set path_suffix = request.path[3:] %}
|
|
<link rel="alternate" hreflang="en" href="{{ config.BASE_URL }}/en{{ path_suffix }}">
|
|
<link rel="alternate" hreflang="de" href="{{ config.BASE_URL }}/de{{ path_suffix }}">
|
|
<link rel="alternate" hreflang="x-default" href="{{ config.BASE_URL }}/en{{ path_suffix }}">
|
|
{% endif %}
|
|
<meta name="twitter:card" content="summary_large_image">
|
|
|
|
<script>window.__GEO = {country: "{{ user_country }}", city: "{{ user_city }}"};</script>
|
|
{% block head %}
|
|
<meta property="og:title" content="{{ config.APP_NAME }}">
|
|
<meta property="og:description" content="">
|
|
<meta property="og:type" content="website">
|
|
<meta property="og:url" content="{{ config.BASE_URL }}{{ request.path }}">
|
|
<meta property="og:image" content="{{ url_for('static', filename='images/logo.png', _external=True) }}">
|
|
{% endblock %}
|
|
</head>
|
|
<body>
|
|
<nav class="nav-bar" id="main-nav">
|
|
<div class="nav-inner">
|
|
<!-- Hamburger (mobile only, left side) — outside .nav-links so it's never hidden by the parent -->
|
|
<button type="button" class="nav-hamburger" id="nav-hamburger-btn" aria-label="Open navigation" aria-expanded="false">
|
|
<svg width="20" height="20" viewBox="0 0 20 20" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true">
|
|
<rect y="3" width="20" height="2" rx="1" fill="#0F172A"/>
|
|
<rect y="9" width="20" height="2" rx="1" fill="#0F172A"/>
|
|
<rect y="15" width="20" height="2" rx="1" fill="#0F172A"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Left: demand / investor side (desktop only) -->
|
|
<div class="nav-links nav-links--left">
|
|
<a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a>
|
|
<a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a>
|
|
<a href="{{ url_for('leads.quote_request') }}">{{ t.nav_quotes }}</a>
|
|
</div>
|
|
|
|
<!-- Center: logo (always visible) -->
|
|
<a href="{{ url_for('public.landing') }}" class="nav-logo" style="display:inline-flex;align-items:center;text-decoration:none">
|
|
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:1.125rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
|
|
</a>
|
|
|
|
<!-- Right: supply side + auth (desktop only) -->
|
|
<div class="nav-links nav-links--right">
|
|
<a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a>
|
|
<a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a>
|
|
<a href="{{ url_for('public.landing') }}#faq">{{ t.nav_help }}</a>
|
|
{% if user %}
|
|
<a href="{{ url_for('dashboard.index') }}">{{ t.nav_dashboard }}</a>
|
|
{% if is_admin %}
|
|
<a href="{{ url_for('admin.index') }}" class="nav-badge">{{ t.nav_admin }}</a>
|
|
{% endif %}
|
|
<form method="post" action="{{ url_for('auth.logout') }}" class="nav-form">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="nav-auth-btn">{{ t.nav_signout }}</button>
|
|
</form>
|
|
{% else %}
|
|
<a href="{{ url_for('auth.login') }}" class="nav-auth-btn">{{ t.nav_signin }}</a>
|
|
{% endif %}
|
|
</div>
|
|
|
|
<!-- Mobile-only auth (right side) — sign-in always visible outside the hamburger menu -->
|
|
<div class="nav-auth-mobile">
|
|
{% if not user %}
|
|
<a href="{{ url_for('auth.login') }}" class="nav-auth-btn">{{ t.nav_signin }}</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Mobile menu panel — sign-in excluded (it lives in the bar); account section shown only for logged-in users -->
|
|
<div class="nav-mobile" id="nav-mobile-panel">
|
|
<span class="nav-mobile-section">{{ t.nav_section_plan }}</span>
|
|
<a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a>
|
|
<a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a>
|
|
<a href="{{ url_for('leads.quote_request') }}">{{ t.nav_quotes }}</a>
|
|
|
|
<span class="nav-mobile-section">{{ t.nav_section_suppliers }}</span>
|
|
<a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a>
|
|
<a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a>
|
|
<a href="{{ url_for('public.landing') }}#faq">{{ t.nav_help }}</a>
|
|
|
|
{% if user %}
|
|
<span class="nav-mobile-section">{{ t.nav_section_account }}</span>
|
|
<a href="{{ url_for('dashboard.index') }}">{{ t.nav_dashboard }}</a>
|
|
{% if is_admin %}
|
|
<a href="{{ url_for('admin.index') }}">{{ t.nav_admin }}</a>
|
|
{% endif %}
|
|
<form method="post" action="{{ url_for('auth.logout') }}" style="margin:0.5rem 0 0;padding:0">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<button type="submit" class="nav-auth-btn">{{ t.nav_signout }}</button>
|
|
</form>
|
|
{% endif %}
|
|
</div>
|
|
</nav>
|
|
|
|
<!-- Overlay — closes mobile menu when clicked -->
|
|
<div class="nav-overlay" id="nav-overlay"></div>
|
|
|
|
<script>
|
|
(function() {
|
|
var nav = document.getElementById('main-nav');
|
|
var btn = document.getElementById('nav-hamburger-btn');
|
|
var overlay = document.getElementById('nav-overlay');
|
|
function open() {
|
|
nav.setAttribute('data-navopen', 'true');
|
|
btn.setAttribute('aria-expanded', 'true');
|
|
}
|
|
function close() {
|
|
nav.removeAttribute('data-navopen');
|
|
btn.setAttribute('aria-expanded', 'false');
|
|
}
|
|
btn.addEventListener('click', function() {
|
|
nav.hasAttribute('data-navopen') ? close() : open();
|
|
});
|
|
overlay.addEventListener('click', close);
|
|
document.addEventListener('keydown', function(e) {
|
|
if (e.key === 'Escape') close();
|
|
});
|
|
})();
|
|
</script>
|
|
|
|
<!-- Flash messages -->
|
|
{% with messages = get_flashed_messages(with_categories=true) %}
|
|
{% if messages %}
|
|
<div class="container-page">
|
|
{% for category, message in messages %}
|
|
<div class="{% if category == 'error' %}flash-error{% elif category == 'success' %}flash-success{% elif category == 'warning' %}flash-warning{% else %}flash{% endif %}">
|
|
{{ message }}
|
|
</div>
|
|
{% endfor %}
|
|
</div>
|
|
{% endif %}
|
|
{% endwith %}
|
|
|
|
{% block content %}{% endblock %}
|
|
|
|
<!-- Footer -->
|
|
<footer class="container-page mt-16 pt-8 border-t border-light-gray">
|
|
<div class="grid-4 mb-8">
|
|
<div>
|
|
<div class="mb-1">
|
|
<span style="font-family:'Bricolage Grotesque',sans-serif;font-weight:800;font-size:0.9375rem;color:#0F172A;letter-spacing:-0.02em">padelnomics</span>
|
|
</div>
|
|
<p class="text-sm text-slate">{{ t.footer_tagline }}</p>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-navy text-sm mb-2">{{ t.footer_product }}</p>
|
|
<ul class="space-y-1 text-sm">
|
|
<li><a href="{{ url_for('planner.index') }}">{{ t.nav_planner }}</a></li>
|
|
<li><a href="{{ url_for('directory.index') }}">{{ t.nav_directory }}</a></li>
|
|
<li><a href="{{ url_for('content.markets') }}">{{ t.nav_markets }}</a></li>
|
|
<li><a href="{{ url_for('public.padelnomics_score') }}">{{ t.footer_padelnomics_score }}</a></li>
|
|
<li><a href="{{ url_for('public.suppliers') }}">{{ t.nav_suppliers }}</a></li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-navy text-sm mb-2">{{ t.footer_company }}</p>
|
|
<ul class="space-y-1 text-sm">
|
|
<li><a href="{{ url_for('public.about') }}">{{ t.base_about }}</a></li>
|
|
</ul>
|
|
</div>
|
|
<div>
|
|
<p class="font-semibold text-navy text-sm mb-2">{{ t.footer_legal }}</p>
|
|
<ul class="space-y-1 text-sm">
|
|
<li><a href="{{ url_for('public.terms') }}">{{ t.link_terms }}</a></li>
|
|
<li><a href="{{ url_for('public.privacy') }}">{{ t.link_privacy }}</a></li>
|
|
<li><a href="{{ url_for('public.imprint') }}">{{ t.link_imprint }}</a></li>
|
|
<li><a href="#" onclick="document.cookie='cookie_consent=;path=/;max-age=0';location.reload();return false">{{ t.base_manage_cookies }}</a></li>
|
|
</ul>
|
|
</div>
|
|
</div>
|
|
<!-- Language toggle -->
|
|
{% if request.path.startswith('/en/') or request.path.startswith('/de/') %}
|
|
<div class="text-center text-xs text-slate mb-4">
|
|
{% if lang == 'en' %}
|
|
<span style="font-weight:600;color:#0F172A">EN</span>
|
|
|
|
|
<a href="{{ request.path | replace('/en/', '/de/', 1) }}" style="color:#64748B">DE</a>
|
|
{% else %}
|
|
<a href="{{ request.path | replace('/de/', '/en/', 1) }}" style="color:#64748B">EN</a>
|
|
|
|
|
<span style="font-weight:600;color:#0F172A">DE</span>
|
|
{% endif %}
|
|
</div>
|
|
{% endif %}
|
|
<p class="text-center text-xs text-slate pb-6">
|
|
© {{ now.year }} {{ config.APP_NAME }}. {{ t.footer_rights }}
|
|
</p>
|
|
</footer>
|
|
|
|
<!-- HTMX (self-hosted) -->
|
|
<script src="{{ url_for('static', filename='js/htmx-2.0.4.min.js') }}"></script>
|
|
<script>
|
|
// CSRF token for HTMX requests
|
|
document.body.addEventListener('htmx:configRequest', function(e) {
|
|
const csrfMeta = document.querySelector('input[name="csrf_token"]');
|
|
if (csrfMeta) e.detail.headers['X-CSRF-Token'] = csrfMeta.value;
|
|
});
|
|
</script>
|
|
|
|
<!-- Clickable table rows (admin lists) -->
|
|
<style>tr[data-href]{cursor:pointer}tr[data-href]:hover{background:#F8FAFC}</style>
|
|
<script>
|
|
document.addEventListener("click",function(e){
|
|
var row=e.target.closest("tr[data-href]");
|
|
if(!row||e.target.closest("a,button,form,input,select,textarea"))return;
|
|
window.location=row.dataset.href;
|
|
});
|
|
</script>
|
|
|
|
{% block scripts %}{% endblock %}
|
|
|
|
{% include "_cookie_banner.html" %}
|
|
|
|
<!-- Floating feedback button -->
|
|
<div id="feedback-wrap" style="position:fixed;bottom:1.5rem;right:1.5rem;z-index:200;">
|
|
<button type="button" id="feedback-toggle"
|
|
aria-label="{{ t.nav_feedback }}"
|
|
onclick="(function(){var p=document.getElementById('feedback-popover');p.hidden=!p.hidden;})()"
|
|
style="height:38px;border-radius:999px;background:#1D4ED8;border:none;cursor:pointer;box-shadow:0 4px 16px rgba(29,78,216,0.35);display:inline-flex;align-items:center;gap:6px;padding:0 14px 0 10px;color:#fff;font-size:0.8125rem;font-weight:600;font-family:inherit;white-space:nowrap;">
|
|
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg" aria-hidden="true" style="flex-shrink:0;">
|
|
<path d="M21 15C21 15.5304 20.7893 16.0391 20.4142 16.4142C20.0391 16.7893 19.5304 17 19 17H7L3 21V5C3 4.46957 3.21071 3.96086 3.58579 3.58579C3.96086 3.21071 4.46957 3 5 3H19C19.5304 3 20.0391 3.21071 20.4142 3.58579C20.7893 3.96086 21 4.46957 21 5V15Z" stroke="white" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
<span class="feedback-label">{{ t.nav_feedback }}</span>
|
|
</button>
|
|
<div id="feedback-popover" hidden
|
|
style="position:absolute;bottom:calc(100% + 0.75rem);right:0;width:300px;background:white;border:1px solid #E2E8F0;border-radius:12px;padding:1rem;box-shadow:0 8px 32px rgba(0,0,0,0.12);">
|
|
<form hx-post="{{ url_for('public.feedback') }}" hx-target="#feedback-popover" hx-swap="innerHTML">
|
|
<input type="hidden" name="csrf_token" value="{{ csrf_token() }}">
|
|
<input type="hidden" name="page_url" id="feedback-page-url">
|
|
<input type="hidden" name="umami_id" id="feedback-umami-id">
|
|
<p style="font-size:0.8125rem;font-weight:600;color:#1E293B;margin:0 0 8px">{{ t.nav_feedback }}</p>
|
|
<textarea name="message" rows="3" required placeholder="{{ t.base_feedback_placeholder }}"
|
|
style="width:100%;box-sizing:border-box;border:1px solid #E2E8F0;border-radius:6px;padding:8px;font-size:0.8125rem;font-family:inherit;resize:vertical"></textarea>
|
|
{% if not g.get("user") %}
|
|
<input type="text" name="contact" placeholder="{{ t.base_feedback_contact_placeholder }}"
|
|
style="width:100%;box-sizing:border-box;border:1px solid #E2E8F0;border-radius:6px;padding:8px;font-size:0.8125rem;font-family:inherit;margin-top:6px">
|
|
{% endif %}
|
|
<button type="submit" class="btn" style="width:100%;margin-top:8px;font-size:0.8125rem;padding:8px">{{ t.nav_send }}</button>
|
|
</form>
|
|
</div>
|
|
</div>
|
|
<script>
|
|
document.getElementById('feedback-page-url').value = window.location.pathname;
|
|
(function() {
|
|
var uid = null;
|
|
try { uid = localStorage.getItem('umami.uuid'); } catch(e) {}
|
|
if (uid) document.getElementById('feedback-umami-id').value = uid;
|
|
})();
|
|
document.addEventListener('click', function(e) {
|
|
var wrap = document.getElementById('feedback-wrap');
|
|
if (wrap && !wrap.contains(e.target)) {
|
|
var pop = document.getElementById('feedback-popover');
|
|
if (pop) pop.hidden = true;
|
|
}
|
|
});
|
|
</script>
|
|
</body>
|
|
</html>
|