ongames.cc">gaming in 2026: These privacy-protecting virtual networks keep your games lag-free" style="max-width:100%;border-radius:12px;margin-bottom:25px">
The best VPN for gaming in 2026: These privacy-protecting virtual networks keep your games lag-free
The best VPNs for gaming are ones that deliver not just a secure and private online experience but are also easy to use. It shouldn’t take a computer science or cybersecurity degree to figure out, and perhaps most importantly, you should be able to get started browsing and gaming over a VPN with as little latency as possible.
There are obvious reasons to use a VPN for browsing and video streaming, such as preventing tracking, or bypassing geo-blocking measures by making it seem like you’re connecting to a site or service from somewhere else (beware doing this in games as it could be a bannable offence). But there are reasons to use a VPN for gaming, too.
We only recommend VPNs that we would use ourselves and that we feel can be trusted with our data. That means no VPNs with known ties to governments or other state actors. It also means VPNs with no-logging policies or a good history of protecting user privacy.
For one, using a VPN for gaming means more peace of mind when you’re playing online games with questionable teammates or enemies. It’s just another layer of defence against any possible rage-induced 1337 hackerman attempts to track you down. That’s a very niche case, though.
Much more relevant is that sometimes connecting over a good VPN can actually improve your network performance in online games, for instance if your ISP throttles gaming downloads or has worse routing in your home country than the VPN. Or maybe the VPN just happens to have a server that’s right next to the game server. With my testing, for instance, I discovered that the fastest VPN for gaming, Windscribe, actually gives me slightly lower ping than playing without any VPN at all.
That doesn’t mean it’s the best VPN overall, though. That honour is reserved for NordVPN, because while Windscribe is fast and there are benefits to other VPNs on this list, nothing beats the full package that NordVPN offers, from server coverage to feature set, ease of use, performance, and everything in between. And if you want to save some money, Surfshark pips the competition there with its regular and consistently low sale prices.
Quick list
The best overall VPN for gaming

window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777388973332-fz3hg6tw3’, true); }); } else { initialize(‘fv-chart-1777388973332-fz3hg6tw3’, true); } })();
NordVPN is the best VPN for gaming primarily because it covers absolutely all bases and wraps its features up in a very easy-to-use package. Ease of use doesn’t feel like a secondary thing for this VPN, rather its guiding principle in all areas of the desktop app. Selecting a location is easy and intuitive and general in-app navigation is a breeze.
Which isn’t to say there’s a limited feature-set, here. Far from it, as you’re getting all the bells and whistles you might want, from split tunnelling to a kill switch, extra things like a password manager with the more expensive tier, and plenty of protocols to choose between.
That protocol list includes NordLynx, which is WireGuard-based and offers all the great performance you might expect from the latter protocol. In my testing, I found it to offer gaming performance pretty much on par with—okay, fractionally behind—my 500 Gb non-VPN connection. That’s whether we’re talking gaming performance, speed tests, or actual download times.
I’d also like to take a moment appreciate that the very first setting on the Settings page is your choice of Windows start-up behaviour. There’s no hiding that setting away like you find in most apps.
Finally, despite how much love I have for smaller companies like Mullvad there’s something to be said for NordVPN being a big brand. It being a bigger brand means you get a little extra peace of mind knowing the software should receive plenty of support, and you also get its widespread server coverage. It’s probably therefore no surprise that, unlike some other VPNs I tested, Valorant let me actually use NordVPN. So its compatibility, too, is something that makes it the best overall VPN.
The best VPN for ping

