Header Image

Muc-Off X-3 Dirty Chain Machine

Quantity
_item_property_enabled":true,"custom_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 a nd 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":"DHMS","countdown_timer_use_schedule_dates":true,"countdown_timer_custom_start_date":null,"countdown_timer_custom_end_date":null,"countdown_timer_starts_text":null,"countdown_timer_ends_text":null,"schedule_offer":false,"schedule_start_date":null,"schedule_end_date":null,"updated_at":"2025-08-19T10:05:43.042Z","allow_mixed_cart":true,"mixed_cart_error_message":"Preorders must be purchased separately from regular items. Please complete your current order first, or clear your cart to continue.","b2b_enabled":true,"preorder_progress_bar_enabled":false,"preorder_progress_bar_text":"{{ sold }} of {{ total }} claimed","preorder_progress_bar_fill_color":"#000000","preorder_progress_b
"shopify","barcode":null,"requires_selling_plan":false,"selling_plan_allocations":[],"quantity_rule":{"min":1,"max":null,"increment":1}},{"id":55482671530367,"title":"52","option1":"52","option2":null,"option3":null,"sku":"","requires_shipping":true,"taxable":true,"featured_image":null,"available":true,"name":"Frameset - Gloria Titanium All World - Brushed Finish - 52","public_title":"52","options":["52"],"price":189900,"weight":0,"compare_at_price":null,"inventory_management":"shopify","barcode":null,"requires_selling_plan":false,"selling_plan_allocations":[],"quantity_rule":{"min":1,"max":null,"increment":1}},{"id":55482671563135,"title":"54","option1":"54","option2":null,"option3":null,"sku":"","requires_shipping":true,"taxable":true,"featured_image":null,"available":true,"name":"Frameset - Gloria Titanium All World - Brushed Finish - 54","public_title":"54","options":["54"],"price":189900,"weight":0,"compare_at_price":null,"inventory_management":"shopify","barcode":null,"requires_selling_plan":false,"sell ing_plan_allocations":[],"quantity_rule":{"min":1,"max":null,"increment":1}},{"id":55482671595903,"title":"56","option1":"56","option2":null,"option3":null,"sku":"","requires_shipping":true,"taxable":true,"featured_image":null,"available":true,"name":"Frameset - Gloria Titanium All World - Brushed Finish - 56","public_title":"56","options":["56"],"price":189900,"weight":0,"compare_at_price":null,"inventory_management":"shopify","barcode":null,"requires_selling_plan":false,"selling_plan_allocations":[],"quantity_rule":{"min":1,"max":null,"increment":1}},{"id":55482671628671,"title":"58","option1":"58","option2":null,"option3":null,"sku":"","requires_shipping":true,"taxable":true,"featured_image":null,"available":true,"name":"Frameset - Gloria Titanium All World - Brushed Finish - 58","public_title":"58","options":["58"],"price":189900,"weight":0,"compare_at_price":null,"inventory_management":"shopify","barcode":null,"requires_selling_plan":false,"selling_plan_allocations":[],"quantity_rule":{"min":1,"max":null,"increment":1}}],"images":["\/\/projektride.co.uk\/cdn\/shop\/files\/GC-75.webp?v=1747757890","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-2.webp?v=1747757898","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-60.webp?v=1747757904","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-67.webp?v=1747757910","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-71.webp?v=1747757916","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-72.webp?v=1747757924","\/\/projektride.co.uk\/cdn\/shop\/files\/GC-105.webp?v=1747757930"],"featured_image":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-75.webp?v=1747757890","options":["Size"],"media":[{"alt":null,"id":65914090094975,"position":1,"preview_image":{"aspect_ratio":1.5,"height":1024,"width":1536,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-75.webp?v=1747757890"},"aspect_ratio":1.5,"height":1024,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-75.webp?v=1747757890","width":1536},{"alt":null,"id":65914094748031,"position":2,"preview_image":{"aspect_ratio":1.333,"height" :1536,"width":2048,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-2.webp?v=1747757898"},"aspect_ratio":1.333,"height":1536,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-2.webp?v=1747757898","width":2048},{"alt":null,"id":65914098614655,"position":3,"preview_image":{"aspect_ratio":0.931,"height":1649,"width":1536,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-60.webp?v=1747757904"},"aspect_ratio":0.931,"height":1649,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-60.webp?v=1747757904","width":1536},{"alt":null,"id":65914101399935,"position":4,"preview_image":{"aspect_ratio":1.332,"height":1537,"width":2048,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-67.webp?v=1747757910"},"aspect_ratio":1.332,"height":1537,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-67.webp?v=1747757910","width":2048},{"alt":null,"id":65914105889151,"position":5,"preview_image":{"aspect_ratio":0.75,"height":2048,"width":1537,"src":"\/\/projektride.co.u
k\/cdn\/shop\/files\/GC-71.webp?v=1747757916"},"aspect_ratio":0.75,"height":2048,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-71.webp?v=1747757916","width":1537},{"alt":null,"id":65914109657471,"position":6,"preview_image":{"aspect_ratio":1.334,"height":1535,"width":2048,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-72.webp?v=1747757924"},"aspect_ratio":1.334,"height":1535,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-72.webp?v=1747757924","width":2048},{"alt":null,"id":65914111361407,"position":7,"preview_image":{"aspect_ratio":0.751,"height":3115,"width":2338,"src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-105.webp?v=1747757930"},"aspect_ratio":0.751,"height":3115,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/files\/GC-105.webp?v=1747757930","width":2338}],"requires_selling_plan":false,"selling_plan_groups":[],"content":"\u003ch2 class=\"product-about__accordion-title active\"\u003e\u003cspan class=\"h5\"\u003eAll World Frame Detai ls\u003c\/span\u003e\u003c\/h2\u003e\n\u003cdiv class=\"product-about__more-link-wrapper\"\u003e\n\u003cdiv class=\"product-about__more-link\"\u003e\u003csvg class=\"icon icon-filter-two\" width=\"12\" height=\"7\" viewbox=\"0 0 12 7\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\u003e\u003cpath d=\"M10 1.5L6 5.5L2 1.5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"square\"\u003e\u003c\/path\u003e\u003c\/svg\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv class=\"product-about__accordion-description\"\u003e\n\u003cul\u003e\n\u003cli\u003eZS44\/EC44 headset included\u003c\/li\u003e\n\u003cli\u003eUDH Hanger included\u003c\/li\u003e\n\u003cli\u003eSRAM (D.O.T) Ready Zeno Brake Hoses included\u003c\/li\u003e\n\u003cli\u003eAlloy seatpost collar included\u003c\/li\u003e\n\u003cli\u003eTitanium seatpost collar (optional extra)\u003c\/li\u003e\n\u003cli\u003e3AL-2.5V grade 9\u003c\/li\u003e\n\u003cli\u003eButted tubes\u003c\/li\u003e\n\u003cli\u003eThru-axles front \u0026amp; rear and
all appropriate cable guides included\u003c\/li\u003e\n\u003cli\u003eLifetime Frame Warranty\u003c\/li\u003e\n\u003cli\u003eFrameset weight: ~ 2.3kg\u003c\/li\u003e\n\u003cli\u003eFrame weight: ~1.9 kg\u003c\/li\u003e\n\u003cli\u003eFrame finish - Brushed\u003c\/li\u003e\n\u003cli\u003eAxles 12mm x 100mm front and 12mm x 142mm The Robert Axle Project bolt thru\u003c\/li\u003e\n\u003cli\u003eBottom bracket Standard BSA\u003c\/li\u003e\n\u003cli\u003eMudguards and rack mounts compatible\u003c\/li\u003e\n\u003cli\u003eBrake mount: Flat mount 140mm or 160mm rotors\u003c\/li\u003e\n\u003cli\u003eSeat tube internal 27.2mm \u0026amp; Seat tube external 31.8\u003c\/li\u003e\n\u003cli\u003eFrame Tyre clearance 700 x 45c\u003c\/li\u003e\n\u003cli\u003eFront mech clamp - 31.8 \u003c\/li\u003e\n\u003cli\u003eAxle size 166XM12 - TP1.0\u003c\/li\u003e\n\u003c\/ul\u003e\n\u003ch2 class=\"h5 product-about__accordion-title active\"\u003e\u003cspan\u003eGeometry, Weight \u0026amp; Specs\u003c\/span\u003e\u003c\/h2\u003e\n\u00 3cdiv class=\"product-about__more-link-wrapper\"\u003e\n\u003cdiv class=\"product-about__more-link\"\u003e\u003csvg class=\"icon icon-filter-two\" width=\"12\" height=\"7\" viewbox=\"0 0 12 7\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\u003e\u003cpath d=\"M10 1.5L6 5.5L2 1.5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"square\"\u003e\u003c\/path\u003e\u003c\/svg\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv class=\"product-about__accordion-description main-product-description\"\u003e\n\u003cp\u003eWeight:\u003c\/p\u003e\n\u003cp\u003eFrame Weight: 1.9kg +\/- 50g\u003c\/p\u003e\n\u003cp\u003eCarbon Fork weight: 490g +\/- 50g\u003c\/p\u003e\n\u003cp\u003eGeometry:\u003c\/p\u003e\n\u003cp\u003e\u003ca rel=\"noopener\" href=\"https:\/\/geometrygeeks.bike\/bike\/gloria-all-road-2025\/\" target=\"_blank\"\u003eCompare the geometry with your existing bike on the Geometry Geeks website\u003cbr\u003e\u003c\/a\u003e\u003c\/p\u003e\n\u003cp\u003e \u003c\/p\u003e\n\u003ctable border=
\"0\" cellpadding=\"0\" cellspacing=\"0\" width=\"516\"\u003e\n\u003ctbody\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl65\" width=\"176\"\u003e \u003c\/td\u003e\n\u003ctd class=\"xl66\" width=\"68\"\u003e50\u003c\/td\u003e\n\u003ctd class=\"xl66\" width=\"68\"\u003e52\u003c\/td\u003e\n\u003ctd class=\"xl66\" width=\"68\"\u003e54\u003c\/td\u003e\n\u003ctd class=\"xl66\" width=\"68\"\u003e56\u003c\/td\u003e\n\u003ctd class=\"xl66\" width=\"68\"\u003e58\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eStack\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl67\"\u003e543.1\u003c\/td\u003e\n\u003ctd class=\"xl68\"\u003e562.0\u003c\/td\u003e\n\u003ctd class=\"xl68\"\u003e580.9\u003c\/td\u003e\n\u003ctd class=\"xl68\"\u003e600.0\u003c\/td\u003e\n\u003ctd class=\"xl68\"\u003e619.0\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\" \u003eReach\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl69\"\u003e358.1\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e366.5\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e380.9\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e382.3\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e390.0\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eHead Tube Angle\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl69\"\u003e70.0\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e71.0\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e72.0\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e72.5\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e72.5\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eSeat Tube Angle\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl69\"\u003e73.5\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e73.5\u003c
\/td\u003e\n\u003ctd class=\"xl70\"\u003e73.5\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e73.5\u003c\/td\u003e\n\u003ctd class=\"xl70\"\u003e73.5\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eEffective Top Tube\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl71\"\u003e519\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e533\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e547\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e560\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e574\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"26\"\u003e\n\u003ctd height=\"26\" class=\"xl73\" width=\"176\"\u003eSeat Tube C-T\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl71\"\u003e456\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e484\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e518\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e544\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e558\u003c\/td\u003e\n\u003c \/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eHead Tube\u003c\/td\u003e\n\u003ctd class=\"xl71\"\u003e110\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e126\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e142\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e160\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e180\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eChain stay\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl71\"\u003e425\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e425\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e425\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e425\u003c\/td\u003e\n\u003ctd class=\"xl72\"\u003e425\u003c\/td\u003e\n\u003c\/tr\u003e\n\u003ctr height=\"19\"\u003e\n\u003ctd height=\"19\" class=\"xl73\" width=\"176\"\u003eWheelbase\u003cspan\u003e \u003c\/span\u003e\n\u003c\/td\u003e\n\u003ctd class=\"xl71\"\u003e998\u003c\/td\u003e\n\u003cteThe Gloria Titanium All-World is a highly versatile bike designed with drop bars. While we won’t claim it can do everything and take you everywhere, it’s built to push the boundaries as much as possible. By continuing with same design philosophy of the All-Road, but adding the benefit of packability, we have created something truly versatile. With clearance for 700x45c tires, this bike can accommodate chunky tires that not only enhance comfort but also perform well both on and off the road. We have successfully designed the All-World to be efficient on the road without being twitchy off-road.\u003cbr\u003e\u003cbr\u003eWe’re proud of the compatibility we've achieved across multiple modern groupsets. For example, it’s compatible with externally routed mechanical setups for both 2x and 1x drivetrains. It also supports 1x mechanical internally routed through a full outer cable inside the frame and is fully compatible with Di2 2x and 1x. Moreover, it can be routed through the headset, making it SMR or DC R compatible. And don’t worry, the coolest thing is that if you don’t run external cables, the frame will look neat and without any cable guides or stops, because they are designed to be removed when not in use.\u003c\/p\u003e\n\u003cp\u003eWe have multiple fork options available, including fully internally routed forks, cage mounts, dynamo compatible and other options. If you have any special requirements, please let us know ... I’m sure we can help.\u003cbr\u003e\u003cbr\u003eIn a nutshell the Gloria All-World can fit almost any bike tool or accessory you desire to enhance your experience, ride on most terrain you come across and at the end of the day pack it away in it's compact suitcase.\u003c\/p\u003e\n\u003ch2 class=\"product-about__accordion-title active\"\u003e\u003cspan class=\"h5\"\u003eAll-World Highlights\u003c\/span\u003e\u003c\/h2\u003e\n\u003cdiv class=\"product-about__more-link-wrapper\"\u003e\n\u003cdiv class=\"product-about__more-link\"\u003e\u003csvg class=\"icon icon-filter-two\" wid
th=\"12\" height=\"7\" viewbox=\"0 0 12 7\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\u003e\u003cpath d=\"M10 1.5L6 5.5L2 1.5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"square\"\u003e\u003c\/path\u003e\u003c\/svg\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv class=\"product-about__accordion-description\"\u003e\n\u003cp\u003e🛠️\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eEasy Assembly:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eQuick setup, zero hassle.\u003c\/p\u003e\n\u003cp\u003e🚲\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eInternal Cable Routing:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eSleek look, disc brake performance.\u003c\/p\u003e\n\u003cp\u003e🛄\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eNo Oversized Fees:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eFits in suitcase with the following dimmentions Measures 8.5” \/ 21.6cm W x 26.5” \/ 67.3cm H x 31” \/ 78.8cmL w\/ wheels.\u003c\/p\u 003e\n\u003cp\u003e🌍\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eTravel-Friendly:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eLightweight and compact for easy transport.\u003c\/p\u003e\n\u003cp\u003e🛡️\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eAdventure-Ready:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eBuilt for road and gravel durability.\u003c\/p\u003e\n\u003cp\u003e🔄 Warranty\u003cstrong\u003e:\u003c\/strong\u003e\u003cspan\u003e \u003c\/span\u003eLife-time\u003c\/p\u003e\n\u003ch2 class=\"product-about__accordion-title active\"\u003e\u003cspan class=\"h5\"\u003eWhat Our Customers Say\u003c\/span\u003e\u003c\/h2\u003e\n\u003cdiv class=\"product-about__more-link-wrapper\"\u003e\n\u003cdiv class=\"product-about__more-link\"\u003e\u003csvg class=\"icon icon-filter-two\" width=\"12\" height=\"7\" viewbox=\"0 0 12 7\" fill=\"none\" xmlns=\"http:\/\/www.w3.org\/2000\/svg\"\u003e\u003cpath d=\"M10 1.5L6 5.5L2 1.5\" stroke=\"currentColor\" stroke-width=\"2\" s
troke-linecap=\"square\"\u003e\u003c\/path\u003e\u003c\/svg\u003e\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv class=\"product-about__accordion-description\"\u003e\n\u003cp\u003e⭐⭐⭐⭐⭐\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003e\"\u003c\/strong\u003eIt rides like a dream and is a superb bike.\"\u003cbr\u003e—\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eSteven\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp\u003e⭐⭐⭐⭐⭐ \"5\/5 stars for the All-World and also to Carlos and the Gloria team for perseverance with my ever-changing desires!\"\u003cbr\u003e—\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eDavid\u003c\/strong\u003e\u003c\/p\u003e\n\u003cp\u003e⭐⭐⭐⭐⭐ \"I have had a great customer experience dealing with Carlos and team. Quick to respond.\"\u003cbr\u003e—\u003cspan\u003e \u003c\/span\u003e\u003cstrong\u003eDennis\u003c\/strong\u003e\u003c\/p\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e"}; window._RestockRocketConfig.variantsInventoryPolicy = {55482671497599 : "deny",55482671530367 : "deny",55482671563135 : "deny",55482671595903 : "deny",55482671628671 : "deny",}; window._RestockRocketConfig.variantsInventoryQuantity = {55482671497599 : parseInt("1"),55482671530367 : parseInt("1"),55482671563135 : parseInt("1"),55482671595903 : parseInt("1"),55482671628671 : parseInt("1"),}; window._RestockRocketConfig.variantsPreorderCount = {55482671497599 : parseInt(""),55482671530367 : parseInt(""),55482671563135 : parseInt(""),55482671595903 : parseInt(""),55482671628671 : parseInt(""),}; window._RestockRocketConfig.variantsPreorderCountForMarket = {55482671497599 : null,55482671530367 : null,55482671563135 : null,55482671595903 : null,55482671628671 : null,}; window._RestockRocketConfig.variantsPreorderMaxCount = {55482671497599 : parseInt(""),55482671530367 : parseInt(""),55482671563135 : parseInt(""),55482671595903 : parseInt(""),55482671628671 : parseInt(""),}; window._RestockRocke
tConfig.variantsPreorderMaxCountForMarket = {55482671497599 : null,55482671530367 : null,55482671563135 : null,55482671595903 : null,55482671628671 : null,}; window._RestockRocketConfig.variantsShippingText = {55482671497599 : "",55482671530367 : "",55482671563135 : "",55482671595903 : "",55482671628671 : "",}; window._RestockRocketConfig.variantsShippingTextForMarket = {55482671497599 : null,55482671530367 : null,55482671563135 : null,55482671595903 : null,55482671628671 : null,}; window._RestockRocketConfig.selected_variant_id = 55482671497599; 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: wh ether this shop has preorders // configured at all — an enabled selling plan plus at least one preorder variant id, // independent of the block toggle. Fail-open: on error the flag stays unset and both // consumers no-op. try { var _sqCfg = window._RestockRocketConfig; var _sqPlanEnabled = Array.isArray(_sqCfg.sellingPlans) && _sqCfg.sellingPlans.some(function (p) { return p && p.enabled; }); var _sqPreCount = ((_sqCfg.cachedPreorderVariantIds && _sqCfg.cachedPreorderVariantIds.preorder_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 w

e'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.preordersConfigured) { if (_sqc.pageType === 'product' && _sqc.product && _sqc.product.variants) { _sqArm = _sqc.product.variants.some(function (v) { return _sqPre.indexOf(Number(v.id)) !== -1; }); } else { _sqArm = (_sqc.integrations || []).some(function (i) { return i && i.type === 'featured_product' && i.enabled && Array.isArray(i.page_types) && i.page_types.indexOf(_sqc.pageType) !== -1; }); } } if (_sqArm && document.documentElemenDoes your chain suffer from regular grit and grime build up, tired of spending AGES cleaning your chain when all you want to do is get on the saddle and ride? Well Muc-Off have the solution... introducing the new Muc-Off X-3 Dirty Chain Machine! This patent pending ergonomic design boasts a heavy-duty construction and can clean your chain in seconds!t) { _sqc.acceleratedGateArmed = true; document.documentElement.classList.add('stoq-buy-now-pending'); setTimeout(function () { document.documentElement.classList.remove('stoq-buy-now-pending'); }, 6000); } } catch (e) { /* fail open */ } window._RestockRocketConfig.scriptUrlProduct = 'https://cdn.shopify.com/extensions/019faf5b-94c9-7a24-9306-bf1118ca3099/restockrocket-1-554/assets/restockrocket-product.js' window._RestockRocketConfig.scriptUrlCollection = 'https://cdn.shopify.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._RestockRoc

ketConfig.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), so the // setting.json + fetchEmbedConfig calls below route to the same host as the // rest of the extension's API traffic instead of staying on the baked host. window._RestockRocketConfig.host = (window._RestockRocketConfig.cachedSettings && window._RestockRocketConfig.cachedSettings.api_host) || 'https://app.restockrocket.io' // Deployed extension build number, read from the CDN asset host Shopify generates: // https://cdn.shopify.com/extensions/ <

