rder_button_text_color":"#ffffff","preorder_button_background_color":"#565557","preorder_button_colors_enabled":true,"markets_enabled":false,"market_id":13779632354,"shopify_market_ids":[],"use_shopify_selling_plan":true,"use_simplified_shipping_text":false,"translations":{},"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":fal
FF","preorder_progress_bar_border_radius":4,"preorder_progress_bar_show_percentage":false}];(function() { const cachedData = {"plans":[{"shopify_selling_plan_group_id":98590196095,"shopify_selling_plan_id":713071886719,"enabled":true,"variant_ids":[55569712382335,55569712415103,55569712480639,55570017616255,55570017550719,55569712447871,55570017583487],"product_variants_source":"custom","name":"Preorder","preorder_button_text":"Preorder","preorder_button_description":"Note: This is a preorder. Items will ship based on the estimated delivery date.","preorder_button_description_enabled":true,"preorder_button_description_background_color":"#ebebeb","preorder_button_description_text_color":"#000000","preorder_button_description_border_radius":10,"preorder_button_description_show_quantity_limit":false,"preorder_button_description_quantity_limit_suffix":" units available for preorder","preorder_button_description_shipping_text_prefix":"Shipping: ","delivery_exact_time":null,"delivery_after_n_intervals":null,"
ustom_line_item_property_text":null,"preorder_button_text_color":"#ffffff","preorder_button_background_color":"#565557","preorder_button_colors_enabled":true,"markets_enabled":false,"market_id":13779632354,"shopify_market_ids":[],"use_shopify_selling_plan":true,"use_simplified_shipping_text":false,"translations":{},"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. "preorder_progress_bar_text_color":"#FFFFFF","preorder_progress_bar_border_radius":4,"preorder_progress_bar_show_percentage":false}],"disabled_plan_ids":[713813721471,713176482175,714631872895],"cached_at":"2026-04-09T09:16:46Z"}; if (cachedData && typeof cachedData === 'object' && cachedData.cached_at) { // 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 cach
6 debug('[RR] Using selling plans from old format (max updated_at: ' + maxUpdatedAt + ')'); } } })();window._RestockRocketConfig.enabledNotifyMeVariantIds = [];window._RestockRocketConfig.disabledNotifyMeVariantIds = [];window._RestockRocketConfig.backInStockTemplates = [];window._RestockRocketConfig.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":7473303027938,"title":"FidLock TWIST bottle 590 + bike base","handle":"fidHeader Image securely. To release the bottle, it only needs a simple clockwise TWIST. The bike base is mounted on the bike to the bottle braze-ons with two screws.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003e\u003cu data-mce-fragment=\"1\"\u003eThe advantages in a nutshell\u003c\/u\u003e\u003c\/p\u003e\n\u003cul data-mce-fragment=\"1\"\u003e\n\u003cli data-mce-fragment=\"1\"\u003e590 ml \/ 20 fl oz volume\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eDishwasher-safe\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eBPA-free polypropylene\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eFits all TWIST bases\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eMounting to the braze-ons\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eBase weighs only 16 g \/ 0.56 oz\u003c\/li\u003e\n\u003c\/ul\u003e","published_at":"2021-12-14T15:30:23+00:00","created_at":"2021-12-14T15:30:21+00:00","vendor":"Fidlock","type":"Water Bottles","tags":["bottles and cages","spo-cs-disabled","spo-default","spo
ons":["Title"],"media":[{"alt":null,"id":28842069852386,"position":1,"preview_image":{"aspect_ratio":0.961,"height":743,"width":714,"src":"\/\/projektride.co.uk\/cdn\/shop\/products\/fidlock-twist-bottle-and-base-590ml-p57261-194198_image.jpg?v=1639500222"},"aspect_ratio":0.961,"height":743,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/products\/fidlock-twist-bottle-and-base-590ml-p57261-194198_image.jpg?v=1639500222","width":714}],"requires_selling_plan":false,"selling_plan_groups":[],"content":"\u003cmeta charset=\"utf-8\"\u003e\n\u003cp data-mce-fragment=\"1\"\u003eThe TWIST bottle 590 has transformed the bike bottle. Without the conventional bottle cage, this magnet-mechanical bike bottle system offers a new kind of freedom in handling and application. It is simply released from the bike with an easy “TWIST” movement and just as easily reattached by bringing together the magnetic bolts, which include a mechanical locking system.\u003c\/p\u003e\n\u003cp data-mce-fragment=\"1\"\u003e\u00 u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eFits all TWIST bases\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eMounting to the braze-ons\u003c\/li\u003e\n\u003cli data-mce-fragment=\"1\"\u003eBase weighs only 16 g \/ 0.56 oz\u003c\/li\u003e\n\u003c\/ul\u003e"}; window._RestockRocketConfig.variantsInventoryPolicy = {42157681082594 : "deny",}; window._RestockRocketConfig.variantsInventoryQuantity = {42157681082594 : parseInt("5"),}; window._RestockRocketConfig.variantsPreorderCount = {42157681082594 : parseInt(""),}; window._RestockRocketConfig.variantsPreorderCountForMarket = {42157681082594 : null,}; window._RestockRocketConfig.variantsPreorderMaxCount = {42157681082594 : parseInt(""),}; window._RestockRocketConfig.variantsPreorderMaxCountForMarket = {42157681082594 : null,}; window._RestockRocketConfig.variantsShippingText = {42157681082594 : "",}; window._RestockRocketConfig.variantsShippingTextForMarket = {42157681082594 : null,}; window._RestockRocke
eorder_variant_ids) || []).length; _sqCfg.preordersConfigured = !!(_sqPlanEnabled && _sqPreCount); } catch (e) { /* fail open */ } // STOQ-1612: block (not hide) Buy Now / Shop Pay until our JS runs, so a tap in the // load window can't bypass preorder. pointer-events via .stoq-buy-now-pending (no // reflow); timeout releases; renderPreorderButton / featured_product.js lift it. // Gated by the `block_accelerated_checkout` toggle (default OFF) for gradual rollout. // Armed ONLY when we're sure preorder is in play — product page: THIS product has a // preorder variant (never block a plain product's Shop Pay); featured: integration // enabled for this page type. Rationale: PR #2784. try { var _sqc = window._RestockRocketConfig; var _sqOn = !!(_sqc.cachedSettings && _sqc.cachedSettings.block_accelerated_checkout); var _sqPre = (_sqc.cachedPreorderVariantIds && _sqc.cachedPreorderVariantIds.preorder_variant_ids) || []; var _sqArm = false; if (_sqOn && _sqc.preorde
7 hopify.com/extensions/019faf5b-94c9-7a24-9306-bf1118ca3099/restockrocket-1-554/assets/restockrocket-collection.js' window._RestockRocketConfig.scriptUrlProductBis = 'https://cdn.shopify.com/extensions/019faf5b-94c9-7a24-9306-bf1118ca3099/restockrocket-1-554/assets/restockrocket-product-bis.js' window._RestockRocketConfig.scriptUrlCollectionBis = 'https://cdn.shopify.com/extensions/019faf5b-94c9-7a24-9306-bf1118ca3099/restockrocket-1-554/assets/restockrocket-collection-bis.js' window._RestockRocketConfig.scriptHost = window._RestockRocketConfig.scriptUrlProduct.substring(0, window._RestockRocketConfig.scriptUrlProduct.lastIndexOf('/') + 1) // Canary override (STOQ-1287): prefer the Render host emitted into the // metafield-cached settings (api_host) when present, mirroring the same // override in assets/js/api.js. cachedSettings is populated above. Falls // back to the build-baked host for shops without the use_render_api_host // toggle (or environments where THEME_EXTENSION_API_HOST is unset),che age if (!liquidRenderedAt || typeof liquidRenderedAt !== 'number' || isNaN(liquidRenderedAt)) { console.debug('STOQ - Invalid or missing liquidRenderedAt timestamp, assuming fresh'); window._RestockRocketConfig.isLiquidCacheFresh = true; window._RestockRocketConfig.liquidCacheAge = null; } else { const now = Math.floor(Date.now() / 1000); // Current time in seconds const liquidCacheAge = now - liquidRenderedAt; // Age in seconds // Surfaced into funnel events: a stale cache means the app rendered with // outdated inventory/selling-plan data — a real "had the opportunity but // failed" cause. Negative (client clock ahead) clamps to 0. window._RestockRocketConfig.liquidCacheAge = Math.max(0, liquidCacheAge); // Handle client clock ahead of server if (liquidCacheAge
0) { console.debug(`STOQ - Client clock appears ahead of server by ${Math.abs(Math.round(liquidCacheAge / 60))} minutes, assuming cache fresh`); window._RestockRocketConfig.isLiquidCacheFresh = true; } else if (liquidCacheAge 8 = LIQUID_CACHE_MAX_AGE) { console.debug(`STOQ - Liquid cache is fresh (${Math.round(liquidCacheAge / 60)} minutes old)`); window._RestockRocketConfig.isLiquidCacheFresh = true; } else { console.debug(`STOQ - Liquid cache is stale (${Math.round(liquidCacheAge / 60)} minutes old, max ${Math.round(LIQUID_CACHE_MAX_AGE / 60)} minutes)`); window._RestockRocketConfig.isLiquidCacheFresh = false; } } function checkSettingsExpiry(settings) { try { if (!settings || !settings.updated_at) { console.debug('STOQ - Invalid settings data structure'); return null; } if (!settings.cache) { console.debug('STOQ - settings caching disabled'); return null; } // Check if translations are enabled but missing from cache // This handles the backfill period where DB has translations but metafield doesn't if (settings.multi_language_enabled) { if (!settings.translations) { // Translations enabled but no
age = Date.now() - updatedAt.getTime(); if (age 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:10 +
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': 'ski
TOQ - 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 ' +
ttings = applyTranslations(settings); window._RestockRocketConfig.settings = settings; console.debug(`STOQ - settings configured for ${window._RestockRocketConfig.pageType}`); // Stale-Liquid resilience (default-on, per-shop opt-out via the // `disable_refresh_on_stale_liquid` Toggle, surfaced as the negative // `disable_refresh_on_stale_liquid` flag in settings.json so that // `undefined` -- in CDN-cached metafield payloads that predate this // key -- reads as `!undefined === true` and gets default-on behavior // immediately, no metafield rewrite required). // When the Liquid CDN cache is older than LIQUID_CACHE_MAX_AGE the in-page // selling_plans / integrations metafields can be wrong; refresh both from // the API before launching scripts. 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 sellingPl
new Promise(function(resolve) { setTimeout(resolve, 1000); }) ]).then(function() { loadScripts(settings); }); return; } loadScripts(settings); } function loadScripts(settings) { // Setup cart selling plan updater BEFORE loading any scripts to avoid race conditions setupCartSellingPlanUpdater(settings); if(settings.enable_app) { const hijackIntegration = window._RestockRocketConfig.integrations.find(function(integration) { return integration.type === 'hijack' && integration.enabled && integration.page_types.includes(window._RestockRocketConfig.pageType); }) // STOQ-1520: serve the lean back-in-stock-only build (no preorder/hijack code) // only to shops with NO preorder plans. Use the full build if preorder is on, // 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)
QuantityHungary (HUFFt)
Decrease Quantity

Montenegro (EUR€)
Handlebar Clamp Diameter:
35mm
Morocco (MADد.م.)
Colour: 
Back
I'm looking to find out more information about a product, where can I find this?North Macedonia (MKDден)

  • Gusset Staff San Marino (EUR€)
  • Bar Tape Genetic Cork São Tomé & Príncipe (STDDb)
  • You May Also Like
  • Saddle Senegal (XOFFr)
  • Serbia (RSDРСД)
  • Seychelles (GBP£)
  • Sierra Leone (SLLLe)
  • Slovakia (EUR€)
  • Slovenia (EUR€)
  • Somalia (GBP£)
  • South Africa (GBP£)
  • South Sudan (GBP£)
  • Spain (EUR€)
  • St. Helena (SHP£)
  • Sudan (GBP£)

  • Servicing
    Book a ServiceTürkiye (GBP£)

    Please get in touch with a member of the team either by phone ( 01313745324 ) or email ( Workshop Prices[email protected]Uganda (UGXUSh) ) where on of the team will be more than happy to help.

    ProjektRide Bike Shop Edinburgh Cycle to Work

    Ukraine (UAH₴)
  • Delivery & Returns
    United Kingdom (GBP£) Information

    If the item is showing in stock, we aim to post the product within 24 hours. Please allow 5 working days to receive the item. Mon - Fri: 09:00 - 18:30

    Postage is free on orders over £50. Orders under £50, our postage charge is £3.99. Sat: 9:00 - 16:00

    We also have a physical store, if you are local please pop in - Sun: Closed

    ProjektRide Bike Shop EdinburghUnited States (GBP£) 0131 374 5324

    82 Newington Road, EH91QN, Edinburgh. Newington Road

    Edinburgh
    EH9 1QN
Vatican City (EUR€) [email protected]