window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777388973332-fz3hg6tw3’, true); }); } else { initialize(‘fv-chart-1777388973332-fz3hg6tw3’, true); } })();
If you want to keep your ping as low as possible in-game while using a VPN, Windscribe is where it’s at. I found it to offer the lowest ping while playing Counter-Strike over multiple test runs, and this translated to gameplay that felt very snappy and smooth. This will, of course, depend on your location and the game server location, too, but if you’re in a popular spot in the UK or US, for instance, you should be safe.
Apart from this, and in addition to offering all the usual features you might expect from a VPN such as split tunnelling, Windscribe also offers some pretty useful privacy features. For instance, it has the option to enable “decoy traffic” to throw off sniffers, and it even has a “circumvent censorship” option. The CEO seems to be pretty invested in privacy, too, for whatever public image is worth, and it’s open-source, too. And while it doesn’t seem to have had a large-scale independent audit that specifically verifies a no-logs policy, it has stuck by user privacy in actual court cases.
The app doesn’t have a kill switch, but Windscribe says that’s because its firewall is a superior solution because it blocks all connectivity outside of the VPN tunnel. So no kill switch needed.
The only downside I found to the VPN, other than its settings—or perhaps just how they’re laid out—initially feeling a little overwhelming, is that it failed to work with Valorant. The game is a good one to test a VPN against because it’s pretty strict and invasive with its kernel-level anti-cheat, and unfortunately in this case it wouldn’t let me boot into the game at all while Windscribe was connected. So, game compatibility might not be 100% with this VPN.
In those games where it does work, however—and few games will be as strict as Valorant, with even CS2 working—my testing leads me to believe the connection should be great. For a low ping while gaming, Windscribe is the best.
The best cheap VPN for gaming

window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777389323160-kdmxwawb1’, true); }); } else { initialize(‘fv-chart-1777389323160-kdmxwawb1’, true); } })();
If you don’t need the fastest of the fast or the most feature-crammed VPN on the market, and instead want something that’s still pretty great but also very cheap, you should consider Surfshark. This VPN consistently goes on sale for cheaper than any other VPN I’ve looked at.
That doesn’t mean it’s missing a bunch of features, either. You’re getting your fast WireGuard protocol, a big collection of servers to choose from, and some nice extras like ad blocking an an ‘alternative ID’ to sign into untrusted websites, too. You don’t get quite the speed, feature list, or server selection as its parent company’s other VPN, NordVPN, but it comes close. And for saving, many will consider the slight sacrifice worth it for the latter’s smaller sibling.
The app itself is easy and intuitive to use, just like NordVPN, and even has a guided setup when you load it up for the first time, so you don’t have to go rifling through all the settings manually after install. It also looks pretty sleek and modern, though aesthetics are of course subjective.
The only real downside is the same one that I found with Windscribe: unlike NordVPN, Surfshark failed the Valorant test. With Surfshark connected, Valorant failed to load, and after this failed loading, even Riot’s in-browser website wouldn’t let me connect as it claimed ‘unusual activity.’ So, stricter games and websites might cause a problem.
For the vast majority of games, apps, and sites, however, Surfshark should work fine, and you can always split tunnel the games that cause a problem. If you want to keep things as cheap as possible, Surfshark offers a heck of a lot for its diminutive price tag.
The best VPN for privacy

window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777389514410-ka0mtjvlm’, true); }); } else { initialize(‘fv-chart-1777389514410-ka0mtjvlm’, true); } })();
Mullvad is probably a name you’ve heard of if you care about privacy; it’s known as the best VPN for privacy for good reason. The Swedish company takes privacy so seriously that it gives you an account number rather than having you use your email, and you can then top up your account with as much ‘time’ as you want, even paying in cash if you prefer, though recurring payments (ie, subscriptions) aren’t allowed.
The VPN has had plenty of audits and has even had its office raided by officers that came up short because the customer data they were trying to seize simply “did not exist.” That’s what a solid no-logs policy gets you.
However, you do pay for this privacy, both in terms of actual cost, which is a bit more expensive than some other options, and also in terms of its overall feature offering. In particular, the 50-country, 578-server pool is much smaller than some other VPNs, and you don’t get a bunch of extra features in addition to the VPN itself. You also only get the WireGuard protocol, though that is what you’d probably want to use anyway.
The app itself looks lovely—it’s actually my favourite of the bunch in terms of simple usability—and performance is decent, too. It gave me slightly higher in-game ping than others when connected to a distant server (connecting to the US from the UK), but ping was as low as the rest when playing locally.
So, it’s not the flashiest or the absolute fastest or brimming with features, but there’s no VPN that seems to prioritise user privacy as much as Mullvad.
The best all-in-one VPN package

