diff --git a/Minecraft-Modern-Theme/js/assistant-widget.js b/Minecraft-Modern-Theme/js/assistant-widget.js new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/Minecraft-Modern-Theme/js/assistant-widget.js @@ -0,0 +1 @@ + diff --git a/Minecraft-Modern-Theme/js/header-scroll.js b/Minecraft-Modern-Theme/js/header-scroll.js index 90b98d9..41ccbee 100644 --- a/Minecraft-Modern-Theme/js/header-scroll.js +++ b/Minecraft-Modern-Theme/js/header-scroll.js @@ -1,4 +1,4 @@ -// header-scroll.js – ergänzt um Header-Suche Toggle +// header-scroll.js – kompakter Header beim Scrollen + Suche Toggle document.addEventListener('DOMContentLoaded', function () { // ── Customizer Guard ────────────────────────────────────────────────── @@ -9,12 +9,79 @@ document.addEventListener('DOMContentLoaded', function () { return; } - // ── Scroll-Effekt ───────────────────────────────────────────────────── + // ── Scroll-Effekt + kompakter Header ───────────────────────────────── const header = document.querySelector('.site-header'); + const brandingRow = header ? header.querySelector('.header-row-branding') : null; + if (header) { - window.addEventListener('scroll', function () { - header.classList.toggle('scrolled', window.scrollY > 50); - }, { passive: true }); + let lastScrollY = Math.max(window.scrollY, 0); + let directionAnchorY = lastScrollY; + let isCompact = header.classList.contains('header-compact'); + let lastStateChangeAt = 0; + let ticking = false; + + const compactEnterThreshold = 110; + const compactExitThreshold = 24; + const directionThreshold = 10; + const expandDelta = 120; + const stateChangeCooldown = 260; + + const setCompactState = function (nextCompact, now) { + if (isCompact === nextCompact) { + return; + } + + isCompact = nextCompact; + lastStateChangeAt = now; + directionAnchorY = Math.max(window.scrollY, 0); + + header.classList.toggle('header-compact', nextCompact); + + if (brandingRow) { + brandingRow.classList.toggle('branding-hidden', nextCompact); + } + }; + + const updateHeaderState = function () { + ticking = false; + + const now = window.performance && typeof window.performance.now === 'function' + ? window.performance.now() + : Date.now(); + const currentScrollY = Math.max(window.scrollY, 0); + const delta = currentScrollY - lastScrollY; + const canChangeState = now - lastStateChangeAt >= stateChangeCooldown; + + header.classList.toggle('scrolled', currentScrollY > 50); + + if (currentScrollY <= compactExitThreshold) { + setCompactState(false, now); + } else if (canChangeState && delta > directionThreshold) { + directionAnchorY = currentScrollY; + + if (currentScrollY > compactEnterThreshold) { + setCompactState(true, now); + } + } else if (canChangeState && delta < -directionThreshold) { + if (directionAnchorY - currentScrollY >= expandDelta) { + setCompactState(false, now); + } + } + + lastScrollY = currentScrollY; + }; + + const onScroll = function () { + if (ticking) { + return; + } + + ticking = true; + window.requestAnimationFrame(updateHeaderState); + }; + + updateHeaderState(); + window.addEventListener('scroll', onScroll, { passive: true }); } // ── Suche Toggle ────────────────────────────────────────────────────── diff --git a/Minecraft-Modern-Theme/js/minecraft-avatar-skinview.js b/Minecraft-Modern-Theme/js/minecraft-avatar-skinview.js new file mode 100644 index 0000000..1d098c6 --- /dev/null +++ b/Minecraft-Modern-Theme/js/minecraft-avatar-skinview.js @@ -0,0 +1,63 @@ +// Lädt skinview3d von CDN +(function(){ + if(document.getElementById('skinview3d-cdn')) return; + var s = document.createElement('script'); + s.id = 'skinview3d-cdn'; + s.src = 'https://unpkg.com/skinview3d@4.1.1/bundles/skinview3d.min.js'; + s.onload = function() { + window.skinview3dReady = true; + }; + document.head.appendChild(s); +})(); + +window.showMinecraftSkinModal = function(uuid) { + if(document.getElementById('minecraft-skin-modal')) return; + var modal = document.createElement('div'); + modal.id = 'minecraft-skin-modal'; + modal.innerHTML = ` +
+
+ +
+
+ `; + document.body.appendChild(modal); + document.querySelector('.sv3d-modal-close').onclick = function(){ modal.remove(); }; + document.querySelector('.sv3d-modal-bg').onclick = function(){ modal.remove(); }; + + function renderSkin() { + var skinUrl = `https://crafatar.com/skins/${uuid}`; + var canvas = document.createElement('canvas'); + canvas.width = 320; canvas.height = 320; + document.getElementById('sv3d-canvas-wrap').appendChild(canvas); + var viewer = new skinview3d.SkinViewer({ + canvas: canvas, + width: 320, + height: 320, + skin: skinUrl + }); + viewer.controls.enableZoom = false; + viewer.animation = new skinview3d.WalkingAnimation(); + viewer.animation.speed = 1.2; + viewer.animation.play(); + } + if(window.skinview3dReady) renderSkin(); + else { + var check = setInterval(function(){ + if(window.skinview3dReady) { clearInterval(check); renderSkin(); } + }, 100); + } +}; + +// Avatar-Widget-Click-Handler +window.addEventListener('DOMContentLoaded', function(){ + var widget = document.getElementById('minecraft-avatar-widget'); + if(widget) { + widget.style.cursor = 'pointer'; + widget.title = 'Klicke für 3D Skin-Ansicht'; + widget.onclick = function(){ + var uuid = widget.getAttribute('data-uuid'); + if(uuid) window.showMinecraftSkinModal(uuid); + }; + } +}); diff --git a/Minecraft-Modern-Theme/js/navigation.js b/Minecraft-Modern-Theme/js/navigation.js index 7f058f2..ce31855 100644 --- a/Minecraft-Modern-Theme/js/navigation.js +++ b/Minecraft-Modern-Theme/js/navigation.js @@ -1,61 +1,174 @@ -( function() { - // FIX: Null-Check – Script bricht auf Login-Seite & Seiten ohne Nav nicht mehr ab - const siteNavigation = document.getElementById( 'site-navigation' ); - if ( ! siteNavigation ) { +( function () { + 'use strict'; + + var isSidebarLayout = document.querySelector( '.site-header--sidebar' ) !== null; + + if ( isSidebarLayout ) { + + // --- Panel öffnen / schließen --- + var panel = document.getElementById( 'header-sidebar' ); + var overlay = document.getElementById( 'sidebar-overlay' ); + var openBtn = document.querySelector( '.sidebar-menu-toggle' ); + var closeBtn = document.querySelector( '.sidebar-menu-close' ); + + function openPanel() { + if ( ! panel ) return; + panel.classList.add( 'is-open' ); + panel.setAttribute( 'aria-hidden', 'false' ); + if ( openBtn ) openBtn.setAttribute( 'aria-expanded', 'true' ); + if ( overlay ) overlay.classList.add( 'is-visible' ); + document.body.classList.add( 'sidebar-nav-open' ); + } + + function closePanel() { + if ( ! panel ) return; + panel.classList.remove( 'is-open' ); + panel.setAttribute( 'aria-hidden', 'true' ); + if ( openBtn ) openBtn.setAttribute( 'aria-expanded', 'false' ); + if ( overlay ) overlay.classList.remove( 'is-visible' ); + document.body.classList.remove( 'sidebar-nav-open' ); + } + + if ( openBtn ) openBtn.addEventListener( 'click', openPanel ); + if ( closeBtn ) closeBtn.addEventListener( 'click', closePanel ); + if ( overlay ) overlay.addEventListener( 'click', closePanel ); + document.addEventListener( 'keydown', function ( e ) { + if ( e.key === 'Escape' ) closePanel(); + } ); + + // --- Submenu Flyout --- + var sidebarNav = document.getElementById( 'site-navigation' ); + if ( sidebarNav ) { + sidebarNav.querySelectorAll( '.menu-item-has-children' ).forEach( function ( item ) { + var subMenu = item.querySelector( ':scope > .sub-menu' ); + if ( ! subMenu ) return; + + var isOpen = false; + var moveHdlr = null; + + function showFlyout() { + if ( isOpen ) return; + isOpen = true; + subMenu.style.top = item.getBoundingClientRect().top + 'px'; + item.classList.add( 'flyout-open' ); + + // Erkennungszone berechnen: + // - vertikal: Höhe des li-Elements + // - horizontal: von linkem Rand der Sidebar (0) bis + // rechtem Rand des Flyout-Panels + // → voller horizontaler Streifen für diesen Menüpunkt + moveHdlr = function ( e ) { + var ir = item.getBoundingClientRect(); + var sr = subMenu.getBoundingClientRect(); + + var zoneLeft = 0; // Bildschirmrand links + var zoneRight = Math.max( ir.right, sr.right ); // bis Ende Flyout + var zoneTop = ir.top; // oberer Rand des li + var zoneBottom = ir.bottom; // unterer Rand des li + + var inZone = e.clientX >= zoneLeft && + e.clientX <= zoneRight && + e.clientY >= zoneTop && + e.clientY <= zoneBottom; + + if ( ! inZone ) hideFlyout(); + }; + + document.addEventListener( 'mousemove', moveHdlr ); + } + + function hideFlyout() { + if ( ! isOpen ) return; + isOpen = false; + item.classList.remove( 'flyout-open' ); + if ( moveHdlr ) { + document.removeEventListener( 'mousemove', moveHdlr ); + moveHdlr = null; + } + } + + item.addEventListener( 'mouseenter', showFlyout ); + + // Klick-Toggle für Touch / Tastatur + var btn = document.createElement( 'button' ); + btn.className = 'submenu-toggle'; + btn.setAttribute( 'aria-expanded', 'false' ); + btn.innerHTML = ''; + var link = item.querySelector( ':scope > a' ); + if ( link ) link.insertAdjacentElement( 'afterend', btn ); + + btn.addEventListener( 'click', function ( e ) { + e.stopPropagation(); + var open = item.classList.toggle( 'active' ); + btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); + if ( open ) { + subMenu.style.top = item.getBoundingClientRect().top + 'px'; + item.classList.add( 'flyout-open' ); + isOpen = true; + } else { + hideFlyout(); + } + } ); + } ); + } + return; } - const menuToggle = siteNavigation.querySelector( '.menu-toggle' ); - if ( ! menuToggle ) { - return; - } + // ========================================================================= + // CLASSIC / CENTERED / MEGA + // ========================================================================= + var siteNavigation = document.getElementById( 'site-navigation' ); + if ( ! siteNavigation ) return; - // Toggle Klassen hinzufügen (Menü öffnen/schließen) - menuToggle.addEventListener( 'click', function() { - siteNavigation.classList.toggle( 'toggled' ); + var menuToggle = siteNavigation.querySelector( '.menu-toggle' ); + var subMenuParents = siteNavigation.querySelectorAll( '.menu-item-has-children' ); - if ( menuToggle.getAttribute( 'aria-expanded' ) === 'true' ) { - menuToggle.setAttribute( 'aria-expanded', 'false' ); - menuToggle.innerHTML = ''; - } else { - menuToggle.setAttribute( 'aria-expanded', 'true' ); - menuToggle.innerHTML = ''; - } - } ); - - // FIX: Menü bei Klick außerhalb schließen - document.addEventListener( 'click', function( e ) { - if ( siteNavigation.classList.contains( 'toggled' ) && ! siteNavigation.contains( e.target ) ) { - siteNavigation.classList.remove( 'toggled' ); - menuToggle.setAttribute( 'aria-expanded', 'false' ); - menuToggle.innerHTML = ''; - } - } ); - - // Mobile Submenu Toggle - const subMenuParents = siteNavigation.querySelectorAll( '.menu-item-has-children' ); - - subMenuParents.forEach( function( subMenuParent ) { - // FIX: Eigenen Toggle-Button erzeugen statt gesamtes
  • klickbar zu machen - const toggleBtn = document.createElement( 'button' ); - toggleBtn.classList.add( 'submenu-toggle' ); - toggleBtn.setAttribute( 'aria-expanded', 'false' ); - toggleBtn.innerHTML = ''; - - const parentLink = subMenuParent.querySelector( 'a' ); - if ( parentLink ) { - parentLink.insertAdjacentElement( 'afterend', toggleBtn ); - } - - toggleBtn.addEventListener( 'click', function( e ) { - e.stopPropagation(); - if ( window.innerWidth <= 992 ) { - const isOpen = subMenuParent.classList.toggle( 'active' ); - toggleBtn.setAttribute( 'aria-expanded', isOpen ? 'true' : 'false' ); - // Icon drehen - toggleBtn.querySelector('i').style.transform = isOpen ? 'rotate(180deg)' : 'rotate(0deg)'; + if ( menuToggle ) { + menuToggle.addEventListener( 'click', function () { + var expanded = siteNavigation.classList.toggle( 'toggled' ); + this.setAttribute( 'aria-expanded', String( expanded ) ); + this.innerHTML = expanded ? '' : ''; + } ); + document.addEventListener( 'click', function ( e ) { + if ( siteNavigation.classList.contains( 'toggled' ) && ! siteNavigation.contains( e.target ) ) { + siteNavigation.classList.remove( 'toggled' ); + menuToggle.setAttribute( 'aria-expanded', 'false' ); + menuToggle.innerHTML = ''; } } ); + window.addEventListener( 'resize', function () { + if ( window.innerWidth > 992 ) { + siteNavigation.classList.remove( 'toggled' ); + menuToggle.setAttribute( 'aria-expanded', 'false' ); + menuToggle.innerHTML = ''; + subMenuParents.forEach( function ( i ) { i.classList.remove( 'active' ); } ); + } + } ); + } + + subMenuParents.forEach( function ( item ) { + var btn = document.createElement( 'button' ); + btn.className = 'submenu-toggle'; + btn.setAttribute( 'aria-expanded', 'false' ); + btn.innerHTML = ''; + var link = item.querySelector( ':scope > a' ); + if ( link ) link.insertAdjacentElement( 'afterend', btn ); + btn.addEventListener( 'click', function ( e ) { + e.stopPropagation(); + var open = item.classList.toggle( 'active' ); + btn.setAttribute( 'aria-expanded', open ? 'true' : 'false' ); + } ); + } ); + + document.addEventListener( 'click', function ( e ) { + if ( ! e.target.closest( '.menu-item-has-children' ) ) { + subMenuParents.forEach( function ( item ) { + item.classList.remove( 'active' ); + var b = item.querySelector( '.submenu-toggle' ); + if ( b ) b.setAttribute( 'aria-expanded', 'false' ); + } ); + } } ); } )(); \ No newline at end of file diff --git a/Minecraft-Modern-Theme/js/slider-init.js b/Minecraft-Modern-Theme/js/slider-init.js index 40230f9..792f039 100644 --- a/Minecraft-Modern-Theme/js/slider-init.js +++ b/Minecraft-Modern-Theme/js/slider-init.js @@ -1,24 +1,25 @@ document.addEventListener('DOMContentLoaded', function() { - // Hole den Slider-Container - const heroSlider = document.querySelector('.hero-slider'); - // Stelle sicher, dass der Slider auf der Seite existiert - if (!heroSlider) { + const heroSlider = document.querySelector('.hero-slider'); + if (!heroSlider) return; + + // BUG-FIX: sliderSettings wird via wp_localize_script gesetzt. Fehlt es + // (z.B. wegen Caching oder falschem Enqueue), würde ein ReferenceError + // den gesamten Slider-Init abschießen. + if (typeof sliderSettings === 'undefined') { + console.warn('Minecraft Modern Theme: sliderSettings nicht gefunden. Slider wird nicht initialisiert.'); + heroSlider.classList.add('swiper-initialized'); // Opacity-Fix trotzdem aufheben return; } - // Konfiguration für den Slider vorbereiten const swiperConfig = { - // Der Effekt ist jetzt fest auf "Überblenden" eingestellt effect: 'fade', fadeEffect: { crossFade: true }, - // Loop-Einstellung ist jetzt DYNAMISCH loop: sliderSettings.loop === '1', - // Autoplay autoplay: { delay: 5000, disableOnInteraction: false, @@ -26,27 +27,24 @@ document.addEventListener('DOMContentLoaded', function() { pauseOnMouseEnter: true, - // Prüfe, ob die Pfeile NICHT ausgeblendet werden sollen navigation: sliderSettings.hideArrows !== '1' ? { nextEl: '.swiper-button-next', prevEl: '.swiper-button-prev', } : false, - // Prüfe, ob die Paginierung NICHT ausgeblendet werden soll pagination: sliderSettings.hidePagination !== '1' ? { el: '.swiper-pagination', clickable: true, } : false, - + on: { init: function () { - setTimeout(() => { + setTimeout(function() { heroSlider.classList.add('swiper-initialized'); }, 50); }, }, }; - // Initialisiere den Slider mit der konfigurierten Optionen new Swiper('.hero-slider', swiperConfig); }); \ No newline at end of file diff --git a/Minecraft-Modern-Theme/js/theme-toggle.js b/Minecraft-Modern-Theme/js/theme-toggle.js index 8a271b4..53a471b 100644 --- a/Minecraft-Modern-Theme/js/theme-toggle.js +++ b/Minecraft-Modern-Theme/js/theme-toggle.js @@ -1,25 +1,21 @@ document.addEventListener('DOMContentLoaded', function () { - // FIX: Null-Check – Login-Seite hat keinen Toggle-Button const toggle = document.querySelector('.theme-toggle'); - if ( ! toggle ) { - return; - } + if ( ! toggle ) return; - const html = document.documentElement; + const html = document.documentElement; const iconMoon = toggle.querySelector('.icon-moon'); const iconSun = toggle.querySelector('.icon-sun'); - const defaultMode = typeof sliderSettings !== 'undefined' ? sliderSettings.defaultMode : 'dark'; + const defaultMode = typeof sliderSettings !== 'undefined' && sliderSettings.defaultMode + ? sliderSettings.defaultMode + : 'dark'; - // FIX: Systemschema respektieren wenn kein gespeicherter Wert vorhanden + // Gespeicherten Wert laden oder OS-Schema/Theme-Default als Fallback let saved = localStorage.getItem('themeMode'); if ( ! saved ) { - // Prüfe OS-Einstellung, falle sonst auf Theme-Default zurück - if ( window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ) { - saved = 'light'; - } else { - saved = defaultMode; - } + saved = ( window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches ) + ? 'light' + : defaultMode; } function applyMode( mode ) { @@ -36,17 +32,19 @@ document.addEventListener('DOMContentLoaded', function () { applyMode( saved ); + // BUG-FIX: Vorher wurde html.classList.toggle() aufgerufen und danach + // applyMode() – das hat die Klasse doppelt manipuliert. Jetzt lesen wir + // den aktuellen Zustand aus und rufen nur applyMode() auf. toggle.addEventListener('click', function () { - const isLight = html.classList.toggle('light-mode'); - const newMode = isLight ? 'light' : 'dark'; + const isCurrentlyLight = html.classList.contains('light-mode'); + const newMode = isCurrentlyLight ? 'dark' : 'light'; applyMode( newMode ); localStorage.setItem('themeMode', newMode); }); - // Live-Reaktion auf OS-Umschalten (optional aber nice-to-have) + // Live-Reaktion auf OS-Umschalten (nur wenn keine manuelle Auswahl) if ( window.matchMedia ) { window.matchMedia('(prefers-color-scheme: light)').addEventListener('change', function( e ) { - // Nur reagieren wenn der User noch keine manuelle Auswahl getroffen hat if ( ! localStorage.getItem('themeMode') ) { applyMode( e.matches ? 'light' : 'dark' ); }