PageSourceSearch

https://www.polytechnicfc.co.uk/index.js?v=20260729-home-mobile-final

js polytechnicfc.co.uk collected 2026-10-02 18:56:10 UTC 21,584 bytes, 374 lines download raw bytes

1const menuButton = document.querySelector(".menu-toggle");
2const navLinks = document.querySelectorAll(".primary-nav a");
3const menuLabel = menuButton?.querySelector(".sr-only");
4const contactForm = document.querySelector(".contact-form");
5const formStatus = document.querySelector(".form-status");
6const demoForms = document.querySelectorAll("[data-demo-form]");
7const carousel = document.querySelector("[data-news-carousel]");
8const scrollCarousels = document.querySelectorAll("[data-scroll-carousel]");
9const teamSelector = document.querySelector(".team-selector-nav");
10const mapFrame = document.querySelector("[data-map-frame]");
11const mapEnable = document.querySelector("[data-map-enable]");
12const mapReset = document.querySelector("[data-map-reset]");
13
14function applyClubIdentityDetails() {
15  document.querySelectorAll(".main-header").forEach((header) => {
16    const mark = header.querySelector(":scope > .club-mark");
17    if (!mark || header.querySelector(".header-partnership")) return;
18    const lockup = document.createElement("div");
19    lockup.className = "club-identity-lockup";
20    mark.before(lockup);
21    lockup.append(mark);
22    lockup.insertAdjacentHTML("beforeend", '<small class="header-partnership">In partnership with <a href="https://www.thehound.london/" target="_blank" rel="noopener">The Hound</a> of the <a href="https://www.ardentpubgroup.com/" target="_blank" rel="noopener">Ardent Pub Group</a>.</small>');
23  });
24  document.querySelectorAll(".site-footer").forEach((footer) => {
25    if (footer.querySelector(".footer-legal")) return;
26    const bottom = footer.querySelector(".footer-bottom");
27    const legal = document.createElement("div");
28    legal.className = "section-shell footer-legal";
29    legal.innerHTML = '<div class="footer-legal-copy"><strong>Polytechnic Football Club</strong><span>Registered charity in England and Wales, number 1127155.</span></div><div class="footer-affiliations" aria-label="Club affiliations"><span>Affiliated to</span><div><a href="https://www.thefa.com/" target="_blank" rel="noopener" aria-label="Visit The Football Association website"><img src="/assets/logos/The-FA.png" alt="The Football Association"></a><a href="https://www.amateur-fa.com/" target="_blank" rel="noopener" aria-label="Visit the Amateur Football Alliance website"><img src="/assets/logos/Amateur_Football_Alliance_logo.png" alt="Amateur Football Alliance"></a><a href="https://www.southernamateurleague.co.uk/" target="_blank" rel="noopener" aria-label="Visit the Southern Amateur League website"><img src="/assets/logos/SAL_short.png" alt="Southern Amateur League"></a></div></div>';
30    if (bottom) bottom.before(legal); else footer.append(legal);
31  });
32}
33applyClubIdentityDetails();
34
35function applySecondaryMobileNavigation() {
36  const path = window.location.pathname.replace(/\/+$/, "") || "/";
37  const isHome = path === "/" || path === "/index.html";
38  const excluded = document.body.classList.contains("admin-dashboard-page") || document.body.dataset.adminArea || /\/(?:admin[^/]*|login|password-reset)\.html$/.test(path);
39  const nav = document.querySelector(".primary-nav:not(.admin-primary-nav)");
40  if (!nav || isHome || excluded) return;
41  const login = nav.querySelector(".mobile-login-link");
42  const routes = [
43    { label: "Home", href: "/index.html", className: "mobile-global-home" },
44    { label: "Teams", href: "/teams/index.html", className: "mobile-global-teams" },
45    { label: "Join", href: "/join.html", className: "mobile-global-join" },
46  ];
47  routes.forEach((route) => {
48    const existing = [...nav.querySelectorAll("a")].find((link) => new URL(link.href, window.location.href).pathname === route.href);
49    const link = existing || document.createElement("a");
50    link.classList.add("mobile-global-route", route.className);
51    if (path === route.href) link.setAttribute("aria-current", "page");
52    if (!existing) {
53      link.classList.add("mobile-global-injected");
54      link.href = route.href;
55      link.textContent = route.label;
56      nav.insertBefore(link, login || null);
57    }
58  });
59}
60applySecondaryMobileNavigation();
61
62const resultForm=document.querySelector("[data-result-form]");
63const requiredGoals=resultForm?.querySelector(".result-goals-required");
64const optionalTeamSheet=resultForm?.querySelector(".is-optional-section[data-result-match-details]");
65if(requiredGoals&&optionalTeamSheet)resultForm.insertBefore(requiredGoals,optionalTeamSheet);
66
67document.querySelectorAll("option").forEach((option) => {
68  if (option.textContent.trim() === "Development") {
69    option.textContent = "Development XI";
70    option.value = "Development XI";
71  }
72});
73document.querySelectorAll('.team-page-hero h1, #overview h2, .team-card strong').forEach((element) => {
74  if (element.textContent.trim() === "Development") element.textContent = "Development XI";
75});
76
77function prepareTeamDataShell() {
78  const team = document.querySelector(".team-page-hero h1")?.textContent.trim();
79  const grid = document.querySelector("#overview .team-detail-grid");
80  const stats = grid?.querySelector(".team-stat-grid");
81  if (!team || !grid || !stats) return;
82  const copy = grid.firstElementChild;
83  copy.classList.add("team-overview-copy");
84  if (!copy.querySelector(".team-overview-label")) copy.querySelector(".team-intro")?.insertAdjacentHTML("beforebegin", '<p class="team-overview-label">About the team</p>');
85  stats.className = "team-performance-panel team-performance-loading";
86  stats.innerHTML = '<div class="team-performance-heading"><div><p class="section-kicker">Season performance</p><h3>Current statistics</h3></div><span>Loading competitive results</span></div><div class="team-record-summary"><div><strong>0</strong><span>Played</span></div><div class="is-won"><strong>0</strong><span>Won</span></div><div class="is-drawn"><strong>0</strong><span>Drawn</span></div><div class="is-lost"><strong>0</strong><span>Lost</span></div></div><div class="team-form-bar"></div><div class="team-performance-metrics"><article><span>Goals</span><strong>0</strong></article><article><span>Clean sheets</span><strong>0</strong></article><article class="is-average"><span>Average goals for</span><strong>0.0</strong></article><article class="is-average"><span>Average goals against</span><strong>0.0</strong></article></div>';
87  const table = document.querySelector("#stats .team-data-grid");
88  if (table) {
89    table.className = "section-shell team-player-stats team-player-stats-loading";
90    table.innerHTML = `<div class="team-player-stats-heading"><div><p class="section-kicker">Player statistics</p><h2>${team} season stats</h2><p>
90Competitive matches only. Select a heading to reorder the table.</p></div><span>Loading matches</span></div><div class="team-stats-number-loading">Loading player statistics…</div>`;
91  }
92  document.body.classList.add("team-data-shell-ready");
93}
94
95function initializeTeamMediaLightbox() {
96  const cards = [...document.querySelectorAll("#media .media-card")];
97  if (!cards.length) return;
98  const images = cards.map((card) => ({ src: card.querySelector("img")?.src, alt: card.querySelector("img")?.alt || "", title: card.querySelector("span")?.textContent.trim() || "Team photo" })).filter((item) => item.src);
99  if (!images.length) return;
100  const dialog = document.createElement("dialog");
101  dialog.className = "gallery-lightbox team-media-lightbox";
102  dialog.innerHTML = '<button type="button" class="gallery-lightbox-close" aria-label="Close gallery">×</button><button type="button" class="gallery-lightbox-arrow gallery-lightbox-prev" aria-label="Previous photo">←</button><figure><img src="" alt=""><figcaption></figcaption></figure><button type="button" class="gallery-lightbox-arrow gallery-lightbox-next" aria-label="Next photo">→</button>';
103  document.body.append(dialog);
104  let active = 0;
105  const show = (index) => { active = (index + images.length) % images.length; const item = images[active]; dialog.querySelector("img").src = item.src; dialog.querySelector("img").alt = item.alt; dialog.querySelector("figcaption").textContent = `${active + 1} of ${images.length} · ${item.title}`; };
106  cards.forEach((card, index) => {
107    card.tabIndex = 0; card.setAttribute("role", "button"); card.setAttribute("aria-label", `Open ${images[index]?.title || "team photo"}`);
108    const open = () => { show(index); dialog.showModal(); };
109    card.addEventListener("click", open);
110    card.addEventListener("keydown", (event) => { if (event.key === "Enter" || event.key === " ") { event.preventDefault(); open(); } });
111  });
112  dialog.querySelector(".gallery-lightbox-close").addEventListener("click", () => dialog.close());
113  dialog.querySelector(".gallery-lightbox-prev").addEventListener("click", () => show(active - 1));
114  dialog.querySelector(".gallery-lightbox-next").addEventListener("click", () => show(active + 1));
115  dialog.addEventListener("click", (event) => { if (event.target === dialog) dialog.close(); });
116}
117
118function initializeHomeTeamShowcase() {
119  const root = document.querySelector("[data-team-showcase]");
120  const buttons = [...document.querySelectorAll("[data-home-team]")];
121  const teamSelect = document.querySelector("[data-home-team-select]");
122  if (!root || !buttons.length) return;
123  const teams = {
124    first: { title: "1st XI", kicker: "Senior football", image: "assets/images/1s.jpg", alt: "Polytechnic FC 1st XI", href: "teams/first-xi.html", copy: "Polytechnic 1st XI is the club’s flagship men’s team and its principal representative in competitive football. Bringing together outstanding players from across the club, the side competes ambitiously in the Southern Amateur League and sets the standard for the wider Polytechnic pathway. The team proudly represents the club’s history, values and traditions, combining high performance with the strong team spirit that defines Polytechnic FC." },
125    second: { title: "2nd XI", kicker: "Senior football", image: "assets/images/2s.jpg", alt: "Polytechnic FC 2nd XI", href: "teams/second-xi.html", copy: "Polytechnic 2nd XI is a strong, competitive Saturday side and a vital link between the club’s wider senior squads and the 1st XI. The team gives committed players the opportunity to test themselves in high-quality league football, develop within a demanding and supportive environment, and compete for progression. With its own ambitions and identity, the 2nd XI plays an important role in driving standards across the club." },
126    third: { title: "3rd XI", kicker: "Saturday football", image: "assets/images/3s.jpg", alt: "Polytechnic FC 3rd XI", href: "teams/third-xi.html", copy: "A strong squad balancing performance, player development and club spirit through regular Southern Amateur League and AFA competition." },
127    fifth: { title: "Development XI", kicker: "Saturday football", image: "assets/images/5s.jpg", alt: "Polytechnic FC Development XI", href: "teams/fifth-xi.html", copy: "A competitive squad for players building rhythm and enjoying regular football within the club’s broad Saturday programme." },
128    sixth: { title: "6th XI", kicker: "Saturday football", image: "assets/images/150th_3s.jpg", alt: "Polytechnic FC 6th XI", href: "teams/sixth-xi.html", copy: "A social but competitive side offering regular fixtures, cup football and a welcoming route into Polytechnic’s Saturday teams." },
129    seventh: { title: "7th XI", kicker: "Saturday football", image: "assets/images/150th_men.jpg", alt: "Polytechnic FC men’s teams", href: "teams/seventh-xi.html", copy: "A welcoming route into regular football for players across the men’s section, combining competitive matches with the club’s strong social spirit." },
130    development: { title: "4th XI", kicker: "Player pathway", image: "assets/images/development.jpg", alt: "Polytechnic FC 4th XI", href: "teams/development.html", copy: "A pathway squad for younger adult players, broadly aged 16–21, focused on coaching, confidence and progression into open-age Polytechnic teams." },
131    womens: { title: "Womens XI", kicker: "Women’s football", image: "assets/images/150th_women.jpg", alt: "Polytechnic Women’s X
131I team", href: "teams/womens-xi.html", copy: "Founded in 2025, Polytechnic Women’s XI is a friendly, inclusive and ambitious squad preparing for the 2026 London 11s AFA eleven-a-side league. Training takes place on Thursdays from 7.30–9pm at Quintin Hogg Memorial Ground, and everyone is welcome." },
132    vets: { title: "Vets", kicker: "Veterans football", image: "assets/images/vets.jpg", alt: "Polytechnic FC Veterans", href: "teams/vets.html", copy: "Competitive and social football for experienced players in the club’s 40+ Veterans section, maintaining long-standing friendships and club connections." },
133    strollers: { title: "Strollers", kicker: "Casual football", image: "assets/images/strollers.jpg", alt: "Polytechnic FC Strollers", href: "teams/strollers.html", copy: "Casual Saturday football, friendly fixtures and a welcoming place for players who want to keep enjoying the game and the Polytechnic community." },
134  };
135  const select = (key) => {
136    const team = teams[key]; if (!team) return;
137    root.querySelector("[data-team-showcase-image]").src = team.image;
138    root.querySelector("[data-team-showcase-image]").alt = team.alt;
139    root.querySelector(".section-kicker").textContent = team.kicker;
140    root.querySelector("[data-team-showcase-title]").textContent = team.title;
141    root.querySelector("[data-team-showcase-copy]").textContent = team.copy;
142    const link = root.querySelector("[data-team-showcase-link]");
143    link.href = team.href; link.textContent = `Visit the ${team.title} page →`;
144    buttons.forEach((button) => { const active = button.dataset.homeTeam === key; button.classList.toggle("is-active", active); button.setAttribute("aria-selected", String(active)); });
145    if (teamSelect) teamSelect.value = key;
146  };
147  buttons.forEach((button) => button.addEventListener("click", () => select(button.dataset.homeTeam)));
148  teamSelect?.addEventListener("change", () => select(teamSelect.value));
149}
150
151prepareTeamDataShell();
152initializeTeamMediaLightbox();
153initializeHomeTeamShowcase();
154
155function closeMenu() {
156  document.body.classList.remove("menu-open");
157  menuButton?.setAttribute("aria-expanded", "false");
158  if (menuLabel) {
159    menuLabel.textContent = "Open menu";
160  }
161}
162
163menuButton?.addEventListener("click", () => {
164  const isOpen = document.body.classList.toggle("menu-open");
165  menuButton.setAttribute("aria-expanded", String(isOpen));
166  if (menuLabel) {
167    menuLabel.textContent = isOpen ? "Close menu" : "Open menu";
168  }
169});
170
171navLinks.forEach((link) => {
172  link.addEventListener("click", closeMenu);
173});
174
175teamSelector?.querySelector(".active")?.scrollIntoView({
176  block: "nearest",
177  inline: "center",
178});
179
180document.querySelectorAll('a[href="#top"]').forEach((link) => {
181  link.addEventListener("click", (event) => {
182    event.preventDefault();
183    closeMenu();
184    history.replaceState(null, "", `${window.location.pathname}${window.location.search}`);
185    window.scrollTo({ top: 0, behavior: "smooth" });
186  });
187});
188
189document.querySelectorAll('a[href*="#"]').forEach((link) => {
190  link.addEventListener("click", (event) => {
191    const destination = new URL(link.href, window.location.href);
192    const normalisePath = (path) => path.replace(/index\.html$/, "").replace(/\/+$/, "");
193    if (
194      destination.origin !== window.location.origin ||
195      normalisePath(destination.pathname) !== normalisePath(window.location.pathname) ||
196      !destination.hash
197    ) return;
198
199    const target = document.querySelector(destination.hash);
200    if (!target) return;
201    event.preventDefault();
202    closeMenu();
203    if (destination.hash === "#top") {
204      history.replaceState(null, "", `${window.location.pathname}${window.location.search}`);
205      window.scrollTo({ top: 0, behavior: "smooth" });
206      return;
207    }
208    history.replaceState(null, "", destination.hash);
209    target.scrollIntoView({ behavior: "smooth", block: "start" });
210  });
211});
212
213window.addEventListener("resize", () => {
214  if (window.innerWidth > 920) {
215    closeMenu();
216  }
217});
218
219contactForm?.addEventListener("submit", (event) => {
220  if (
221    contactForm.dataset.demoForm !== undefined ||
222    contactForm.dataset.authLogin !== undefined ||
223    contactForm.dataset.authRegister !== undefined ||
224    contactForm.dataset.publicEnquiryForm !== undefined
225  ) return;
226
227  event.preventDefault();
228  contactForm.reset();
229
230  if (formStatus) {
231    formStatus.textContent = "Thanks. Your enquiry is ready for the club team to pick up.";
232  }
233});
234
235demoForms.forEach((form) => {
236  form.addEventListener("submit", (event) => {
237    event.preventDefault();
238    const status = form.querySelector(".form-status");
239    form.reset();
240
241    if (status) {
242      status.textContent = "This form is ready to connect to the club admin system.";
243    }
244  });
245});
246
247function initializeNewsCarousel() {
248  if (!carousel || carousel.dataset.carouselReady) return;
249  const slides = Array.from(carousel.querySelectorAll("[data-slide]"));
250  if (!slides.length) return;
251  carousel.dataset.carouselReady = "true";
252  const triggers = Array.from(carousel.querySelectorAll("[data-carousel-trigger]"));
253  const title = carousel.querySelector("[data-carousel-title]");
254  const category = carousel.querySelector("[data-carousel-category]");
255  const summary = carousel.querySelector("[data-carousel-summary]");
256  const date = carousel.querySelector("[data-carousel-date]");
257  const link = carousel.querySelector("[data-carousel-link]");
258  const previousButton = carousel.querySelector("[data-carousel-prev]");
259  const nextButton = carousel.querySelector("[data-carousel-next]");
260  let activeIndex = 0;
261  let rotationTimer;
262  let touchStartX = 0;
263
264  function showSlide(index) {
265    activeIndex = (index + slides.length) % slides.length;
266    const activeSlide = slides[activeIndex];
267
268    slides.forEach((slide, slideIndex) => {
269      slide.classList.toggle("is-active", slideIndex === activeIndex);
270    });
271
272    triggers.forEach((trigger, triggerIndex) => {
273      const isActive = triggerIndex === activeIndex;
274      trigger.classList.toggle("is-active", isActive);
275      trigger.setAttribute("aria-pressed", String(isActive));
276    });
277
278    if (title) title.textContent = activeSlide.dataset.title || "";
279    if (category) category.textContent = activeSlide.dataset.category || "";
280    if (summary) summary.textContent = activeSlide.dataset.summary || "";
281    if (date) date.textContent = activeSlide.dataset.date || "";
282    if (link) link.href = activeSlide.dataset.link || "#";
283  }
284
285  function restartRotation() {
286    window.clearInterval(rotationTimer);
287    rotationTimer = window.setInterval(() => {
288      showSlide(activeIndex + 1);
289    }, 6500);
290  }
291
292  triggers.forEach((trigger) => {
293    trigger.addEventListener("click", () => {
294      showSlide(Number(trigger.dataset.slideIndex));
295      restartRotation();
296    });
297  });
298
299  previousButton?.addEventListener("click", () => {
300    showSlide(activeIndex - 1);
301    restartRotation();
302  });
303
304  nextButton?.addEventListener("click", () => {
305    showSlide(activeIndex + 1);
306    restartRotation();
307  });
308
309  carousel.addEventListener("mouseenter", () => window.clearInterval(rotationTimer));
310  carousel.addEventListener("mouseleave", restartRotation);
311  carousel.addEventListener("focusin", () => window.clearInterval(rotationTimer));
312  carousel.addEventListener("focusout", restartRotation);
313
314  carousel.addEventListener("touchstart", (event) => {
315    touchStartX = event.changedTouches[0].clientX;
316  }, { passive: true });
317
318  carousel.addEventListener("touchend", (event) => {
319    const touchEndX = event.changedTouches[0].clientX;
320    const swipeDistance = touchEndX - touchStartX;
321
322    if (Math.abs(swipeDistance) > 45) {
323      showSlide(activeIndex + (swipeDistance < 0 ? 1 : -1));
324      restartRotation();
325    }
326  }, { passive: true });
327
328  showSlide(0);
329  restartRotation();
330}
331
332if (carousel?.dataset.dynamicSpotlight !== undefined) {
333  window.addEventListener("spotlight-ready", initializeNewsCarousel, { once: true });
334  window.setTimeout(initializeNewsCarousel, 2500);
335} else {
336  initializeNewsCarousel();
337}
338
339mapEnable?.addEventListener("click", () => {
340  const isUnlocked = mapFrame?.classList.toggle("is-unlocked");
341  mapEnable.textContent = isUnlocked ? "Lock map" : "Use map";
342});
343
344mapReset?.addEventListener("click", () => {
345  if (mapFrame?.dataset.mapSrc) {
346    mapFrame.src = mapFrame.dataset.mapSrc;
347  }
348});
349
350scrollCarousels.forEach((scrollCarousel) => {
351  const group = scrollCarousel.closest("[data-scroll-group]") || scrollCarousel.closest("section");
352  const previous = group?.querySelector("[data-scroll-prev]");
353  const next = group?.querySelector("[data-scroll-next]");
354
355  function scrollFixtures(direction) {
356    const card = scrollCarousel.querySelector(":scope > *");
357    const distance = card ? card.getBoundingClientRect().width + 16 : 320;
358    const max = Math.max(0, scrollCarousel.scrollWidth - scrollCarousel.clientWidth);
359    if (max < 2) return;
360    if (direction > 0 && scrollCarousel.scrollLeft >= max - 2) {
361      scrollCarousel.scrollTo({ left: 0, behavior: "smooth" });
362      return;
363    }
364    if (direction < 0 && scrollCarousel.scrollLeft <= 2) {
365      scrollCarousel.scrollTo({ left: max, behavior: "smooth" });
366      return;
367    }
368    const target = Math.min(max, Math.max(0, scrollCarousel.scrollLeft + distance * direction));
369    scrollCarousel.scrollTo({ left: target, behavior: "smooth" });
370  }
371
372  previous?.addEventListener("click", () => scrollFixtures(-1));
373  next?.addEventListener("click", () => scrollFixtures(1));
374});

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.