uuid>/With bicycle chains carrying an ever increasing price tag, it makes sense to make them last. The only way to get the best out of your chain and its running efficiency is to make sure it’s regularly and properly cleaned and lubed. The X-3 Dirty Chain Machine is by far the easiest way to give your chain a good scrub!<

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 caIncluded in the kit is a 75ml measure of Muc-Off Drivetrain Cleaner. This revolutionary little liquid can be either sprayed over the drivetrain or poured directly into the top reservoir of the machine, which is then released by a control button. This ensures the application of the cleaner is quick and easy to control. The X-3 machine and Drivetrain Cleaner partner up to attack the worst of grease and grime from every possible angle to deliver the ultimate, deep clean on even the dirtiest bike chains.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

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 age = Date.now() - updatedAt.getTime(); if (age

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; // Per-session page-load counter, incremented here in the INLINE code so it exists // even when the deferred bundle never runs. Paired with stoq_bundle_ready's // page_load_seq, it tells order-debug which page load an add fired on (and whether // the bundle had loaded on that load). var _stoqPageSeq = 0; try { _stoqPageSeq = (parseInt(window.sessi onStorage.getItem('_stoq_pls') || '0', 10) || 0) + 1; window.sessionStorage.setItem('_stoq_pls', String(_stoqPageSeq)); } catch (e) { /* sessionStorage unavailable (private mode) — leave 0 */ } 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 || '', // Buyer's storefront country (localization.country.iso_code). Seeds the // pixel's extension_details blob so it rides forward onto Shopify's native // events too — a market can span countries, so this is the finer signal. country_code: _stoqInitConfig.countryIsoCode || '', detected_variant_ids: _stoqDetectedVariantIds, selected_variant_id: _stoqSelectedVariantId || '', page_load_seq: _stoqPageSeq, liquid_rendered_at: _stoqInitConfig.liquidRenderedAt || 0, app_version
    : _stoqInitConfig.appVersion || '', liquid_cache_age: _stoqInitConfig.liquidCacheAge, // Selected variant's stock posture as our app saw it at render — explains // whether we *should* have treated it as a preorder candidate. inventory_policy: (_stoqInitConfig.variantsInventoryPolicy || {})[_stoqSelectedVariantId] || '', inventory_quantity: (_stoqInitConfig.variantsInventoryQuantity || {})[_stoqSelectedVariantId], }); } catch (e) { console.debug('STOQ - stoq_initialized publish failed:', e); } // STOQ-1595: publish stoq_accelerated_checkout_clicked for every accelerated-checkout // click (Buy Now / dynamic checkout / Shop Pay installments) on ALL surfaces, before our // bundle loads, via one delegated capture-phase listener (catches late-rendered buttons). // Only on shops with preorders configured (shared `preordersConfigured` signal from the // derivation block above) — nothing to bypass otherwise. variant_id is read per-clicked-button // from its fo rm 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.closest('form'); var vidEl = form && form.querySelector('[name="id"]'); var variantId =
  • (vidEl && vidEl.value) || _sqTrack.selected_variant_id || ''; // The enabled STOQ selling plan the clicked variant belongs to (if any), found once and // reused for in_enabled_plan / enrolled_plan_id / enrolled_plan_provider below. var _sqVidNum = Number(variantId); var _sqPlan = null; // Array.isArray guard (not `|| []`) so a truthy non-array sellingPlans can't throw and // silently drop the whole publish. var _sqPlans = Array.isArray(_sqTrack.sellingPlans) ? _sqTrack.sellingPlans : []; _sqPlans.forEach(function (pl) { if (!_sqPlan && pl && pl.enabled && Array.isArray(pl.variant_ids) && pl.variant_ids.indexOf(_sqVidNum) !== -1) { _sqPlan = pl; } }); Shopify?.analytics?.publish?.('stoq_accelerated_checkout_clicked', { variant_id: variantId, product_id: (_sqTrack.product && _sqTrack.product.id) || '', channel: channel, market_id: _sqTracEasily removes grease, grime and chain oil in secondsk.marketId || '', country_code: _sqTrack.countryIsoCode || '', 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 a like-typed compare. is_preorder_variant: (function () { var vid = Number(variantId); var has = function (a) { return Array.isArray(a) && a.indexOf(vid) !== -1; }; return has(_sqTrack.preorderVariantIds) || has(_sqTrack.cachedPreorderVariantIds && _sqTrack.cachedPreorderVariantIds.preorder_variant_ids); })(), // Narrow, authoritative preorder signal: enrolled in an ENABLED STOQ selling plan // (is_preorder_variant above is the broad OOS+continue heuristic — non-STOQ too). in_enabled_plan: !!_sqPlan, // In-stock preorder signal: enrolled but currently IN STOCK (in the inStockVariantIds // set, not the OOS preorderVariantIds set). Same live-OR-cached pattern as // is_preorder_variant. is_preorder_variant OR is_instock_variant = "should sell via // the plan" regar dless of stock, so an in-stock preorder 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.acceleratedGateArmed, gate_active_at_click: !!(document.documentElement && document.documentElement.classList.contains('stoq-buy-now-pending')), }, }); } catch (err) { /* never break checkout */ } }, true); } } catch (e) { /* fail open */ } function applyTranslations(settings) { try { // Skip translation logic entirely if multi-language is not enabled if (!settings || !settings.multi_language_enabled) { return settings; } if (!settings.translations) { console.debug('STOQ - No translations found, skipping translation'); return settings; } const normalizedLocale = window._RestockRocketConfig.normalizedLocale; const translations = settings.translations; if (!normalizedLocale) { // No matching locale has translations; drop payload to save memory console.debug('STOQ - No matching locale for translations. AvailabDeepest clean thanks to 120 contact pointsle:', Object.keys(translations || {})); 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 l
  • oaded to avoid race conditions function setupCartSellingPlanUpdater(settings) { // Setup listener regardless - updateCartSellingPlans has its own guards // This ensures cleanup happens even when preorders are disabled globally // Listen for stoq:inventory-data-loaded event dispatched by api.js window.addEventListener('stoq:inventory-data-loaded', function(event) { console.debug('STOQ - Inventory data loaded, updating cart selling plans'); if (window._RestockRocket && window._RestockRocket.updateCartSellingPlans) { window._RestockRocket.updateCartSellingPlans() .then(hasUpdates => { if (hasUpdates) { console.debug('STOQ - cart selling plans updated successfully'); } else { console.debug('STOQ - no cart selling plan updates needed'); } }) .catch(error => { console.error('STOQ - error updating cart selling plans:', error); }); } }); } // ---- Liv e settings via the Storefront API (additive, 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/
  • /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:
  • "settings") { value } } }'; fetch( `https://${cfg.shop}/api/2025-07/graphql.json`, { method: 'POST', headers: { 'Content-Type': 'application/json', 'X-Shopify-Storefront-Access-Token': cfg.storefrontAccessToken, }, body: JSON.stringify({ query: query, variables: { namespace: cfg.metafieldNamespace } }), } ) .then(function(response) { if (!response.ok) { throw new Error('Storefront API HTTP ' + response.status); } return response.json(); }) .then(function(body) { if (body && body.errors && body.errors.length) { throw new Error('Storefront API errors: ' + body.errors.map(function(e) { return e && e.message; }).join(', ')); } const value = body && body.data && body.data.shop && body.data.shop.metafield && body.data.shop.metafield.value; if (!value) { throw new Error('Storefront API returned no settings metafield value'); } cfg.sto 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
  • p' }; if (window.Shopify?.theme?.role === 'main') { headers['X-Shopify-Theme-Schema-Name'] = window.Shopify.theme.schema_name; headers['X-Shopify-Theme-Schema-Version'] = window.Shopify.theme.schema_version; headers['X-Shopify-Theme-Store-Id'] = window.Shopify.theme.theme_store_id; } fetch( `${window._RestockRocketConfig.host}/api/v1/setting.json?translation_locale=${window._RestockRocketConfig.normalizedLocale}`, { headers } ) .then(function(response) { if (!response.ok) { throw new Error('Network response was not ok'); } return response.json(); }) .then(function(settings) { initializeScripts(settings); }) .catch(function(error) { // If request failed and we have cached settings (even if expired), use them as fallback if (cachedSettings) { console.debug('STOQ - using expired cached settings as fallback'); initializeScripts(cachedSettings); } else { console.error('SHeavy duty construction that's built to lastTOQ - 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 ' +
  • endpoint + ':', applyError); throw applyError; } }) .catch(function(error) { console.debug(`STOQ - using cached ${endpoint}:`, error.message); }); } function initializeScripts(settings) { settings = 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 scr 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 || [];
  • } }), fetchEmbedConfig('integrations', function(data) { if (Array.isArray(data)) { window._RestockRocketConfig.integrations = data; } }) ]), 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 oIncludes 75ml Muc-Off Drivetrain Cleanern, // 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 collecti
  • onPageTypes = ['collection', 'index', 'search', 'page']; if(collectionPageTypes.indexOf(pageType) !== -1 && (settings[`show_button_on_${pageType}`] || settings[`preorder_${pageType}_enabled`])) { createRestockRocketScript(collectionScriptUrl); } else if(pageType === 'product') { createRestockRocketScript(productScriptUrl); } else if(hijackIntegration) { createRestockRocketScript(window._RestockRocketConfig.scriptUrlCollection); } else if(usePreorderBuild) { // cart/article/blog/list-collections: full build so the cart sweep runs. createRestockRocketScript(window._RestockRocketConfig.scriptUrlCollection); } else { console.debug(`STOQ - no scripts enabled for ${pageType}`); } // Dispatch custom event when app is loaded // Cart selling plan updates will be triggered by stoq:inventory-data-loaded event const appLoadedEvent = new CustomEvent('stoq:loaded', { detail: { pageType: window._RestockR ocketConfig.pageType, enabled: settings.enable_app, settings: settings, preorderEnabled: settings.preorder_enabled } }); console.debug('STOQ - dispatching app loaded event'); window.dispatchEvent(appLoadedEvent); } }
  • Perfect for Road, MTB or Cyclocross bicycle chains

Frequently Asked Questions

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

    ProjektRide Bike Shop Edinburgh

  • 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.

    Postage is free on orders over £50. Orders under £50, our postage charge is £3.99.

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

    ProjektRide Bike Shop Edinburgh

    82 Newington Road, EH91QN, Edinburgh.