E-bike Batteries and chargers elivery_type":"asap","quantity_limit_text":"{{ quantity }} units available for preorder","preorder_button_description_show_shipping":true,"preorder_button_description_icons_enabled":true,"preorder_shipping_text":"Shipping: {{ date }}","shipping_applies_to_all_products":true,"shipping_text":"Estimated to ship within 2 months","payment_type":"full","billing_checkout_charge_type":"percentage","billing_checkout_charge_amount":null,"billing_checkout_charge_percentage":"100.0","pricing_type":"no_discount","pricing_amount":null,"pricing_percentage":null,"discount_text":"Save {{ discount }}","billing_title":"Full payment","billing_description":null,"enable_billing_widget":false,"inventory_provider":"stoq","preorder_badge_enabled":false,"preorder_badge_text":"Preorder","preorder_badge_text_color":"#FFFFFF","preorder_badge_background_color":"#000000","preorder_discounted_price_enabled":null,"payment_line_item_property_enabled":false,"shipping_line_item_property_enabled":true,"custom_line_item_property_text":null,"preorBrake Rotors
red_image":null,"available":false,"name":"Genesis Flyer- - X-Large","public_title":"X-Large","options":["X-Large"],"price":69900,"weight":0,"compare_at_price":69999,"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\/products\/gn23250_detail_01.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_02.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_03.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_04.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_05.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_06.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_07.jpg?v=1709550154","\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_08.jpg?v=1709550154"],"featured_image":"\/\/projektride.co.uk
_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_03.jpg?v=1709550154","width":1800},{"alt":null,"id":32991120916706,"position":4,"preview_image":{"aspect_ratio":1.0,"height":1800,"width":1800,"src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_04.jpg?v=1709550154"},"aspect_ratio":1.0,"height":1800,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_04.jpg?v=1709550154","width":1800},{"alt":null,"id":32991120949474,"position":5,"preview_image":{"aspect_ratio":1.0,"height":1800,"width":1800,"src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_05.jpg?v=1709550154"},"aspect_ratio":1.0,"height":1800,"media_type":"image","src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_05.jpg?v=1709550154","width":1800},{"alt":null,"id":32991120982242,"position":6,"preview_image":{"aspect_ratio":1.0,"height":1800,"width":1800,"src":"\/\/projektride.co.uk\/cdn\/shop\/products\/gn23250_detail_06.jpg?v=1709550154"},"aspect_raSee More
Complete Wheelslyer goes disc and wide clearance, making it the ultimate foul weather winter bike - or sharp stopping city bike.\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003e\u003cbr data-mce-fragment=\"1\"\u003e\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003e\u003cbr data-mce-fragment=\"1\"\u003e\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003eNow based on our best-selling CDA frameset, the Flyer now has our hybrid double-butted alloy tubeset at its heart.\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003e\u003cbr data-mce-fragment=\"1\"\u003e\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003e\u003cbr data-mce-fragment=\"1\"\u003e\u003c\/span\u003e\u003cspan data-mce-fragment=\"1\" style=\"font-weight: 400;\"\u003eA clever mix of two differing grade alloys help give us high levels of stiffness in certain areas and compliance in others.\u003c
3e \u003c\/p\u003e\n\u003cdiv class=\"card grid-item\"\u003e\n\u003cdiv id=\"specheadingrims\" class=\"card-header pl-0 pr-0\"\u003e\n\u003cdiv data-toggle=\"collapse\" data-target=\"#specrims\" aria-expanded=\"true\" aria-controls=\"specrims\" class=\"btn btn-link pl-0 pr-0 text-center text-md-left collapsed text-uppercase\"\u003e\u003cstrong\u003eBrake Type :\u003c\/strong\u003e\u003c\/div\u003e\n\u003cspan\u003e \u003c\/span\u003e\u003cspan\u003eMechanical Disc\u003c\/span\u003e\n\u003c\/div\u003e\n\u003c\/div\u003e\n\u003cdiv class=\"card grid-item\"\u003e\n\u003cdiv id=\"specheadingrims\" class=\"card-header pl-0 pr-0\"\u003e\n\u003cdiv data-toggle=\"collapse\" data-target=\"#specrims\" aria-expanded=\"true\" aria-controls=\"specrims\" class=\"btn btn-link pl-0 pr-0 text-center text-md-left collapsed text-uppercase\"\u003e\u003cstrong\u003eFork Material :\u003c\/strong\u003e\u003c\/div\u003e\n\u003cspan\u003e \u003c\/span\u003e\u003cspan\u003eChromoly\u003c\/span\u003e\n\u003c\/div\u003e\n\u003c\/div\
_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 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 = f
criptUrlCollection = 'https://cdn.shopify.com/extensions/019f37a7-f058-7bcb-a02d-3fb0b8a7522a/restockrocket-1-539/assets/restockrocket-collection.js'
window._RestockRocketConfig.scriptUrlProductBis = 'https://cdn.shopify.com/extensions/019f37a7-f058-7bcb-a02d-3fb0b8a7522a/restockrocket-1-539/assets/restockrocket-product-bis.js'
window._RestockRocketConfig.scriptUrlCollectionBis = 'https://cdn.shopify.com/extensions/019f37a7-f058-7bcb-a02d-3fb0b8a7522a/restockrocket-1-539/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
<
Sorry, looks like we don't have enough of this product.
Tools & Maintenanceche 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
= 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 Tyres
SETTINGS_CACHE_DURATION) {
console.debug('STOQ - settings changed recently, skipping cache');
return null;
}
return settings;
} catch (error) {
console.debug('STOQ - Error checking settings cache:', error);
return null;
}
}
function createRestockRocketContainer() {
const restockRocketContainer = document.createElement('div');
restockRocketContainer.id = 'restock-rocket';
document.body.appendChild(restockRocketContainer);
}
function createRestockRocketScript(scriptUrl) {
const restockRocketScriptElement = document.createElement('script');
restockRocketScriptElement.setAttribute('defer', 'defer');
restockRocketScriptElement.src = scriptUrl;
document.body.appendChild(restockRocketScriptElement);
}
createRestockRocketContainer()
console.debug('STOQ - extension activated')
// Fire stoq_initialized once per page load so the funnel pipeline has a definitive
// "our code ran on this page" signal independent of any cus
ctedVariantId || '',
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
// derivati
Clothing and Protectionst('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: (_sqTrTubes
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" regardless of stock, so an in-stock preord
See More
dating 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);
});
}
});
}
// ---- Live 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/
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': 'skiProjektRide MerchandiseSaddles
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 collectiHelmetsSeat Posts & Clamps
Promax mechanical disc brakes provide ample power for adults and children alike, ideal for first forays on drop handlebars. Cable opertated and simple to set up, servicing is a doddle.
Aeore
Altura
Pink
Amflow
Apidura
Advent
Argon 18plumgrey/black
The microSHIFT Advent 1 x 9-speed drivetrain provides wide ratios for the steepest of slopes whilst small levers keep controls in touch.Atran Velo
AtranVelo
Podium Champagne
Basso
Benno
Little Limbsbicpolarlight/prism
Even full size riders are opting for ever wider gravel tyres for improved comfort and confidence. Now imagine you're half the size...
Maxxis Pace 2.1" tyres provide a cushioned ride, minimising fatigue, as well as a great tread pattern for a variety of terrain.Bombtrack
Purple
brooks
Brooks
Brother CyclesPurple Flip/Black Fade
Burgtec
Frame & ForkCane Creek
Purple Phaze/Amethyst Marble
Continental
Genesis ALX8 6061 Aluminium Tubingcontinental
Amplitude G100 Chromoly ForkRainbow GreyCoros
CUBE
Cube
Red
DrivetrainDiamant
dynaplug
microSHIFT AdventEarly RiderRED
1x9-speedEnigma
32T Chainring Enve
Red Smoke
11-42T CassetteEovolt
Ergon
ESIreedbeige/black
ComponentsEvoc
extra
reedbeige/chrome
Amplitude G50 Finishing KitFidlock
Madison Junior SaddleFlux
Frogreedgreen/matrix
Genesis
Gloria
Root Beer
halo
Wheelsethappy bottom
Hoperoyalgreen/prism
Amplitude G50Humber Bicycles
Six-Bolt Rotor MountsHydrapack
Sea Foam / Neon
ortlieb
Park Tool
solareclipse/gold
Parktool
Passport
Pirellisunglow/chrome
ProjektRide
renthal
Restrap
Ridgeback
RockShoxTeam Replica
Rockshox
Safety Labs
Titanium / Racing Red
Frequently Asked Questions
Schwalbe
schwalbe
Fury Red
Gecko Phaze
glacier/reflex
Gloss Crystal White/Matte Deep Smoke
Gloss Dark Star/Matte Deep Smoke