PageSourceSearch

https://broadbandswitch.uk/assets/js/home-secondary-interactions.js

js broadbandswitch.uk collected 2026-10-04 08:38:35 UTC 8,769 bytes, 243 lines download raw bytes

1(() => {
2  'use strict';
3
4  // ===== Waste counter with period selector (One Last Look section) =====
5  var wasteEl = document.getElementById('wastedTodayCounter');
6  var wasteLabelEl = document.getElementById('wasteCounterLabel');
7  var wasteTickEl = document.getElementById('wasteCounterTick');
8  var wastePills = document.querySelectorAll('.waste-period-pill');
9  var DAILY_WASTE = 0.8;
10  var liveInterval = null;
11  var animFrame = null;
12
13  function formatGBP(n, isLive) {
14    if (isLive || n < 100) {
15      return '£' + n.toFixed(2);
16    }
17    return '£' + Math.round(n).toLocaleString('en-GB');
18  }
19
20  function updateLiveTicker() {
21    if (!wasteEl) return;
22    var now = new Date();
23    var midnight = new Date(now.getFullYear(), now.getMonth(), now.getDate());
24    var secondsSinceMidnight = (now.getTime() - midnight.getTime()) / 1000;
25    var wasted = (DAILY_WASTE / 86400) * secondsSinceMidnight;
26    wasteEl.textContent = formatGBP(wasted, true);
27  }
28
29  function animateToValue(targetValue, isLive) {
30    if (!wasteEl) return;
31    if (animFrame) cancelAnimationFrame(animFrame);
32    var startText = wasteEl.textContent.replace(/[£,\s]/g, '');
33    var startValue = parseFloat(startText) || 0;
34    var duration = 800;
35    var startTime = performance.now();
36
37    function tick(now) {
38      var elapsed = now - startTime;
39      var progress = Math.min(elapsed / duration, 1);
40      var eased = 1 - Math.pow(1 - progress, 3);
41      var current = startValue + (targetValue - startValue) * eased;
42      wasteEl.textContent = formatGBP(current, isLive);
43      if (progress < 1) {
44        animFrame = requestAnimationFrame(tick);
45      } else {
46        animFrame = null;
47      }
48    }
49
50    animFrame = requestAnimationFrame(tick);
51  }
52
53  function setWastePeriod(periodKey, days) {
54    if (!wasteEl || !wasteLabelEl) return;
55
56    if (liveInterval) {
57      clearInterval(liveInterval);
58      liveInterval = null;
59    }
60
61    wastePills.forEach(function (p) {
62      var isActive = p.dataset.period === periodKey;
63      p.classList.toggle('is-active', isActive);
64      p.setAttribute('aria-selected', isActive ? 'true' : 'false');
65    });
66
67    wasteEl.classList.remove('is-flashing');
68    void wasteEl.offsetWidth;
69    wasteEl.classList.add('is-flashing');
70
71    var labels = {
72      today: 'Wasted today, on average, by an out-of-contract household',
73      month: 'Wasted over 1 month out of contract, on average',
74      '3months': 'Wasted over 3 months out of contract, on average',
75      '6months': 'Wasted over 6 months out of contract, on average',
76      year: 'Wasted over 12 months out of contract, on average',
77      '2years': 'Wasted over 2 years out of contract, on average',
78    };
79    wasteLabelEl.textContent = labels[periodKey] || labels.year;
80
81    if (periodKey === 'today') {
82      if (wasteTickEl) wasteTickEl.hidden = false;
83      updateLiveTicker();
84      liveInterval = setInterval(updateLiveTicker, 1000);
85      return;
86    }
87
88    if (wasteTickEl) wasteTickEl.hidden = true;
89    animateToValue(days * DAILY_WASTE, false);
90  }
91
92  wastePills.forEach(function (p) {
93    p.addEventListener('click', function () {
94      var periodKey = p.dataset.period;
95      var days = parseInt(p.dataset.days, 10);
96      setWastePeriod(periodKey, days);
97    });
98  });
99
100  if (wasteEl) {
101    document.addEventListener('visibilitychange', function () {
102      var todayActive = document.querySelector('.waste-period-pill[data-period="today"].is-active');
103      if (document.hidden && liveInterval) {
104        clearInterval(liveInterval);
105        liveInterval = null;
106      } else if (!document.hidden && todayActive && !liveInterval) {
107        updateLiveTicker();
108        liveInterval = setInterval(updateLiveTicker, 1000);
109      }
110    });
111  }
112
113  // v3: Postcode pulse manager
114  (function postcodePulse() {
115    var forms = document.querySelectorAll('.postcode-form-v5');
116    forms.forEach(function (form) {
117      var input = form.querySelector('.postcode-input');
118      if (!input) return;
119      function check() {
120        if (input.value && input.value.length > 0) {
121          form.classList.add('has-input');
122        } else {
123          form.classList.remove('has-input');
124        }
125      }
126      input.addEventListener('input', check);
127      input.addEventListener('change', check);
128      check();
129    });
130  })();
131
132  // YouTube video lazy embed
133  var videoBtn = document.getElementById('videoPlayBtn');
134  var videoFrame = document.getElementById('videoFrame');
135  var videoThumb = document.getElementById('videoThumb');
136  var VIDEO_ID = 'Toe-fMy0YCI';
137
138  if (videoThumb && VIDEO_ID) {
139    videoThumb.src = 'https://i.ytimg.com/vi/' + VIDEO_ID + '/maxresdefault.jpg';
140    videoThumb.alt = 'BroadbandSwitch.uk: 90 second animated explainer thumbnail';
141    videoThumb.addEventListener('load', function () {
142      if (videoThumb.naturalWidth <= 120) {
143        videoThumb.src = 'https://i.ytimg.com/vi/' + VIDEO_ID + '/hqdefault.jpg';
144      } else {
145        videoThumb.classList.add('loaded');
146      }
147    });
148    videoThumb.addEventListener('error', function () {
149      videoThumb.src = 'https://i.ytimg.com/vi/' + VIDEO_ID + '/hqdefault.jpg';
150    });
151  }
152
153  if (videoBtn && videoFrame) {
154    videoBtn.addEventListener('click', function (e) {
155      e.preventDefault();
156      if (videoFrame.classList.contains('is-playing')) return;
157      var iframe = document.createElement('iframe');
158      iframe.src = 'https://www.youtube-nocookie.com/embed/' + VIDEO_ID + '?autoplay=1&rel=0&modestbranding=1&playsinline=1';
159      iframe.setAttribute('allow', 'accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share');
160      iframe.setAttribute('allowfullscreen', '');
161      iframe.setAttribute('frameborder', '0');
162      iframe.setAttribute('title', 'BroadbandSwitch.uk: 90 second animated guide');
163      iframe.style.cssText = 'position:absolute;top:0;left:0;width:100%;height:100%;border:0;z-index:5;';
164      videoFrame.appendChild(iframe);
165      videoFrame.classList.add('is-playing');
166    });
167  }
168
169  // Editor bubble interactions
170  var bubble = document.getElementById('editorBubble');
171  var bubbleToggle = document.getElementById('editorBubbleToggle');
172  var bubbleClose = document.getElementById('editorBubbleClose');
173
174  if (bubble && bubbleToggle) {
175    bubbleToggle.addEventListener('click', function () {
176      var isOpen = bubble.classList.toggle('is-open');
177      bubbleToggle.setAttribute('aria-expanded', isOpen ? 'true' : 'false');
178    });
179    if (bubbleClose) {
180      bubbleClose.addEventListener('click', function (e) {
181        e.stopPropagation();
182        bubble.classList.remove('is-open');
183        bubbleToggle.setAttribute('aria-expanded', 'false');
184      });
185    }
186    document.addEventListener('click', function (e) {
187      if (!bubble.contains(e.target)) {
188        bubble.classList.remove('is-open');
189        bubbleToggle.setAttribute('aria-expanded', 'false');
190      }
191    });
192    document.addEventListener('keydown', function (e) {
193      if (e.key === 'Escape') {
194        bubble.classList.remove('is-open');
195        bubbleToggle.setAttribute('aria-expanded', 'false');
196      }
197    });
198  }
199
200  // Sticky mobile CTA + editor bubble visibility
201  var hero = document.getElementById('hero');
202  var stickyCta = document.getElementById('stickyCta');
203
204  // Show sticky postcode + editor bubble only after the hero has fully scrolled off-screen
205  // (any intersect with viewport = hide bar).  Avoid rootMargin here: a bottom shrink like -100%
206  // collapses the intersection root and forces the bar visible on load.
207  if (hero && 'IntersectionObserver' in window) {
208    var observer = new IntersectionObserver(
209      function (entries) {
210        entries.forEach(function (entry) {
211          var showSticky = !entry.isIntersecting;
212          if (stickyCta) {
213            stickyCta.classList.toggle('is-visible', showSticky);
214            stickyCta.setAttribute('aria-hidden', showSticky ? 'false' : 'true');
215          }
216          if (bubble) {
217            bubble.classList.toggle('is-visible', showSticky);
218            bubble.setAttribute('aria-hidden', showSticky ? 'false' : 'true');
219          }
220        });
221      },
222      { threshold: 0 }
223    );
224    observer.observe(hero);
225  }
226
227  // Lightweight in-view marker for compare section
228  var comparison = document.getElementById('comparison-results');
229  if (comparison && 'IntersectionObserver' in window) {
230    var compObserver = new IntersectionObserver(
231      function (entries) {
232        entries.forEach(function (entry) {
233          if (entry.isIntersecting) {
234            comparison.setAttribute('data-in-view', 'true');
235            compObserver.disconnect();
236          }
237        });
238      },
239      { rootMargin: '300px' }
240    );
241    compObserver.observe(comparison);
242  }
243})();

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.