function GetElemSNR( $Elem ) { var snr = $Elem.data( 'snr' ); if ( typeof snr != 'undefined' ) { return snr; } // look for links with snr parameter var links = $Elem.is( 'a' ) ? $Elem : $Elem.find( 'a' ); snr = null; for ( var i = 0; i < links.length; ++i ) { var link = links[i]; var navinfo = link.href.match( /[\?&]snr=([a-zA-Z0-9\-\_ ]+)/ ); if ( navinfo ) { snr = navinfo[1]; break; } } $Elem.data( 'snr', snr ); return snr; } // given an array of impressions as strings, this will handle joining them all together into a singular string, but enforcing that it doesn't // go above the cookie size limit which can otherwise cause users to become stuck since the page requests will start failing function JoinImpressionsUpToLimit( rgImpressions ) { //cookies generally can go up to 4k bytes, but we can have problems when we start getting that close, so cut it off earlier var nRemainingLen = 3200; var result = ''; for ( var i = 0; i < rgImpressions.length; i++ ) { var impression = String( rgImpressions[ i ] ); var nImpressionLen = encodeURIComponent( impression + '|' ).length; //did we run out of room in our list? if ( nRemainingLen < nImpressionLen ) break; //add the separator if not the first entry if ( result !== '' ) result += '|'; //add our impression and remove that space from what is available result += impression; nRemainingLen -= nImpressionLen; } return result; } GDynamicStore = { m_bLoadComplete: false, s_rgWishlist: {}, s_rgOwnedPackages: {}, s_rgOwnedApps: {}, s_rgMasterSubApps: {}, s_rgAutoGrantApps: {}, s_rgPackagesInCart: {}, s_rgAppsInCart: {}, s_rgRecommendedTags: [], s_rgIgnoredApps: {}, s_rgIgnoredPackages: {}, s_rgCurators: {}, s_rgCurations: {}, s_rgCreatorsFollowed: {}, s_rgCreatorsIgnored: {}, s_preferences: {}, s_rgExcludedTags: {}, s_rgExcludedDescIDs: {}, s_rgPersonalizedBundleData: {}, s_rgfnOnReadyCallbacks: [], s_rgDisplayedApps: [], s_rgDisplayedBundles: [], s_rgDisplayedPackages: [], s_bUserOnMacOS: false, s_bUserOnLinux: false, s_bUserOnWindows: false, s_rgRecommendedApps: [], s_ImpressionTracker: false, s_bAllowAppImpressions: false, Init: function( accountid, bForceRefresh, strOS, preferences, strCC, optsIn ) { var opts = $J.extend( { bNoDefaultDescriptors: false }, optsIn || {} ); var rgDesiredOSTypes = strOS ? strOS.split(',') : 'any'; for( var i=0; i < rgDesiredOSTypes.length; i++ ) { switch( rgDesiredOSTypes[i] ) { case 'mac': GDynamicStore.s_bUserOnMacOS = true; break; case 'linux': GDynamicStore.s_bUserOnLinux = true; break; default: case 'win': GDynamicStore.s_bUserOnWindows = true; break; } } GDynamicStore.s_preferences = preferences || {}; var fnRunOnLoadCallbacks = function() { GDynamicStore.m_bLoadComplete = true; GDynamicStore.InitAppearHandler(); for ( var i = 0; i < GDynamicStore.s_rgfnOnReadyCallbacks.length; i++ ) { try { GDynamicStore.s_rgfnOnReadyCallbacks[i](); } catch ( e ) { console.error( e ); } } GDynamicStore.s_rgfnOnReadyCallbacks = null; GDynamicStore.DecorateDynamicItems(); GDynamicStore.PopulateRecommendedTagList(); }; try { this.RemoveSNRFromURL(); this.RemoveUTMFromURL(); } catch ( e ) { } // Create a new monitor to track impressions this.s_ImpressionTracker = new CAppearMonitor( function( elElement ){ var fnTrack = function( el ) { var $Elem = $J(el); if ( $Elem.data( 'trackedForImpressions' ) ) { return; } $Elem.data( 'trackedForImpressions', true ); // must have appids var strAppIDs = $Elem.data('dsAppid'); if ( !strAppIDs || strAppIDs.length == 0 ) { return; } var snr = GetElemSNR( $Elem ); if ( !snr ) { return; } GDynamicStore.AddImpressionFromDynamicItem( $Elem ); }; fnTrack( elElement ); // Also track sub-elements, which may exist inside our container. This is useful for nested clusters etc. $J(elElement).find("*[data-ds-appid]").each(function(i, j){ fnTrack(j); }); } ); if ( accountid ) { if ( bForceRefresh ) GDynamicStore.InvalidateCache(); var url = 'https://store.steampowered.com/dynamicstore/userdata/?id=' + accountid + '&cc=' + strCC; var unUserdataVersion = WebStorage.GetLocal( 'unUserdataVersion' ); if ( unUserdataVersion ) url += '&v=' + parseInt( unUserdataVersion ); $J.get( url ).done( function( data ) { var fnEnsureObject = function ( rgMaybeArray ) { return ( !rgMaybeArray || ( typeof rgMaybeArray.length != 'undefined' && rgMaybeArray.length == 0 ) ) ? {} : rgMaybeArray; }; var fnConvertToMap = function ( rgData ) { var out = {}; if ( rgData && rgData.length ) { for ( var i = 0; i < rgData.length; i++ ) out[ rgData[i] ] = true; } return out; }; GDynamicStore.s_rgWishlist = fnConvertToMap( data.rgWishlist ); GDynamicStore.s_rgOwnedPackages = fnConvertToMap( data.rgOwnedPackages ); GDynamicStore.s_rgOwnedApps = fnConvertToMap( data.rgOwnedApps ); GDynamicStore.s_rgMasterSubApps = fnConvertToMap( data.rgMasterSubApps ); GDynamicStore.s_rgAutoGrantApps = fnConvertToMap( data.rgAutoGrantApps ); GDynamicStore.s_rgPackagesInCart = fnConvertToMap( data.rgPackagesInCart ); GDynamicStore.s_rgAppsInCart = fnConvertToMap( data.rgAppsInCart ); GDynamicStore.s_rgRecommendedTags = data.rgRecommendedTags || []; GDynamicStore.s_rgIgnoredApps = data.rgIgnoredApps || {} GDynamicStore.s_rgIgnoredPackages = data.rgIgnoredPackages || {}; GDynamicStore.s_rgCurators = data.rgCurators || {}; GDynamicStore.s_rgCurations = data.rgCurations || {}; GDynamicStore.s_rgCreatorsFollowed = fnConvertToMap( data.rgCreatorsFollowed ); GDynamicStore.s_rgCreatorsIgnored = fnConvertToMap( data.rgCreatorsIgnored ); GDynamicStore.s_bAllowAppImpressions = data.bAllowAppImpressions || false; if ( data.rgExcludedTags && data.rgExcludedTags.length > 0 ) { for ( var i = i = 0; i < data.rgExcludedTags.length; ++i ) { var tag = data.rgExcludedTags[i]; GDynamicStore.s_rgExcludedTags[tag.tagid] = tag.name; } } if ( data.rgExcludedContentDescriptorIDs && data.rgExcludedContentDescriptorIDs.length > 0 && !V_GetCookie( 'wants_mature_content') ) { for ( var i = i = 0; i < data.rgExcludedContentDescriptorIDs.length; ++i ) { var id = data.rgExcludedContentDescriptorIDs[i]; GDynamicStore.s_rgExcludedDescIDs[id] = id; } } GDynamicStore.s_nRemainingCartDiscount = data.nRemainingCartDiscount ? data.nRemainingCartDiscount : 0; GDynamicStore.s_nTotalCartDiscount = data.nTotalCartDiscount ? data.nTotalCartDiscount : 0; GDynamicStore.s_rgRecommendedApps = data.rgRecommendedApps || []; GDynamicStore.s_nPromotionalDiscount = data.nPromotionalDiscount ? data.nPromotionalDiscount : 0; GDynamicStore.s_nPromotionalDiscountMinCartAmount = data.nPromotionalDiscountMinCartAmount ? data.nPromotionalDiscountMinCartAmount : 0; GDynamicStore.s_nPromotionalDiscountAvailableUseCount = data.nPromotionalDiscountAvailableUseCount ? data.nPromotionalDiscountAvailableUseCount : 0; }).always( function() { $J(fnRunOnLoadCallbacks); } ); } else { if ( !opts.bNoDefaultDescriptors ) GDynamicStore.s_rgExcludedDescIDs[3] = 3; var url = 'https://store.steampowered.com/dynamicstore/saledata/?cc=' + strCC; $J.get( url ).done( function( data ) { GDynamicStore.s_nPromotionalDiscount = data.nPromotionalDiscount ? data.nPromotionalDiscount : 0; GDynamicStore.s_nPromotionalDiscountMinCartAmount = data.nPromotionalDiscountMinCartAmount ? data.nPromotionalDiscountMinCartAmount : 0; GDynamicStore.s_nPromotionalDiscountAvailableUseCount = data.nPromotionalDiscountAvailableUseCount ? data.nPromotionalDiscountAvailableUseCount : 0; GDynamicStore.s_bAllowAppImpressions = data.bAllowAppImpressions || false; }).always( function() { $J(fnRunOnLoadCallbacks); } ); } }, RemoveSNRFromURL: function() { if ( !window.history || !window.history.replaceState || !window.location.search ) return; GDynamicStore.RemoveParamFromURL( 'snr' ); GDynamicStore.RemoveParamFromURL( 'ser' ); }, RemoveParamFromURL: function( strParamName ) { // find snr param var strParamPrefix = '' + strParamName + '='; var strSearch = window.location.search; if ( strSearch.indexOf( '?' ) == 0 ) strSearch = strSearch.slice( 1 ); var rgParams = strSearch.split( '&' ); var iParam = -1; for ( var i = 0; i < rgParams.length; i++ ) { var strParam = rgParams[i]; if (strParam.indexOf( strParamPrefix ) == 0) { iParam = i; break; } } if ( iParam < 0 ) return; var strRemove = ''; if ( rgParams.length == 1 || (rgParams.length == 2 && rgParams[1].length == 0) ) { // remove the entire search.. just SNR strRemove = '?' + strSearch; } else if ( iParam == 0 ) { // first param of multiple. Remove snr and trailing & strRemove = rgParams[iParam] + '&'; } else { // 2nd+ param of multiple. Remove snr and preceeding & strRemove = '&' + rgParams[iParam]; } if ( strRemove.length > 0 ) { var strNewURL = window.location.href.replace( strRemove, '' ); window.history.replaceState( history.state, null, strNewURL ); } }, RemoveUTMFromURL: function() { if ( !window.history || !window.history.replaceState || !window.location.search ) return; var strSearch = window.location.search; if ( strSearch.indexOf( '?' ) == 0 ) strSearch = strSearch.slice( 1 ); var rgParams = strSearch.split( '&' ); var rgNewParams = []; for ( var i = 0; i < rgParams.length; ++i ) { var strParam = rgParams[i]; if ( strParam.indexOf( 'utm_') == -1 ) { rgNewParams.push( strParam ); } } if ( rgParams.length != rgNewParams.length ) { var strNewURL = window.location.href.replace( window.location.search, '' ); for ( var i = 0; i < rgNewParams.length; ++i ) { var strParam = rgNewParams[i]; strNewURL += ( i == 0 ? '?' : '&' ) + strParam; } window.history.replaceState( history.state, null, strNewURL ); } }, // Fixup name portion of URL via history API, if support and if name portion is incorrect FixupNamePortion: function() { var rel = $J( "link[rel='canonical']" ); if ( rel.length && window.history ) { // have rel=canonical URL and access to history API. // parse out href portion of navigated URL and see if it's OK var detachedAnchor = document.createElement( 'a' ); detachedAnchor.href = rel.attr( "href" ); if ( window.location.pathname != detachedAnchor.pathname ) { // URL portion does not match canonical URL; rewrite it, preserving query params and hash window.history.replaceState( null, null, rel[0].href + window.location.search + window.location.hash ); } } }, s_strAppearSelector: '[data-ds-appid], [data-ds-packageid]', InitAppearHandler: function() { $J(GDynamicStore.s_strAppearSelector).each(function(i, elTarget ){ var $Elem = $J(elTarget); // these are handled manually, so don't add the impression here if ( $Elem.hasClass( 'cluster_capsule' ) || $Elem.hasClass( 'carousel_cap') ) { return; } GDynamicStore.s_ImpressionTracker.RegisterElement( elTarget ) }); // find our horizontal scrollers and add tracking to them $J('.store_horizontal_autoslider' ).each(function(i, elTarget ){ GDynamicStore.s_ImpressionTracker.RegisterScrollEvent( elTarget ); }); GDynamicStore.s_ImpressionTracker.CheckVisibility(); }, s_oImpressionsTracked: {}, AddImpressionFromDynamicItem: function( $Elem ) { if ( !GDynamicStore.s_bAllowAppImpressions ) { return; } if ( $Elem.hasClass( 'app_impression_tracked' ) ) { return; } $Elem.addClass( 'app_impression_tracked' ); var strImpressions = V_GetDecodedCookie( "app_impressions" ); var rgImpressions = strImpressions && strImpressions.length != 0 ? strImpressions.split( "|" ) : []; // commas not allowed in cookie value var strAppIDs = $Elem.data('dsAppid'); if ( !strAppIDs ) { return; } var rgAppIds; if ( typeof strAppIDs == 'string' && ( strAppIDs.indexOf( ',' ) >= 0 || strAppIDs.indexOf( ':' ) >= 0 )) { rgAppIds = strAppIDs.split( /[,:]/ ); } else { rgAppIds = [ parseInt( strAppIDs ) ]; } var snr = GetElemSNR( $Elem ); if ( !snr ) { return; } var rgAppIDsToReport = []; for ( var i = 0; i < rgAppIds.length; i++ ) { var nAppID = rgAppIds[i]; var strImpressionData = nAppID + '@' + snr; if ( !GDynamicStore.s_oImpressionsTracked[ strImpressionData ] ) { GDynamicStore.s_oImpressionsTracked[ strImpressionData ] = true; rgAppIDsToReport.push( nAppID ); } } if ( !rgAppIDsToReport.length ) return; rgImpressions.push( rgAppIDsToReport.join( ':' ) + '@' + snr ); V_SetCookie( "app_impressions", JoinImpressionsUpToLimit( rgImpressions ) ); }, AddImpression: function( $Elem, appID, strLink ) { if ( $Elem.hasClass( 'app_impression_tracked' ) ) { return; } $Elem.addClass( 'app_impression_tracked' ); var navinfo = strLink.match( /[\?&]snr=([^&#]*)(&|$|#)/ ); if ( navinfo ) { var snr = navinfo[1]; var strImpressions = V_GetCookie( "app_impressions" ); var rgImpressions = strImpressions && strImpressions.length != 0 ? strImpressions.split( "|" ) : []; var strImpressionData = appID + '@' + snr; rgImpressions.push( strImpressionData ); V_SetCookie( "app_impressions", JoinImpressionsUpToLimit( rgImpressions ) ); } }, MarkAppDisplayed: function( rgDisplayList, cItemsToMark ) { // jquery map takes care of arrays as well as array-ish GDynamicStore.MarkAppIDsAsDisplayed( $J.map( rgDisplayList, function ( item ) { return item.appid; } ), cItemsToMark ); }, MarkItemsAsDisplayed: function( rgItems, cItemsToMark ) { for ( var i = 0; i < rgItems.length; i++ ) { const item = rgItems[i]; if ( item.appid ) { GDynamicStore.s_rgDisplayedApps.push( item.appid ); // if this appid is a demo, also mark the parent app as displayed var rgAppData = GStoreItemData.rgAppData[ item.appid ]; if ( rgAppData && rgAppData.demo_for_app ) GDynamicStore.s_rgDisplayedApps.push( rgAppData.demo_for_app ); } else if ( item.bundleid ) { GDynamicStore.s_rgDisplayedBundles.push( item.bundleid ); } else if ( item.packageid ) { GDynamicStore.s_rgDisplayedPackages.push( item.packageid ); } if ( cItemsToMark !== undefined && --cItemsToMark == 0 ) break; } }, MarkAppIDsAsDisplayed: function( rgAppIDs, cItemsToMark ) { for ( var i = 0; i < rgAppIDs.length; i++ ) { if ( rgAppIDs[i] ) { GDynamicStore.s_rgDisplayedApps.push( rgAppIDs[i] ); // if this appid is a demo, also mark the parent app as displayed var rgAppData = GStoreItemData.rgAppData[ rgAppIDs[i] ]; if ( rgAppData && rgAppData.demo_for_app ) GDynamicStore.s_rgDisplayedApps.push( rgAppData.demo_for_app ); if ( cItemsToMark !== undefined && --cItemsToMark == 0 ) break; } } }, HandleClusterChange: function( cluster ) { GDynamicStore.s_ImpressionTracker.CheckVisibility(); var $ScrollingContainer = $J( cluster.elScrollArea ); var capsules = $ScrollingContainer.find( '.cluster_capsule' ); GDynamicStore.s_ImpressionTracker.TrackAppearanceIfVisible( capsules[cluster.nCurCap] ); }, HandleCarouselChange: function( targetid, curPos, pageSize ) { var $ScrollingContainer = $J( "#" + targetid ); var capsules = $ScrollingContainer.find( '.recommendation_carousel_item' ); var idx = (curPos * pageSize); if ( capsules.length != 0 && idx < capsules.length ) { GDynamicStore.s_ImpressionTracker.TrackAppearanceIfVisible( capsules[idx] ); } }, OnReady: function( fnCallback ) { if ( GDynamicStore.m_bLoadComplete ) fnCallback(); else GDynamicStore.s_rgfnOnReadyCallbacks.push( fnCallback ); }, DecorateDynamicItems: function( $Selector, bForceRecalculate ) { if ( !GDynamicStore.m_bLoadComplete ) { GDynamicStore.OnReady( function() { GDynamicStore.DecorateDynamicItems( $Selector ) } ); return; } // locate elements with dynamic store data var strSelector = '[data-ds-appid], [data-ds-packageid], [data-ds-bundleid]'; // update prices for cart if ( GDynamicStore.s_nRemainingCartDiscount != 'undefined ') { UpdatePricesForAdditionalCartDiscount($Selector, GDynamicStore.s_nRemainingCartDiscount); } var bBannerShown = false; if ( GDynamicStore.s_nTotalCartDiscount != 'undefined ') { bBannerShown = UpdateStoreBannerForAdditionalCartDiscount( GDynamicStore.s_nTotalCartDiscount ); } if ( !bBannerShown && GDynamicStore.s_nPromotionalDiscount != 'undefined' ) { bBannerShown = UpdateStoreBannerForPromotionalDiscount( GDynamicStore.s_nPromotionalDiscount, GDynamicStore.s_nPromotionalDiscountMinCartAmount, GDynamicStore.s_nPromotionalDiscountAvailableUseCount ); } var $DynamicElements; if ( $Selector ) { if ( $Selector.is( strSelector ) ) { $DynamicElements = $Selector; } else { $DynamicElements = $Selector.find( strSelector ); } } else { $DynamicElements = $J(strSelector); } $DynamicElements.each( function() { var $El = $J(this); if ( bForceRecalculate ) { $El.removeClass( 'ds_flagged ds_owned ds_wishlist ds_incart' ).children( '.ds_flag' ).remove(); } else if ( $El.data('dsInstrumented') ) { return; } $El.data('dsInstrumented', true); var bOwned = false; var bWanted = false; var bInCart = false; var bIgnored = false; var unBundleID = $El.data('dsBundleid'); var unPackageID = $El.data('dsPackageid'); var strAppIDs = $El.data('dsAppid'); var eSteamDeckCompatCategory = $El.data('dsSteamDeckCompatCategory'); if ( eSteamDeckCompatCategory !== undefined && !$El.data( 'dsSteamDeckCompatHandled' ) ) { $El.data('dsSteamDeckCompatHandled', true); var strClasses = 'ds_steam_deck_compat '; switch( eSteamDeckCompatCategory ) { case 3: strClasses += 'verified'; break; case 2: strClasses += 'playable'; break; case 1: strClasses += 'unsupported'; break; case 0: default: strClasses += 'unknown'; break; } var elSteamDeckCompatCategory = $J( '
', { class: strClasses } ); $El.append( elSteamDeckCompatCategory ); } if ( unBundleID ) { var Bundle = GDynamicStore.GetPersonalizedBundleData( unBundleID, $El.data('dsBundleData') ); if ( !Bundle ) // no data available return; if ( Bundle.m_cUserItemsInBundle == 0 ) { bOwned = true; } else { // pull out all the appids and let the strAppIDs code below handle it var rgAllAppIDsInBundle = []; for( var iBundleItem = 0; iBundleItem < Bundle.m_rgBundleItems.length; iBundleItem++ ) { var BundleItem = Bundle.m_rgBundleItems[iBundleItem]; for ( var iApp = 0; iApp < BundleItem.m_rgIncludedAppIDs.length; iApp++ ) { rgAllAppIDsInBundle.push( BundleItem.m_rgIncludedAppIDs[iApp] ); } } strAppIDs = rgAllAppIDsInBundle.join(','); } GDynamicStore.UpdateDynamicBundleElements( Bundle, $El ); } else if ( unPackageID ) { if ( GDynamicStore.s_rgPackagesInCart[unPackageID] ) bInCart = true; else if ( GDynamicStore.s_rgOwnedPackages[unPackageID] ) bOwned = true; else if ( unPackageID in GDynamicStore.s_rgIgnoredPackages ) bIgnored = true; } if ( strAppIDs && typeof strAppIDs == 'string' && strAppIDs.indexOf( ',' ) >= 0 ) { var rgAppIDs = strAppIDs.split( ',' ); var bValid = false; var bAllOwned = true, bAllWanted = true, bAllInCart = true; for ( var i = 0; i < rgAppIDs.length; i++ ) { var unAppID = parseInt( rgAppIDs[i] ); if ( !unAppID ) continue; bValid = true; if ( !GDynamicStore.s_rgOwnedApps[unAppID] ) bAllOwned = false; if ( !GDynamicStore.s_rgWishlist[unAppID] ) bAllWanted = false; if ( !GDynamicStore.s_rgAppsInCart[unAppID] ) bAllInCart = false; } if ( bValid ) { if ( bAllInCart ) bInCart = bAllInCart; else if ( bAllOwned ) bOwned = bAllOwned; else if ( bAllWanted ) bWanted = bAllWanted; GDynamicStore.s_ImpressionTracker.RegisterElement( this ); } } else if ( parseInt( strAppIDs ) ) { // simple case of single appid var unAppID = parseInt( strAppIDs ); if ( GDynamicStore.s_rgAppsInCart[unAppID] ) bInCart = true; else if ( GDynamicStore.s_rgOwnedApps[unAppID] ) bOwned = true; else if ( GDynamicStore.s_rgWishlist[unAppID] ) bWanted = true; else if ( unAppID in GDynamicStore.s_rgIgnoredApps ) bIgnored = true; GDynamicStore.s_ImpressionTracker.RegisterElement( this ); } var rgExcludedTagNames = GDynamicStore.GetExcludedTagsOverlap( $El ); var rgExcludedContentDescriptorIDs = GDynamicStore.GetExcludedContentDescriptorOverlap( $El ); var rgExcludedCreatorIDs = GDynamicStore.GetExcludedCreatorOverlap( $El ); if ( !$El.hasClass('ds_no_flags') ) { // owned and wishlist are mutually exclusive if ( bOwned ) { $El.addClass( 'ds_flagged ds_owned' ); $El.append( '
В БИБЛИОТЕКЕ  
'); } else if ( bWanted ) { $El.addClass( 'ds_flagged ds_wishlist' ); $El.append( '
В ЖЕЛАЕМОМ  
'); } else if ( bIgnored ) { $El.addClass( 'ds_flagged ds_ignored' ); $El.append( '
СКРЫТО  
'); } if ( bInCart ) { $El.addClass( 'ds_flagged ds_incart' ); $El.append( '
В КОРЗИНЕ  
'); } if ( $El.hasClass( 'ds_excluded_blur' ) ) { $El.addClass( 'ds_flagged' ); $El.append( '
ИСКЛЮЧЕНО ИСХОДЯ ИЗ НАСТРОЕК  
' ); } else if ( rgExcludedContentDescriptorIDs.length != 0 ) { $El.addClass( 'ds_flagged ds_excluded_by_preferences' ); $El.append( '
ИСКЛЮЧЕНО ИСХОДЯ ИЗ НАСТРОЕК  
' ); } else if ( rgExcludedTagNames.length != 0 ) { $El.addClass( 'ds_flagged ds_excluded_by_preferences' ); $El.append( '
ИСКЛЮЧЁННЫЕ МЕТКИ: ' + rgExcludedTagNames.join(", " ) + '
' ); } else if ( rgExcludedCreatorIDs.length != 0 ) { $El.addClass( 'ds_flagged ds_excluded_by_preferences' ); $El.append( '
ИСКЛЮЧЕНО, ПОСКОЛЬКУ ЭТО ИГНОРИРУЕМЫЙ РАЗРАБОТЧИК, ИЗДАТЕЛЬ ИЛИ СЕРИЯ ИГР  
' ); } if( g_AccountID && unAppID && $El.data('ds-options') !== 0 ) // Only add if we have an appid { var $elMenu = $J ( '
', { 'class': 'ds_options' } ).append($J('
')); $El.append ( $elMenu ); $El.one( 'mouseenter', function() { $elMenu.v_tooltip ( { 'tooltipClass': 'ds_options_tooltip', 'location': 'bottom left', 'offsetY': -20, 'useClickEvent': true, 'useMouseEnterEvent': false, func: GDynamicStore.CapsuleSettingsMenu } ); } ); } } }); // make sure that the elements are registered as "appearing" if necessary GDynamicStore.s_ImpressionTracker.CheckVisibility(); }, CapsuleSettingsMenu: function( elSource ) { var $El = $J(this); var $elSource = $J(elSource.parentNode); $El.empty(); var strAppIDs = $elSource.data('dsAppid'); // AppID specific controls if( strAppIDs ) { var rgAppIds = []; if (strAppIDs && typeof strAppIDs == 'string' && strAppIDs.indexOf(',') >= 0) { rgAppIds = strAppIDs.split(','); } else if (parseInt(strAppIDs)) { rgAppIds = [parseInt(strAppIDs)]; } var bIgnored = false; var bOnWishlist = false; var bFilteredByContentPreferences = false; for( var i = 0; i < rgAppIds.length; i++ ) { bIgnored |= GDynamicStore.BIsAppIgnored( rgAppIds[i] ); bOnWishlist |= GDynamicStore.BIsAppOnWishlist( rgAppIds[i] ); } if (!bOnWishlist) { var fnClick = function ( event ) { event.preventDefault(); $elSource.addClass( 'ds_flagged ds_wishlist' ); $elSource.append( '
В ЖЕЛАЕМОМ  
'); for( var i=0; iВ ЖЕЛАЕМОМ  
'); } ); $El.trigger('mouseleave'); return false; }; var strText = "\u0412 \u0441\u043f\u0438\u0441\u043a\u0435 \u0436\u0435\u043b\u0430\u0435\u043c\u043e\u0433\u043e"; } var $elAddToWishlist = $J ( '
' ).click ( fnClick ).text ( strText ).addClass( 'option' ); $El.append($elAddToWishlist); if (!bIgnored) { var fnClick = function () { $elSource.addClass('ds_ignored ds_flagged'); $elSource.append( '
СКРЫТО  
'); for( var i=0; i' ).click ( fnClick ).text ( strText ).addClass( 'option' ); $El.append($elNotInterested); var $elPreferences = $J ( '' ).attr('href', 'https://store.steampowered.com/account/preferences' ).text ( "\u041d\u0430\u0441\u0442\u0440\u043e\u0439\u043a\u0438 \u043c\u0430\u0433\u0430\u0437\u0438\u043d\u0430" ).addClass( 'option' ); $El.append($elPreferences); } }, ModifyWishlist: function( $elSource, appid, bRemove, fnOnSuccess, fnOnFail ) { var url = 'https://store.steampowered.com/api/addtowishlist'; GDynamicStore.s_rgWishlist[appid] = !bRemove; if( bRemove ) url = 'https://store.steampowered.com/api/removefromwishlist'; $J.post( url, { sessionid: g_sessionID, appid: appid, snr: $elSource.data( 'snr' ) }).done( function( data ) { if( fnOnSuccess ) fnOnSuccess( appid ); GDynamicStore.InvalidateCache(); }).fail( function() { if( fnOnFail ) fnOnFail( appid ); GDynamicStore.s_rgWishlist[appid] = false; }); }, ToggleClientsideFilter: function( elControl, strToggleClass, elResults, strFilterClass ) { $Control = $J(elControl); $Results = $J(elResults); $Control.toggleClass( strToggleClass ); if ( $Control.hasClass( strToggleClass ) ) { $Results.addClass( strFilterClass ); } else { $Results.removeClass( strFilterClass ); } }, ModifyIgnoredApp: function( $elSource, appid, bRemove, fnOnSuccess, fnOnFail ) { if ( bRemove ) { delete GDynamicStore.s_rgIgnoredApps[appid]; } else { GDynamicStore.s_rgIgnoredApps[appid] = 0; } $J.post( 'https://store.steampowered.com/recommended/ignorerecommendation/', { sessionid: g_sessionID, appid: appid, remove: bRemove ? 1 : 0, snr: $elSource.data( 'snr' ) }).done( function() { if( fnOnSuccess ) fnOnSuccess( appid ); GDynamicStore.InvalidateCache(); }).fail( function( jqXHR ) { if( fnOnFail ) fnOnFail( appid ); delete GDynamicStore.s_rgIgnoredApps[appid]; }); }, ModifyIgnoredPackage: function( packageid, bRemove, fnOnSuccess, fnOnFail ) { if ( bRemove ) { delete GDynamicStore.s_rgIgnoredPackages[packageid]; } else { GDynamicStore.s_rgIgnoredPackages[packageid] = 0; } $J.post( 'https://store.steampowered.com/recommended/ignorerecommendation/', { sessionid: g_sessionID, subid: packageid, remove: bRemove }).done( function() { if( fnOnSuccess ) fnOnSuccess( packageid ); GDynamicStore.InvalidateCache(); }).fail( function() { if( fnOnFail ) fnOnFail( packageid ); delete GDynamicStore.s_rgIgnoredPackages[packageid]; }); }, GetPersonalizedBundleData: function( unBundleID, rgPageBundleData ) { if ( !GDynamicStore.s_rgPersonalizedBundleData[unBundleID] ) { if ( !GStoreItemData.rgPersonalizedBundleData[ unBundleID ] && rgPageBundleData && rgPageBundleData.m_rgItems ) { GStoreItemData.rgPersonalizedBundleData[ unBundleID ] = rgPageBundleData; } var Bundle = GStoreItemData.rgPersonalizedBundleData[ unBundleID ]; if ( !Bundle ) return null; var BundleForUser = { m_nDiscountPct: Bundle.m_nDiscountPct, m_bMustPurchaseAsSet: Bundle.m_bMustPurchaseAsSet, m_cTotalItemsInBundle: Bundle.m_rgItems.length, m_bContainsDiscountedPackage: false, m_cUserItemsInBundle: 0, m_nPackageBasePriceInCents: 0, m_nFinalPriceInCents: 0, m_nFinalPriceInCentsWithBundleDiscount: 0, m_rgBundleItems: [], m_bIsCommercial: Bundle.m_bIsCommercial, m_bRestrictGifting: Bundle.m_bRestrictGifting }; for ( var i = 0; i < Bundle.m_rgItems.length; i++ ) { var BundleItem = Bundle.m_rgItems[i]; if ( !BundleItem.m_nPackageID || GDynamicStore.s_rgOwnedPackages[ BundleItem.m_nPackageID ] ) continue; if ( BundleItem.m_rgIncludedAppIDs.length ) { if ( GDynamicStore.BAreAllAppsOwned( BundleItem.m_rgIncludedAppIDs, { bExcludeMasterSub: true } ) ) continue; } BundleForUser.m_bContainsDiscountedPackage |= BundleItem.m_bPackageDiscounted; BundleForUser.m_cUserItemsInBundle++; BundleForUser.m_nPackageBasePriceInCents += BundleItem.m_nBasePriceInCents; BundleForUser.m_nFinalPriceInCents += BundleItem.m_nFinalPriceInCents; BundleForUser.m_nFinalPriceInCentsWithBundleDiscount += BundleItem.m_nFinalPriceWithBundleDiscount; BundleForUser.m_rgBundleItems.push( BundleItem ); } // fix the price BundleForUser.m_nFinalPriceInCentsWithBundleDiscount = GStoreItemData.CalculateCurrencyAppropriatePrice( BundleForUser.m_nFinalPriceInCentsWithBundleDiscount ); GDynamicStore.s_rgPersonalizedBundleData[ unBundleID ] = BundleForUser; } return GDynamicStore.s_rgPersonalizedBundleData[ unBundleID ]; }, UpdateDynamicBundleElements: function( Bundle, $El ) { var $CartBtn = $El.find('.btn_addtocart:not(.btn_packageinfo)' ).children(); var $DiscountBlocks = $El.find('.discount_block'); if ( !Bundle.m_bIsCommercial && ( !Bundle.m_rgBundleItems.length || ( Bundle.m_bMustPurchaseAsSet && Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle ) ) ) { var strTooltip = 'Этот набор невозможно купить: у вас уже имеются все товары из него.'; if ( Bundle.m_bMustPurchaseAsSet && !Bundle.m_bRestrictGifting ) { strTooltip = 'Данное предложение действительно при единовременной покупке всех %s товаров. Вы можете приобрести этот набор в подарок своему другу.'.replace(/%s/, Bundle.m_cTotalItemsInBundle); $CartBtn.find('span').text( 'Купить в подарок' ); } else { // completely owned "complete the set" bundle $DiscountBlocks.hide(); $CartBtn.addClass('btn_disabled' ).attr( 'href', 'javascript:void(0)' ); $CartBtn.parent().css( 'background', '#000000' ); } $CartBtn.data('tooltip-text', strTooltip ); } else if ( !Bundle.m_bMustPurchaseAsSet ) { var strFormattedFinalPrice = GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCentsWithBundleDiscount ); $DiscountBlocks.show(); $DiscountBlocks.find('.discount_original_price' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nPackageBasePriceInCents ) ); if ( !Bundle.m_bContainsDiscountedPackage ) { $DiscountBlocks.addClass('no_discount'); $DiscountBlocks.find('.discount_final_price' ).addClass('your_price' ).empty().append($J('
', {'class': 'your_price_label'} ).text('Цена для вас:'), $J('
' ).text( strFormattedFinalPrice ) ); } else { $DiscountBlocks.removeClass('no_discount'); var nDiscountPct = Math.round( ( Bundle.m_nPackageBasePriceInCents - Bundle.m_nFinalPriceInCentsWithBundleDiscount ) / Bundle.m_nPackageBasePriceInCents * 100 ); nDiscountPct = Math.min( nDiscountPct, 99 ); $DiscountBlocks.find('.discount_pct' ).text( '-' + nDiscountPct + '%' ); $DiscountBlocks.find('.discount_final_price' ).removeClass('your_price').text( strFormattedFinalPrice ); } if ( Bundle.m_nFinalPriceInCentsWithBundleDiscount == 0 && Bundle.m_cUserItemsInBundle ) { // Complete the set bundle with only free items remaining var $AddToAcctBtn = $El.find('.btn_addtoaccount' ); if ( $AddToAcctBtn.length ) { $CartBtn.hide(); $AddToAcctBtn.show(); } } } var $Description = $El.find('.package_contents'); if ( $Description.length && $El.hasClass('dynamic_bundle_description') && !Bundle.m_bMustPurchaseAsSet ) GDynamicStore.BuildBundleDescription( Bundle, $Description ); if ( $El.is( '.package_totals_area' ) && !Bundle.m_bMustPurchaseAsSet ) { if ( !Bundle.m_rgBundleItems.length ) { $El.hide(); } else { $El.show(); if ( Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle ) { $El.find('.bundle_final_package_price_desc' ).text( 'Отдельная цена товаров, которых у вас нет (%s):'.replace( '%s', Bundle.m_cUserItemsInBundle ) ); } $El.find('.bundle_final_package_price' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCents ) ); $El.find('.bundle_final_price_with_discount' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCentsWithBundleDiscount ) ); $El.find('.bundle_savings' ).text( GStoreItemData.fnFormatCurrency( Bundle.m_nFinalPriceInCents - Bundle.m_nFinalPriceInCentsWithBundleDiscount ) ); } } }, BuildBundleDescription: function( Bundle, $Description ) { if ( Bundle.m_cUserItemsInBundle == 0 ) { // already own everything $Description.html( 'Вы собрали полную коллекцию! Все перечисленные товары уже в вашей библиотеке.'.replace( '%1$s', Bundle.m_cTotalItemsInBundle ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ) ); } else if ( Bundle.m_cUserItemsInBundle < Bundle.m_cTotalItemsInBundle ) { // own some but not all. $Description.html( '
Часть товаров (%1$s из %2$s) из этого набора уже есть в вашей библиотеке.
'.replace( '%1$s', Bundle.m_cTotalItemsInBundle - Bundle.m_cUserItemsInBundle ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ) ); $Description.append( '
Купите этот набор, чтобы сэкономить %1$s%% на покупке товаров (%2$s), которых у вас пока нет!
'.replace( '%1$s', Bundle.m_nDiscountPct ).replace( '%2$s', Bundle.m_cUserItemsInBundle ).replace( '%%', '%' ) ); // add "complete the set" flag $Description.parents('.dynamic_bundle_description' ).append( $J('
', {'class': 'ds_flag ds_completetheset'} ).text('СОБЕРИТЕ ПОЛНУЮ КОЛЛЕКЦИЮ!') ); } else { $Description.html( '
Купите этот набор, чтобы сэкономить %1$s%% на покупке %2$s товаров!
'.replace( '%1$s', Bundle.m_nDiscountPct ).replace( '%2$s', Bundle.m_cTotalItemsInBundle ).replace( '%%', '%' ) ); } var rgItemsWithCaps = []; for ( var iBundleItem = 0; iBundleItem < Bundle.m_rgBundleItems.length; iBundleItem++ ) { var unPackageID = Bundle.m_rgBundleItems[iBundleItem].m_nPackageID; var PackageData = GStoreItemData.rgPackageData[ unPackageID]; if ( PackageData && PackageData.tiny_capsule ) { rgItemsWithCaps.push( unPackageID ); } } if ( rgItemsWithCaps.length ) { // show no more than 9 items rgItemsWithCaps = rgItemsWithCaps.slice( 0, 9 ); // if there's more than 5 items, we overlap them a little bit on the display var bNeedToCollapse = rgItemsWithCaps.length > 5; var $BundleContentsCtn = $J('
', {'class': 'bundle_contents_preview'} ); if ( bNeedToCollapse ) $BundleContentsCtn.addClass( 'collapsed' ); var $BundleContentsPosition = $J('
', {'class': 'bundle_contents_preview_position'} ); for ( var i = 0; i < rgItemsWithCaps.length; i++ ) { var rgLinkParams = { 'class': 'bundle_contents_preview_item ds_collapse_flag', 'data-panel': '{"focusable":false}' } var Item = GStoreItemData.GetCapParams( 'bundle_component_preview', null, rgItemsWithCaps[i], null, rgLinkParams ); var $Link = $J('', rgLinkParams ); var $Img = $J('', {'src': Item.tiny_capsule, 'class': 'bundle_contents_preview_img' } ); if ( i > 0 && bNeedToCollapse ) { var flPositionRight = 100 * i / rgItemsWithCaps.length; $Link.addClass( 'floated' ).css( 'left', flPositionRight + '%' ).css('z-index', (rgItemsWithCaps.length - i)); } $BundleContentsPosition.append( $Link.append( $Img ) ); GStoreItemData.BindHoverEvents( $Img.parent(), Item.appids.length == 1 ? Item.appids[0] : null, rgItemsWithCaps[i] ); } $Description.append( $BundleContentsCtn.append($BundleContentsPosition) ); GDynamicStore.DecorateDynamicItems( $BundleContentsCtn ); } }, PopulateRecommendedTagList: function() { var $Element = $J('#foryou_yourtags'); if ( !$Element.length ) return; // no menu $Element.empty(); $Element.css( 'min-height', '' ); for( var i = 0; i < GDynamicStore.s_rgRecommendedTags.length && i < 4; i++ ) { var tag = GDynamicStore.s_rgRecommendedTags[i]; var url = 'https://store.steampowered.com/tags/ru/' + encodeURIComponent( tag.name ); var $Link = $J('', {'class': 'popup_menu_item', 'href': GStoreItemData.AddNavEventParamsToURL( url, 'storemenu_recommendedtags' ) }); $Link.text( tag.name ); $Element.append( $Link ); } }, InvalidateCache: function() { WebStorage.SetLocal( 'unUserdataVersion', parseInt( WebStorage.GetLocal( 'unUserdataVersion' ) || 0 ) + 1 ); }, BIsAppOwned: function( appid, optsIn ) { var opts = { bExcludeIfAutoGrant: true, bExcludeMasterSub: false }; if ( optsIn === false ) $J.extend( opts, { bExcludeIfAutoGrant: false } ); else if ( optsIn ) $J.extend( opts, optsIn ); if ( GDynamicStore.s_rgOwnedApps[appid] ) { if ( opts.bExcludeIfAutoGrant && this.BIsAutoGrantedApp( appid ) ) return false; if ( opts.bExcludeMasterSub && this.BIsMasterSubApp( appid ) ) return false; return true; } return false; }, BIsAutoGrantedApp: function( appid ) { return GDynamicStore.s_rgAutoGrantApps[appid] ? true : false; }, BIsMasterSubApp: function( appid ) { // presence in s_rgMasterSubApps indicates the user owns the app via a subscription like EA Play // in some places we allow user to repurchase return GDynamicStore.s_rgMasterSubApps[appid] ? true : false; }, BIsSalePageAppID: function( appid ) { return GStoreItemData.rgAppData[ appid ] && GStoreItemData.rgAppData[ appid ].url; }, BIsPackageOwned: function( packageid ) { return GDynamicStore.s_rgOwnedPackages[packageid] ? true : false; }, BIsAppIgnored: function( appid ) { return ( appid in GDynamicStore.s_rgIgnoredApps ); }, GetIgnoredAppCount: function( ) { return Object.keys(GDynamicStore.s_rgIgnoredApps).length; }, GetExcludedTagsOverlap: function( $e ) { var rgOverlappingTagNames = []; var rgTagIDs = $e.data( 'dsTagids' ); if ( rgTagIDs && rgTagIDs.length > 0 ) { for ( var i = 0; i < rgTagIDs.length; ++i ) { var tagid = rgTagIDs[i]; if ( GDynamicStore.s_rgExcludedTags[tagid] ) { rgOverlappingTagNames.push( GDynamicStore.s_rgExcludedTags[tagid] ); } } } return rgOverlappingTagNames; }, GetExcludedContentDescriptorOverlap: function( $e ) { var rgOverlappingDescIDs = []; var rgIDs = $e.data( 'dsDescids' ); if ( rgIDs && rgIDs.length > 0 ) { for ( var i = 0; i < rgIDs.length; ++i ) { var id = rgIDs[i]; if ( GDynamicStore.s_rgExcludedDescIDs[id] ) { rgOverlappingDescIDs.push( id ); } } } return rgOverlappingDescIDs; }, GetExcludedCreatorOverlap: function( $e ) { var rgOverlappingDescIDs = []; var rgIDs = $e.data( 'dsCrtrids' ); if ( rgIDs && rgIDs.length > 0 ) { for ( var i = 0; i < rgIDs.length; ++i ) { var id = rgIDs[i]; if ( GDynamicStore.s_rgCreatorsIgnored[id] ) { rgOverlappingDescIDs.push( id ); } } } return rgOverlappingDescIDs; }, BIsAppOnWishlist: function( appid ) { return GDynamicStore.s_rgWishlist[appid] ? true: false; }, BIsPackageIgnored: function( packageid ) { return ( packageid in GDynamicStore.s_rgIgnoredPackages ); }, GetCuratorForApp: function( unAppID, bOnlyPositive ) { var curator = null; if( GDynamicStore.s_rgCurations[unAppID] ) { var rgFilteredCuratorIDs = []; $J.each(GDynamicStore.s_rgCurations[unAppID], function( unCuratorID, unRecommendationState ){ if( !bOnlyPositive || unRecommendationState == 0 ) rgFilteredCuratorIDs.push( unCuratorID ); }); if( rgFilteredCuratorIDs.length ) { var unCuratorID = rgFilteredCuratorIDs[ Math.floor( Math.random() * rgFilteredCuratorIDs.length ) ]; return { 'recommendation_state': GDynamicStore.s_rgCurations[unAppID][unCuratorID], 'curator': GDynamicStore.s_rgCurators[unCuratorID] } } } return null; }, GetCurator: function( clanid ) { return GDynamicStore.s_rgCurators[ clanid ]; }, GetMatchingCreatorFollowed: function( rgAppCreatorRelationship ) { if( GDynamicStore.s_rgCreatorsFollowed ) { for( var clanid in rgAppCreatorRelationship ) { if( rgAppCreatorRelationship.hasOwnProperty( clanid ) && clanid in GDynamicStore.s_rgCreatorsFollowed ) { return { 'clanid': clanid, 'relationship': rgAppCreatorRelationship[clanid] } } } } return null; }, BAreAllAppsOwned: function( rgAppIds, opts ) { for ( var i = 0; i < rgAppIds.length; i++ ) { if ( !GDynamicStore.BIsAppOwned( rgAppIds[i], opts ) ) return false; } return true; }, DisplayBundleSimulator: function( unBundleID ) { if ( !GDynamicStore.m_bLoadComplete ) { GDynamicStore.OnReady( function() { GDynamicStore.DisplayBundleSimulator( unBundleID ) } ); return; } var Bundle = GStoreItemData.rgPersonalizedBundleData[ unBundleID ]; if ( !Bundle ) { ShowAlertDialog( '', 'Unknown bundle ID ' + unBundleID ); return; } var $Form = $J('
', {'class': 'bundle_simulator_form'}); $Form.append( $J('

').text('Выберите товары, которыми «владеете»') ); $Form.append( $J('
', {'class': 'bundle_simulator_secondary' } ).append( $J('', {'href': 'javascript:void(0);'} ).click( function() { $Form.find('input').prop('checked', true ); } ).text( "Выбрать всё" ), ' - ', $J('', {'href': 'javascript:void(0);'} ).click( function() { $Form.find('input').prop('checked', false ); } ).text( "Снять выбор" ) ) ); for ( var i =0; i < Bundle.m_rgItems.length; i++ ) { var BundleItem = Bundle.m_rgItems[i]; var id = 'bundle_check_' + i; var nPackageID = BundleItem.m_nPackageID; var rgAppIDs = BundleItem.m_rgIncludedAppIDs; var $Row = $J('
', {'class': 'bundle_simulator_row'} ); var $Checkbox = $J('', {type: 'checkbox', id: id } ); if ( GDynamicStore.s_rgOwnedPackages[nPackageID] || ( rgAppIDs.length && GDynamicStore.BAreAllAppsOwned( rgAppIDs ) ) ) $Checkbox.prop('checked', true); $Checkbox.data( 'BundleItem', BundleItem ); $Row.append( $Checkbox ); var strLabel = GStoreItemData.rgPackageData[nPackageID].name; if ( rgAppIDs.length == 1 ) strLabel = GStoreItemData.rgAppData[rgAppIDs[0]].name; $Row.append( $J('', params ); if ( !rgOptions.no_hover ) GStoreItemData.BindHoverEvents( $CapCtn, unAppID, unPackageID ); var $ImgCtn = $J('
').addClass( rgOptions.capsule_size ); var rgImageProperties = { src: rgItemData[rgOptions.capsule_size], alt: rgItemData.name }; if( rgOptions.lazy ) rgImageProperties = { 'data-image-url': rgItemData[rgOptions.capsule_size] } $ImgCtn.append( $J('', rgImageProperties ) ); $CapCtn.append( $ImgCtn ); if( rgOptions.include_title ) $CapCtn.append( $J('
', {'class': 'title ellipsis' } ).html( rgItemData.name ) ); if( rgOptions.html_before_price ) $CapCtn.append( rgOptions.html_before_price ); if( rgItemData.has_live_broadcast ) $CapCtn.append( $J( '
').addClass( 'broadcast_live_stream_icon' ).append( 'Трансляция' ) ); $CapCtn.append( $J('
').html( rgItemData.discount_block ? $J(rgItemData.discount_block).addClass( rgOptions.discount_class ) : ' ' ) ); return $CapCtn; }, FillPagedCapsuleCarousel: function( rgCapsules, $elTarget, fnCapsule, strNavContext, nCapsules, rgBaseOptions ) { var $elCapsuleTarget = $J('.carousel_items', $elTarget); var $elThumbTarget = $J('.carousel_thumbs', $elTarget); var bPaginated = !$elCapsuleTarget.hasClass('no_paging'); if ( !bPaginated ) nCapsules = rgCapsules.length; if( rgCapsules.length < nCapsules ) return; for( var j=0; j 0 && j+nCapsules > rgCapsules.length && bPaginated ) break; var $elPageContainer = $J('
'); for( var k=0; k < nCapsules && k + j < rgCapsules.length; k++ ) { var oItem = rgCapsules[ k + j ]; var rgOptions = $J.extend({}, rgBaseOptions); if( j > 0 ) rgOptions.lazy = true; var $CapCtn = fnCapsule( oItem, strNavContext, rgOptions ); if( !$CapCtn ) continue; // Don't try to do automatic visibilty tracking on non-visible clusters. if( j > 0 ) $CapCtn.attr('data-manual-tracking', 1); $elPageContainer.append( $CapCtn ); } $elCapsuleTarget.append($elPageContainer); if ( bPaginated ) { $elThumbTarget.append($J('
')); } } if ( $elCapsuleTarget.children().length > 0 ) { $elCapsuleTarget.InstrumentLinks(); GDynamicStore.DecorateDynamicItems( $elCapsuleTarget ); $elTarget.show(); } if ( bPaginated ) CreateFadingCarousel( $elTarget, 0 ); }, AddSNRDepthParamsToCapsuleList: function( $Capsules ) { var nDepth = 1; $Capsules.filter('a:visible').each( function() { ModifyLinkSNR( $J(this), function( snr ) { var rgParts = snr.split('_'); rgParts[5] = nDepth++; return rgParts.join('_'); }); }); } }; function OnDynamicStorePageException(e) { } GDynamicStorePage = { oSettings: {}, oApplicableSettings: {"main_cluster":{"top_sellers":true,"early_access":true,"games_already_in_library":true,"recommended_for_you":true,"prepurchase":true,"games":"always","software":true,"dlc_for_you":true,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":"always","games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":true,"hidden":null},"new_on_steam":{"top_sellers":null,"early_access":true,"games_already_in_library":true,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"recently_updated":{"top_sellers":null,"early_access":true,"games_already_in_library":null,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":true,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"tabs":null,"specials":null,"more_recommendations":null,"friend_recommendations":null,"curators":{"top_sellers":null,"early_access":true,"games_already_in_library":true,"recommended_for_you":null,"prepurchase":null,"games":"always","software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":true,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null},"home":{"top_sellers":null,"early_access":true,"games_already_in_library":null,"recommended_for_you":null,"prepurchase":true,"games":null,"software":true,"dlc_for_you":null,"dlc":null,"recently_viewed":null,"new_on_steam":null,"popular_new_releases":null,"games_not_in_library":null,"only_current_platform":null,"video":true,"localized":true,"virtual_reality":true,"recommended_by_curators":null,"hidden":null}}, InitUserData: function( rgParams ) { try { GDynamicStorePage.oSettings = rgParams.oSettings; } catch( e ) { OnDynamicStorePageException(e); } }, ItemIDFromCapsule: function( $capsule ) { var unPackageId = $capsule.data('ds-packageid'); var unBundleId = $capsule.data('ds-bundleid'); var unAppId = $capsule.data('ds-appid'); // bundles set package and appids, packages set appids, so start from the outside and work down. if ( unBundleId ) return { bundleid: unBundleId }; else if ( unPackageId) return { packageid: unPackageId }; else if ( unAppId ) return { appid: unAppId }; return null; }, BItemValid: function( item, oShownItems, opts ) { var unAppId = item.appid; if( unAppId ) { var rgAppIds = [ unAppId ]; if( unAppId.toString().indexOf(',') !== -1 ) { rgAppIds = unAppId.toString().split( ',' ); unAppId = rgAppIds[0]; } for ( var i = 0; i < rgAppIds.length; i++ ) { if ( oShownItems.rgAppIds.indexOf( rgAppIds[i] ) !== -1 ) { return false; } } if ( opts && opts.filter_dlc ) { var rgAppData = GStoreItemData.rgAppData[unAppId]; // Treat DLC as the base app; so we either show the DLC or the base game; but only one (and whichever is in top position). // If the user owns the base game already, only show the DLC if ( rgAppData && rgAppData.dlc_for_app ) { if ( !GDynamicStore.BIsAppOwned( rgAppData.dlc_for_app, false ) ) { return false; } oShownItems.rgAppIds.push( rgAppData.dlc_for_app ); } } for ( var i = 0; i < rgAppIds.length; i++ ) oShownItems.rgAppIds.push( rgAppIds[i] ); } if ( item.packageid ) { if ( oShownItems.rgPackageIds.indexOf( item.packageid ) !== -1 ) return false; oShownItems.rgPackageIds.push( item.packageid ); } if ( item.bundleid ) { if ( oShownItems.rgBundleIds.indexOf( item.bundleid ) !== -1 ) return false; oShownItems.rgBundleIds.push( item.bundleid ); } return true; }, FilterCapsules: function( nMin, nMax, $elElements, $elContainer, rgFilterParams, bFilterDLC ) { // by default we want to filter DLC if ( bFilterDLC === undefined ) bFilterDLC = true; // Get a list of appids to filter var rgItems = []; var oShownItems = { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] }; var rgValidElements = []; // Remove duplicates or DLC from the list for( var i = 0; i < $elElements.length; i++ ) { var $capsule = $J( $elElements[i] ); var item = GDynamicStorePage.ItemIDFromCapsule( $capsule ); if ( !item ) continue; if ( GDynamicStorePage.BItemValid( item, oShownItems, { filter_dlc: bFilterDLC } ) ) { rgItems.push( item ); rgValidElements.push( $capsule ); } else $capsule.remove(); } // Filter var rgFilteredItems = this.FilterItemsForDisplay( rgItems, 'home', nMin, nMax, rgFilterParams ); // Now follow filters as long we we can keep 4 items in the capsule for( var i = 0; i < rgValidElements.length; i++ ) { var $capsule = rgValidElements[i]; var item = GDynamicStorePage.ItemIDFromCapsule( $capsule ); if ( !item ) continue; // Test our filtered list var bVisible = false; if ( this.GetItemFromList( item, rgFilteredItems ) ) bVisible = true; if( bVisible ) { $capsule.removeClass('hidden'); if ( $capsule.parent().length ) { $capsule.parent().append( $capsule ); } else { $J( $elContainer ).append( $capsule ); } } else $capsule.remove(); } GDynamicStoreHelpers.AddSNRDepthParamsToCapsuleList( $elElements ); $elElements.parent().trigger('v_contentschanged'); //if( nCapsules < nMin && $elContainer ) // $elContainer.hide(); }, GetItemFromList: function( oItem, rgList ) { if( rgList ) { for ( var i = 0; i < rgList.length; i++ ) { if ( oItem.bundleid && rgList[i].bundleid == oItem.bundleid ) { return rgList[i]; } else if ( oItem.packageid && rgList[i].packageid == oItem.packageid ) { return rgList[i]; } else if ( oItem.appid && rgList[i].appid == oItem.appid ) { return rgList[i]; } } } return null; }, FilterItemsForDisplay: function( rgItems, strSettingsName, cMinItemsToDisplay, cMaxItemsToDisplay, rgAdditionalSettings ) { var Settings = this.oSettings[strSettingsName] || {}; var ApplicableSettings = this.oApplicableSettings[strSettingsName] || {}; // Allow sections to have additional, section-specific settings. We'll use jQuery to shallow copy the settings // object so we don't pollute future calls. if( rgAdditionalSettings ) { Settings = jQuery.extend({}, Settings, rgAdditionalSettings); // Ensure our feature is turned on as an applicable setting for( var strKey in rgAdditionalSettings ) rgAdditionalSettings[strKey] = true; ApplicableSettings = jQuery.extend({}, ApplicableSettings, rgAdditionalSettings); } if ( !cMaxItemsToDisplay ) cMaxItemsToDisplay = cMinItemsToDisplay; return GStoreItemData.FilterItemsForDisplay( rgItems, Settings, ApplicableSettings, cMaxItemsToDisplay, cMinItemsToDisplay ) }, FilterAndPrioritizeCapsules: function( rgCapsules, strPriorityListKey, strSettingsName, AdditionalSettings, oShownItems, cMinItemsToDisplay ) { // initialize the shown item list if ( !oShownItems.rgAppIds ) $J.extend( oShownItems, { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] }, oShownItems ); var rgPriorityList = g_rgAppPriorityLists[strPriorityListKey] || []; var rgItems = []; var rgUnidentifiedCaps = []; for ( var i = 0; i < rgCapsules.length; i++ ) { var $capsule = $J( rgCapsules[i] ); var itemid = GDynamicStorePage.ItemIDFromCapsule( $capsule ); if ( !itemid || ( itemid.appid && GDynamicStore.BIsSalePageAppID( itemid.appid ) ) ) { // if there's no item associated, preseve it; it's probably a sale page or event rgUnidentifiedCaps.push( i ); continue; } else if ( !GDynamicStorePage.BItemValid( itemid, oShownItems ) ) { // duplicate continue; } itemid.capsule = $capsule; rgItems.push( itemid ); } rgItems = SortItemListByPriorityList( rgItems, strPriorityListKey ); var rgItems = GDynamicStorePage.FilterItemsForDisplay( rgItems, strSettingsName, cMinItemsToDisplay, rgItems.length, AdditionalSettings ); // splce anything we don't have info about back in the list in the same position it was before. for ( var i = 0; i < rgUnidentifiedCaps.length; i++ ) { var $capsule = $J( rgCapsules[ rgUnidentifiedCaps[i] ] ); rgItems.splice( rgUnidentifiedCaps[i], 0, { capsule: $capsule, priority: 1 } ); } return rgItems; }, FilterAndPrioritizeItems: function( rgItems, strPriorityListKey, strSettingsName, AdditionalSettings, oShownItems, cMinItemsToDisplay ) { // initialize the shown item list if ( !oShownItems.rgAppIds ) $J.extend( oShownItems, { rgAppIds: [], rgPackageIds: [], rgBundleIds: [] }, oShownItems ); // first filter out items that are dupes of ones we've already been asked to list var rgItemsNoDupes = []; for( var i = 0; i < rgItems.length; i++ ) { if ( GDynamicStorePage.BItemValid( rgItems[i], oShownItems ) ) rgItemsNoDupes.push( rgItems[i] ); } rgItemsNoDupes = SortItemListByPriorityList( rgItemsNoDupes, strPriorityListKey ); return GDynamicStorePage.FilterItemsForDisplay( rgItemsNoDupes, strSettingsName, cMinItemsToDisplay, rgItemsNoDupes.length, AdditionalSettings ); }, }; var g_rgAppPriorityLists = {}; var g_rgAppPriorityListMaps = {}; function InitAppPriorityLists( rgAppPriorityLists ) { $J.extend( g_rgAppPriorityLists, rgAppPriorityLists ); } function GetAppPriorityListMap( strPriorityListName ) { if ( !g_rgAppPriorityListMaps[strPriorityListName] ) { var rgAppPriorityList = g_rgAppPriorityLists[strPriorityListName] || []; var rgPositionByApp = {}; for ( var i = 0; i < rgAppPriorityList.length; i++ ) rgPositionByApp[ ItemKey( rgAppPriorityList[i] ) ] = i; g_rgAppPriorityListMaps[strPriorityListName] = rgPositionByApp; } return g_rgAppPriorityListMaps[strPriorityListName]; } function ItemKey( rgItem ) { if ( rgItem.appid ) return 'a' + rgItem.appid; else if ( rgItem.packageid ) return 'p' + rgItem.packageid; else if ( rgItem.bundleid ) return 'b' + rgItem.bundleid; return 'unknown'; } function SortItemListByPriorityList( rgItemList, strPriorityListName ) { var rgPositionByApp = GetAppPriorityListMap( strPriorityListName ); if ( !rgPositionByApp ) return rgItemList.slice(); /* javascript sort is stable (except in IE), so this shouldn't be needed for ( var i = 0; i < rgItemList.length; i++ ) { var key = ItemKey( rgItemList[i] ); if ( key && rgPositionByApp[key] === undefined ) rgPositionByApp[key] = i + 1000; } */ var rgItemListSorted = rgItemList.slice(); rgItemListSorted.sort( function( a, b ) { var posA = rgPositionByApp[ ItemKey( a ) ]; var posB = rgPositionByApp[ ItemKey( b ) ]; return ( posA !== undefined ? posA : 1000 ) - ( posB !== undefined ? posB : 1000 ); }); DEBUG_LogItemList( strPriorityListName, rgItemListSorted ); return rgItemListSorted; } function DEBUG_LogItemList( strListName, rgItems ) { } function ShowHowDoDiscoveryQueuesWorkDialog() { $J.get( 'https://store.steampowered.com/explore/howitworks/', { l : 'russian' }, function( data ) { ShowAlertDialog( 'Как это работает?', data ); } ); } function GetAvatarURL( strHash, strSize ) { return "https:\/\/avatars.akamai.steamstatic.com\/" + strHash + strSize + '.jpg'; } function GetScreenshotURL( appid, filename, sizeStr ) { if( sizeStr ) return 'https://cdn.akamai.steamstatic.com/steam/' + 'apps/' + appid + '/' + filename.replace('.jpg', sizeStr + '.jpg'); return 'https://cdn.akamai.steamstatic.com/steam/' + 'apps/' + appid + '/' + filename; } function UpdatePricesForAdditionalCartDiscount( $Selector, nCartDiscount ) { if ( !nCartDiscount ) return; var strSelector = '[data-price-final]'; var $DynamicElements = []; if ( $Selector ) { if ( $Selector.is( strSelector ) ) $DynamicElements = $Selector; else $DynamicElements = $Selector.find( strSelector ); } else { $DynamicElements = $J( strSelector ); } for ( var i = 0; i < $DynamicElements.length; i++ ) { $element = $J($DynamicElements[i]); var nFinalPrice = parseInt($element.attr('data-price-final')); $element.addClass( 'additional_cart_discount_container' ); if (!nFinalPrice) continue; var nToSubtract = Math.min(nFinalPrice, nCartDiscount); var bNowFree = (nFinalPrice - nToSubtract) <= 0; var strAdditionalDiscountBlock = '
-' + GStoreItemData.fnFormatCurrency(nToSubtract) + '
'; var strFinalPriceBlock = '
' + GStoreItemData.fnFormatCurrency(nFinalPrice - nToSubtract) + '
'; var $additionalDiscount = $J('
' + strAdditionalDiscountBlock + strFinalPriceBlock + '
'); //var $additionalDiscount = $J('
' + strAdditionalDiscountBlock + strFinalPriceBlock + '
'); var $basePriceStrikeout = $J('
'); $element.append($basePriceStrikeout); $element.append($additionalDiscount); } } function UpdateStoreBannerForAdditionalCartDiscount( nCartDiscount ) { if ( !nCartDiscount ) return false; var strTemplate = ' \
\
\
\
\
\
\
\
\
\
\
\
%header%
%discount%
\
\
\
\
\
\
\
\
\
\
\
\ '; var strAmount = GStoreItemData.fnFormatCurrency( nCartDiscount ); var strHeader = 'Действует режим суперэкономии'; var strDiscount = 'Вы получите скидку %amount%!'.replace( '%amount%', strAmount ); strTemplate = strTemplate.replace( '%header%', strHeader ); strTemplate = strTemplate.replace( '%discount%', strDiscount ); $Elements = $J( '[data-cart-banner-spot]' ); for ( var i = 0; i < $Elements.length; i++ ) { $element = $J( $Elements[i] ); $element.replaceWith( $J( strTemplate ) ); } return true; } function UpdateStoreBannerForPromotionalDiscount( nDiscount, nMinCartAmount, nAvailableUseCount ) { if ( !nDiscount ) return false; if ( !nAvailableUseCount ) return false; if ( $J( 'body.events_hub' ).length ) return false; var strTemplate = ' \ \
\ '; var strAmount = GStoreItemData.fnFormatCurrency( nDiscount, true ); var strMinAmount = GStoreItemData.fnFormatCurrency( nMinCartAmount, true ); var strTitle = 'Дорожный бонус'; var strHeader = 'Сэкономьте ещё %amount% при покупке от %min_amount%'.replace( '%amount%', strAmount ).replace( '%min_amount%', strMinAmount ); var strDiscount = 'Скидка применяется при оплате'; strTemplate = strTemplate.replace( '%title%', strTitle ); strTemplate = strTemplate.replace( '%header%', strHeader ); strTemplate = strTemplate.replace( '%discount%', strDiscount ); $Elements = $J( '[data-cart-banner-spot]' ); for ( var i = 0; i < $Elements.length; i++ ) { $element = $J( $Elements[i] ); $element.replaceWith( $J( strTemplate ) ); $J( '#promo_header_banner' ).slideDown( 500 ); } return true; }