diff --git a/data/team.xml b/data/team.xml new file mode 100644 index 0000000..e9970bf --- /dev/null +++ b/data/team.xml @@ -0,0 +1,189 @@ + + + + + Janečka Martin + 13 + Hráč + img/muzi/Janečka_Martin_1.png + + + Hubinka Adam + 5 + Hráč + img/muzi/Hubinka_Adam_1.png + + + Zapletal Martin + 8 + Hráč + img/muzi/Zapletal_Martin_1.png + + + Drobný Michal + 3 + Hráč + img/muzi/Drobný_Michal_1.png + + + Prokeš Martin + 12 + Hráč + img/muzi/Prokeš_Martin_1.png + + + Janečka Radek + 16 + Brankář + img/muzi/Janečka_Radek_1.png + + + Mačuda Jakub + + + img/muzi/Mačuda_Jakub_1.png + + + Svízela Jakub + 19 + Hráč + img/muzi/Svízela_Jakub_1.png + + + Šipka Jan + 14 + Hráč + img/muzi/Šipka_Jan_1.png + + + Polák David + 9 + Hráč + img/muzi/Polák_David_1.png + + + Kočiš Lukáš + 6 + Hráč + img/muzi/Kočiš_Lukáš_1.png + + + Malý Lukáš + 21 + Hráč + img/muzi/Malý_Lukáš_1.png + + + Řičica Jakub + + sekretář klubu + img/muzi/Řičica_Jakub_1.png + + + Moravec David + + místopředseda klubu + img/muzi/Moravec_David_1.png + + + Janečková Vladimíra + + vedoucí týmu + img/muzi/Janečková_Vladimíra_1.png + + + Stodůlka Štěpán + + PR manažer klubu + img/muzi/Stodůlka_Štěpán_1.png + + + Stojaspal Marek + + + img/muzi/Stojaspal_Marek_1.png + + + Puškáč Lubomír + + + img/muzi/Puškáč_Lubomír_1.png + + + Náplava Jaroslav + + + img/muzi/Náplava_Jaroslav_1.png + + + + + + + Šrámková Sára + 12 + hráč + img/zeny/Šrámková_Sára_1.png + + + Baná Barbora + 21 + hráč + img/zeny/Baná_Barbora_1.png + + + Majerechová Eliška + 69 + hráč + img/zeny/Majerechová_Eliška_1.png + + + Maleňáková Adriana + 11 + hráč + img/zeny/Maleňáková_Adriana_1.png + + + Adamíková Andrea + 6 + hráč + img/zeny/Adamíková_Andrea_1.png + + + Dufková Martina + 17 + hráč + img/zeny/Dufková_Martina_1.png + + + Gorčíková Anna + 16 + hráč + img/zeny/Gorčíková_Anna_1.png + + + Mrázková Denisa + 4 + hráč + img/zeny/Mrázková_Denisa_1.png + + + Prokešová Terezie + 1 + hráč + img/zeny/Prokešová_Terezie_1.png + + + Sekaninová Markéta + 7 + hráč + img/zeny/Sekaninová_Markéta_1.png + + + Štichová Tereza + 10 + hráč + img/zeny/Štichová_Tereza_1.png + + + diff --git a/index.html b/index.html index 0658ac0..8e0bbf3 100644 --- a/index.html +++ b/index.html @@ -751,567 +751,21 @@ - -
-
-
-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
13
- -

Janečka Martin

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
5
- -

Lapčík Martin

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
8
- -

Zapletal Martin

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
3
- -

Drobný Michal

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
12
- -

Brázdil Petr

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
16
- -

Janečka Radek

-
-

- Brankář -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
19
- -

Svízela Jakub

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
14
- -

Šipka Jan

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
18
- -

Hubinka Adam

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
9
- -

Příplata Filip

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
6
- -

Kočiš Lukáš

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
21
- -

Malý Lukáš

-
-

- Hráč -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- -

Řičica Jakub

-
-

sekretář klubu -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- -

Moravec David

-
-

místopředseda klubu -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- -

Janečková Vladimíra

-
-

vedoucí týmu -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- -

Stodůlka Štěpán

-
-

PR manažer klubu -