window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777389671848-dp5d0kabn’, true); }); } else { initialize(‘fv-chart-1777389671848-dp5d0kabn’, true); } })();
Proton VPN has the best all-in-one package of any that I’ve tested, and that’s because it’s designed to be part of the bigger Proton ecosystem. If you’ve ever thought ‘I wish there was an alternative to the Google ecosystem’, Proton is on its way there and might be worth a look.
If you set up a Proton account, you not only get access to its VPN (a very limited one if you opt for the free version) but also tons of other cloud-based things like a calendar, drive, email, and password manager. If you get a Proton subscription, you get more features and less restrictions with many of these things, so it’s worth doing if you want to switch over to a new cloud ecosystem. The main benefit to doing this rather than getting a standalone VPN is that you have one less account to worry about, which keeps things simple.
You should also get great support thanks to it being a pretty big company, and that company is based in Switzerland, which tends to be preferred for privacy thanks to the country’s laws. Its server coverage is vast, too.
The main downside appears when it comes to actual performance, because over multiple runs in CS2 I found Proton VPN to consistently give me slightly higher ping than other VPNs, and my speed tests showed higher jitter, too. So, if you want the ecosystem that Proton provides, it might be best to use that split tunnel and go no-VPN while gaming, using it only for browsing, downloading, and so on.
VPN benchmarks
window.iFrameResizer = { heightCalculationMethod: ‘taggedElement’ }; (function() { // Global animation function for slideshow re-use window.fvAnimateCharts = function(chartWrapper) { if (!chartWrapper) return; function animateBars(chartElement) { if (!chartElement) return; var bars = chartElement.querySelectorAll(‘.fv-bar, .fv-stacked-segment’); bars.forEach(function(bar, index) { // Reset to 0 first to ensure animation triggers bar.style.setProperty(‘width’, ‘0%’, ‘important’); bar.style.setProperty(‘transition’, ‘none’, ‘important’); var targetWidth = bar.dataset.targetWidth; if (targetWidth === undefined) return; // Force reflow void bar.offsetWidth; setTimeout(function() { bar.style.setProperty(‘transition’, ‘opacity 0.2s ease, width 0.8s ease-out’, ‘important’); bar.style.setProperty(‘width’, targetWidth + ‘%’, ‘important’); }, index * 50 + 50); // Reduced initial delay }); } function animateLineChart(chartElement) { if (!chartElement) return; var lineSvg = chartElement.querySelector(‘svg’); if (!lineSvg) return; var paths = lineSvg.querySelectorAll(‘.riv-line-path’); paths.forEach(function(p, i) { if (typeof p.getTotalLength === ‘function’) { var len = p.getTotalLength(); p.style.transition = ‘none’; p.style.strokeDasharray = len; p.style.strokeDashoffset = len; p.getBoundingClientRect(); setTimeout(function() { p.style.transition = ‘stroke-dashoffset 1s ease-out ‘ + (i * 0.1) + ‘s, stroke-width 0.2s, opacity 0.2s’; p.style.strokeDashoffset = ‘0’; }, 100); } }); var dots = lineSvg.querySelectorAll(‘.riv-dot’); dots.forEach(function(dot, i) { dot.style.opacity = ‘0’; setTimeout(function() { dot.style.transition = ‘opacity 0.3s ease’; dot.style.opacity = ‘1’; }, 500 + i * 10); }); } // Execute var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); charts.forEach(function(chart) { // If in carousel/dropdown mode, hidden charts are display:none. // We only animate what is visible. if (window.getComputedStyle(chart).display === ‘none’) return; var chartType = chart.dataset.chartType; if (chartType === ‘Line’) { animateLineChart(chart); } else if (chartType !== ‘Pie’) { animateBars(chart); } }); }; function initialize(uniqueId, isSlideshow) { var root = document.getElementById(uniqueId); // In slideshow mode, ‘root’ will be null because the container has ‘-slideshow’ suffix. // We handle that logic below. if (!root && !isSlideshow) return; // Setup internal interactions (Carousel/Dropdown/LineChart) for a specific chart wrapper function setupWrapper(chartWrapper) { if (!chartWrapper) return; // Responsive mobile view handling function checkMobileView() { var width = chartWrapper.getBoundingClientRect().width; var isMobileDevice = window.screen && Math.min(window.screen.width, window.screen.height) <= 599; var isMobile; if (width === 0) { // Fallback for when width isn't available yet (e.g., hidden tab) isMobile = isMobileDevice || window.matchMedia('(max-width: 599px)').matches; } else { // Mobile if container is small OR if it's a physical mobile device (overriding fixed-width iframes) isMobile = isMobileDevice || width rightNum) { winner = ‘left’; if (rightNum > 0) { var diff = Math.round(((leftNum – rightNum) / rightNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } else if (rightNum > leftNum) { winner = ‘right’; if (leftNum > 0) { var diff = Math.round(((rightNum – leftNum) / leftNum) * 100); pctDiffStr = ‘+’ + diff.toLocaleString() + ‘%’; } } } var leftDisplay = data.productData[leftProduct] && data.productData[leftProduct].displayValue !== undefined ? data.productData[leftProduct].displayValue : (leftIsNum ? leftNum.toLocaleString() : (leftVal !== undefined ? leftVal : ‘-‘)); var rightDisplay = data.productData[rightProduct] && data.productData[rightProduct].displayValue !== undefined ? data.productData[rightProduct].displayValue : (rightIsNum ? rightNum.toLocaleString() : (rightVal !== undefined ? rightVal : ‘-‘)); var unit = (data.productData[leftProduct] && data.productData[leftProduct].unit) || (data.productData[rightProduct] && data.productData[rightProduct].unit) || ”; var leftTextStr = leftDisplay; var rightTextStr = rightDisplay; var leftBar = row.querySelector(‘.fv-versus-bar-left’); var rightBar = row.querySelector(‘.fv-versus-bar-right’); var leftText = row.querySelector(‘.fv-inside-left’); var rightText = row.querySelector(‘.fv-inside-right’); var labelText = row.querySelector(‘.fv-versus-label span’); var leftWrapper = row.querySelector(‘.fv-versus-bar-left-wrapper’); var rightWrapper = row.querySelector(‘.fv-versus-bar-right-wrapper’); var existingPctDiffs = row.querySelectorAll(‘.fv-versus-pct-diff’); existingPctDiffs.forEach(function(el) { el.remove(); }); if (winner === ‘left’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (leftBar) leftBar.insertBefore(pctSpan, leftBar.firstChild); } else if (winner === ‘right’ && pctDiffStr) { var pctSpan = document.createElement(‘span’); pctSpan.className = ‘fv-versus-pct-diff’; pctSpan.style.color = ‘rgba(255, 255, 255, 0.9)’; pctSpan.textContent = pctDiffStr; if (rightBar) rightBar.appendChild(pctSpan); } if (leftBar) { leftBar.style.backgroundColor = leftColor; leftBar.dataset.targetWidth = leftWidth; leftBar.style.setProperty(‘–target-width’, leftWidth + ‘%’); leftBar.style.width = leftWidth + ‘%’; } if (rightBar) { rightBar.style.backgroundColor = rightColor; rightBar.dataset.targetWidth = rightWidth; rightBar.style.setProperty(‘–target-width’, rightWidth + ‘%’); rightBar.style.width = rightWidth + ‘%’; } if (leftText) { leftText.innerHTML = leftTextStr; } if (rightText) { rightText.innerHTML = rightTextStr; } if (labelText) { labelText.textContent = data.attribute + (unit ? ‘ (‘ + unit + ‘)’ : ”); } }); } if (leftSelect) leftSelect.addEventListener(‘change’, updateVersusChart); if (rightSelect) rightSelect.addEventListener(‘change’, updateVersusChart); }); // Carousel & Dropdown Logic var charts = chartWrapper.querySelectorAll(‘.fv-chart-item’); var dropdown = chartWrapper.querySelector(‘.fv-dropdown-title’); var prevBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.prev’); var nextBtn = chartWrapper.querySelector(‘.fv-carousel-nav-btn.next’); var carouselTitle = chartWrapper.querySelector(‘.fv-carousel-title-controls .fv-benchmark-title’); var counter = chartWrapper.querySelector(‘.fv-carousel-counter’); // Text Elements var subheadEl = chartWrapper.querySelector(‘.fv-chart-subhead’); var captionEl = chartWrapper.querySelector(‘.rv-chart-caption’); var footerContentEl = chartWrapper.querySelector(‘.fv-footer-content’); var bottomBarEl = chartWrapper.querySelector(‘.fv-bottom-bar’); var logoEl = chartWrapper.querySelector(‘.fv-logo’); if (charts.length > 1 && (dropdown || prevBtn)) { var currentChartIndex = 0; var titles = []; if (dropdown) { titles = Array.from(dropdown.options).map(function(o) { return o.text; }); } else { charts.forEach(function(c) { titles.push(c.getAttribute(‘data-title’) || ”); }); } function showInternalChart(index) { if (index = charts.length) index = 0; currentChartIndex = index; charts.forEach(function(c, i) { c.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { var cType = c.dataset.chartType; if (cType === ‘Line’) { // Line chart animations if needed } else if (cType !== ‘Pie’) { window.fvAnimateCharts(chartWrapper); } // Update labels-on-top based on current chart type var labelsOnTop = chartWrapper.dataset.barLabelsOnTop === ‘true’; if (labelsOnTop && (cType === ‘Bar’ || cType === ‘Stacked Bar’ || cType === ‘Versus’)) { chartWrapper.classList.add(‘labels-on-top’); } else { chartWrapper.classList.remove(‘labels-on-top’); } } }); if (dropdown) dropdown.value = index; if (carouselTitle && titles[index]) carouselTitle.textContent = titles[index]; if (counter) counter.textContent = (index + 1) + ‘ of ‘ + charts.length; // Update Subhead and Caption var activeChart = charts[index]; if (activeChart) { var newSubhead = activeChart.getAttribute(‘data-subhead’); var newCaption = activeChart.getAttribute(‘data-caption’); if (subheadEl) subheadEl.textContent = newSubhead || ”; if (captionEl) captionEl.textContent = newCaption || ”; if (footerContentEl) { if (newCaption && newCaption.trim().length > 0) { footerContentEl.style.display = ‘block’; if (bottomBarEl) bottomBarEl.style.display = ‘flex’; } else { footerContentEl.style.display = ‘none’; if (bottomBarEl && !logoEl) { bottomBarEl.style.display = ‘none’; } } } } } if (dropdown) dropdown.addEventListener(‘change’, function(e) { showInternalChart(parseInt(e.target.value)); }); if (prevBtn) prevBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex – 1); }); if (nextBtn) nextBtn.addEventListener(‘click’, function() { showInternalChart(currentChartIndex + 1); }); } // Image Comparison Logic var imageCompareWrappers = chartWrapper.querySelectorAll(‘.fv-image-compare-wrapper’); imageCompareWrappers.forEach(function(wrapper) { var inner = wrapper.querySelector(‘.fv-image-compare-inner’) || wrapper; var slider = wrapper.querySelector(‘.fv-image-compare-slider’); var fgImage = wrapper.querySelector(‘.fv-image-compare-fg’); var bgImage = wrapper.querySelector(‘.fv-image-compare-bg’); var labelLeft = wrapper.querySelector(‘.fv-image-compare-label-left’); var labelRight = wrapper.querySelector(‘.fv-image-compare-label-right’); var isDragging = false; // Zoom state var scale = 1; var panX = 0; var panY = 0; var isPanning = false; var hasPanned = false; var lastClientX = 0; var lastClientY = 0; var initialDistance = null; var lastCenterX = null; var lastCenterY = null; function updateTransform() { if (wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { inner.style.setProperty(‘transform’, ‘translate(‘ + panX + ‘px, ‘ + panY + ‘px) scale(‘ + scale + ‘)’, ‘important’); } else { inner.style.removeProperty(‘transform’); scale = 1; panX = 0; panY = 0; } } function constrainPan() { var rect = wrapper.getBoundingClientRect(); // Max pan depends on how much the image is scaled beyond the wrapper var maxPanX = Math.max(0, (rect.width * scale – rect.width) / 2); var maxPanY = Math.max(0, (rect.height * scale – rect.height) / 2); panX = Math.max(-maxPanX, Math.min(panX, maxPanX)); panY = Math.max(-maxPanY, Math.min(panY, maxPanY)); } wrapper.addEventListener(‘wheel’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; e.preventDefault(); var zoomSensitivity = 0.005; var zoomFactor = Math.exp(-e.deltaY * zoomSensitivity); var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); if (newScale === scale) return; var rect = wrapper.getBoundingClientRect(); var mouseX = e.clientX – rect.left – rect.width / 2; var mouseY = e.clientY – rect.top – rect.height / 2; var ratio = newScale / scale; panX = mouseX – (mouseX – panX) * ratio; panY = mouseY – (mouseY – panY) * ratio; scale = newScale; constrainPan(); updateTransform(); }, { passive: false }); wrapper.addEventListener(‘mousedown’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’) || scale 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.clientX; lastClientY = e.clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); }); window.addEventListener(‘mouseup’, function() { isPanning = false; }); wrapper.addEventListener(‘touchstart’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; initialDistance = Math.sqrt(dx * dx + dy * dy); var rect = wrapper.getBoundingClientRect(); lastCenterX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; lastCenterY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; hasPanned = true; // Prevent click after pinch } else if (e.touches.length === 1 && scale > 1) { if (e.target.closest(‘.fv-image-compare-slider’) || e.target.closest(‘button’)) return; isPanning = true; hasPanned = false; lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; } }, { passive: false }); wrapper.addEventListener(‘touchmove’, function(e) { if (!wrapper.classList.contains(‘fv-image-compare-fullscreen’)) return; if (e.touches.length === 2 && initialDistance !== null) { e.preventDefault(); var dx = e.touches[0].clientX – e.touches[1].clientX; var dy = e.touches[0].clientY – e.touches[1].clientY; var distance = Math.sqrt(dx * dx + dy * dy); if (initialDistance > 0) { var zoomFactor = distance / initialDistance; var newScale = Math.max(1, Math.min(scale * zoomFactor, 5)); var rect = wrapper.getBoundingClientRect(); var centerX = (e.touches[0].clientX + e.touches[1].clientX) / 2 – rect.left – rect.width / 2; var centerY = (e.touches[0].clientY + e.touches[1].clientY) / 2 – rect.top – rect.height / 2; var ratio = newScale / scale; panX = centerX – (centerX – panX) * ratio; panY = centerY – (centerY – panY) * ratio; if (lastCenterX !== null && lastCenterY !== null) { panX += (centerX – lastCenterX); panY += (centerY – lastCenterY); } scale = newScale; lastCenterX = centerX; lastCenterY = centerY; constrainPan(); updateTransform(); } initialDistance = distance; } else if (e.touches.length === 1 && isPanning) { e.preventDefault(); var dx = e.touches[0].clientX – lastClientX; var dy = e.touches[0].clientY – lastClientY; if (Math.abs(dx) > 2 || Math.abs(dy) > 2) { hasPanned = true; } lastClientX = e.touches[0].clientX; lastClientY = e.touches[0].clientY; panX += dx; panY += dy; constrainPan(); updateTransform(); } }, { passive: false }); wrapper.addEventListener(‘touchend’, function(e) { if (e.touches.length < 2) { initialDistance = null; } if (e.touches.length === 0) { isPanning = false; } }); function handleMove(clientX) { var rect = inner.getBoundingClientRect(); var x = Math.max(0, Math.min(clientX – rect.left, rect.width)); var percent = Math.max(0, Math.min((x / rect.width) * 100, 100)); if (slider) slider.style.setProperty('left', percent + '%', 'important'); if (fgImage) fgImage.style.setProperty('clip-path', 'polygon(0 0, ' + percent + '% 0, ' + percent + '% 100%, 0 100%)', 'important'); if (labelLeft) { if (percent 90) { labelRight.style.setProperty(‘opacity’, ‘0’, ‘important’); } else { labelRight.style.setProperty(‘opacity’, ‘1’, ‘important’); } } } function onMouseMove(e) { if (!isDragging) return; handleMove(e.clientX); } function onTouchMove(e) { if (!isDragging) return; e.preventDefault(); handleMove(e.touches[0].clientX); } function stopDragging() { isDragging = false; window.removeEventListener(‘mousemove’, onMouseMove); window.removeEventListener(‘mouseup’, stopDragging); window.removeEventListener(‘touchmove’, onTouchMove); window.removeEventListener(‘touchend’, stopDragging); } if (slider) { var startDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘mousemove’, onMouseMove); window.addEventListener(‘mouseup’, stopDragging); }; var startTouchDrag = function(clientX) { isDragging = true; handleMove(clientX); window.addEventListener(‘touchmove’, onTouchMove, { passive: false }); window.addEventListener(‘touchend’, stopDragging); }; slider.addEventListener(‘mousedown’, function(e) { e.preventDefault(); startDrag(e.clientX); }); slider.addEventListener(‘touchstart’, function(e) { e.preventDefault(); startTouchDrag(e.touches[0].clientX); }, { passive: false }); } // Expand/Close Logic var expandBtn = wrapper.querySelector(‘.fv-image-compare-expand-btn’); var closeBtn = wrapper.querySelector(‘.fv-image-compare-close-btn’); if (expandBtn) { if (window !== window.parent) { expandBtn.style.display = ‘none’; } else { expandBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.add(‘fv-image-compare-fullscreen’); document.body.style.overflow = ‘hidden’; // Load high-res images if available if (fgImage && fgImage.dataset.highresSrc) { fgImage.src = fgImage.dataset.highresSrc; fgImage.removeAttribute(‘srcset’); fgImage.removeAttribute(‘sizes’); } if (bgImage && bgImage.dataset.highresSrc) { bgImage.src = bgImage.dataset.highresSrc; bgImage.removeAttribute(‘srcset’); bgImage.removeAttribute(‘sizes’); } }); } } if (closeBtn) { closeBtn.addEventListener(‘click’, function(e) { // e.preventDefault(); // Removed to allow text selection e.stopPropagation(); wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); }); } // Close on Escape document.addEventListener(‘keydown’, function(e) { if (e.key === ‘Escape’ && wrapper.classList.contains(‘fv-image-compare-fullscreen’)) { wrapper.classList.remove(‘fv-image-compare-fullscreen’); document.body.style.overflow = ”; updateTransform(); } }); }); // Shop The Look Hotspots var hotspots = chartWrapper.querySelectorAll(‘.fv-stl-hotspot-btn’); var allProductsModal = chartWrapper.querySelector(‘.fv-stl-all-products-modal’); var shopAllBtn = chartWrapper.querySelector(‘.fv-stl-shop-all-btn’); var allProductsList = chartWrapper.querySelector(‘.fv-stl-all-products-list’); var stlContainer = chartWrapper.querySelector(‘.fv-stl-container’); function closeAllModals() { if (allProductsModal) { allProductsModal.classList.remove(‘is-active’); // Remove highlights var items = allProductsModal.querySelectorAll(‘.fv-stl-all-products-item’); items.forEach(function(item) { item.classList.remove(‘is-highlighted’); }); // Remove min-height after transition if (stlContainer) { setTimeout(function() { if (!allProductsModal.classList.contains(‘is-active’)) { stlContainer.style.minHeight = ”; if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } }, 300); } } hotspots.forEach(function(btn) { btn.setAttribute(‘aria-expanded’, ‘false’); }); if (‘parentIFrame’ in window) { window.parentIFrame.size(); } } hotspots.forEach(function(btn) { btn.addEventListener(‘click’, function(e) { e.stopPropagation(); var hotspotId = btn.getAttribute(‘data-hotspot-id’); var isExpanded = btn.getAttribute(‘aria-expanded’) === ‘true’; closeAllModals(); if (!isExpanded && allProductsModal) { btn.setAttribute(‘aria-expanded’, ‘true’); allProductsModal.classList.add(‘is-active’); // Ensure container is tall enough var container = btn.closest(‘.fv-stl-container’); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } // Highlight and scroll to item var targetItem = allProductsModal.querySelector('.fv-stl-all-products-item[data-product-id="' + hotspotId + '"]'); if (targetItem) { targetItem.classList.add('is-highlighted'); setTimeout(function() { targetItem.scrollIntoView({ behavior: 'smooth', block: 'center' }); }, 100); } if ('parentIFrame' in window) { window.parentIFrame.size(); } } }); }); // Shop All button if (shopAllBtn && allProductsModal) { shopAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); allProductsModal.classList.add('is-active'); // Ensure container is tall enough var container = shopAllBtn.closest('.fv-stl-container'); if (container && container.offsetHeight < 450) { container.style.minHeight = '450px'; } if ('parentIFrame' in window) { window.parentIFrame.size(); } }); } // Close button in all products modal if (allProductsModal) { var closeAllBtn = allProductsModal.querySelector('.fv-stl-all-products-close'); if (closeAllBtn) { closeAllBtn.addEventListener('click', function(e) { e.stopPropagation(); closeAllModals(); }); } } // Close modals when clicking outside chartWrapper.addEventListener('click', function(e) { // Only close if clicking on the wrapper or container, not inside a modal content if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); if (allProductsModal) { allProductsModal.addEventListener('click', function(e) { if (!e.target.closest('.fv-stl-all-products-content')) { closeAllModals(); } }); } // Initial Animation window.fvAnimateCharts(chartWrapper); } if (true) { var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); if (slideshowContainer) { var slides = slideshowContainer.querySelectorAll('.fv-slide'); slides.forEach(function(slide) { setupWrapper(slide.querySelector('.fv-chart-wrapper')); }); } var slideshowContainer = document.getElementById(uniqueId + '-slideshow'); // Check if container exists to prevent errors if (slideshowContainer) { var currentSlideIndex = 0; var slides = slideshowContainer.querySelectorAll('.fv-slide'); var counter = slideshowContainer.querySelector('.fv-slide-counter'); var prevBtn = slideshowContainer.querySelector('.fv-nav-btn.prev'); var nextBtn = slideshowContainer.querySelector('.fv-nav-btn.next'); var dropdown = slideshowContainer.querySelector('.fv-slideshow-select'); function updateControls(index) { if (counter) counter.textContent = (index + 1) + ' / ' + slides.length; if (dropdown) dropdown.value = index; if (prevBtn) { if (index === 0) prevBtn.classList.add('disabled'); else prevBtn.classList.remove('disabled'); } if (nextBtn) { if (index === slides.length – 1) nextBtn.classList.add('disabled'); else nextBtn.classList.remove('disabled'); } } function showSlide(index) { if (index = slides.length) return; // index = 0; currentSlideIndex = index; slides.forEach(function(slide, i) { slide.style.display = i === index ? ‘block’ : ‘none’; if (i === index) { // Re-trigger animations for the active slide var chartEl = slide.querySelector(‘.fv-chart-wrapper’); if (chartEl && window.fvAnimateCharts) { window.fvAnimateCharts(chartEl); } } }); updateControls(currentSlideIndex); } if (prevBtn) { prevBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex – 1); }); } if (nextBtn) { nextBtn.addEventListener(‘click’, function() { showSlide(currentSlideIndex + 1); }); } if (dropdown) { dropdown.addEventListener(‘change’, function(e) { showSlide(parseInt(e.target.value)); }); } // Initial setup updateControls(currentSlideIndex); // Initial animation for the first slide if (slides.length > 0) { var firstChart = slides[0].querySelector(‘.fv-chart-wrapper’); if (firstChart && window.fvAnimateCharts) { setTimeout(function() { window.fvAnimateCharts(firstChart); }, 100); } } } } else { setupWrapper(root); } } if (document.readyState === ‘loading’) { document.addEventListener(‘DOMContentLoaded’, function() { initialize(‘fv-chart-1777388404907-3j17eyssr’, true); }); } else { initialize(‘fv-chart-1777388404907-3j17eyssr’, true); } })();
Test rig: Windows 11 | 500 Gb wired ethernet | AMD Ryzen 7 7800X3D | 32 GB DDR5-6000 | RX 9070 XT | 2 TB SSD.
How we test VPNs for gaming
All the VPNs on this list have been thoroughly tested in different games and for different uses such as browsing and downloading.
For gaming, in addition to simply using a VPN in some different online games to get used to how it performs, we also collect runs of ping data in Counter-Strike 2 using our bespoke ping capture app. We ensure that the game server, system, and other variables remain the same each time we connect, and we perform multiple runs to average our data.
For downloads, we time the same 15.3 GB Steam download with a stopwatch, ensuring we are connected to the same Steam server.
It’s also important to get a feel for the app itself, so we make sure we spend some time poking around all the different areas of the app and trying its different features.
We test and review VPN services in the context of legal recreational uses. For example:
1. Accessing a service from another country (subject to the terms and conditions of that service).
2. Protecting your online security and strengthening your online privacy when abroad.
We do not support or condone the illegal or malicious use of VPN services. Consuming pirated content that is paid-for is neither endorsed nor approved by Future Publishing.




