From a0926fcd1a3ec802852b3686287f1d97d26b8b74 Mon Sep 17 00:00:00 2001 From: Meik Date: Sat, 21 Feb 2026 12:26:08 +0100 Subject: [PATCH] Add sortable trade fair bookmarks subpage --- web/index.html | 40 ++- web/style.css | 112 +++++++++ web/trade-fairs.js | 592 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 743 insertions(+), 1 deletion(-) create mode 100644 web/trade-fairs.js diff --git a/web/index.html b/web/index.html index 702e9de..2e09514 100644 --- a/web/index.html +++ b/web/index.html @@ -33,7 +33,8 @@ 'ai-debug.js', 'vendor/list.min.js', 'automation.js', - 'daily-bookmarks.js' + 'daily-bookmarks.js', + 'trade-fairs.js' ]; function withVersion(value) { @@ -1497,6 +1498,43 @@

Öffnet die drei Varianten ohne ein Bookmark anzulegen.

+
+ 📍 Top 20 Messen in Deutschland (nach Besucherzahlen) +
+
+ +
+
+
+ + + + + + + + + + + + + + + + + + + + + + +
+
+
+
diff --git a/web/style.css b/web/style.css index 3d1931f..7aac68a 100644 --- a/web/style.css +++ b/web/style.css @@ -1810,6 +1810,110 @@ h1 { border-color: #a5b4fc; } +.bookmark-subpage { + border: 1px solid #e5e7eb; + border-radius: 12px; + background: #f9fafb; + padding: 10px 12px; +} + +.bookmark-subpage[open] { + background: #ffffff; +} + +.bookmark-subpage__summary { + cursor: pointer; + font-weight: 600; + color: #1f2937; + list-style: none; +} + +.bookmark-subpage__summary::-webkit-details-marker { + display: none; +} + +.bookmark-subpage__summary::before { + content: "▸"; + margin-right: 8px; + color: #6b7280; +} + +.bookmark-subpage[open] .bookmark-subpage__summary::before { + content: "▾"; +} + +.bookmark-subpage__content { + margin-top: 12px; + display: flex; + flex-direction: column; + gap: 10px; +} + +.bookmark-subpage__toolbar { + display: flex; + flex-wrap: wrap; + gap: 12px; + align-items: flex-end; +} + +.bookmark-subpage__meta { + font-size: 12px; + color: #4b5563; +} + +.bookmark-subpage__table-wrap { + width: 100%; + overflow-x: auto; +} + +.bookmark-subpage__table { + width: 100%; + border-collapse: collapse; + min-width: 1680px; +} + +.bookmark-subpage__table th, +.bookmark-subpage__table td { + border: 1px solid #e5e7eb; + padding: 8px 10px; + font-size: 12px; + vertical-align: top; +} + +.bookmark-subpage__table th { + background: #f3f4f6; + white-space: nowrap; +} + +.bookmark-subpage__sort { + border: none; + background: transparent; + cursor: pointer; + font-weight: 600; + color: #1f2937; + font-size: 12px; + display: inline-flex; + align-items: center; + gap: 4px; +} + +.bookmark-subpage__sort:hover { + color: #111827; +} + +.bookmark-subpage__sort.is-active { + color: #0f4bb8; +} + +.bookmark-subpage__table td:nth-child(4), +.bookmark-subpage__table td:nth-child(15) { + min-width: 320px; +} + +.bookmark-subpage__table a { + color: #1d4ed8; +} + @media (max-width: 640px) { .bookmark-panel { width: min(480px, 94vw); @@ -1823,6 +1927,14 @@ h1 { .bookmark-row { grid-template-columns: minmax(0, 1fr) auto; } + + .bookmark-subpage { + padding: 10px; + } + + .bookmark-subpage__toolbar { + gap: 8px; + } } .screenshot-modal { diff --git a/web/trade-fairs.js b/web/trade-fairs.js new file mode 100644 index 0000000..1976c63 --- /dev/null +++ b/web/trade-fairs.js @@ -0,0 +1,592 @@ +(function initTradeFairsSubpage() { + const tableBody = document.getElementById('tradeFairTableBody'); + const searchInput = document.getElementById('tradeFairSearchInput'); + const meta = document.getElementById('tradeFairMeta'); + const sortButtons = Array.from(document.querySelectorAll('.bookmark-subpage__sort[data-trade-sort]')); + + if (!tableBody || !searchInput || !meta || !sortButtons.length) { + return; + } + + const TRADE_FAIRS = [ + { + rang: 1, + messe: 'bauma', + thema: 'Baumaschinen, Baustoffmaschinen, Bergbau, Baufahrzeuge', + stadt: 'München', + bundesland: 'Bayern', + termin_start: '2028-04-03', + termin_ende: '2028-04-09', + besucher: 605974, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 420107, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://bauma.de' + }, + { + rang: 2, + messe: 'AGRITECHNICA', + thema: 'Landtechnik und Agrartechnologie', + stadt: 'Hannover', + bundesland: 'Niedersachsen', + termin_start: '2027-11-14', + termin_ende: '2027-11-20', + besucher: 477055, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 229886, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.agritechnica.com' + }, + { + rang: 3, + messe: 'gamescom', + thema: 'Games, digitale Unterhaltung, Hardware/Software', + stadt: 'Köln', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2026-08-26', + termin_ende: '2026-08-30', + besucher: 357416, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 73515, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.gamescom.global/en' + }, + { + rang: 4, + messe: 'Grüne Woche', + thema: 'Ernährung, Landwirtschaft, Garten, Genuss', + stadt: 'Berlin', + bundesland: 'Berlin', + termin_start: '2027-01-15', + termin_ende: '2027-01-24', + besucher: 307530, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 42500, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.gruenewoche.de' + }, + { + rang: 5, + messe: 'CARAVAN SALON DÜSSELDORF', + thema: 'Reisemobile, Caravans, Camping, Touristik', + stadt: 'Düsseldorf', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2026-08-28', + termin_ende: '2026-09-06', + besucher: 270421, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 121439, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.caravan-salon.de' + }, + { + rang: 6, + messe: 'Maimarkt Mannheim', + thema: 'Publikums-Mehrbranchenmesse', + stadt: 'Mannheim', + bundesland: 'Baden-Württemberg', + termin_start: '2026-04-25', + termin_ende: '2026-05-05', + besucher: 268613, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 52769, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.maimarkt.de' + }, + { + rang: 7, + messe: 'CMT - Die Urlaubsmesse', + thema: 'Tourismus, Caravaning, Freizeit', + stadt: 'Stuttgart', + bundesland: 'Baden-Württemberg', + termin_start: '2027-01-16', + termin_ende: '2027-01-24', + besucher: 261004, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 75587, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.messe-stuttgart.de/cmt' + }, + { + rang: 8, + messe: 'Leipziger Buchmesse / Manga Comic Con', + thema: 'Buchmarkt, Medien, Manga/Comic', + stadt: 'Leipzig', + bundesland: 'Sachsen', + termin_start: '2026-03-19', + termin_ende: '2026-03-22', + besucher: 242238, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 20788, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.leipziger-buchmesse.de' + }, + { + rang: 9, + messe: 'ESSEN MOTOR SHOW', + thema: 'Performance, Tuning, Motorsport, Classic Cars', + stadt: 'Essen', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2026-11-28', + termin_ende: '2026-12-06', + besucher: 202827, + besucher_jahr: 2024, + besucher_status: 'AUMA-Kennzahl 2024 (kein neuerer Wert)', + ausstellungsflaeche_m2: 33781, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Vorjahreswert, da kein neuerer veröffentlichter Wert in AUMA. Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.essen-motorshow.de' + }, + { + rang: 10, + messe: 'boot Düsseldorf', + thema: 'Boote, Wassersport, Tauchen, Yachting', + stadt: 'Düsseldorf', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2027-01-23', + termin_ende: '2027-01-31', + besucher: 198339, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 94867, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.boot.de' + }, + { + rang: 11, + messe: 'IFA', + thema: 'Consumer Electronics, Home Appliances, Tech', + stadt: 'Berlin', + bundesland: 'Berlin', + termin_start: '2026-09-04', + termin_ende: '2026-09-08', + besucher: 191997, + besucher_jahr: 2024, + besucher_status: 'AUMA-Kennzahl 2024 (kein neuerer Wert)', + ausstellungsflaeche_m2: 107546, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Vorjahreswert, da kein neuerer veröffentlichter Wert in AUMA. Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.ifa-berlin.com' + }, + { + rang: 12, + messe: 'FIBO', + thema: 'Fitness, Wellness, Gesundheit', + stadt: 'Köln', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2026-04-16', + termin_ende: '2026-04-19', + besucher: 154890, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 56904, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.fibo.com' + }, + { + rang: 13, + messe: 'Anuga', + thema: 'Lebensmittel und Getränke', + stadt: 'Köln', + bundesland: 'Nordrhein-Westfalen', + termin_start: '2027-10-09', + termin_ende: '2027-10-13', + besucher: 143432, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 157545, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.anuga.com' + }, + { + rang: 14, + messe: 'HANNOVER MESSE', + thema: 'Industrie, Automation, Energie, Digital', + stadt: 'Hannover', + bundesland: 'Niedersachsen', + termin_start: '2026-04-20', + termin_ende: '2026-04-24', + besucher: 123035, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 101699, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.hannovermesse.de' + }, + { + rang: 15, + messe: 'CONSUMENTA Nürnberg', + thema: 'Publikumsmesse: Haushalt, Freizeit, Bauen, Genuss', + stadt: 'Nürnberg', + bundesland: 'Bayern', + termin_start: '2026-10-31', + termin_ende: '2026-11-08', + besucher: 121579, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 30448, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.consumenta.de' + }, + { + rang: 16, + messe: 'infa', + thema: 'Publikumsmesse: Lifestyle, Haushalt, Genuss', + stadt: 'Hannover', + bundesland: 'Niedersachsen', + termin_start: '2026-10-10', + termin_ende: '2026-10-18', + besucher: 100361, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 16423, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.infa.de' + }, + { + rang: 17, + messe: 'ILA Berlin', + thema: 'Luft- und Raumfahrt', + stadt: 'Berlin', + bundesland: 'Berlin', + termin_start: '2026-06-10', + termin_ende: '2026-06-14', + besucher: 95000, + besucher_jahr: 2024, + besucher_status: 'AUMA-Kennzahl 2024 (kein neuerer Wert)', + ausstellungsflaeche_m2: 32008, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Vorjahreswert, da kein neuerer veröffentlichter Wert in AUMA. Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.ila-berlin.de' + }, + { + rang: 18, + messe: 'offerta', + thema: 'Einkaufs- und Erlebnismesse für Verbraucher', + stadt: 'Karlsruhe', + bundesland: 'Baden-Württemberg', + termin_start: '2026-10-24', + termin_ende: '2026-11-01', + besucher: 88738, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 16242, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.offerta.info' + }, + { + rang: 19, + messe: 'RETRO CLASSICS', + thema: 'Klassische Fahrzeuge und Fahrkultur', + stadt: 'Stuttgart', + bundesland: 'Baden-Württemberg', + termin_start: '2026-02-19', + termin_ende: '2026-02-22', + besucher: 72386, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 46496, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026). Ticketpreise je Kategorie/VVK.', + quelle_homepage: 'https://www.retro-classics.de' + }, + { + rang: 20, + messe: 'ITB Berlin', + thema: 'Reiseindustrie (B2B/Fachbesucher)', + stadt: 'Berlin', + bundesland: 'Berlin', + termin_start: '2026-03-03', + termin_ende: '2026-03-05', + besucher: 58232, + besucher_jahr: 2025, + besucher_status: 'AUMA-Kennzahl 2025', + ausstellungsflaeche_m2: 78892, + ticketpreis_we_eur: null, + ticketpreis_unterderwoche_eur: null, + notiz: 'Fachbesucherfokus. Kennzahlen laut AUMA-Messedatenbank (Abruf 21.02.2026).', + quelle_homepage: 'https://www.itb.com' + } + ]; + + const SORT_TYPES = { + tage_bis_start: 'number', + rang: 'number', + messe: 'text', + thema: 'text', + stadt: 'text', + bundesland: 'text', + termin_start: 'date', + termin_ende: 'date', + besucher: 'number', + besucher_jahr: 'number', + besucher_status: 'text', + ausstellungsflaeche_m2: 'number', + ticketpreis_we_eur: 'number', + ticketpreis_unterderwoche_eur: 'number', + notiz: 'text', + quelle_homepage: 'text' + }; + + let sortKey = 'rang'; + let sortDirection = 'asc'; + let searchTerm = ''; + + function toNumber(value) { + if (typeof value === 'number' && Number.isFinite(value)) { + return value; + } + if (typeof value === 'string' && value.trim()) { + const normalized = value.replace(/\./g, '').replace(',', '.').replace(/[^\d.-]/g, ''); + const parsed = Number(normalized); + return Number.isFinite(parsed) ? parsed : null; + } + return null; + } + + function toDate(value) { + if (!value) { + return null; + } + const parsed = new Date(value); + if (Number.isNaN(parsed.getTime())) { + return null; + } + return parsed; + } + + function getDaysUntil(startIso) { + const start = toDate(startIso); + if (!start) { + return null; + } + const now = new Date(); + now.setHours(0, 0, 0, 0); + start.setHours(0, 0, 0, 0); + return Math.round((start.getTime() - now.getTime()) / 86400000); + } + + function normalizeRow(row) { + return { + ...row, + tage_bis_start: getDaysUntil(row.termin_start) + }; + } + + function getSortValue(row, key) { + const type = SORT_TYPES[key] || 'text'; + const value = row[key]; + + if (type === 'number') { + const numeric = toNumber(value); + return numeric === null ? Number.NEGATIVE_INFINITY : numeric; + } + + if (type === 'date') { + const date = toDate(value); + return date ? date.getTime() : Number.NEGATIVE_INFINITY; + } + + return String(value || '').toLocaleLowerCase('de-DE'); + } + + function formatNumber(value) { + if (!Number.isFinite(value)) { + return 'k.A.'; + } + return value.toLocaleString('de-DE'); + } + + function formatPrice(value) { + if (!Number.isFinite(value)) { + return 'k.A.'; + } + return value.toLocaleString('de-DE', { minimumFractionDigits: 2, maximumFractionDigits: 2 }); + } + + function formatDate(iso) { + const date = toDate(iso); + if (!date) { + return 'k.A.'; + } + return date.toLocaleDateString('de-DE'); + } + + function matchesSearch(row, term) { + if (!term) { + return true; + } + const haystack = [ + row.messe, + row.thema, + row.stadt, + row.bundesland, + row.notiz + ].join(' ').toLocaleLowerCase('de-DE'); + return haystack.includes(term); + } + + function sortRows(rows) { + return [...rows].sort((a, b) => { + const aValue = getSortValue(a, sortKey); + const bValue = getSortValue(b, sortKey); + + if (aValue === bValue) { + const fallback = (a.rang || 0) - (b.rang || 0); + return sortDirection === 'asc' ? fallback : -fallback; + } + + if (aValue > bValue) { + return sortDirection === 'asc' ? 1 : -1; + } + return sortDirection === 'asc' ? -1 : 1; + }); + } + + function updateSortButtonState() { + sortButtons.forEach((button) => { + const key = button.dataset.tradeSort; + const isActive = key === sortKey; + button.classList.toggle('is-active', isActive); + const indicator = isActive ? (sortDirection === 'asc' ? '▲' : '▼') : '↕'; + const baseLabel = button.dataset.baseLabel || button.textContent.trim(); + button.textContent = `${baseLabel} ${indicator}`; + }); + } + + function createCell(content) { + const td = document.createElement('td'); + td.textContent = content; + return td; + } + + function render() { + const normalizedRows = TRADE_FAIRS.map(normalizeRow); + const filtered = normalizedRows.filter((row) => matchesSearch(row, searchTerm)); + const sorted = sortRows(filtered); + + tableBody.innerHTML = ''; + + if (!sorted.length) { + const emptyRow = document.createElement('tr'); + const emptyCell = document.createElement('td'); + emptyCell.colSpan = 16; + emptyCell.textContent = 'Keine Messe zum Suchbegriff gefunden.'; + emptyRow.appendChild(emptyCell); + tableBody.appendChild(emptyRow); + } else { + sorted.forEach((row) => { + const tr = document.createElement('tr'); + + tr.appendChild(createCell(Number.isFinite(row.tage_bis_start) ? String(row.tage_bis_start) : 'k.A.')); + tr.appendChild(createCell(String(row.rang))); + tr.appendChild(createCell(row.messe)); + tr.appendChild(createCell(row.thema)); + tr.appendChild(createCell(row.stadt)); + tr.appendChild(createCell(row.bundesland)); + tr.appendChild(createCell(formatDate(row.termin_start))); + tr.appendChild(createCell(formatDate(row.termin_ende))); + tr.appendChild(createCell(formatNumber(row.besucher))); + tr.appendChild(createCell(formatNumber(row.besucher_jahr))); + tr.appendChild(createCell(row.besucher_status)); + tr.appendChild(createCell(formatNumber(row.ausstellungsflaeche_m2))); + tr.appendChild(createCell(formatPrice(row.ticketpreis_we_eur))); + tr.appendChild(createCell(formatPrice(row.ticketpreis_unterderwoche_eur))); + tr.appendChild(createCell(row.notiz)); + + const sourceCell = document.createElement('td'); + if (row.quelle_homepage) { + const link = document.createElement('a'); + link.href = row.quelle_homepage; + link.target = '_blank'; + link.rel = 'noopener'; + link.textContent = row.quelle_homepage; + sourceCell.appendChild(link); + } else { + sourceCell.textContent = 'k.A.'; + } + tr.appendChild(sourceCell); + + tableBody.appendChild(tr); + }); + } + + const activeSortButton = sortButtons.find((button) => button.dataset.tradeSort === sortKey); + const sortLabel = activeSortButton?.dataset.baseLabel || sortKey; + meta.textContent = `${sorted.length} von ${TRADE_FAIRS.length} Messen | Sortierung: ${sortLabel} (${sortDirection === 'asc' ? 'aufsteigend' : 'absteigend'})`; + } + + sortButtons.forEach((button) => { + const label = button.textContent.trim(); + button.dataset.baseLabel = label; + button.addEventListener('click', () => { + const key = button.dataset.tradeSort; + if (!key) { + return; + } + + if (sortKey === key) { + sortDirection = sortDirection === 'asc' ? 'desc' : 'asc'; + } else { + sortKey = key; + sortDirection = key === 'rang' ? 'asc' : 'desc'; + } + sortButtons.forEach((btn) => { + const baseLabel = btn.dataset.baseLabel || btn.textContent; + btn.textContent = baseLabel; + }); + updateSortButtonState(); + render(); + }); + }); + + searchInput.addEventListener('input', () => { + searchTerm = searchInput.value.trim().toLocaleLowerCase('de-DE'); + render(); + }); + + updateSortButtonState(); + render(); +})();