-
-
-
-
-
- - - -
- -
    -
  • - -
  • -
  • - -
  • -
  • - -
  • -
-
-
-
- -

Náplava Jaroslav

-
-

předseda klubu -

-
-
-
-
-
-
-
-
+
+ +
-
+
+
-
+
@@ -2686,5 +2140,6 @@ }); + diff --git a/js/team-switcher.js b/js/team-switcher.js new file mode 100644 index 0000000..f28099d --- /dev/null +++ b/js/team-switcher.js @@ -0,0 +1,334 @@ +// team-switcher.js +// Loads team data from XML and populates the team slider. Adds a men/women switcher. + +(function () { + const XML_URL = 'data/team.xml'; + const SWITCHER_ID = 'gender-switcher'; + const WRAPPER_ID = 'team-swiper-wrapper-1'; + const PRELOADER_ID = 'team-preloader-1'; + const SECTION_ID = 'team-section-1'; + + let teamData = null; // cached parsed XML data + let currentGender = 'men'; + // Autoscroll timers + let autoTimer = null; + let resumeTimer = null; + const AUTO_DELAY = 2000; // 5s + const RESUME_AFTER = 10000; // resume 10s after user interaction + + function qs(sel, root = document) { return root.querySelector(sel); } + function qsa(sel, root = document) { return Array.from(root.querySelectorAll(sel)); } + + async function loadXML() { + if (teamData) return teamData; + const res = await fetch(XML_URL, { cache: 'no-cache' }); + if (!res.ok) throw new Error('Failed to fetch team.xml'); + const text = await res.text(); + const parser = new DOMParser(); + const xml = parser.parseFromString(text, 'application/xml'); + const parseError = xml.querySelector('parsererror'); + if (parseError) throw new Error('Invalid XML in team.xml'); + teamData = xml; + return xml; + } + + function getMembersByCategory(xml, categoryName) { + const cat = Array.from(xml.querySelectorAll('team > category')) + .find(c => (c.getAttribute('name') || '').toLowerCase() === categoryName); + if (!cat) return []; + return Array.from(cat.querySelectorAll('member')).map(m => ({ + name: (m.querySelector('name')?.textContent || '').trim(), + number: (m.querySelector('number')?.textContent || '').trim(), + role: (m.querySelector('role')?.textContent || '').trim(), + image: (m.querySelector('image')?.textContent || '').trim(), + })); + } + + function slideHTML(member) { + const numHTML = member.number ? `
${member.number}
` : '
'; + const safeImg = member.image || ''; + return ( + `
+
+ + + +
+ ${numHTML} + +

${member.name}

+
+

${member.role} +

