Files
pingu-concerts/app/templates/index.html
T
kai b2af866cf0 Add bilingual UI and improve event actions layout
Add persistent German/English switching, translated UI messages and English 12-hour times. Align desktop event actions while preserving mobile layouts and record bilingual project guidance.

Include pending diary deletion exclusions. Validate both languages with nine tests.
2026-09-14 15:31:56 +02:00

360 lines
13 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="{{ language() }}">
<head>
<meta charset="UTF-8">
<meta
name="viewport"
content="width=device-width, initial-scale=1.0"
>
<title>MetalCircle</title>
<link rel="icon" type="image/png" href="/static/images/metalcircle-circle.png">
<link
rel="stylesheet"
href="/static/css/style.css"
>
<style>
.page-header {
display: flex;
justify-content: space-between;
align-items: center;
gap: 20px;
margin-bottom: 35px;
}
.page-header h1 {
margin: 0;
font-size: 2.5rem;
}
.subtitle {
color: #8b949e;
margin-top: 8px;
}
.new-concert-button {
display: inline-block;
padding: 12px 18px;
background: #238636;
color: white;
text-decoration: none;
border-radius: 10px;
font-weight: bold;
white-space: nowrap;
}
.new-concert-button:hover {
background: #2ea043;
}
.header-actions {
display: flex;
gap: 10px;
align-items: center;
}
.admin-button {
display: inline-block;
padding: 12px 18px;
border: 1px solid #30363d;
border-radius: 10px;
color: #b8c1cc;
text-decoration: none;
white-space: nowrap;
}
.admin-button:hover {
color: #ffffff;
border-color: #58a6ff;
}
.concert-list {
display: grid;
gap: 15px;
}
.event-group { display: grid; gap: 8px; }
.linked-events {
display: grid;
gap: 7px;
margin-left: 30px;
}
.concert-card.linked-event {
padding: 14px 18px;
border-left: 3px solid #a78bfa;
background: #121720;
}
.linked-event .concert-artist { font-size: 1.05rem; margin-bottom: 5px; }
.linked-event .concert-meta { font-size: .9rem; }
.event-category {
display: inline-block;
margin-bottom: 7px;
color: #aab4c3;
font-size: .78rem;
font-weight: 700;
letter-spacing: .04em;
text-transform: uppercase;
}
.past-concerts-title {
margin: 28px 0 0;
}
.concert-card {
display: block;
padding: 22px;
background: #161b22;
border: 1px solid #30363d;
border-radius: 14px;
text-decoration: none;
transition:
transform 0.15s ease,
border-color 0.15s ease,
background 0.15s ease;
}
.concert-card:hover {
transform: translateY(-2px);
border-color: #58a6ff;
background: #1b222c;
}
.concert-artist {
font-size: 1.4rem;
font-weight: bold;
color: #ffffff;
margin-bottom: 10px;
}
.concert-meta {
color: #b8c1cc;
line-height: 1.6;
}
.concert-date {
color: #58a6ff;
font-weight: bold;
}
.empty-state {
padding: 40px;
text-align: center;
background: #161b22;
border: 1px dashed #30363d;
border-radius: 14px;
color: #8b949e;
}
.search-panel { margin-bottom: 26px; padding: 16px; background: rgba(16,16,16,.92); border: 1px solid #3a3030; border-radius: 14px; }
.search-form { display: grid; grid-template-columns: minmax(0, 1fr) auto; gap: 10px; align-items:start; }
.search-form input { margin: 0; }
.search-results-title { margin: 24px 0 12px; font-size: 1.15rem; }
.user-results { display: grid; grid-template-columns: repeat(auto-fill, minmax(210px, 1fr)); gap: 10px; margin-bottom: 24px; }
.user-result { display: flex; align-items: center; gap: 11px; padding: 12px; background: #101010; border: 1px solid #3a3030; border-radius: 11px; }
.user-result:hover { border-color: #dc2626; }
.user-result-avatar { width: 44px; height: 44px; flex: 0 0 44px; object-fit: cover; border-radius: 50%; background: #292524; display: grid; place-items: center; font-weight: 800; }
.user-result-name { min-width: 0; }
.user-result-name strong, .user-result-name span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.user-result-name span { color: #a8a29e; font-size: .86rem; }
body {
background:
linear-gradient(rgba(4, 4, 6, .58), rgba(4, 4, 6, .72)),
url('/static/images/crowd-stage-background.webp') center top / cover fixed no-repeat,
#050505;
min-height: 100vh;
}
.page-header { padding: 24px; border: 1px solid #5f1515; border-left: 4px solid #dc2626; border-radius: 14px; background: linear-gradient(110deg, rgba(10,10,10,.9), rgba(69,10,10,.28)); box-shadow: 0 12px 30px rgba(0,0,0,.3); }
.page-header h1 { font-family: "Nimbus Sans Narrow", Impact, Haettenschweiler, "Arial Narrow Bold", sans-serif; letter-spacing: .05em; text-transform: uppercase; text-shadow: 0 2px 18px rgba(185, 28, 28, .45); }
.new-concert-button { background: #991b1b; border: 1px solid #dc2626; }
.new-concert-button:hover { background: #b91c1c; }
.admin-button:hover { border-color: #dc2626; }
.concert-card { background: linear-gradient(145deg, #101010, #181313); border-color: #3a3030; }
.concert-card:hover { border-color: #dc2626; background: #1c1212; }
.concert-date { color: #ef4444; }
.concert-card.linked-event { border-left-color: #dc2626; background: #0d0d0d; }
@media (max-width: 600px) {
.page-header {
align-items: flex-start;
flex-direction: column;
}
.new-concert-button {
width: 100%;
text-align: center;
}
.linked-events { margin-left: 16px; }
.search-form { grid-template-columns: 1fr; }
}
</style>
<link rel="stylesheet" href="/static/css/language.css">
</head>
{% macro event_card(concert, linked=false) %}
<a href="/concerts/{{ concert.id }}" class="concert-card{% if linked %} linked-event{% endif %}">
<span class="event-category">{{ concert.event_type_label | t }}</span>
{% if concert.is_invited %}<span class="event-category"> {{ _('· ✉ Eingeladen') }}</span>{% elif concert.visibility == 'friends' %}<span class="event-category"> {{ _('· 🔒 Nur Freunde') }}</span>{% elif concert.visibility == 'private' %}<span class="event-category"> {{ _('· 🔐 Privat') }}</span>{% endif %}
<div class="concert-artist">
{% if concert.event_type == 'festival' %}🎪{% elif concert.event_type == 'other' %}🥂{% else %}🎸{% endif %}
{{ concert.artist }}
</div>
<div class="concert-meta">
<div class="concert-date">
📅 {{ concert.date }}{% if concert.end_date %} {{ concert.end_date }}{% else %} {{ _('um') }} {{ concert.time }} {{ _('Uhr') }}{% endif %}
</div>
<div>📍 {{ concert.venue }}</div>
</div>
</a>
{% endmacro %}
<body>
<div class="container">
<header class="page-header">
<div>
<h1>
{% if archive %}{{ _('☠ Vergangene Veranstaltungen') }}{% else %}<img class="brand-logo" src="/static/images/metalcircle-full.png" alt="MetalCircle">{% endif %}
</h1>
<div class="subtitle">
<strong>Enter the Pit. Join the Circle.</strong>
</div>
</div>
<div class="header-actions">
<a href="{% if archive %}/{% else %}/events/past{% endif %}" class="admin-button">
{% if archive %}{{ _('⚡ Bevorstehend') }}{% else %}{{ _('☠ Vergangene') }}{% endif %}
</a>
<a
href="/concerts/new"
class="new-concert-button"
>
{{ _('+ Veranstaltung hinzufügen') }}
</a>
{% include '_language_switch.html' %}
{% include '_user_menu.html' %}
</div>
</header>
<section class="search-panel" aria-label="{{ _('Suche') }}">
<form class="search-form" method="get" action="{% if archive %}/events/past{% else %}/{% endif %}">
<input type="search" name="q" value="{{ search_query }}" placeholder="{{ _('Veranstaltungen, Orte oder User suchen …') }}" aria-label="{{ _('Veranstaltungen oder User suchen') }}">
<button class="new-concert-button" type="submit">{{ _('Suchen') }}</button>
<details class="advanced-filters" {% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %}open{% endif %}>
<summary>{{ _('Weitere Filter') }}{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %} {{ _('· aktiv') }}{% endif %}</summary>
<div class="advanced-filter-fields">
<label>{{ _('Von Datum') }}<input type="date" name="date_from" value="{{ filters.date_from }}"></label>
<label>{{ _('Bis Datum') }}<input type="date" name="date_to" value="{{ filters.date_to }}"></label>
<label>{{ _('Ort / Location') }}<input type="search" name="venue" value="{{ filters.venue }}" placeholder="{{ _('z. B. Düsseldorf') }}"></label>
<label>{{ _('Land') }}<input type="search" name="country" value="{{ filters.country }}" placeholder="{{ _('z. B. Deutschland') }}"></label>
<label>{{ _('Kategorie') }}<select name="category"><option value="">{{ _('Alle Kategorien') }}</option><option value="concert" {% if filters.category == 'concert' %}selected{% endif %}>{{ _('Konzert') }}</option><option value="festival" {% if filters.category == 'festival' %}selected{% endif %}>Festival</option><option value="other" {% if filters.category == 'other' %}selected{% endif %}>{{ _('Sonstiges') }}</option></select></label>
</div>
{% if filters.date_from or filters.date_to or filters.venue or filters.country or filters.category %}<a class="clear-filters" href="{% if archive %}/events/past{% else %}/{% endif %}{% if search_query %}?q={{ search_query|urlencode }}{% endif %}">{{ _('Filter löschen') }}</a>{% endif %}
</details>
</form>
</section>
{% if search_query %}
<h2 class="search-results-title">{{ _('User zu „') }}{{ search_query }}“</h2>
{% if user_results %}
<div class="user-results">
{% for result in user_results %}
<a class="user-result" href="/users/{{ result.username }}">
{% if result.avatar_path %}<img class="user-result-avatar" src="{{ result.avatar_path }}" alt="">{% else %}<span class="user-result-avatar">{{ result.display_name[:1] }}</span>{% endif %}
<span class="user-result-name"><strong>{{ result.display_name }}</strong><span>@{{ result.username }}</span></span>
</a>
{% endfor %}
</div>
{% else %}<p class="subtitle">{{ _('Keine passenden User gefunden.') }}</p>{% endif %}
<h2 class="search-results-title">{{ _('Veranstaltungen zu „') }}{{ search_query }}“</h2>
{% endif %}
{% if upcoming_concerts or past_concerts %}
<div class="concert-list" id="event-invitations">
{% for concert in upcoming_concerts %}
<div class="event-group">
{{ event_card(concert) }}
{% if concert.children %}
<div class="linked-events">
{% for child in concert.children %}{{ event_card(child, true) }}{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% if archive and past_concerts %}
<h2 class="past-concerts-title">
{{ _('Archiv') }}
</h2>
{% for concert in past_concerts %}
<div class="event-group">
{{ event_card(concert) }}
{% if concert.children %}
<div class="linked-events">
{% for child in concert.children %}{{ event_card(child, true) }}{% endfor %}
</div>
{% endif %}
</div>
{% endfor %}
{% endif %}
</div>
{% else %}
<div class="empty-state">
<h2>
{% if archive %}{{ _('Noch keine vergangenen Veranstaltungen ☠') }}{% else %}{{ _('Noch keine Veranstaltungen 🎸') }}{% endif %}
</h2>
<p>
{% if archive %}{{ _('Hier erscheinen abgeschlossene Veranstaltungen.') }}{% else %}{{ _('Leg die erste Veranstaltung an!') }}{% endif %}
</p>
</div>
{% endif %}
</div>
</body>
</html>