":{},"payment_options":[{"billing_type":"no_remaining_balance","billing_checkout_charge_type":"percentage","billing_checkout_charge_amount":null,"billing_checkout_charge_percentage":"100.0","billing_at":"2025-07-26T09:20:38.472Z","billing_after_n_intervals":7,"billing_after_interval_type":"day","pricing_type":"no_discount","pricing_amount":null,"pricing_percentage":null,"billing_title":"Full payment","billing_description":null,"discount_text":"Save {{ discount }}","shopify_selling_plan_id":713071886719,"is_default":true,"type":"full","translations":{}}],"require_preorder_acknowledgement":false,"preorder_acknowledgement_text":"I acknowledge and agree to the preorder terms and conditions for this product.","disable_button_until_acknowledged":false,"preorder_min_quantity":null,"preorder_max_quantity":null,"countdown_timer_enabled":false,"countdown_timer_style":"text","countdown_timer_text_color":"#000000","countdown_timer_background_color":"#f5f5f5","countdown_timer_border_radius":8,"countdown_timer_format":"DHM
{
// Find the maximum updated_at from all items in old array
const oldPlans = window._RestockRocketConfig.sellingPlans;
const maxUpdatedAt = Array.isArray(oldPlans) && oldPlans.length > 0
? oldPlans.reduce(function(max, plan) {
// Parse dates for proper comparison (handles mixed ISO formats)
if (plan.updated_at) {
const planDate = new Date(plan.updated_at);
const maxDate = max ? new Date(max) : null;
return (!maxDate || (planDate && !isNaN(planDate) && planDate > maxDate)) ? plan.updated_at : max;
}
return max;
}, '')
: null;
// Use cached if old array is empty/has no timestamps, or cached is newer
// Parse dates for comparison to handle format differences (+00:00 vs .000Z)
const cachedDate = new Date(cachedData.cached_at);
const maxDate = maxUpdatedAt ? new Date(maxUpdatedAt) : null;
const useCached = !maxUpdatonfig.restockNotes = {};window._RestockRocketConfig.integrations = [{"id":"15c94526-b6b8-4de1-9bc1-23b1ca52ddb0","shop_id":38436,"enabled":true,"page_types":["product","collection","index","search","page","cart","list-collections","article","blog"],"configuration":{"toastDuration":10000,"toastPosition":"bottom-right","enableXHRHijack":true,"enableFetchHijack":true,"quantityLimitDisabled":false},"type":"hijack","css_config":null,"js_config":null,"created_at":"2025-07-26T09:16:04.076Z","updated_at":"2025-07-26T09:16:04.076Z"}];window._RestockRocketConfig.obfuscateInventoryQuantity = false;window._RestockRocketConfig.product = {"id":15172398645631,"title":"CUBE ACCESS WS EAZ BLUSH\/SILVER","handle":"cube-access-ws-eaz-blush-silver","description":"\u003cdiv class=\"h5 product-detail-specifications-title\"\u003e\u003cspan\u003e\u003cmeta charset=\"utf-8\"\u003eSpeed is useless if you can't control it. That's why we've equipped the Access WS EAZ with safe and practical disc brakes. They'll bring you to a quick stop03e\n\u003cdiv class=\"h5 product-detail-specifications-title\"\u003e\u003cspan\u003eFrame\u003c\/span\u003e\u003c\/div\u003e\n\u003ctable class=\"table product-detail-properties-table\"\u003e\n\u003ctbody\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eFrame\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eAluminum Lite, AMF, Double Butted, Internal Cable Routing, Tapered Head Tube, Flat Mount Brake, SIC Mount, FM Kickstand Mount\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eFront fork\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eSR Suntour XCT Disc, 100mm\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003c\/tbody\u003e\n\u003c\/table\u003e\n\u003cdiv class=\e\u003cspan\u003eRear derailleur\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eShimano RD-M360, 8-Speed\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eDerailleur for\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eShimano FD-M315, Top Swing, 31.8mm Clamp\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eShifters\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eShimano SL-M315, Rapidfire Plus\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u
\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eRims\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eCube ZX20, 32H, Disc\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eFront hub\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eCube Alloy Light, QR, 6-Bolt\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eRear hub\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eCube Alloy Light, QR, 6-Bolt\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eNatural Fit Venec Lite WS\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eSeatpost\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eCube Performance Post, 27.2mm\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003eSeatpost clamp\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col-md-8 properties-value\"\u003e\u003cspan\u003eCube Varioclose, 31.8mm\u003c\/span\u003e\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr class=\"row properties-row\"\u003e\n\u003cth class=\"col col-sm-6 col-md-4 properties-label\"\u003e\u003cspan\u003ePedals\u003c\/span\u003e\u003c\/th\u003e\n\u003ctd class=\"col col-sm-6 col window._RestockRocketConfig.variantsPreorderMaxCountForMarket = {55656602403199 : null,55656602435967 : null,55656602468735 : null,55656602501503 : null,};
window._RestockRocketConfig.variantsShippingText = {55656602403199 : "",55656602435967 : "",55656602468735 : "",55656602501503 : "",};
window._RestockRocketConfig.variantsShippingTextForMarket = {55656602403199 : null,55656602435967 : null,55656602468735 : null,55656602501503 : null,};
window._RestockRocketConfig.selected_variant_id = 55656602403199;
window._RestockRocketConfig.selected_variant_available = window._RestockRocketConfig.product.variants.find(function(variant) { return variant.id == window._RestockRocketConfig.selected_variant_id }).available;// STOQ-1595/1612 shared signal, derived once here so both the accelerated-checkout block
// gate (STOQ-1612) and the click tracker (STOQ-1595) read one clearly-owned flag rather
// than a side effect buried in either consumer's try/catch: whether this shop has preorders
// configuruuid>/handle>-version>/assets/...
// Trailing digits (e.g. ".../restockrocket-1-521/assets/" -> "521"). Kept numeric to
// match ParseStoqData, so funnel app_version lines up with the order-attribution
// app_version. Reflects the ACTUAL deployed build. This is the SINGLE source of the
// parsed version — preorder.js getAppVersion() reads it back off config rather than
// re-parsing, so the regex lives in exactly one place.
try {
const _stoqVersionMatch = window._RestockRocketConfig.scriptHost.match(/(\d+)\/?(?:assets\/?)?$/);
window._RestockRocketConfig.appVersion = (_stoqVersionMatch && _stoqVersionMatch[1]) || '';
} catch (e) {
window._RestockRocketConfig.appVersion = '';
}
const SETTINGS_CACHE_DURATION = 15 * 60 * 1000; // 15 minutes in milliseconds
const LIQUID_CACHE_MAX_AGE = 15 * 60; // 15 minutes in seconds
// Calculate Liquid cache freshness once at initialization
const liquidRenderedAt = window._RestockRocketConfig.liquidRenderedAt;
// Validate timestamp and calculate ca<<translation data in metafield
// Metafield hasn't been backfilled yet - force refresh
console.debug('STOQ - multi-language enabled but no translation data in cache, fetching fresh');
return null;
}
// Translations object exists in metafield - cache is valid
// If current locale isn't translated, applyTranslations will gracefully use default locale from base fields
if (window._RestockRocketConfig.normalizedLocale &&
!Object.prototype.hasOwnProperty.call(settings.translations, window._RestockRocketConfig.normalizedLocale)) {
console.debug('STOQ - locale not explicitly translated, will use default language from cache');
}
// Don't return null - continue using cache even for untranslated locales
}
const updatedAt = new Date(settings.updated_at);
if (isNaN(updatedAt.getTime())) {
console.debug('STOQ - Invalid updated_at date format in settings');
return null;
}
const
<
tomer interaction.
// Detected variants: the variants present in this page's Liquid context (product page has them;
// collection/index/etc. don't expose variants from Liquid). Used to disambiguate "embed didn't
// load" vs "embed loaded but the variant wasn't a preorder/BIS candidate" in order debug.
try {
const _stoqInitConfig = window._RestockRocketConfig;
const _stoqDetectedVariantIds = (_stoqInitConfig.product && Array.isArray(_stoqInitConfig.product.variants))
? _stoqInitConfig.product.variants.map(function(v) { return v.id })
: [];
const _stoqSelectedVariantId = _stoqInitConfig.selected_variant_id;
Shopify?.analytics?.publish?.('stoq_initialized', {
cart_token: _stoqInitConfig.cartToken || '',
page_url: window.location.href,
page_type: _stoqInitConfig.pageType || '',
shop_domain: _stoqInitConfig.shop || '',
market_id: _stoqInitConfig.marketId || '',
detected_variant_ids: _stoqDetectedVariantIds,
selected_variant_id: _stoqSele
on block above) — nothing to bypass otherwise. variant_id is read per-clicked-button
// from its form so it's correct on collection/index/search too. block_accelerated_checkout
// context (STOQ-1612) rides in `properties` (forwarded verbatim by the pixel, no schema
// change): a buy_now click on a preorder variant while the gate was armed is a visible
// bypass signal for order debug. Also sends page_url (top-level, like the pixel's native
// events) and two preorder signals in properties (see below).
try {
var _sqTrack = window._RestockRocketConfig;
if (_sqTrack.preordersConfigured) {
document.addEventListener('click', function (e) {
try {
var t = e.target;
if (!t || !t.closest) return;
var host = t.closest('.shopify-payment-button, shopify-payment-terms');
if (!host) return;
var channel = host.matches && host.matches('shopify-payment-terms')
? 'shop_pay_installments' : 'buy_now';
var form = host.closeack.product && _sqTrack.product.id) || '',
channel: channel,
market_id: _sqTrack.marketId || '',
page_type: _sqTrack.pageType || '',
cart_token: _sqTrack.cartToken || '',
page_url: (window.location && window.location.href) || '',
properties: {
// Mirror the bundle's own preorder check (preorder.js checkInventoryProvider-
// WithCachedFallback): the live set config.preorderVariantIds (refreshed after
// page load by loadVariantInventoryData) OR the Liquid-injected
// cachedPreorderVariantIds — always present, so it covers clicks before the
// bundle loads (why this tracker is inline). ORing both (not preferring one)
// matches the bundle and is correct even if the live set is an empty array. Both
// hold integer ids; the click-time id comes off a DOM input (.value → string) or
// selected_variant_id, so coerce with Number for
er bypass is detectable too.
is_instock_variant: (function () {
var has = function (a) { return Array.isArray(a) && a.indexOf(_sqVidNum) !== -1; };
return has(_sqTrack.inStockVariantIds) ||
has(_sqTrack.cachedInStockVariantIds &&
_sqTrack.cachedInStockVariantIds.in_stock_variant_ids);
})(),
// The enrolled plan's Shopify selling-plan id + inventory_provider (stoq = STOQ tracks
// the OOS count; shopify = in-stock preorder). Tells order-debug what kind of preorder
// the Buy Now bypassed, without a downstream lookup.
enrolled_plan_id: (_sqPlan && (_sqPlan.shopify_selling_plan_id || _sqPlan.shopify_selling_plan_group_id)) || '',
enrolled_plan_provider: (_sqPlan && _sqPlan.inventory_provider) || '',
block_enabled: !!(_sqTrack.cachedSettings && _sqTrack.cachedSettings.block_accelerated_checkout),
gate_armed: !!_sqTrack.a
);
delete settings.translations;
return settings;
}
console.debug(`STOQ - Applying translations for normalized locale: ${normalizedLocale} (original: ${window._RestockRocketConfig.locale})`);
const translatedFields = translations[normalizedLocale];
if (translatedFields && typeof translatedFields === 'object') {
Object.keys(translatedFields).forEach(function(key) {
const value = translatedFields[key];
if (value !== null && value !== undefined && value !== '') {
settings[key] = value;
}
});
} else {
console.debug('STOQ - No translated fields found for locale:', normalizedLocale);
}
delete settings.translations;
return settings;
} catch (e) {
console.debug('STOQ - error applying translations:', e);
return settings;
}
}
// Setup event listener for cart selling plan updates
// This must be called before any scripts are loaded to avoid race conditions
func
ditive, does not gate init) ----
// The inlined `cachedSettings` comes from the app-embed Liquid render, which
// Shopify edge-caches and can serve stale. Reading the same `settings` metafield
// back over the Storefront API is a POST to /api/
version>/graphql.json — never
// CDN-cached — so it returns the live value on every page load. We fetch it
// best-effort and stash it on `window._RestockRocketConfig.storefrontSettings`
// so individual behaviours can migrate onto the fresh value over time. This is
// PURELY ADDITIVE: it does NOT change the resolution flow below, never blocks
// init, and silently no-ops on any failure / missing token.
(function loadStorefrontSettings() {
const cfg = window._RestockRocketConfig;
if (!cfg.storefrontAccessToken || cfg.disableStorefrontApi === true) return;
// Deferred to browser idle so this best-effort read runs strictly AFTER the
// critical init flow and never competes with it for a connection — the call
// is a live, uncached Storefront round-trip and can be slow (multi-second
// TTFB observed). Nothing on the critical path waits for it.
function run() {
const query = 'query StoqSettings($namespace: String!) { shop { metafield(namespace: $namespace, key:
refrontSettings = JSON.parse(value);
console.debug('STOQ - stored live settings from Storefront API on config.storefrontSettings');
})
.catch(function(e) {
console.debug('STOQ - Storefront settings fetch failed (non-fatal):', e && e.message);
});
}
if (typeof window.requestIdleCallback === 'function') {
window.requestIdleCallback(run, { timeout: 5000 });
} else {
setTimeout(run, 0);
}
})();
// First try to get settings from metafields with expiry check
const cachedSettings = window._RestockRocketConfig.cachedSettings;
const validCachedSettings = cachedSettings ? checkSettingsExpiry(cachedSettings) : null;
if (validCachedSettings) {
console.debug('STOQ - using cached settings');
initializeScripts(validCachedSettings);
} else {
console.debug('STOQ - fetching fresh settings');
const headers = {
'X-Shopify-Shop-Domain': window._RestockRocketConfig.shop || window.Shopify.shop,
'ngrok-skip-browser-warning': 'skiTOQ - failed to load settings:', error);
}
})
.catch(function(e) {
console.error(e)
})
}
function fetchEmbedConfig(endpoint, apply) {
return fetch(
`${window._RestockRocketConfig.host}/api/v1/embed/${endpoint}.json`,
{
headers: {
'X-Shopify-Shop-Domain': window._RestockRocketConfig.shop || window.Shopify.shop,
'ngrok-skip-browser-warning': 'skip'
}
}
)
.then(function(response) {
if (!response.ok) throw new Error(`Failed to fetch ${endpoint}`);
return response.json();
})
.then(function(data) {
try {
apply(data);
} catch (applyError) {
// Apply failures are programming bugs (e.g. response shape changed
// server-side and the assignment threw). Surface them as console.error
// so they're visible in browser logs, then re-throw to fall through
// to the same Liquid-cached fallback as a fetch failure.
console.error('STOQ - apply failed for ' +
ipts. Race against a 1000ms timeout so a slow
// API can't block init indefinitely. If the timeout wins, the in-flight
// fetches still complete and update window._RestockRocketConfig — the
// bundle re-reads sellingPlans/integrations on every interaction, so the
// late-arriving values benefit subsequent renders even though the first
// paint may use the Liquid-cached values. On any failure the existing
// Liquid-loaded values stay in place via fetchEmbedConfig's catch.
if (!window._RestockRocketConfig.isLiquidCacheFresh && !settings.disable_refresh_on_stale_liquid) {
console.debug('STOQ - Liquid cache stale, refreshing selling_plans + integrations');
Promise.race([
Promise.all([
fetchEmbedConfig('selling_plans', function(data) {
if (data && Array.isArray(data.plans)) {
window._RestockRocketConfig.sellingPlans = data.plans;
window._RestockRocketConfig.disabledSellingPlanIds = data.disabled_plan_ids || [];
n,
// an enabled offer exists, or a disabled-but-kept plan id remains (cart sweep
// must still strip those). Rationale in the PR.
const hasEnabledOffer = Array.isArray(window._RestockRocketConfig.sellingPlans)
&& window._RestockRocketConfig.sellingPlans.some(function(plan) { return plan && plan.enabled; });
const hasDisabledPlanIds = Array.isArray(window._RestockRocketConfig.disabledSellingPlanIds)
&& window._RestockRocketConfig.disabledSellingPlanIds.length > 0;
const usePreorderBuild = settings.preorder_enabled || hasEnabledOffer || hasDisabledPlanIds;
const collectionScriptUrl = usePreorderBuild
? window._RestockRocketConfig.scriptUrlCollection
: window._RestockRocketConfig.scriptUrlCollectionBis;
const productScriptUrl = usePreorderBuild
? window._RestockRocketConfig.scriptUrlProduct
: window._RestockRocketConfig.scriptUrlProductBis;
const pageType = window._RestockRocketConfig.pageType;
const collectiocketConfig.pageType,
enabled: settings.enable_app,
settings: settings,
preorderEnabled: settings.preorder_enabled
}
});
console.debug('STOQ - dispatching app loaded event');
window.dispatchEvent(appLoadedEvent);
}
}