+
+
+
` + ); + } + + function renderMembers(members) { + const wrapper = document.getElementById(WRAPPER_ID); + if (!wrapper) return; + const swiperEl = wrapper.closest('.swiper-container'); + const swiper = swiperEl && swiperEl.swiper; + + // Use DOM-based rendering to match theme's slider expectations + wrapper.innerHTML = members.map(slideHTML).join(''); + + // Strong refresh + if (swiper) { + try { + if (typeof swiper.updateSlides === 'function') swiper.updateSlides(); + if (typeof swiper.updateSize === 'function') swiper.updateSize(); + if (typeof swiper.updateAutoHeight === 'function') swiper.updateAutoHeight(0); + if (typeof swiper.slideTo === 'function') swiper.slideTo(0, 0, false); + if (typeof swiper.update === 'function') swiper.update(); + } catch (e) {} + } + // Ask the theme to re-init this slider completely so arrows/loop/order are consistent + const sliderContainer = wrapper.closest('.lte-swiper-slider'); + // For consistent sequential navigation, disable coverflow loop and multi-view + if (sliderContainer && sliderContainer.dataset) { + sliderContainer.dataset.effect = 'slide'; // avoid forced loop in coverflow + sliderContainer.dataset.loop = '0'; + sliderContainer.dataset.breakpoints = '1;1;1;1;1;1'; // 1 per view on all widths + } + if (sliderContainer) sliderContainer.classList.remove('lte-inited'); + if (typeof window.initSwiperWrappers === 'function') { + try { window.initSwiperWrappers(); } catch (_) {} + } + // Remove any duplicate arrow bars the theme may have added on re-init + cleanupDuplicateArrows(); + setTimeout(() => window.dispatchEvent(new Event('resize')), 0); + + // Ensure arrows exist and are bound; manual endless wrap + setupEndlessNavigation(swiperEl); + setupDragWrap(swiper); + // Restart autoscroll on fresh render + stopAutoScroll(); + startAutoScroll(); + } + + // Keep only one arrows bar; prefer the one whose anchors already have our data-ts-bound + function cleanupDuplicateArrows() { + const wrapper = document.getElementById(WRAPPER_ID); + if (!wrapper) return; + const slider = wrapper.closest('.lte-swiper-slider'); + if (!slider) return; + + // Arrows can be siblings of slider or children inside slider depending on theme config + const candidates = []; + const parent = slider.parentElement; + if (parent) { + Array.from(parent.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); }); + } + Array.from(slider.children).forEach((el) => { if (el.classList && el.classList.contains('lte-arrows')) candidates.push(el); }); + + if (candidates.length <= 1) return; + + // Prefer the one that already has data-ts-bound anchors + const hasBound = candidates.find(a => a.querySelector('a[data-ts-bound="1"]')); + const keep = hasBound || candidates[0]; + candidates.forEach((a) => { if (a !== keep && a.parentElement) a.parentElement.removeChild(a); }); + } + + async function switchGender(gender) { + currentGender = gender; + try { + showPreloader(); + const xml = await loadXML(); + // Keep the order as in XML so the first visible is the first listed (e.g., Janečka Martin) + const list = getMembersByCategory(xml, gender); + renderMembers(list); + updateActiveButton(); + markReady(); + hidePreloader(); + } catch (e) { + console.error(e); + hidePreloader(); + } + } + + function updateActiveButton() { + const container = document.getElementById(SWITCHER_ID); + if (!container) return; + qsa('button[data-gender]', container).forEach(btn => { + btn.classList.toggle('active', btn.dataset.gender === currentGender); + }); + } + + function bindUI() { + const container = document.getElementById(SWITCHER_ID); + if (!container) return; + container.addEventListener('click', (e) => { + const btn = e.target.closest('button[data-gender]'); + if (!btn) return; + const gender = btn.dataset.gender; + if (gender && gender !== currentGender) switchGender(gender); + }); + } + + function ensureBasicStyles() { + const css = ` + #${SWITCHER_ID}{display:flex;gap:.5rem;justify-content:center;margin:10px 0} + #${SWITCHER_ID} .switch-btn{background:#eee;border:1px solid #ccc;border-radius:20px;padding:.35rem .9rem;font-weight:600;cursor:pointer} + #${SWITCHER_ID} .switch-btn.active{background:#111;color:#fff;border-color:#111} + #${PRELOADER_ID}{display:none;align-items:center;justify-content:center;gap:.6rem;color:#fff;padding:8px 0} + #${PRELOADER_ID}.visible{display:flex} + #${PRELOADER_ID} .spinner{width:16px;height:16px;border:2px solid rgba(255,255,255,.3);border-top-color:#fff;border-radius:50%;animation:ts-spin .8s linear infinite} + @keyframes ts-spin{to{transform:rotate(360deg)}} + #${SECTION_ID}.not-ready .lte-swiper-slider-wrapper{visibility:hidden} + `; + const style = document.createElement('style'); + style.textContent = css; + document.head.appendChild(style); + } + + function getSwiperInstance() { + const wrapper = document.getElementById(WRAPPER_ID); + const swiperEl = wrapper && wrapper.closest('.swiper-container'); + return swiperEl && swiperEl.swiper ? { el: swiperEl, api: swiperEl.swiper } : null; + } + + function setupEndlessNavigation(swiperContainerEl) { + const inst = getSwiperInstance(); + if (!inst) return; + const { el, api } = inst; + + // Ensure only one set of arrows remains before binding + cleanupDuplicateArrows(); + + // Theme uses .lte-arrow-left / .lte-arrow-right (see frontend.js init) + let nextBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-right'); + let prevBtn = el.parentElement && el.parentElement.querySelector('.lte-arrows .lte-arrow-left'); + // Fallback to common Swiper classes if theme structure changes + if (!nextBtn) nextBtn = el.querySelector('.swiper-button-next, .lte-swiper-button-next, .lte-next, .lte-arrow-next, .lte-arrow-right'); + if (!prevBtn) prevBtn = el.querySelector('.swiper-button-prev, .lte-swiper-button-prev, .lte-prev, .lte-arrow-prev, .lte-arrow-left'); + + // Do not create fallback arrows; rely on theme arrows only + + function bind(btn, dir) { + if (!btn || btn.dataset.tsBound) return; + btn.addEventListener('click', (e) => { + e.preventDefault(); + if (!api) return; + // User override: pause and schedule resume + stopAutoScroll(); + scheduleAutoResume(); + if (dir === 'next') { + if (typeof api.slideNext === 'function') api.slideNext(400); + else api.slideTo((api.activeIndex || 0) + 1, 400, false); + } else { + if (typeof api.slidePrev === 'function') api.slidePrev(400); + else api.slideTo(Math.max((api.activeIndex || 0) - 1, 0), 400, false); + } + }); + btn.dataset.tsBound = '1'; + } + + bind(nextBtn, 'next'); + bind(prevBtn, 'prev'); + + // Hover pause/resume on the whole slider area + if (el && !el.__tsHoverBound) { + el.addEventListener('mouseenter', () => stopAutoScroll()); + el.addEventListener('mouseleave', () => startAutoScroll()); + el.__tsHoverBound = true; + } + } + + function setupDragWrap(swiper) { + if (!swiper || !swiper.on) return; + if (!swiper.__tsWrapBound) { + swiper.on('reachEnd', () => { swiper.slideTo(0, 400, false); }); + swiper.on('reachBeginning', () => { + const last = (swiper.slides && swiper.slides.length ? swiper.slides.length - 1 : 0); + swiper.slideTo(last, 400, false); + }); + swiper.__tsWrapBound = true; + } + + // Pause autoscroll on user touch/drag and schedule resume on release + if (!swiper.__tsAutoBound) { + try { + swiper.on('touchStart', () => { stopAutoScroll(); }); + swiper.on('touchEnd', () => { scheduleAutoResume(); }); + swiper.on('pointerDown', () => { stopAutoScroll(); }); + swiper.on('pointerUp', () => { scheduleAutoResume(); }); + } catch (_) {} + swiper.__tsAutoBound = true; + } + } + + function startAutoScroll() { + const inst = getSwiperInstance(); + if (!inst) return; + const { api } = inst; + stopAutoScroll(); + autoTimer = window.setInterval(() => { + if (!api) return; + try { + // If not looping, wrap to first when at end + const loop = api.params && api.params.loop; + if (!loop && api.isEnd) { + api.slideTo(0, 600, false); + } else if (typeof api.slideNext === 'function') { + api.slideNext(600); + } + } catch (_) {} + }, AUTO_DELAY); + } + + function stopAutoScroll() { + if (autoTimer) { + clearInterval(autoTimer); + autoTimer = null; + } + if (resumeTimer) { + clearTimeout(resumeTimer); + resumeTimer = null; + } + } + + function scheduleAutoResume() { + if (resumeTimer) { + clearTimeout(resumeTimer); + resumeTimer = null; + } + resumeTimer = window.setTimeout(() => { + startAutoScroll(); + }, RESUME_AFTER); + } + + function showPreloader() { + const el = document.getElementById(PRELOADER_ID); + if (el) el.classList.add('visible'); + } + + function hidePreloader() { + const el = document.getElementById(PRELOADER_ID); + if (el) el.classList.remove('visible'); + } + + function markNotReady() { + const sec = document.getElementById(SECTION_ID); + if (sec) sec.classList.add('not-ready'); + } + + function markReady() { + const sec = document.getElementById(SECTION_ID); + if (sec) sec.classList.remove('not-ready'); + } + + document.addEventListener('DOMContentLoaded', () => { + ensureBasicStyles(); + markNotReady(); + showPreloader(); + bindUI(); + }); + + // Defer initial population until all assets and theme scripts (e.g., sliders) are fully initialized + window.addEventListener('load', () => { + switchGender(currentGender); + }); +})();