PageSourceSearch

https://relaxcrm.de/site.js?v=155fccc5

js relaxcrm.de collected 2026-10-09 03:13:02 UTC 7,186 bytes, 167 lines download raw bytes

1/**
2 * Gemeinsames Verhalten aller Seiten von relaxcrm.de.
3 * Eine Datei für alle Seiten – sonst driftet das Menü zwischen den Seiten auseinander.
4 */
5(function () {
6  'use strict';
7
8  // ── Mobil-Menü ───────────────────────────────────────────────────────────
9  var toggle = document.getElementById('navToggle');
10  if (!toggle) return;
11  var header = toggle.closest('header');
12  var links = document.getElementById('navLinks');
13
14  function setOpen(open) {
15    header.classList.toggle('open', open);
16    toggle.setAttribute('aria-expanded', String(open));
17    toggle.setAttribute('aria-label', open ? 'Menü schließen' : 'Menü öffnen');
18  }
19
20  toggle.addEventListener('click', function () {
21    setOpen(!header.classList.contains('open'));
22  });
23
24  // Nach der Auswahl schließen – sonst verdeckt das offene Menü das Sprungziel.
25  links.addEventListener('click', function (e) {
26    if (e.target.closest('a')) setOpen(false);
27  });
28
29  // Escape schließt; Fokus zurück auf den Button, damit die Tastatur nicht ins Leere läuft.
30  document.addEventListener('keydown', function (e) {
31    if (e.key === 'Escape' && header.classList.contains('open')) {
32      setOpen(false);
33      toggle.focus();
34    }
35  });
36
37  // Klick außerhalb schließt.
38  document.addEventListener('click', function (e) {
39    if (header.classList.contains('open') && !header.contains(e.target)) setOpen(false);
40  });
41
42  // Beim Wechsel auf Desktop-Breite zurücksetzen: die Links sind dort wieder
43  // dauerhaft sichtbar, ein hängengebliebener "open"-Zustand wäre unsichtbar
44  // aber aktiv (und würde Escape/Außenklick weiter abfangen).
45  var mq = window.matchMedia('(min-width: 1081px)');
46  (mq.addEventListener ? mq.addEventListener.bind(mq, 'change') : mq.addListener.bind(mq))(function (ev) {
47    if (ev.matches) setOpen(false);
48  });
49})();
50
51// ── Magazin-Übersicht: Branchen-Filter + Seiten ────────────────────────────
52// Rein clientseitig (statische Seite): Karten werden nach data-cat gefiltert
53// und in Seiten à PER Stück gezeigt.
54(function () {
55  'use strict';
56  var grid = document.getElementById('magGrid');
57  if (!grid) return;
58  var cards = Array.prototype.slice.call(grid.querySelectorAll('.card'));
59  var chips = Array.prototype.slice.call(document.querySelectorAll('.mag-chip'));
60  var pager = document.getElementById('magPager');
61  var empty = document.getElementById('magEmpty');
62  var PER = 9;
63  var cat = '';
64  var page = 1;
65
66  function passend() {
67    return cat ? cards.filter(function (c) { return c.getAttribute('data-cat') === cat; }) : cards;
68  }
69
70  function render() {
71    var list = passend();
72    var seiten = Math.max(1, Math.ceil(list.length / PER));
73    if (page > seiten) page = seiten;
74    cards.forEach(function (c) { c.style.display = 'none'; });
75    list.slice((page - 1) * PER, page * PER).forEach(function (c) { c.style.display = ''; });
76    if (empty) empty.hidden = list.length !== 0;
77
78    pager.innerHTML = '';
79    if (seiten <= 1) return;
80    var mk = function (text, ziel, opts) {
81      var b = document.createElement('button');
82      b.type = 'button';
83      b.textContent = text;
84      if (opts && opts.active) { b.className = 'is-active'; b.setAttribute('aria-current', 'page'); }
85      if (opts && opts.disabled) b.disabled = true;
86      b.addEventListener('click', function () {
87        page = ziel; render();
88        grid.scrollIntoView({ behavior: 'smooth', block: 'start' });
89      });
90      pager.appendChild(b);
91    };
92    mk('‹', page - 1, { disabled: page === 1 });
93    for (var i = 1; i <= seiten; i++) mk(String(i), i, { active: i === page });
94    mk('›', page + 1, { disabled: page === seiten });
95  }
96
97  chips.forEach(function (ch) {
98    ch.addEventListener('click', function () {
99      chips.forEach(function (x) { x.classList.remove('is-active'); });
100      ch.classList.add('is-active');
101      cat = ch.getAttribute('data-cat') || '';
102      page = 1;
103      render();
104    });
105  });
106
107  render();
108})();
109
110
111// ── Kundenstimmen-Karussell ─────────────────────────────────────────────────
112// Der Streifen funktioniert ohne dieses Skript (scroll-snap, wischbar). Hier
113// kommen dazu: Pfeile, Punkte, Pfeiltasten und ein langsamer Selbstlauf – alle
114// 9 s, nie bei „reduzierte Bewegung“, pausiert bei Hover/Fokus/verstecktem Tab
115// und endgültig aus, sobald jemand selbst eingreift.
116(function () {
117  var ruhig = window.matchMedia && matchMedia('(prefers-reduced-motion: reduce)').matches;
118  document.querySelectorAll('[data-karussell]').forEach(function (k) {
119    var bahn = k.querySelector('.karussell-bahn');
120    if (!bahn) return;
121    var folien = Array.prototype.slice.call(bahn.querySelectorAll('.stimme'));
122    var punkte = Array.prototype.slice.call(k.querySelectorAll('.karussell-punkte button'));
123    if (folien.length < 2) return;
124    var aktiv = 0, timer = null, t;
125
126    function zeige(i) {
127      i = (i + folien.length) % folien.length;
128      bahn.scrollTo({ left: folien[i].offsetLeft, behavior: ruhig ? 'auto' : 'smooth' });
129    }
130    function markiere() {
131      var mitte = bahn.scrollLeft + bahn.clientWidth / 2, best = 0, abstand = Infinity;
132      folien.forEach(function (f, i) {
133        var d = Math.abs(f.offsetLeft + f.offsetWidth / 2 - mitte);
134        if (d < abstand) { abstand = d; best = i; }
135      });
136      aktiv = best;
137      punkte.forEach(function (p, i) { p.setAttribute('aria-selected', i === best ? 'true' : 'false'); });
138    }
139    function halt() { if (timer) { clearInterval(timer); timer = null; } }
140    function start() {
141      if (ruhig || timer) return;
142      timer = setInterval(function () {
143        if (document.hidden || k.matches(':hover') || k.contains(document.activeElement)) return;
144        zeige(aktiv + 1);
145      }, 9000);
146    }
147
148    var vor = k.querySelector('.karussell-pfeil.vor'), zurueck = k.querySelector('.karussell-pfeil.zurueck');
149    if (vor) vor.addEventListener('click', function () { halt(); zeige(aktiv + 1); });
150    if (zurueck) zurueck.addEventListener('click', function () { halt(); zeige(aktiv - 1); });
151    punkte.forEach(function (p, i) { p.addEventListener('click', function () { halt(); zeige(i); }); });
152    bahn.addEventListener('keydown', function (e) {
153      if (e.key === 'ArrowRight') { halt(); zeige(aktiv + 1); e.preventDefault(); }
154      if (e.key === 'ArrowLeft') { halt(); zeige(aktiv - 1); e.preventDefault(); }
155    });
156    bahn.addEventListener('scroll', function () { clearTimeout(t); t = setTimeout(markiere, 80); }, { passive: true });
157    // Nach dem Wischen endet die Snap-Animation erst später als der letzte
158    // scroll-Event – scrollend (Chrome 114+, Firefox 109+) meldet den Moment,
159    // in dem die Folie wirklich steht. Safari kennt es noch nicht; dort greift
160    // der Zeitgeber oben.
161    bahn.addEventListener('scrollend', markiere);
162    bahn.addEventListener('pointerdown', halt, { passive: true });
163
164    markiere();
165    start();
166  });
167})();

Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.