Welcome to the RPG community

Your home for latest RPG news, must-watch videos, guides, and conversations.

Latest RPG Trailers v0.2

Latest RPG Trailers

'; } return node; } var dialogTrigger = null; var dialogItem = null; var dialogAction = null; function openDialog(item, action, trigger) { var node = dialog(); dialogItem = item; dialogAction = action; dialogTrigger = trigger; var following = action === 'follow'; node.querySelector('#gr-hub-dialog-title').textContent = following ? 'Following is coming soon' : 'Saving is coming soon'; node.querySelector('.gr-hub-dialog__copy').textContent = following ? 'This feature is not available yet, but your click tells us you would use it. Thanks for helping shape ' + HUB_CONFIG.brandLabel + '.' : 'This feature is not available yet, but your click tells us you would use it. Thanks for helping shape ' + HUB_CONFIG.brandLabel + '.'; node.hidden = false; document.documentElement.classList.add('gr-hub-dialog-open'); track(item, 'open', action, trigger, { interactionState: 'opened' }); node.querySelector('[data-gr-dialog-confirm]').focus(); } function closeDialog(reason) { var node = document.getElementById('gr-content-hub-fake-door-dialog-v2'); if (!node || node.hidden) return; track( dialogItem, reason === 'confirm' ? 'click' : 'dismiss', dialogAction || 'dialog', node.querySelector('[data-gr-dialog-confirm]'), { interactionState: reason === 'confirm' ? 'acknowledged' : 'dismissed', dialogReason: reason, }, ); node.hidden = true; document.documentElement.classList.remove('gr-hub-dialog-open'); if (dialogTrigger && !dialogTrigger.hidden) { dialogTrigger.focus(); } else if (dialogItem) { dialogItem.setAttribute('tabindex', '-1'); dialogItem.focus(); } dialogItem = null; dialogAction = null; dialogTrigger = null; } function hideSaveControls() { document.querySelectorAll('[data-gr-action="save"]').forEach(function (button) { button.hidden = true; button.setAttribute('aria-hidden', 'true'); }); } function restoreItemState(item) { if (storageGet(FOLLOW_KEY)) hideFollowControls(); if (storageGet(SAVE_KEY)) hideSaveControls(); } function restoreSessionState() { if (storageGet(FOLLOW_KEY)) hideFollowControls(); if (storageGet(SAVE_KEY)) hideSaveControls(); document.querySelectorAll('[data-gr-item]').forEach(restoreItemState); } function qualifiesForView(item) { var rect = item.getBoundingClientRect(); var viewportHeight = window.innerHeight || document.documentElement.clientHeight; var viewportWidth = window.innerWidth || document.documentElement.clientWidth; var visibleHeight = Math.max(0, Math.min(rect.bottom, viewportHeight) - Math.max(rect.top, 0)); var visibleWidth = Math.max(0, Math.min(rect.right, viewportWidth) - Math.max(rect.left, 0)); var requiredHeight = Math.min(rect.height * 0.5, viewportHeight * 0.5); return ( visibleHeight >= requiredHeight && visibleWidth >= Math.min(rect.width * 0.5, viewportWidth * 0.5) ); } function observeItem(item) { if (item.hidden || !shouldTrackItem(item)) return; if (registered.has(item)) return; registered.add(item); restoreItemState(item); if (!('IntersectionObserver' in window)) { if (!viewed.has(item)) { viewed.add(item); track(item, 'view', 'component', item); } return; } observer.observe(item); } function observeItems() { if (!('IntersectionObserver' in window)) { document.querySelectorAll('[data-gr-item]').forEach(observeItem); return; } observer = new IntersectionObserver( function (entries) { entries.forEach(function (entry) { var item = entry.target; if (viewed.has(item)) return; if (entry.isIntersecting && qualifiesForView(item)) { if (!viewTimers.get(item)) { viewTimers.set( item, window.setTimeout(function () { if (!viewed.has(item) && qualifiesForView(item)) { viewed.add(item); track(item, 'view', 'component', item); observer.unobserve(item); } viewTimers.delete(item); }, VIEW_DELAY), ); } } else if (viewTimers.get(item)) { window.clearTimeout(viewTimers.get(item)); viewTimers.delete(item); } }); }, { threshold: [0, 0.5] }, ); document.querySelectorAll('[data-gr-item]').forEach(observeItem); } function share(button, item) { var title = button.dataset.grShareTitle || item.querySelector('h2, h3')?.textContent || document.title; var url = button.dataset.grUrl || item.querySelector('a[href]')?.href || window.location.href; track(item, 'click', 'share', button, { interactionState: 'attempted' }); var shareData = { title: title.trim(), url: url }; var canNativeShare = Boolean(navigator.share); if (canNativeShare && navigator.canShare) { try { canNativeShare = navigator.canShare(shareData); } catch (error) { canNativeShare = false; } } if (canNativeShare) { navigator .share(shareData) .then(function () { track(item, 'complete', 'share-native', button, { interactionState: 'shared' }); }) .catch(function (error) { if (error && error.name === 'AbortError') { track(item, 'dismiss', 'share-native', button, { interactionState: 'cancelled' }); } else { track(item, 'error', 'share-native', button, { interactionState: 'failed', errorMessage: (error && error.name) || 'Native share failed', }); } }); } else if (navigator.clipboard) { navigator.clipboard .writeText(url) .then(function () { track(item, 'complete', 'share-clipboard', button, { interactionState: 'shared' }); }) .catch(function (error) { track(item, 'error', 'share-clipboard', button, { interactionState: 'failed', errorMessage: (error && error.name) || 'Clipboard share failed', }); }); } else { track(item, 'error', 'share-unavailable', button, { interactionState: 'unavailable', errorMessage: 'Native share and clipboard are unavailable', }); } } function onClick(event) { var control = event.target.closest ? event.target.closest('[data-gr-action]') : null; var authorLink = event.target.closest ? event.target.closest('.gr-hub-article__byline a[href], .gr-hub-article__author a[href], .content-hub-card__byline a[href], .content-hub-card__author a[href]') : null; if (!control && authorLink) { track(itemFor(authorLink), 'click', 'author', authorLink); return; } if (!control) return; var item = itemFor(control); var action = control.dataset.grAction; var target = control.dataset.grActionTarget || action; if (action === 'follow') { event.preventDefault(); track(item, 'click', 'follow', control, { interactionState: 'intent' }); if (!storageGet(FOLLOW_KEY)) storageSet(FOLLOW_KEY, '1'); hideFollowControls(); openDialog(item, 'follow', control); return; } if (action === 'save') { event.preventDefault(); track(item, 'click', 'save', control, { interactionState: 'intent' }); if (!storageGet(SAVE_KEY)) storageSet(SAVE_KEY, '1'); item.dataset.grInteractionState = 'acknowledged'; hideSaveControls(); openDialog(item, 'save', control); return; } if (action === 'share') { event.preventDefault(); share(control, item); return; } if (action === 'timeline-toggle') { event.preventDefault(); var targetNode = document.getElementById(control.getAttribute('aria-controls')); var open = control.getAttribute('aria-expanded') !== 'true'; control.setAttribute('aria-expanded', String(open)); if (targetNode) targetNode.hidden = !open; track(item, 'select', 'timeline-toggle', control, { interactionState: open ? 'expanded' : 'collapsed', }); return; } if (action === 'playlist-item') { event.preventDefault(); var playlistItem = Number(control.dataset.contentHubJwIndex); var player = item && jwPlayerFor(item); if (player && Number.isInteger(playlistItem) && playlistItem >= 0) { try { player.playlistItem(playlistItem); } catch (error) { track(item, 'error', 'playlist-item', control, { interactionState: 'failed', errorMessage: 'JW playlist item could not be selected', }); return; } } track(item, 'click', 'playlist-item', control); return; } track(item, 'click', target, control); } function onDialogClick(event) { var close = event.target.closest ? event.target.closest('[data-gr-dialog-close]') : null; if (close) closeDialog('dismiss'); var confirm = event.target.closest ? event.target.closest('[data-gr-dialog-confirm]') : null; if (confirm) closeDialog('confirm'); } function onKeydown(event) { if (event.key === 'Escape') closeDialog('escape'); } window.GamesRadarContentHub = window.GamesRadarContentHub || {}; window.GamesRadarContentHub.refreshRelativeDates = function () { renderRelativeDates(document); }; window.GamesRadarContentHub.refreshKwizlyPolls = function () { hydrateKwizlyPolls(document); assignRenderedPositions(); }; window.GamesRadarContentHub.refreshJwPlaylists = function () { hydrateJwPlaylists(document); }; window.GamesRadarContentHub.refreshPositions = function () { return assignRenderedPositions().length; }; window.GamesRadarContentHub.resetSessionState = function () { storageRemove(FOLLOW_KEY); storageRemove(SAVE_KEY); showControls('follow'); showControls('save'); }; function boot() { if (booted) return; booted = true; bridgeNeutralComponents(document); /* Capture keeps hub controls reliable when a site-level listener stops bubbling. */ document.addEventListener('click', onClick, true); document.addEventListener('click', onDialogClick, true); document.addEventListener('keydown', onKeydown); hydrateKwizlyPolls(document); hydrateJwPlaylists(document); assignRenderedPositions(); restoreSessionState(); renderRelativeDates(document); reportContentErrors(document); observeItems(); window.setTimeout(trackPageLoad, 0); new MutationObserver(function (mutations) { mutations.forEach(function (mutation) { mutation.addedNodes.forEach(function (node) { if (node.nodeType !== 1) return; bridgeNeutralComponents(node); hydrateKwizlyPolls(node); hydrateJwPlaylists(node); renderRelativeDates(node); reportContentErrors(node); var newItems = []; if (node.matches && node.matches('[data-gr-item]')) newItems.push(node); if (node.querySelectorAll) { node.querySelectorAll('[data-gr-item]').forEach(function (item) { newItems.push(item); }); } if (newItems.length) { assignRenderedPositions(); newItems.forEach(observeItem); } }); }); }).observe(document.body, { childList: true, subtree: true }); } if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', boot, { once: true }); else boot(); })();