{"id":10058,"date":"2026-02-20T11:31:08","date_gmt":"2026-02-20T10:31:08","guid":{"rendered":"https:\/\/www.displayhersteller.de\/ratgeber\/?page_id=10058"},"modified":"2026-03-02T11:25:59","modified_gmt":"2026-03-02T10:25:59","slug":"messekalender","status":"publish","type":"page","link":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender","title":{"rendered":"Messekalender"},"content":{"rendered":"[vc_row type=&#8221;in_container&#8221; full_screen_row_position=&#8221;middle&#8221; column_margin=&#8221;default&#8221; column_direction=&#8221;default&#8221; column_direction_tablet=&#8221;default&#8221; column_direction_phone=&#8221;default&#8221; scene_position=&#8221;center&#8221; text_color=&#8221;dark&#8221; text_align=&#8221;left&#8221; row_border_radius=&#8221;none&#8221; row_border_radius_applies=&#8221;bg&#8221; overflow=&#8221;visible&#8221; overlay_strength=&#8221;0.3&#8243; gradient_direction=&#8221;left_to_right&#8221; shape_divider_position=&#8221;bottom&#8221; bg_image_animation=&#8221;none&#8221;][vc_column column_padding=&#8221;no-extra-padding&#8221; column_padding_tablet=&#8221;inherit&#8221; column_padding_phone=&#8221;inherit&#8221; column_padding_position=&#8221;all&#8221; column_element_direction_desktop=&#8221;default&#8221; column_element_spacing=&#8221;default&#8221; desktop_text_alignment=&#8221;default&#8221; tablet_text_alignment=&#8221;default&#8221; phone_text_alignment=&#8221;default&#8221; background_color_opacity=&#8221;1&#8243; background_hover_color_opacity=&#8221;1&#8243; column_backdrop_filter=&#8221;none&#8221; column_shadow=&#8221;none&#8221; column_border_radius=&#8221;none&#8221; column_link_target=&#8221;_self&#8221; column_position=&#8221;default&#8221; gradient_direction=&#8221;left_to_right&#8221; overlay_strength=&#8221;0.3&#8243; width=&#8221;1\/1&#8243; tablet_width_inherit=&#8221;default&#8221; animation_type=&#8221;default&#8221; bg_image_animation=&#8221;none&#8221; border_type=&#8221;simple&#8221; column_border_width=&#8221;none&#8221; column_border_style=&#8221;solid&#8221;][vc_column_text]\n<h1>Messekalender \u2013 Alle wichtigen Messetermine im \u00dcberblick<\/h1>\n<p>Unser Messekalender im displayhersteller Ratgeber bietet einen kompakten \u00dcberblick \u00fcber anstehende Messen und wichtige Branchentermine. So behalten Sie relevante Events im Blick und planen Ihre Messeauftritte fr\u00fchzeitig. Wenn Sie Ihren n\u00e4chsten <a title=\"Messestand Systeme &amp; Sets von displayhersteller\" href=\"https:\/\/www.displayhersteller.de\/de\/messestaende-sets\">Messestand<\/a> strategisch vorbereiten m\u00f6chten, finden Sie passende modulare L\u00f6sungen f\u00fcr professionelle und flexible Messeauftritte.<\/p>\n<h2>Aktuelle Messetermine nach Branchen und Standorten<\/h2>\n[\/vc_column_text][\/vc_column][\/vc_row][vc_row type=&#8221;in_container&#8221; full_screen_row_position=&#8221;middle&#8221; column_margin=&#8221;default&#8221; column_direction=&#8221;default&#8221; column_direction_tablet=&#8221;default&#8221; column_direction_phone=&#8221;default&#8221; scene_position=&#8221;center&#8221; text_color=&#8221;dark&#8221; text_align=&#8221;left&#8221; row_border_radius=&#8221;none&#8221; row_border_radius_applies=&#8221;bg&#8221; overflow=&#8221;visible&#8221; overlay_strength=&#8221;0.3&#8243; gradient_direction=&#8221;left_to_right&#8221; shape_divider_position=&#8221;bottom&#8221; bg_image_animation=&#8221;none&#8221;][vc_column column_padding=&#8221;no-extra-padding&#8221; column_padding_tablet=&#8221;inherit&#8221; column_padding_phone=&#8221;inherit&#8221; column_padding_position=&#8221;all&#8221; column_element_direction_desktop=&#8221;default&#8221; column_element_spacing=&#8221;default&#8221; desktop_text_alignment=&#8221;default&#8221; tablet_text_alignment=&#8221;default&#8221; phone_text_alignment=&#8221;default&#8221; background_color_opacity=&#8221;1&#8243; background_hover_color_opacity=&#8221;1&#8243; column_backdrop_filter=&#8221;none&#8221; column_shadow=&#8221;none&#8221; column_border_radius=&#8221;none&#8221; column_link_target=&#8221;_self&#8221; column_position=&#8221;default&#8221; gradient_direction=&#8221;left_to_right&#8221; overlay_strength=&#8221;0.3&#8243; width=&#8221;1\/1&#8243; tablet_width_inherit=&#8221;default&#8221; animation_type=&#8221;default&#8221; bg_image_animation=&#8221;none&#8221; border_type=&#8221;simple&#8221; column_border_width=&#8221;none&#8221; column_border_style=&#8221;solid&#8221;][vc_column_text]<!-- Messe Verzeichnis -->\n<style>\n@import url('https:\/\/fonts.googleapis.com\/css2?family=Ubuntu:wght@300;400;500;700&display=swap');\n\n.mv-wrap *, .mv-wrap *::before, .mv-wrap *::after { box-sizing: border-box; margin: 0; padding: 0; }\n.mv-wrap { font-family: 'Ubuntu', sans-serif; color: #111111; font-size: 15px; }\n\n\/* \u2500\u2500 FILTER BAR \u2500\u2500 *\/\n.mv-filters {\n  background: #004F9E;\n  padding: 14px 18px;\n  margin-bottom: 0;\n  display: flex;\n  gap: 12px;\n  align-items: flex-end;\n  flex-wrap: wrap;\n}\n.mv-filter-group {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  flex: 1;\n  min-width: 130px;\n}\n.mv-filter-group label {\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 1.1px;\n  text-transform: uppercase;\n  color: #ffffff;\n}\n.mv-filter-group select {\n  width: 100%;\n  padding: 8px 10px;\n  background: #ffffff;\n  border: 1px solid #ffffff;\n  border-radius: 0;\n  color: #111111;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 14px;\n  outline: none;\n  appearance: none;\n  cursor: pointer;\n  height: 38px;\n}\n.mv-filter-group select:focus { border-color: #F68900; }\n\n\/* Datum *\/\n.mv-period-wrap {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  flex: 1;\n  min-width: 160px;\n}\n.mv-period-wrap label {\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 1.1px;\n  text-transform: uppercase;\n  color: #ffffff;\n}\n.mv-period-selects { display: flex; gap: 6px; }\n.mv-period-selects select {\n  flex: 1;\n  padding: 8px 10px;\n  background: #ffffff;\n  border: 1px solid #ffffff;\n  border-radius: 0;\n  color: #111111;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 14px;\n  outline: none;\n  appearance: none;\n  cursor: pointer;\n  height: 38px;\n}\n.mv-period-selects select:focus { border-color: #F68900; }\n\n\/* Suche *\/\n.mv-search-wrap {\n  display: flex;\n  flex-direction: column;\n  gap: 4px;\n  flex: 1;\n  min-width: 130px;\n}\n.mv-search-wrap label {\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 1.1px;\n  text-transform: uppercase;\n  color: #ffffff;\n}\n.mv-search-btn {\n  width: 100%;\n  height: 38px;\n  background: #ffffff;\n  border: 1px solid #ffffff;\n  border-radius: 0;\n  color: #004F9E;\n  cursor: pointer;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 6px;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 14px;\n  font-weight: 500;\n  transition: background .15s, color .15s;\n}\n.mv-search-btn:hover,\n.mv-search-btn.active { background: #F68900; border-color: #F68900; color: #ffffff; }\n.mv-search-btn svg { width: 16px; height: 16px; fill: none; stroke: currentColor; stroke-width: 2; stroke-linecap: round; flex-shrink: 0; }\n\n.mv-search-expanded {\n  display: none;\n  padding: 10px 18px 14px;\n  background: #004F9E;\n  border-top: 1px solid rgba(255,255,255,.25);\n  margin: 0 -18px -14px;\n}\n.mv-search-expanded.visible { display: block; }\n.mv-search-expanded input {\n  width: 100%;\n  padding: 8px 12px;\n  background: #ffffff;\n  border: 1px solid #ffffff;\n  border-radius: 0;\n  color: #111111;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 14px;\n  outline: none;\n  height: 38px;\n}\n.mv-search-expanded input::placeholder { color: #888888; }\n.mv-search-expanded input:focus { border-color: #F68900; }\n\n\/* \u2500\u2500 CHIPS \u2500\u2500 *\/\n.mv-chips {\n  display: flex;\n  gap: 6px;\n  flex-wrap: wrap;\n  padding: 7px 0 4px 0;\n  min-height: 0;\n}\n.mv-chip {\n  display: inline-flex;\n  align-items: center;\n  gap: 5px;\n  padding: 3px 10px;\n  background: #F68900;\n  color: #ffffff;\n  font-size: 12px;\n  font-weight: 600;\n}\n.mv-chip button {\n  background: none;\n  border: none;\n  color: rgba(255,255,255,.85);\n  cursor: pointer;\n  font-size: 14px;\n  line-height: 1;\n  padding: 0;\n}\n\n\/* \u2500\u2500 TOOLBAR \u2500\u2500 *\/\n.mv-toolbar { font-size: 14px; color: #666; margin-bottom: 5px; }\n.mv-toolbar strong { color: #111; }\n\n\/* \u2500\u2500 TABELLENKOPF \u2500\u2500 *\/\n.mv-table-header {\n  display: grid;\n  grid-template-columns: 110px 1fr 140px 100px 36px;\n  background: #004F9E;\n  color: #ffffff;\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 1px;\n  text-transform: uppercase;\n  padding: 8px 16px;\n}\n\n\/* \u2500\u2500 ERGEBNISLISTE \u2500\u2500 *\/\n.mv-list {\n  display: flex;\n  flex-direction: column;\n  border: 1px solid #cccccc;\n  border-top: none;\n}\n.mv-row {\n  background: #ffffff;\n  border-bottom: 1px solid #cccccc;\n  animation: mvFadeIn .2s ease both;\n}\n.mv-row:last-child { border-bottom: none; }\n@keyframes mvFadeIn { from { opacity: 0; } to { opacity: 1; } }\n\n.mv-row-head {\n  display: grid;\n  grid-template-columns: 110px 1fr 140px 100px 36px;\n  gap: 0;\n  align-items: center;\n  padding: 12px 16px;\n  cursor: pointer;\n  transition: background .1s;\n  user-select: none;\n}\n\/* Accessibility: Fokus sichtbar machen *\/\n.mv-row-head:focus { outline: 2px solid #F68900; outline-offset: -2px; }\n.mv-row-head:hover { background: #f4f8ff; }\n.mv-row.open .mv-row-head { background: #edf2fb; }\n\n\/* Datum 3-zeilig *\/\n.mv-row-date {\n  font-size: 13px;\n  font-weight: 700;\n  color: #F68900;\n  line-height: 1.6;\n  padding-right: 10px;\n  white-space: nowrap;\n}\n.mv-row-date .mv-date-sep {\n  display: block;\n  font-size: 12px;\n  font-weight: 400;\n  color: #888;\n  line-height: 1.4;\n}\n.mv-row-name {\n  font-weight: 500;\n  font-size: 15px;\n  color: #111;\n  line-height: 1.45;\n  padding-right: 12px;\n  word-break: break-word;\n}\n.mv-row-city {\n  font-size: 14px;\n  color: #444;\n  padding-right: 8px;\n  word-break: break-word;\n}\n\/* Messestadt mit Blogbeitrag: fett und in Firmenblau *\/\n.mv-city-link {\n  color: #004F9E;\n  font-weight: 700;\n  text-decoration: none;\n}\n.mv-city-link:hover {\n  text-decoration: underline;\n}\n.mv-row-country {\n  font-size: 13px;\n  color: #666;\n  padding-right: 8px;\n  word-break: break-word;\n}\n\n\/* Aktionen (ICS + Pfeil) *\/\n.mv-row-actions {\n  display: flex;\n  align-items: center;\n  justify-content: flex-end;\n  gap: 2px;\n}\n.mv-ics-btn {\n  background: none;\n  border: none;\n  cursor: pointer;\n  padding: 4px;\n  color: #aaaaaa;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  transition: color .15s, background .15s;\n  flex-shrink: 0;\n  position: relative;\n  border-radius: 2px;\n}\n.mv-ics-btn:hover { color: #004F9E; background: #e8f0fb; }\n.mv-ics-btn:focus { outline: 2px solid #F68900; }\n.mv-ics-btn svg { width: 16px; height: 16px; }\n.mv-ics-btn::after {\n  content: 'Im Kalender speichern';\n  position: absolute;\n  bottom: calc(100% + 6px);\n  right: 0;\n  background: #222;\n  color: #fff;\n  font-size: 11px;\n  font-family: 'Ubuntu', sans-serif;\n  white-space: nowrap;\n  padding: 4px 8px;\n  pointer-events: none;\n  opacity: 0;\n  transition: opacity .15s;\n  z-index: 100;\n}\n.mv-ics-btn:hover::after { opacity: 1; }\n\n.mv-row-arrow {\n  color: #999;\n  font-size: 10px;\n  transition: transform .2s;\n  padding-top: 3px;\n  pointer-events: none;\n}\n.mv-row.open .mv-row-arrow { transform: rotate(180deg); }\n\n\/* \u2500\u2500 DETAIL \u2500\u2500 *\/\n.mv-row-body {\n  display: none;\n  padding: 14px 16px 16px;\n  border-top: 1px solid #e0e0e0;\n  background: #f7faff;\n}\n.mv-row.open .mv-row-body { display: block; }\n\n.mv-detail-grid {\n  display: grid;\n  grid-template-columns: repeat(auto-fill, minmax(180px, 1fr));\n  gap: 12px 20px;\n}\n.mv-detail-item { display: flex; flex-direction: column; gap: 3px; }\n.mv-detail-label {\n  font-size: 11px;\n  font-weight: 700;\n  letter-spacing: 1px;\n  text-transform: uppercase;\n  color: #888;\n}\n.mv-detail-value { font-size: 14px; color: #111; line-height: 1.45; }\n.mv-detail-value a { color: #004F9E; text-decoration: none; font-weight: 500; }\n.mv-detail-value a:hover { text-decoration: underline; }\n\n\/* ICS-Button im Detail *\/\n.mv-ics-detail-btn {\n  display: inline-flex;\n  align-items: center;\n  gap: 7px;\n  margin-top: 14px;\n  padding: 8px 16px;\n  background: #ffffff;\n  border: 1px solid #004F9E;\n  color: #004F9E;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 13px;\n  font-weight: 500;\n  cursor: pointer;\n  transition: background .15s, color .15s;\n  text-decoration: none;\n}\n.mv-ics-detail-btn:hover { background: #004F9E; color: #ffffff; }\n.mv-ics-detail-btn:focus { outline: 2px solid #F68900; }\n.mv-ics-detail-btn svg { width: 15px; height: 15px; flex-shrink: 0; }\n\n.mv-description-full {\n  margin-top: 13px;\n  padding-top: 12px;\n  border-top: 1px solid #e0e0e0;\n  font-size: 15px;\n  color: #666;\n  line-height: 1.6;\n  font-style: italic;\n}\n\n\/* \u2500\u2500 LOADING \/ LEER \u2500\u2500 *\/\n.mv-loading {\n  display: flex;\n  align-items: center;\n  justify-content: center;\n  gap: 10px;\n  padding: 50px;\n  color: #888;\n  font-size: 14px;\n}\n.mv-spinner {\n  width: 18px; height: 18px;\n  border: 2px solid #ddd;\n  border-top-color: #004F9E;\n  border-radius: 50%;\n  animation: mvSpin .65s linear infinite;\n  flex-shrink: 0;\n}\n@keyframes mvSpin { to { transform: rotate(360deg); } }\n.mv-empty { text-align: center; padding: 40px; color: #888; font-size: 14px; }\n.mv-empty-icon { font-size: 28px; margin-bottom: 8px; }\n.mv-empty h3 { font-size: 16px; color: #111; margin-bottom: 4px; }\n\n\/* \u2500\u2500 PAGINATION \u2500\u2500 *\/\n.mv-pagination {\n  display: flex;\n  justify-content: center;\n  align-items: center;\n  gap: 4px;\n  margin-top: 16px;\n  flex-wrap: wrap;\n}\n.mv-page-btn {\n  min-width: 34px; height: 34px;\n  padding: 0 8px;\n  border: 1px solid #cccccc;\n  background: #ffffff;\n  color: #111;\n  font-family: 'Ubuntu', sans-serif;\n  font-size: 14px;\n  cursor: pointer;\n  transition: all .12s;\n  display: flex;\n  align-items: center;\n  justify-content: center;\n}\n.mv-page-btn:hover { border-color: #004F9E; color: #004F9E; }\n.mv-page-btn.active { background: #004F9E; color: #ffffff; border-color: #004F9E; font-weight: 600; }\n.mv-page-btn:disabled { opacity: .3; cursor: default; pointer-events: none; }\n.mv-page-btn:focus { outline: 2px solid #F68900; }\n.mv-page-info { font-size: 13px; color: #888; padding: 0 6px; }\n\n\/* \u2500\u2500 RESPONSIVE \u2500\u2500 *\/\n@media (max-width: 900px) {\n  .mv-table-header,\n  .mv-row-head {\n    grid-template-columns: 100px 1fr 120px 36px;\n  }\n  .mv-row-country { display: none; }\n  .mv-table-header .mv-th-country { display: none; }\n}\n@media (max-width: 768px) {\n  .mv-filters { flex-direction: column; align-items: stretch; }\n  .mv-filter-group,\n  .mv-period-wrap,\n  .mv-search-wrap { flex: none; width: 100%; min-width: 0; }\n  .mv-table-header { display: none; }\n  .mv-row-head { grid-template-columns: 90px 1fr 30px; }\n  .mv-row-city,\n  .mv-row-country { display: none; }\n  .mv-ics-btn { display: none; }\n}\n@media (max-width: 480px) {\n  .mv-row-head { padding: 10px 12px; grid-template-columns: 85px 1fr 18px; }\n  .mv-row-body { padding: 12px; }\n  .mv-detail-grid { grid-template-columns: 1fr 1fr; }\n}\n<\/style>\n\n<div class=\"mv-wrap\" id=\"mv-app\">\n\n  <!-- FILTER BAR -->\n  <div class=\"mv-filters\" id=\"mv-filters\">\n\n    <div class=\"mv-filter-group\">\n      <label for=\"mv-location\">Ort<\/label>\n      <select id=\"mv-location\" aria-label=\"Nach Ort filtern\">\n        <option value=\"\">Alle Orte<\/option>\n      <\/select>\n    <\/div>\n\n    <div class=\"mv-filter-group\">\n      <label for=\"mv-country\">Land<\/label>\n      <select id=\"mv-country\" aria-label=\"Nach Land filtern\">\n        <option value=\"\">Alle L\u00e4nder<\/option>\n      <\/select>\n    <\/div>\n\n    <div class=\"mv-period-wrap\">\n      <label>Datum<\/label>\n      <div class=\"mv-period-selects\">\n        <select id=\"mv-month\" aria-label=\"Nach Monat filtern\">\n          <option value=\"\">Monat<\/option>\n          <option value=\"1\">Januar<\/option>\n          <option value=\"2\">Februar<\/option>\n          <option value=\"3\">M\u00e4rz<\/option>\n          <option value=\"4\">April<\/option>\n          <option value=\"5\">Mai<\/option>\n          <option value=\"6\">Juni<\/option>\n          <option value=\"7\">Juli<\/option>\n          <option value=\"8\">August<\/option>\n          <option value=\"9\">September<\/option>\n          <option value=\"10\">Oktober<\/option>\n          <option value=\"11\">November<\/option>\n          <option value=\"12\">Dezember<\/option>\n        <\/select>\n        <select id=\"mv-year\" aria-label=\"Nach Jahr filtern\">\n          <option value=\"\">Jahr<\/option>\n        <\/select>\n      <\/div>\n    <\/div>\n\n    <div class=\"mv-filter-group\">\n      <label for=\"mv-branch\">Branche<\/label>\n      <select id=\"mv-branch\" aria-label=\"Nach Branche filtern\">\n        <option value=\"\">Alle Branchen<\/option>\n      <\/select>\n    <\/div>\n\n    <div class=\"mv-search-wrap\">\n      <label>Suche<\/label>\n      <button class=\"mv-search-btn\" id=\"mv-search-toggle\"\n              onclick=\"MV.toggleSearch()\"\n              aria-expanded=\"false\"\n              aria-controls=\"mv-search-expanded\">\n        <svg viewBox=\"0 0 24 24\" aria-hidden=\"true\"><circle cx=\"11\" cy=\"11\" r=\"8\"\/><line x1=\"21\" y1=\"21\" x2=\"16.65\" y2=\"16.65\"\/><\/svg>\n        Suche\n      <\/button>\n    <\/div>\n\n    <div class=\"mv-search-expanded\" id=\"mv-search-expanded\" role=\"search\">\n      <input type=\"search\" id=\"mv-search-input\"\n             placeholder=\"Name, Ort, Land, Branche ...\"\n             autocomplete=\"off\"\n             aria-label=\"Freitextsuche\">\n    <\/div>\n  <\/div>\n\n  <!-- CHIPS -->\n  <div class=\"mv-chips\" id=\"mv-chips\" aria-live=\"polite\"><\/div>\n\n  <!-- TOOLBAR -->\n  <div class=\"mv-toolbar\" aria-live=\"polite\">\n    <span id=\"mv-count\">Lade ...<\/span>\n  <\/div>\n\n  <!-- TABELLENKOPF -->\n  <div class=\"mv-table-header\" id=\"mv-table-header\" style=\"display:none\" aria-hidden=\"true\">\n    <div>Datum<\/div>\n    <div>Veranstaltung<\/div>\n    <div>Ort<\/div>\n    <div class=\"mv-th-country\">Land<\/div>\n    <div><\/div>\n  <\/div>\n\n  <!-- ERGEBNISLISTE -->\n  <div id=\"mv-results\" role=\"list\">\n    <div class=\"mv-loading\"><div class=\"mv-spinner\"><\/div> Veranstaltungen werden geladen ...<\/div>\n  <\/div>\n\n  <!-- PAGINATION -->\n  <nav class=\"mv-pagination\" id=\"mv-pagination\" aria-label=\"Seitennavigation\"><\/nav>\n<\/div>\n\n<script>\n(function() {\n\"use strict\";\n\nconst CFG = {\n  proxyEvents : \"https:\\\/\\\/www.displayhersteller.de\\\/ratgeber\\\/wp-json\\\/messe-proxy\\\/v1\\\/events\",\n  perPage     : 30,\n};\n\nconst state = {\n  all        : [],\n  filtered   : [],\n  page       : 1,\n  openId     : null,\n  _pageSlice : [],\n  filters    : { location:'', country:'', month:'', year:'', branch:'', search:'' },\n};\n\n\/\/ \u2500\u2500 ICS DOWNLOAD \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction toIcsDate(ms) {\n  if (!ms) return null;\n  const d  = new Date(ms);\n  const yy = d.getUTCFullYear();\n  const mm = String(d.getUTCMonth() + 1).padStart(2, '0');\n  const dd = String(d.getUTCDate()).padStart(2, '0');\n  return yy + mm + dd;\n}\n\nfunction escIcs(str) {\n  if (!str) return '';\n  return String(str)\n    .replace(\/\\\\\/g, '\\\\\\\\')\n    .replace(\/;\/g,  '\\\\;')\n    .replace(\/,\/g,  '\\\\,')\n    .replace(\/\\n\/g, '\\\\n')\n    .replace(\/\\r\/g, '');\n}\n\nfunction foldIcsLine(line) {\n  if (line.length <= 75) return line;\n  let out = '';\n  while (line.length > 75) {\n    out  += line.substring(0, 75) + '\\r\\n ';\n    line  = line.substring(75);\n  }\n  return out + line;\n}\n\nfunction downloadIcs(ev) {\n  const dtStart = toIcsDate(ev.date_start);\n  if (!dtStart) { alert('Kein Startdatum vorhanden.'); return; }\n\n  const endMs   = ev.date_end ? ev.date_end : ev.date_start;\n  const endPlus = new Date(endMs);\n  endPlus.setUTCDate(endPlus.getUTCDate() + 1);\n  const dtEnd   = toIcsDate(endPlus.getTime());\n\n  const uid   = 'messe-' + (ev.id || Math.random().toString(36).substring(2)) + '@messekalender';\n  const now   = new Date();\n  const stamp = now.getUTCFullYear()\n    + String(now.getUTCMonth()+1).padStart(2,'0')\n    + String(now.getUTCDate()).padStart(2,'0')\n    + 'T'\n    + String(now.getUTCHours()).padStart(2,'0')\n    + String(now.getUTCMinutes()).padStart(2,'0')\n    + String(now.getUTCSeconds()).padStart(2,'0')\n    + 'Z';\n\n  const descParts = [];\n  if (ev.branch_name)  descParts.push('Branche: '    + ev.branch_name);\n  if (ev.country_name) descParts.push('Land: '       + ev.country_name);\n  if (ev.type)         descParts.push('Format: '     + ev.type);\n  if (ev.scope)        descParts.push('Reichweite: ' + ev.scope);\n  if (ev.turnus)       descParts.push('Turnus: '     + ev.turnus);\n  if (ev.website)      descParts.push('Website: '    + ev.website);\n  if (ev.description)  descParts.push('\\n' + ev.description);\n\n  const location = [ev.city, ev.country_name].filter(Boolean).join(', ');\n\n  const lines = [\n    'BEGIN:VCALENDAR',\n    'VERSION:2.0',\n    'PRODID:-\/\/Messekalender\/\/DE',\n    'CALSCALE:GREGORIAN',\n    'METHOD:PUBLISH',\n    'BEGIN:VEVENT',\n    foldIcsLine('UID:' + escIcs(uid)),\n    'DTSTAMP:' + stamp,\n    'DTSTART;VALUE=DATE:' + dtStart,\n    'DTEND;VALUE=DATE:' + dtEnd,\n    foldIcsLine('SUMMARY:' + escIcs(ev.name)),\n    location ? foldIcsLine('LOCATION:' + escIcs(location)) : '',\n    descParts.length ? foldIcsLine('DESCRIPTION:' + escIcs(descParts.join('\\n'))) : '',\n    ev.website ? foldIcsLine('URL:' + escIcs(ev.website)) : '',\n    'END:VEVENT',\n    'END:VCALENDAR',\n  ].filter(Boolean).join('\\r\\n');\n\n  const safeName = (ev.name || 'event')\n    .replace(\/[^a-zA-Z0-9\u00e4\u00f6\u00fc\u00c4\u00d6\u00dc\u00df\\s\\-]\/g, '')\n    .replace(\/\\s+\/g, '_')\n    .substring(0, 60);\n\n  const blob = new Blob([lines], { type: 'text\/calendar;charset=utf-8' });\n  const url  = URL.createObjectURL(blob);\n  const a    = document.createElement('a');\n  a.href     = url;\n  a.download = safeName + '.ics';\n  document.body.appendChild(a);\n  a.click();\n  document.body.removeChild(a);\n  setTimeout(() => URL.revokeObjectURL(url), 1000);\n}\n\n\/\/ \u2500\u2500 HILFSFUNKTIONEN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction fmtDay(ms) {\n  if (!ms) return '-';\n  const d  = new Date(ms);\n  const dd = String(d.getDate()).padStart(2,'0');\n  const mm = String(d.getMonth()+1).padStart(2,'0');\n  return dd + '.' + mm + '.' + d.getFullYear();\n}\n\nfunction fmtDateBlock(s, e) {\n  if (!s) return '-';\n  const start = fmtDay(s);\n  const end   = e ? fmtDay(e) : null;\n  if (!end || start === end) return start;\n  return start + '<span class=\"mv-date-sep\">bis<\/span>' + end;\n}\n\nfunction esc(s) {\n  return String(s == null ? '' : s)\n    .replace(\/&\/g,'&amp;').replace(\/<\/g,'&lt;').replace(\/>\/g,'&gt;').replace(\/\"\/g,'&quot;');\n}\n\n\/\/ \u2500\u2500 API LADEN \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nasync function loadEvents() {\n  \/\/ \u2500\u2500 Browser-Cache (sessionStorage) pr\u00fcfen \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n  \/\/ Cache-Key enth\u00e4lt das Datum, damit er t\u00e4glich automatisch invalidiert wird.\n  const cacheKey = 'mv_events_' + new Date().toISOString().substring(0, 10);\n  try {\n    const cached = sessionStorage.getItem(cacheKey);\n    if (cached) {\n      const parsed = JSON.parse(cached);\n      if (parsed && Array.isArray(parsed.events) && parsed.events.length) {\n        state.all = parsed.events;\n        applyFilters();\n        return; \/\/ Sofort fertig \u2013 kein Server-Request n\u00f6tig\n      }\n    }\n  } catch(e) { \/* sessionStorage nicht verf\u00fcgbar (z.B. Private Mode) \u2013 ignorieren *\/ }\n\n  showLoading();\n  try {\n    const r = await fetch(CFG.proxyEvents, { credentials: 'same-origin' });\n    if (!r.ok) throw new Error('HTTP ' + r.status);\n    const j = await r.json();\n    state.all = j.events || (Array.isArray(j) ? j : []);\n    if (!state.all.length) {\n      document.getElementById('mv-results').innerHTML =\n        '<div class=\"mv-empty\"><div class=\"mv-empty-icon\">&#128269;<\/div><h3>Keine Daten<\/h3><p>API hat geantwortet aber keine Events geliefert.<\/p><\/div>';\n      return;\n    }\n    \/\/ Im sessionStorage ablegen f\u00fcr Wiederholungsbesuche in derselben Session\n    try {\n      \/\/ Alte Cache-Keys aufr\u00e4umen (andere Tage)\n      Object.keys(sessionStorage)\n        .filter(k => k.startsWith('mv_events_') && k !== cacheKey)\n        .forEach(k => sessionStorage.removeItem(k));\n      sessionStorage.setItem(cacheKey, JSON.stringify({ events: state.all }));\n    } catch(e) { \/* Quota \u00fcberschritten oder nicht verf\u00fcgbar \u2013 kein Problem *\/ }\n\n    applyFilters();\n  } catch(e) {\n    document.getElementById('mv-results').innerHTML =\n      '<div class=\"mv-empty\"><div class=\"mv-empty-icon\">&#9888;<\/div><h3>Ladefehler<\/h3><p>' + esc(e.message) + '<\/p><\/div>';\n  }\n}\n\nfunction populateSelect(id, items, placeholder, currentValue) {\n  const sel = document.getElementById(id);\n  if (!sel) return;\n  sel.innerHTML = '<option value=\"\">' + esc(placeholder) + '<\/option>' +\n    items.map(item =>\n      '<option value=\"' + esc(item.value) + '\"' + (item.value === currentValue ? ' selected' : '') + '>'\n      + esc(item.label) + '<\/option>'\n    ).join('');\n}\n\n\/**\n * Bedingte Filteroptionen:\n * F\u00fcr jeden Dropdown werden nur die Werte angeboten, die in den Events\n * vorkommen, die durch ALLE ANDEREN aktiven Filter bereits gefiltert sind.\n * So bleibt die aktuelle Auswahl erhalten, aber inkompatible Optionen\n * verschwinden.\n *\/\nfunction updateFilterOptions() {\n  const f = state.filters;\n  const q = f.search.toLowerCase().trim();\n\n  \/\/ Hilfsfunktion: filtert Events, l\u00e4sst dabei einen bestimmten Filter-Key weg\n  function eventsWithout(skipKey) {\n    return state.all.filter(ev => {\n      if (skipKey !== 'location' && f.location && ev.city         !== f.location)  return false;\n      if (skipKey !== 'country'  && f.country  && ev.country_name !== f.country)   return false;\n      if (skipKey !== 'branch'   && f.branch   && ev.branch_name  !== f.branch)    return false;\n      if (f.month || f.year) {\n        const skipMonth = (skipKey === 'month');\n        const skipYear  = (skipKey === 'year');\n        const needDate  = (!skipMonth && f.month) || (!skipYear && f.year);\n        if (needDate) {\n          if (!ev.date_start) return false;\n          const d = new Date(ev.date_start);\n          if (!skipMonth && f.month && (d.getMonth() + 1) !== parseInt(f.month)) return false;\n          if (!skipYear  && f.year  && d.getFullYear()     !== parseInt(f.year))  return false;\n        }\n      }\n      if (skipKey !== 'search' && q &&\n          ![ev.name, ev.city, ev.country_name, ev.branch_name, ev.audience, ev.type, ev.description, ev.scope, ev.turnus]\n            .join(' ').toLowerCase().includes(q)) return false;\n      return true;\n    });\n  }\n\n  \/\/ \u2500\u2500 ORT \u2500\u2500\n  const locPool = eventsWithout('location');\n  const locs    = [...new Set(locPool.map(e => e.city).filter(Boolean))].sort();\n  populateSelect('mv-location', locs.map(v => ({ value: v, label: v })), 'Alle Orte', f.location);\n\n  \/\/ \u2500\u2500 LAND \u2500\u2500\n  const cntPool = eventsWithout('country');\n  const cnts    = [...new Set(cntPool.map(e => e.country_name).filter(Boolean))].sort();\n  populateSelect('mv-country', cnts.map(v => ({ value: v, label: v })), 'Alle L\u00e4nder', f.country);\n\n  \/\/ \u2500\u2500 BRANCHE \u2500\u2500\n  const brPool  = eventsWithout('branch');\n  const brs     = [...new Set(brPool.map(e => e.branch_name).filter(Boolean))].sort();\n  populateSelect('mv-branch', brs.map(v => ({ value: v, label: v })), 'Alle Branchen', f.branch);\n\n  \/\/ \u2500\u2500 JAHR \u2500\u2500 (Monat bleibt statisch, Jahr passt sich an)\n  const yrPool  = eventsWithout('year');\n  const years   = [...new Set(\n    yrPool.filter(e => e.date_start).map(e => new Date(e.date_start).getFullYear())\n  )].sort();\n  const yrSel   = document.getElementById('mv-year');\n  if (yrSel) {\n    yrSel.innerHTML = '<option value=\"\">Jahr<\/option>' +\n      years.map(y => '<option value=\"' + y + '\"' + (String(y) === f.year ? ' selected' : '') + '>' + y + '<\/option>').join('');\n  }\n}\n\n\/\/ \u2500\u2500 FILTER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nfunction applyFilters() {\n  const f = state.filters;\n  const q = f.search.toLowerCase().trim();\n\n  state.filtered = state.all.filter(ev => {\n    if (f.location && ev.city         !== f.location)  return false;\n    if (f.country  && ev.country_name !== f.country)   return false;\n    if (f.branch   && ev.branch_name  !== f.branch)    return false;\n    if (f.month || f.year) {\n      if (!ev.date_start) return false;\n      const d = new Date(ev.date_start);\n      if (f.month && (d.getMonth() + 1) !== parseInt(f.month)) return false;\n      if (f.year  && d.getFullYear()     !== parseInt(f.year))  return false;\n    }\n    if (q && ![ev.name, ev.city, ev.country_name, ev.branch_name, ev.audience, ev.type, ev.description, ev.scope, ev.turnus]\n               .join(' ').toLowerCase().includes(q)) return false;\n    return true;\n  });\n\n  state.page = 1;\n  updateFilterOptions(); \/\/ \u2190 Dropdowns nach jedem Filter-Schritt neu berechnen\n  renderChips();\n  renderCount();\n  renderPage();\n  renderPagination();\n}\n\n\/\/ \u2500\u2500 CHIPS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst MONTHS_DE = ['','Januar','Februar','M\u00e4rz','April','Mai','Juni','Juli','August','September','Oktober','November','Dezember'];\n\nfunction renderChips() {\n  const f     = state.filters;\n  const chips = [];\n  if (f.location) chips.push({ key:'location', label:'Ort: '     + f.location });\n  if (f.country)  chips.push({ key:'country',  label:'Land: '    + f.country });\n  if (f.month)    chips.push({ key:'month',    label: MONTHS_DE[+f.month] || f.month });\n  if (f.year)     chips.push({ key:'year',     label: f.year });\n  if (f.branch)   chips.push({ key:'branch',   label:'Branche: ' + f.branch });\n  if (f.search)   chips.push({ key:'search',   label:'Suche: '   + f.search });\n\n  document.getElementById('mv-chips').innerHTML = chips.map(c =>\n    '<span class=\"mv-chip\">' + esc(c.label) +\n    '<button onclick=\"MV.clearFilter(\\'' + c.key + '\\')\" aria-label=\"Filter entfernen: ' + esc(c.label) + '\">&times;<\/button><\/span>'\n  ).join('');\n}\n\nfunction renderCount() {\n  const n = state.filtered.length;\n  document.getElementById('mv-count').innerHTML =\n    '<strong>' + n.toLocaleString('de-DE') + '<\/strong> Veranstaltung' + (n !== 1 ? 'en' : '') + ' gefunden';\n}\n\n\/\/ \u2500\u2500 RENDER \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst calSvg = '<svg viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\">'\n  + '<rect x=\"3\" y=\"4\" width=\"18\" height=\"18\"\/>'\n  + '<line x1=\"16\" y1=\"2\" x2=\"16\" y2=\"6\"\/>'\n  + '<line x1=\"8\" y1=\"2\" x2=\"8\" y2=\"6\"\/>'\n  + '<line x1=\"3\" y1=\"10\" x2=\"21\" y2=\"10\"\/>'\n  + '<\/svg>';\n\nfunction renderPage() {\n  const start = (state.page - 1) * CFG.perPage;\n  const slice = state.filtered.slice(start, start + CFG.perPage);\n  const el    = document.getElementById('mv-results');\n  const hdr   = document.getElementById('mv-table-header');\n\n  state._pageSlice = slice;\n\n  if (!slice.length) {\n    if (hdr) hdr.style.display = 'none';\n    el.innerHTML = '<div class=\"mv-empty\"><div class=\"mv-empty-icon\">&#128269;<\/div><h3>Keine Ergebnisse<\/h3><p>Andere Filter probieren.<\/p><\/div>';\n    return;\n  }\n  if (hdr) hdr.style.display = 'grid';\n\n  el.innerHTML = '<div class=\"mv-list\">' + slice.map(function(ev, i) {\n    const isOpen   = (state.openId === ev.id);\n    const dateHTML = fmtDateBlock(ev.date_start, ev.date_end);\n\n    const details = [];\n    if (ev.branch_name)    details.push({ label:'Branche',              val: ev.branch_name });\n    if (ev.type)           details.push({ label:'Format',               val: ev.type });\n    if (ev.scope)          details.push({ label:'Reichweite',           val: ev.scope });\n    if (ev.turnus)         details.push({ label:'Turnus',               val: ev.turnus });\n    if (ev.city) {\n      const cityVal = ev.city_link\n        ? '<a href=\"' + esc(ev.city_link) + '\" class=\"mv-city-link\" target=\"_blank\" rel=\"noopener\">' + esc(ev.city) + '<\/a>'\n        : esc(ev.city);\n      details.push({ label:'Ort', val: cityVal });\n    }\n    if (ev.country_name)   details.push({ label:'Land',                 val: ev.country_name });\n    if (ev.website)        details.push({ label:'Website',              val: '<a href=\"' + esc(ev.website) + '\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">zur Veranstaltung &#8599;<\/a>' });\n    if (ev.exhibitor_list) details.push({ label:'Ausstellerverzeichnis',val: '<a href=\"' + esc(ev.exhibitor_list) + '\" target=\"_blank\" rel=\"nofollow noopener noreferrer\">zum Verzeichnis &#8599;<\/a>' });\n\n    \/\/ Stadtname in der Tabellenzeile: mit Link wenn vorhanden\n    const cityDisplay = ev.city_link\n      ? '<a href=\"' + esc(ev.city_link) + '\" class=\"mv-city-link\" target=\"_blank\" rel=\"noopener\" onclick=\"event.stopPropagation();\">' + esc(ev.city) + '<\/a>'\n      : esc(ev.city || '');\n\n    const detailHTML = details.length\n      ? '<div class=\"mv-detail-grid\">' + details.map(d =>\n          '<div class=\"mv-detail-item\">'\n          + '<span class=\"mv-detail-label\">' + esc(d.label) + '<\/span>'\n          + '<span class=\"mv-detail-value\">' + d.val + '<\/span>'\n          + '<\/div>'\n        ).join('') + '<\/div>'\n      : '';\n\n    const descHTML = ev.description\n      ? '<p class=\"mv-description-full\">' + esc(ev.description) + '<\/p>'\n      : '';\n\n    const icsBtn = '<button class=\"mv-ics-detail-btn\" onclick=\"MV.downloadIcs(' + i + ')\" aria-label=\"Event im Kalender speichern\">'\n      + calSvg + ' Im Kalender speichern<\/button>';\n\n    const evId = esc(String(ev.id ?? i));\n\n    return '<div class=\"mv-row' + (isOpen ? ' open' : '') + '\" id=\"mvrow-' + evId + '\" role=\"listitem\" style=\"animation-delay:' + (i * 0.02) + 's\">'\n      + '<div class=\"mv-row-head\" onclick=\"MV.toggle(\\'' + evId + '\\')\"'\n      +      ' tabindex=\"0\" role=\"button\"'\n      +      ' aria-expanded=\"' + (isOpen ? 'true' : 'false') + '\"'\n      +      ' onkeydown=\"if(event.key===\\'Enter\\'||event.key===\\' \\'){MV.toggle(\\'' + evId + '\\');event.preventDefault();}\">'\n      +   '<div class=\"mv-row-date\">' + dateHTML + '<\/div>'\n      +   '<div class=\"mv-row-name\">' + esc(ev.name) + '<\/div>'\n      +   '<div class=\"mv-row-city\">' + cityDisplay + '<\/div>'\n      +   '<div class=\"mv-row-country\">' + esc(ev.country_name || '') + '<\/div>'\n      +   '<div class=\"mv-row-actions\">'\n      +     '<button class=\"mv-ics-btn\" title=\"Im Kalender speichern\" aria-label=\"Im Kalender speichern\" onclick=\"MV.downloadIcs(' + i + ');event.stopPropagation();\">' + calSvg + '<\/button>'\n      +     '<div class=\"mv-row-arrow\" aria-hidden=\"true\">&#9660;<\/div>'\n      +   '<\/div>'\n      + '<\/div>'\n      + '<div class=\"mv-row-body\" role=\"region\" aria-label=\"Details: ' + esc(ev.name) + '\">'\n      +   detailHTML + descHTML + icsBtn\n      +   (!details.length && !ev.description ? '<p style=\"color:#888;font-size:14px;padding-top:12px\">Noch keine weiteren Informationen verf\u00fcgbar.<\/p>' : '')\n      + '<\/div>'\n      + '<\/div>';\n  }).join('') + '<\/div>';\n}\n\nfunction renderPagination() {\n  const pages = Math.ceil(state.filtered.length \/ CFG.perPage);\n  const pager = document.getElementById('mv-pagination');\n  if (pages <= 1) { pager.innerHTML = ''; return; }\n  const p    = state.page;\n  const nums = pages <= 7\n    ? Array.from({length: pages}, (_, i) => i + 1)\n    : p <= 4       ? [1,2,3,4,5,'...',pages]\n    : p >= pages-3 ? [1,'...',pages-4,pages-3,pages-2,pages-1,pages]\n    : [1,'...',p-1,p,p+1,'...',pages];\n\n  pager.innerHTML =\n    '<button class=\"mv-page-btn\" onclick=\"MV.goPage(' + (p-1) + ')\" aria-label=\"Vorherige Seite\" ' + (p===1?'disabled':'') + '>&#8249;<\/button>' +\n    nums.map(n => n === '...'\n      ? '<span class=\"mv-page-info\">...<\/span>'\n      : '<button class=\"mv-page-btn ' + (n===p?'active':'') + '\" onclick=\"MV.goPage(' + n + ')\" aria-label=\"Seite ' + n + '\" aria-current=\"' + (n===p?'page':'false') + '\">' + n + '<\/button>'\n    ).join('') +\n    '<button class=\"mv-page-btn\" onclick=\"MV.goPage(' + (p+1) + ')\" aria-label=\"N\u00e4chste Seite\" ' + (p===pages?'disabled':'') + '>&#8250;<\/button>' +\n    '<span class=\"mv-page-info\" aria-live=\"polite\">Seite ' + p + ' von ' + pages + '<\/span>';\n}\n\nfunction showLoading() {\n  document.getElementById('mv-results').innerHTML =\n    '<div class=\"mv-loading\"><div class=\"mv-spinner\" role=\"status\" aria-label=\"Wird geladen\"><\/div> Lade Veranstaltungen ...<\/div>';\n}\n\n\/\/ \u2500\u2500 EVENT LISTENERS \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\ndocument.getElementById('mv-location').addEventListener('change', function(e) {\n  state.filters.location = e.target.value; applyFilters();\n});\ndocument.getElementById('mv-country').addEventListener('change', function(e) {\n  state.filters.country = e.target.value; applyFilters();\n});\ndocument.getElementById('mv-branch').addEventListener('change', function(e) {\n  state.filters.branch = e.target.value; applyFilters();\n});\ndocument.getElementById('mv-month').addEventListener('change', function(e) {\n  state.filters.month = e.target.value; applyFilters();\n});\ndocument.getElementById('mv-year').addEventListener('change', function(e) {\n  state.filters.year = e.target.value; applyFilters();\n});\n\nvar searchTimer;\ndocument.getElementById('mv-search-input').addEventListener('input', function(e) {\n  clearTimeout(searchTimer);\n  searchTimer = setTimeout(function() {\n    state.filters.search = e.target.value;\n    applyFilters();\n  }, 260);\n});\n\n\/\/ \u2500\u2500 PUBLIC API \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nwindow.MV = {\n  toggle: function(id) {\n    const wasOpen = (state.openId === id);\n    state.openId  = wasOpen ? null : id;\n    document.querySelectorAll('.mv-row').forEach(function(el) {\n      el.classList.remove('open');\n      const head = el.querySelector('.mv-row-head');\n      if (head) head.setAttribute('aria-expanded', 'false');\n    });\n    if (state.openId) {\n      const row  = document.getElementById('mvrow-' + id);\n      const head = row && row.querySelector('.mv-row-head');\n      if (row)  row.classList.add('open');\n      if (head) head.setAttribute('aria-expanded', 'true');\n    }\n  },\n  goPage: function(p) {\n    const max = Math.ceil(state.filtered.length \/ CFG.perPage);\n    if (p < 1 || p > max) return;\n    state.page   = p;\n    state.openId = null;\n    renderPage();\n    renderPagination();\n    document.getElementById('mv-app').scrollIntoView({ behavior: 'smooth', block: 'start' });\n  },\n  clearFilter: function(key) {\n    state.filters[key] = '';\n    const map = {\n      location : 'mv-location',\n      country  : 'mv-country',\n      branch   : 'mv-branch',\n      month    : 'mv-month',\n      year     : 'mv-year',\n    };\n    if (map[key]) document.getElementById(map[key]).value = '';\n    if (key === 'search') {\n      document.getElementById('mv-search-input').value = '';\n      document.getElementById('mv-search-expanded').classList.remove('visible');\n      const btn = document.getElementById('mv-search-toggle');\n      btn.classList.remove('active');\n      btn.setAttribute('aria-expanded', 'false');\n    }\n    applyFilters();\n  },\n  toggleSearch: function() {\n    const btn      = document.getElementById('mv-search-toggle');\n    const expanded = document.getElementById('mv-search-expanded');\n    const isOpen   = expanded.classList.toggle('visible');\n    btn.classList.toggle('active', isOpen);\n    btn.setAttribute('aria-expanded', String(isOpen));\n    if (isOpen) {\n      setTimeout(function() { document.getElementById('mv-search-input').focus(); }, 50);\n    } else {\n      state.filters.search = '';\n      document.getElementById('mv-search-input').value = '';\n      applyFilters();\n    }\n  },\n  downloadIcs: function(idx) {\n    const ev = state._pageSlice && state._pageSlice[idx];\n    if (ev) downloadIcs(ev);\n  }\n};\n\n\/\/ \u2500\u2500 START \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nloadEvents();\n\n})();\n<\/script>\n    [\/vc_column_text][\/vc_column][\/vc_row]\n","protected":false},"excerpt":{"rendered":"<p>[vc_row type=&#8221;in_container&#8221; full_screen_row_position=&#8221;middle&#8221; column_margin=&#8221;default&#8221; column_direction=&#8221;default&#8221; column_direction_tablet=&#8221;default&#8221; column_direction_phone=&#8221;default&#8221; scene_position=&#8221;center&#8221; text_color=&#8221;dark&#8221; text_align=&#8221;left&#8221; row_border_radius=&#8221;none&#8221; row_border_radius_applies=&#8221;bg&#8221; overflow=&#8221;visible&#8221; overlay_strength=&#8221;0.3&#8243; gradient_direction=&#8221;left_to_right&#8221;&#8230;<\/p>\n","protected":false},"author":6,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"_acf_changed":false,"_lmt_disableupdate":"no","_lmt_disable":"","footnotes":""},"class_list":["post-10058","page","type-page","status-publish"],"acf":[],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v27.3 - https:\/\/yoast.com\/product\/yoast-seo-wordpress\/ -->\n<title>Messekalender | Aktuelle Messetermine im \u00dcberblick<\/title>\n<meta name=\"description\" content=\"Alle wichtigen Messen und Branchentermine auf einen Blick. Jetzt informieren und Messeauftritte fr\u00fchzeitig planen.\" \/>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender\" \/>\n<meta property=\"og:locale\" content=\"de_DE\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Messekalender | Aktuelle Messetermine im \u00dcberblick\" \/>\n<meta property=\"og:description\" content=\"Alle wichtigen Messen und Branchentermine auf einen Blick. Jetzt informieren und Messeauftritte fr\u00fchzeitig planen.\" \/>\n<meta property=\"og:url\" content=\"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender\" \/>\n<meta property=\"og:site_name\" content=\"displayhersteller Ratgeber\" \/>\n<meta property=\"article:modified_time\" content=\"2026-03-02T10:25:59+00:00\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:label1\" content=\"Gesch\u00e4tzte Lesezeit\" \/>\n\t<meta name=\"twitter:data1\" content=\"1\u00a0Minute\" \/>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Messekalender | Aktuelle Messetermine im \u00dcberblick","description":"Alle wichtigen Messen und Branchentermine auf einen Blick. Jetzt informieren und Messeauftritte fr\u00fchzeitig planen.","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender","og_locale":"de_DE","og_type":"article","og_title":"Messekalender | Aktuelle Messetermine im \u00dcberblick","og_description":"Alle wichtigen Messen und Branchentermine auf einen Blick. Jetzt informieren und Messeauftritte fr\u00fchzeitig planen.","og_url":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender","og_site_name":"displayhersteller Ratgeber","article_modified_time":"2026-03-02T10:25:59+00:00","twitter_card":"summary_large_image","twitter_misc":{"Gesch\u00e4tzte Lesezeit":"1\u00a0Minute"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"WebPage","@id":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender","url":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender","name":"Messekalender | Aktuelle Messetermine im \u00dcberblick","isPartOf":{"@id":"https:\/\/www.displayhersteller.de\/ratgeber\/#website"},"datePublished":"2026-02-20T10:31:08+00:00","dateModified":"2026-03-02T10:25:59+00:00","description":"Alle wichtigen Messen und Branchentermine auf einen Blick. Jetzt informieren und Messeauftritte fr\u00fchzeitig planen.","breadcrumb":{"@id":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender#breadcrumb"},"inLanguage":"de","potentialAction":[{"@type":"ReadAction","target":["https:\/\/www.displayhersteller.de\/ratgeber\/messekalender"]}]},{"@type":"BreadcrumbList","@id":"https:\/\/www.displayhersteller.de\/ratgeber\/messekalender#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/www.displayhersteller.de\/ratgeber"},{"@type":"ListItem","position":2,"name":"Messekalender"}]},{"@type":"WebSite","@id":"https:\/\/www.displayhersteller.de\/ratgeber\/#website","url":"https:\/\/www.displayhersteller.de\/ratgeber\/","name":"displayhersteller Ratgeber","description":"Informative Beitr\u00e4ge zu Messest\u00e4nden, Werbeaufstellern f\u00fcr Messen, Events und Veranstaltungen","potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/www.displayhersteller.de\/ratgeber\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"de"}]}},"_links":{"self":[{"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/pages\/10058","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/comments?post=10058"}],"version-history":[{"count":10,"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/pages\/10058\/revisions"}],"predecessor-version":[{"id":10449,"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/pages\/10058\/revisions\/10449"}],"wp:attachment":[{"href":"https:\/\/www.displayhersteller.de\/ratgeber\/wp-json\/wp\/v2\/media?parent=10058"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}