PageSourceSearch

https://leicestershireccc.co.uk/assets/js/main.js?h=1790288163

js leicestershireccc.co.uk collected 2026-09-24 22:16:22 UTC 23,532 bytes, 685 lines download raw bytes

1var last_recap_id;
2
3// $(window).scroll(function(){
4//     var scrollPos = $(document).scrollTop();
5//     if(scrollPos > 0)
6//         $('body').addClass('scrolling');
7//     else
8//         $('body').removeClass('scrolling');
9// });
10
11$(window).on("load resize scroll", function() {
12    var scrollPos = $(document).scrollTop();
13    if(scrollPos > 0)
14        $('body').addClass('scrolling');
15    else
16        $('body').removeClass('scrolling');
17});
18
19
20document.querySelectorAll('li').forEach(function(li) {
21  var child = li.querySelector('span[style*="color"]');
22  if (child) {
23    li.style.color = window.getComputedStyle(child).color;
24  }
25});
26
27$(function() {
28
29    $('a[href^="#"]').on('click', function(e) {
30      e.preventDefault();
31      var target = $(this.hash);
32      if (target.length) {
33        $('html, body').animate({
34          scrollTop: target.offset().top - 80  // Adjust 80 to your header's height
35        }, 800);  // Animation duration in ms
36      }
37    });
38
39    // $('section .accordion .question .title').on('click', function(e) {
40    //     e.preventDefault();
41    //     $(this).parent().toggleClass('open');
42    // });
43
44    $('.language a').on('click', function(e) {
45        var language_domain = $(this).attr('data-url');
46        var url = new URL(window.location.href);
47        var new_url = url.protocol+'//'+language_domain+url.pathname;
48        window.location.href = new_url;
49    });
50
51    let $marquee = $('.marquee3k');
52    if($marquee.length > 0)
53    {
54        Marquee3k.init({
55            selector: 'marquee3k',
56        });
57    }
58
59    // $('section.block_type_faqs .question .title').on('click', function(e) {
60    //     e.preventDefault();
61    //     $(this).parent().toggleClass('open');
62    // });
63    function cleanMenuLink($link) {
64        $link = $link.clone();
65        $link.removeAttr('class');
66        return $link;
67    }
68
69    function buildMmenuList($container) {
70        var $list = $('<ul/>');
71
72        $container.children().each(function() {
73            var $item = $(this);
74
75            if ($item.is('li')) {
76                var $link = $item.children('a').first();
77                var $dropdown = $item.children('.navbar-dropdown, ul').first();
78
79                if ($item.hasClass('has-dropdown') || $dropdown.length) {
80                    var $li = $('<li/>');
81                    if ($link.length) {
82                        $li.append(cleanMenuLink($link));
83                    }
84
85                    if ($dropdown.length) {
86                        var $subList = buildMmenuList($dropdown);
87                        if ($subList.children().length) {
88                            $li.append($subList);
89                        }
90                    }
91
92                    $list.append($li);
93                    return;
94                }
95
96                if ($link.length) {
97                    var $simple = $('<li/>');
98                    $simple.append(cleanMenuLink($link));
99                    $list.append($simple);
100                    return;
101                }
102            }
103
104            if ($item.is('a')) {
105                var $li = $('<li/>');
106                $li.append(cleanMenuLink($item));
107                $list.append($li);
108                return;
109            }
110
111            // Fallback for any unexpected structure
112            if ($item.children().length) {
113                var $fallback = $('<li/>').append($item.clone().removeAttr('class'));
114                $list.append($fallback);
115            }
116        });
117
118        return $list;
119    }
120
121    var $menu = $('#mmenu').clone();
122    var $sourceList = $menu.children('ul').first();
123    if ($sourceList.length) {
124        var $processedList = buildMmenuList($sourceList);
125        $sourceList.replaceWith($processedList);
126    }
127
128    $menu.find('[id]').removeAttr('id');
129    $menu.attr('id', 'menu');
130    // The #mmenu source carries hiding utility classes (is-hidden = display:none
131    // !important); strip them from the clone or the off-canvas menu stays hidden
132    // even when opened (mm-opened).
133    $menu.removeClass('is-hidden navbar-nav');
134    $menu.prependTo('body');
135    $menu.mmenu({
136        slidingSubmenus: false,
137        navbar: {
138            add: false
139        },
140        onClick : {
141            close          : true,
142            preventDefault : false,
143        }
144    }, {
145        // configuration
146        offCanvas: {
147            pageSelector: ".outerwrapper"
148        },
149        clone: false
150    });
151    $menu.find('.mm-title').attr('href', 'javascript:;');
152    // $('[href^="#popup_"]').on('click', function(e) {
153    //     e.preventDefault();
154    //     var popupId = $(this).attr('href');
155    //     $('.modal[data-popup="'+popupId+'"]').addClass('is-active');
156    //     $('html').addClass('is-clipped');
157    // });
158
159    $('.mag-gallery').each(function(u, v) {
160        $(this).magnificPopup({
161            delegate: 'a',
162            type: 'image',
163            mainClass: 'mfp-img-mobile',
164            gallery: {
165                enabled: true,
166                navigateByImgClick: true,
167                preload: [0,1] // Will preload 0 - before current, and 1 after the current image
168            },
169            image: {
170                tError: '<a href="%url%">The image #%curr%</a> could not be loaded.',
171                titleSrc: function(item) {
172                    return item.el.attr('title');
173                }
174            },
175            callbacks: {
176                open: function() {
177                    $('body').hasClass('popupOpen');
178                },
179                close: function() {
180                    $('body').removeClass('popupOpen');
181                }
182            }
183        });
184    });
185
186    $('.js-modal-close').on('click', function(e) {
187        e.preventDefault();
188        $('html').removeClass('is-clipped');
189        var $modal = $(this).closest('.modal');
190        if ($modal.length) {
191            $modal.removeClass('is-active');
192        } else {
193            $(this).parents('.modal.is-active').first().removeClass('is-active');
194        }
195
196        if ($('.modal.is-active').length) {
197            $('html').addClass('is-clipped');
198        }
199    });
200
201    if ($('.modal.is-active').length) {
202        $('html').addClass('is-clipped');
203    }
204
205    $(document).on('click', '.js-team-modal, .js-board-member-modal', function(e) {
206        e.preventDefault();
207        var target = $(this).data('target');
208        if (!target) {
209            return;
210        }
211        var $modal = $('#' + target);
212        if ($modal.length === 0) {
213            return;
214        }
215        $modal.addClass('is-active');
216        $('html').addClass('is-clipped');
217    });
218
219    AOS.init({
220      offset: -100,
221      once: true,
222      duration: 1000,
223      easing: 'ease-in-out',
224      delay: 100,
225    });
226
227    initTwentyTwenty();
228
229    function setFormSubmitting($form, isSubmitting) {
230        var $buttons = $form.find('button[type="submit"], input[type="submit"], .field_type_button button');
231        if ($buttons.length === 0) {
232            return;
233        }
234
235        $buttons.each(function() {
236            var $button = $(this);
237
238            if (!$button.data('original-state')) {
239                $button.data('original-state', {
240                    html: $button.is('button') ? $button.html() : null,
241                    value: $button.is('input') ? $button.val() : null
242                });
243            }
244
245            if (isSubmitting) {
246                $button.prop('disabled', true).addClass('is-loading').attr('aria-busy', 'true');
247                if ($button.is('button')) {
248                    $button.html('Sending...');
249                } else {
250                    $button.val('Sending...');
251                }
252            } else {
253                var originalState = $button.data('original-state') || {};
254                $button.prop('disabled', false).removeClass('is-loading').removeAttr('aria-busy');
255                if ($button.is('button') && typeof originalState.html === 'string') {
256                    $button.html(originalState.html);
257                } else if ($button.is('input') && typeof originalState.value === 'string') {
258                    $button.val(originalState.value);
259                }
260            }
261        });
262    }
263
264    $('.recaptcha_form').each(function() {
265        var $form = $(this);
266        var widgetID = $form.data('widgetid');
267
268        $form.data('turnstileState', {
269            widgetId: widgetID,
270            pendingSubmit: false
271        });
272
273        $form.on('submit', function(event) {
274            var state = $form.data('turnstileState') || {};
275            if (state.submitting) {
276                return true;
277            }
278
279            event.preventDefault();
280            setFormSubmitting($form, true);
281            state.submitting = true;
282            $form.data('turnstileState', state);
283
284            if (typeof turnstile === 'undefined' || !state.widgetId) {
285                state.pendingSubmit = true;
286                state.submitting = false;
287                $form.data('turnstileState', state);
288                return false;
289            }
290
291            var token = turnstile.getResponse(state.widgetId);
292            if (!token) {
293                state.pendingSubmit = true;
294                state.submitting = false;
295                $form.data('turnstileState', state);
296                return false;
297            }
298
299            $form.find('input[name="cf-turnstile-response"]').val(token);
300            state.pendingSubmit = false;
301            $form.data('turnstileState', state);
302            window.setTimeout(function() {
303                $form.get(0).submit();
304            }, 60);
305            return false;
306        });
307
308        $form.on('turnstile:error', function() {
309            var state = $form.data('turnstileState') || {};
310            state.pendingSubmit = false;
311            state.submitting = false;
312            $form.data('turnstileState', state);
313            setFormSubmitting($form, false);
314        });
315    });
316
317    verifyCallback = function(response, formId) {
318        var targetId = formId || last_recap_id;
319        if (!targetId) {
320            return;
321        }
322
323        var $targetForm = $('form[data-recap-id="' + targetId + '"]');
324        if ($targetForm.length === 0) {
325            return;
326        }
327
328        var state = $targetForm.data('turnstileState') || {};
329        $targetForm.find('input[name="cf-turnstile-response"]').val(response || '');
330
331        if (response && state.pendingSubmit) {
332            state.pendingSubmit = false;
333            state.submitting = true;
334            $targetForm.data('turnstileState', state);
335            setFormSubmitting($targetForm, true);
336            window.setTimeout(function() {
337                $targetForm.get(0).submit();
338            }, 60);
339        }
340
341        if (!response) {
342            state.submitting = false;
343            $targetForm.data('turnstileState', state);
344            setFormSubmitting($targetForm, false);
345        }
346    };
347
348    $('.notification .delete').on('click', function(e) {
349        e.preventDefault();
350        $(this).parent().slideUp(function() {
351            $(this).remove()
352        });
353    });
354
355});
356
357function addMessage(msg, type = 'success', selector = '.msg')
358{
359    $(selector).prepend('<div class="notification is-'+type+'"><button class="delete"></button><div class="container">'+msg+'</div></div>');
360    if(selector == '.msg')
361        $("html, body").animate({ scrollTop: 0 });
362}
363
364function clearMessages()
365{
366    $('.notification').remove();
367}
368
369function escapeTags(str)
370{
371  return String( str )
372    .replace( /&/g, '&amp;' )
373    .replace( /"/g, '&quot;' )
374    .replace( /'/g, '&#39;' )
375    .replace( /</g, '&lt;' )
376    .replace( />/g, '&gt;' );
377}
378
379function init_uploader($uploader)
380{
381    var data = $uploader.data();
382    var btn = $uploader.find('.file-input')[0],
383        label = $uploader.find('.file-text')[0],
384        progressBar = $uploader.find('progress')[0],
385        hiddenInput = $uploader.find('input[type="hidden"]')[0];
386
387    if(data.preview)
388        var preview = $(data.preview)[0];
389
390    var uploader = new ss.SimpleUpload({
391        button: btn,
392        url: ADMIN_AJAX_URL+'/upload.php',
393        name: 'file',
394        data: data,
395        multipart: true,
396        queue: false,
397        debug: true,
398        hoverClass: 'hover',
399        focusClass: 'focus',
400        responseType: 'json',
401        startXHR: function() {
402            // progressOuter.style.display = 'block'; // make progress bar visible
403            this.setProgressBar( progressBar );
404        },
405        onSubmit: function() {
406            clearMessages(); // empty the message box
407            $('.is-buttons button, .field_type_button button').prop('disabled', true);
408            label.innerHTML = 'Uploading...'; // change button text to "Uploading..."
409          },
410        onComplete: function( filename, response ) {
411            console.log('response', response);
412            label.innerHTML = 'Choose a file...';
413            $('.is-buttons button, .field_type_button button').prop('disabled', false);
414            progressBar.value = 0; // hide progress bar when upload is completed
415            if ( !response ) {
416                addMessage('Unable to upload file', 'error');
417                return;
418            }
419
420            if ( response.success === true ) {
421                label.innerHTML = 'Uploaded';
422                console.log('success', response);
423                // addMessage('<strong>' + escapeTags( filename ) + '</strong>' + ' successfully uploaded.');
424                hiddenInput.value = response.file;
425                if(response.image)
426                {
427                    if(preview)
428                    {
429                        // if($(preview).hasClass('preview-img'))
430                        // {
431                        //     // $(preview).slideDown();
432                        //     // $(preview).find('img').attr('src', response.image);
433                        //     $(preview).css('background-image', 'url('+response.image+')');
434                        // } else
435                            $(preview).css('background-image', 'url('+response.image+')').addClass('has-preview');
436                    }
437                } else {
438                    var $existing_filename = $uploader.find('.file-name');
439                    if($existing_filename.length > 0)
440                        $existing_filename.text(response.name);
441                    else
442                        $uploader.find('label.file-label').append('<span class="file-name">
442'+response.name+'</span>')
443                }
444            } else {
445                console.log('error', response);
446                label.innerHTML = 'Upload Failed';
447                if ( response.msg )  {
448                    addMessage(escapeTags( response.msg ), 'error');
449
450                } else {
451                    addMessage('An error occurred and the upload failed.', 'error');
452                }
453            }
454          },
455        onError: function(filename, type, status, statusText, response, uploadBtn, size) {
456            console.log('filename', filename);
457            console.log('type', type);
458            console.log('status', status);
459            console.log('statusText', statusText);
460            console.log('response', response);
461            console.log('uploadBtn', uploadBtn);
462            console.log('size', size);
463            progressBar.value = 0;
464            addMessage('Unable to upload file', 'error');
465            $('.is-buttons button, .field_type_button button').prop('disabled', false);
466          }
467    });
468}
469
470function numberFormat(number, dec, dsep, tsep) {
471    if (isNaN(number)) return "";
472    number = number.toFixed(dec || 0);
473    var pindex = number.indexOf('.'), fnums, decimals, parts = [];
474    if (pindex > -1) {
475        fnums = number.substring(0, pindex).split('');
476        decimals = (dsep || '.') + number.substr(pindex+1);
477    }
478    else {
479        fnums = number.split('');
480        decimals = '';
481    }
482    do {
483        parts.unshift(fnums.splice(-3, 3).join(''));
484    } while (fnums.length);
485    return parts.join(tsep || ',') + decimals;
486}
487
488function initTwentyTwenty() {
489    if (typeof $.fn.twentytwenty !== 'function') {
490        return;
491    }
492
493    $('.twentytwenty-container').each(function() {
494        var $container = $(this);
495        if ($container.data('twentytwentyInit')) {
496            return;
497        }
498
499        var offset = parseFloat($container.data('offset'));
500        var orientation = $container.data('orientation') === 'vertical' ? 'vertical' : 'horizontal';
501
502        $container.twentytwenty({
503            default_offset_pct: isNaN(offset) ? 0.5 : offset,
504            orientation: orientation,
505            before_label: $container.data('before-label') || 'Before',
506            after_label: $container.data('after-label') || 'After',
507            no_overlay: !!$container.data('no-overlay'),
508            move_with_handle_only: false,
509            click_to_move: true
510        });
511
512        $container.data('twentytwentyInit', true);
513    });
514}
515
516$(window).on('load', initTwentyTwenty);
517
518
519document.addEventListener('DOMContentLoaded', () => {
520
521    /**
522     * A class to handle fetching and caching Vimeo thumbnails on the client-side.
523     */
524    class VimeoThumbnailManager {
525
526        constructor() {
527            this.localStorageKey = 'vimeoThumbnails';
528            this.cachedThumbnails = this.getThumbnailsFromStorage();
529        }
530
531        /**
532         * Finds all blocks with a background video and processes them.
533         */
534        processBlocks() {
535            const blocks = document.querySelectorAll('.block-item');
536            blocks.forEach(block => {
537                const computedStyle = getComputedStyle(block);
538                const backgroundVideo = computedStyle.getPropertyValue('--block-backgroundVideo').trim();
539                const backgroundImage = computedStyle.getPropertyValue('--block-backgroundImage').trim();
540
541                // Process only if there's a video but no server-rendered image
542                const needsThumbnail = backgroundVideo && backgroundVideo !== 'none' && (!backgroundImage || backgroundImage === 'none');
543                block.classList.toggle('has-video-thumbnail', !!needsThumbnail);
544                if (needsThumbnail) {
545                    this.handleBlock(block, backgroundVideo);
546                }
547            });
548        }
549
550        /**
551         * Handles a single block, either loading from cache or fetching the thumbnail.
552         * @param {HTMLElement} block - The block element.
553         * @param {string} backgroundVideoCss - The CSS value for the background video.
554         */
555        async handleBlock(block, backgroundVideoCss) {
556            // Extract the video ID from the HLS URL (e.g., url(".../12345.m3u8?...") -> "12345")
557            const videoId = this.extractVideoIdFromUrl(backgroundVideoCss);
558            if (!videoId) {
559                return;
560            }
561
562            // If thumbnail is in our cache, apply it immediately.
563            if (this.cachedThumbnails[videoId]) {
564                this.applyThumbnail(block, this.cachedThumbnails[videoId]);
565                return;
566            }
567
568            // If not in cache, fetch from Vimeo API.
569            try {
570                const thumbnailUrl = await this.fetchThumbnailFromVimeo(videoId);
571                if (thumbnailUrl) {
572                    this.applyThumbnail(block, thumbnailUrl);
573                    this.saveThumbnailToStorage(videoId, thumbnailUrl);
574                }
575            } catch (error) {
576                console.error(`Failed to fetch Vimeo thumbnail for video ID ${videoId}:`, error);
577            }
578        }
579
580        /**
581         * Fetches thumbnail URL from Vimeo's oEmbed API.
582         * @param {string} videoId - The ID of the Vimeo video.
583         * @returns {Promise<string|null>} The thumbnail URL or null.
584         */
585        async fetchThumbnailFromVimeo(videoId) {
586            const vimeoUrl = `https://vimeo.com/${videoId}`;
587            const oembedUrl = `https://vimeo.com/api/oembed.json?url=${encodeURIComponent(vimeoUrl)}`;
588
589            const response = await fetch(oembedUrl);
590            if (!response.ok) {
591                throw new Error(`Vimeo API responded with status: ${response.status}`);
592            }
593            const data = await response.json();
594
595            // We can even get a higher quality thumbnail by modifying the URL
596            if (data.thumbnail_url) {
597                return data.thumbnail_url.replace(/_(\d+)x(\d+)/, '_1920x1080');
598            }
599
600            return null;
601        }
602
603        /**
604         * Applies the thumbnail URL as a CSS variable to the block.
605         * @param {HTMLElement} block - The block element.
606         * @param {string} thumbnailUrl - The URL of the thumbnail image.
607         */
608        applyThumbnail(block, thumbnailUrl) {
609            block.style.setProperty('--block-backgroundImage', `url('${thumbnailUrl}')`);
610        }
611
612        /**
613         * Extracts a Vimeo Video ID from various URL formats.
614         * @param {string} urlString - The full CSS url() value.
615         * @returns {string|null}
616         */
617        extractVideoIdFromUrl(urlString) {
618            // Matches Vimeo video IDs from HLS URLs or standard URLs
619            const vimeoRegex = /vimeo\.com\/(?:video\/|external\/)?(\d+)/;
620            const match = urlString.match(vimeoRegex);
621            return match ? match[1] : null;
622        }
623
624        /**
625         * Retrieves the cache from localStorage.
626         * @returns {object}
627         */
628        getThumbnailsFromStorage() {
629            try {
630                const stored = localStorage.getItem(this.localStorageKey);
631                return stored ? JSON.parse(stored) : {};
632            } catch (e) {
633                console.error("Could not parse Vimeo thumbnail cache from localStorage.", e);
634                return {};
635            }
636        }
637
638        /**
639         * Saves a thumbnail URL to the local cache and localStorage.
640         * @param {string} videoId - The Vimeo video ID.
641         * @param {string} thumbnailUrl - The thumbnail URL.
642         */
643        saveThumbnailToStorage(videoId, thumbnailUrl) {
644            this.cachedThumbnails[videoId] = thumbnailUrl;
645            try {
646                localStorage.setItem(this.localStorageKey, JSON.stringify(this.cachedThumbnails));
647            } catch (e) {
648                console.error("Could not save Vimeo thumbnail cache to localStorage.", e);
649            }
650        }
651    }
652
653    // Initialize and run the manager
654    const manager = new VimeoThumbnailManager();
655    manager.processBlocks();
656
657});
658
659
660// FAQ Accordion functionality
661document.addEventListener('DOMContentLoaded', function() {
662    const accordionHeaders = document.querySelectorAll('.accordion-header');
663
664    accordionHeaders.forEach(header => {
665        header.addEventListener('click', function() {
666            const accordionItem = this.closest('.accordion-item');
667            const content = accordionItem.querySelector('.accordion-content');
668            const icon = this.querySelector('.accordion-icon');
669            const isExpanded = this.getAttribute('aria-expanded') === 'true';
670
671            // Toggle the accordion
672            if (isExpanded) {
673                // Collapse
674                content.style.display = 'none';
675                this.setAttribute('aria-expanded', 'false');
676                icon.style.transform = 'rotate(0deg)';
677            } else {
678                // Expand
679                content.style.display = 'block';
680                this.setAttribute('aria-expanded', 'true');
681                icon.style.transform = 'rotate(180deg)';
682            }
683        });
684    });
685});

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.