diff --git a/.gitignore b/.gitignore
index e907d65..9ff540a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,8 +1,37 @@
+# Secrets
.env
-*.pyc
-__pycache__/
-media/
-*.db
-app.py.bak*
-*.bak
+*.env
+.env.bak
.cloudflare_token
+credentials.json
+*.key
+*.pem
+
+# Python
+__pycache__/
+*.py[cod]
+*.egg-info/
+*.bak
+*.bak2
+*.bak3
+*.bak4
+*.bak5
+venv/
+.venv/
+
+# Database
+*.db
+*.db-journal
+*.db-wal
+
+# Media (too large, stored on disk)
+media/uploads/
+*.log
+
+# IDE
+.vscode/
+.idea/
+
+# OS
+.DS_Store
+Thumbs.db
diff --git a/app.py b/app.py
index 5ddcbd7..00ccc17 100644
--- a/app.py
+++ b/app.py
@@ -1486,10 +1486,17 @@ def public_frontend():
query += " ORDER BY published_at DESC LIMIT 50"
rows = db.execute(query, params).fetchall()
- # Get available filter values
- hallen = [r[0] for r in db.execute(
+ # Get available filter values — group by hall number (H1-H7)
+ hallen = []
+ hall_rows = db.execute(
"SELECT DISTINCT halle FROM submissions WHERE halle IS NOT NULL AND status='published' ORDER BY halle"
- ).fetchall()]
+ ).fetchall()
+ seen = set()
+ for r in hall_rows:
+ hnum = r[0][:2] if r[0] else ""
+ if hnum and hnum[0] == 'H' and hnum[1:].isdigit() and hnum not in seen:
+ seen.add(hnum)
+ hallen.append(hnum)
categories = [r[0] for r in db.execute(
"SELECT DISTINCT category FROM submissions WHERE category IS NOT NULL AND status='published' ORDER BY category"
).fetchall()]
@@ -1515,27 +1522,30 @@ def public_frontend():
if not rows:
return empty_state
- # Build filter bar HTML
+ # Build filter bar HTML — hallen as dropdown
filter_html = '
📍 Halle: '
- filter_html += f'
Alle '
- for h in hallen:
- active = " active" if halle_filter and h.startswith(halle_filter) else ""
- url = f"/?halle={h}"
- if cat_filter:
- url += f"&cat={cat_filter}"
- filter_html += f'
{h} '
+ filter_html += '
'
if categories:
filter_html += '
📂 Kategorie: '
- url = "/"
+ filter_html += '
'
+ if halle_filter or cat_filter:
+ filter_html += '
✕ Filter zurücksetzen'
filter_html += "
"
cards = ""
@@ -1699,6 +1709,9 @@ def public_frontend():
.frontend-filter a.filter-pill:hover { border-color:#20228a; color:#20228a; }
.frontend-filter a.filter-pill.active { background:#20228a; color:#fff; border-color:#20228a; font-weight:700; }
.frontend-filter a.filter-reset { color:#999; font-size:0.75rem; }
+ .hall-select { appearance:none; -webkit-appearance:none; background:#fff; color:#20228a; border:1.5px solid #ddd; border-radius:999px; padding:0.35rem 2rem 0.35rem 0.9rem; font-size:0.78rem; font-weight:600; font-family:inherit; cursor:pointer; transition:all 0.15s; background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='12' viewBox='0 0 24 24' fill='none' stroke='%2320228a' stroke-width='2.5'%3E%3Cpath d='M6 9l6 6 6-6'%3E%3C/path%3E%3C/svg%3E"); background-repeat:no-repeat; background-position:right 0.55rem center; }
+ .hall-select:hover { border-color:#20228a; }
+ .hall-select:focus { outline:none; border-color:#20228a; box-shadow:0 0 0 3px rgba(32,34,138,0.12); }
.story-card audio { width:100%; margin-top:0.8rem; }
.story-card img:not(:first-child) { height:auto; max-height:300px; object-fit:contain; margin-top:0.8rem; border-radius:8px; }
.story-card video:not(:first-child) { height:auto; max-height:300px; margin-top:0.8rem; border-radius:8px; }