Selected experiments showing the kinds of problems I solve — SPA-aware
UI, server-load mitigation, enterprise platform quirks, and accessibility-compliant
variants.
Desktop search immersive UI
Large UK home improvement retailer
Vertical search overlay had low engagement. F-pattern
reading suggested a landscape layout could lift SRP progression.
Built a darkened focus-state overlay, repositioned the
search element to a landscape layout, and added search-term-driven subcategory
chips for Variant 2. Maintained existing product-suggestion logic to isolate
layout as the variable. Two variants shipped sitewide on desktop.
Read the case study →
Sample Ratio Mismatch in SPA experiments
Why your GA4 split never matches the platform
GA4 shows 60:40 when the testing platform shows 50:50.
Random, unstable, never matches — and the experiment code is rarely the
actual cause.
Walks through the four root causes of GA4/platform Sample
Ratio Mismatch on SPAs — data layer timing, beacon loss, virtual pageview
context, bot traffic — and the Tealium/GTM tracking pattern (with
utag.link intercept and payload mirroring) that fixes most of
them.
Read the article →
Article
Tooling
I built a CRO build tool
abtestrig — a CRO-specific build tool, open source on
GitHub
Every CRO dev rebuilds the same Gulp/webpack rig.
Hardcoded loader-script IDs, 8–15s rebuilds, page reloads on every CSS tweak,
and no way to test interactions between two live experiments before
shipping.
Built abtestrig around esbuild — sub-200ms builds, a loader
that auto-discovers the active experiment and port (no script edits between
experiments), CSS hot-swap via a stable <style> slot so
the activated state is preserved, stack-mode that runs multiple experiments
in deterministic order for conflict testing, and a shared @lib
helper library so pollerLite/waitForElement stop
getting copy-pasted between projects.
Read the article →
HubSpot CMS landing page build
UK ground engineering / residential subsidence
Geobear runs a HubSpot CMS estate of 50+ pages across several
brand variants, all powered by the same theme. The brief: a new residential
landing page with a different layout and modules — without putting the 50+ live
pages at risk.
Cloned the live theme into a parallel build via
hs cms fetch / hs cms upload, then shipped nine custom
modules from scratch — sticky nav, hero with background video, case carousel,
get-advice flow, logos, testimonials, video gallery, CTA banner, and a Leadoo
form wrapper. Deferred Swiper init via IntersectionObserver, a
ResizeObserver shim for sticky-nav padding under HubSpot's
overflow: hidden wrappers, CSS custom properties for the design
system, and inline schema.org JSON-LD (HowTo,
ItemList, Review) that passed the Rich Results
Test.
Read the case study →
"Shop Similar" follow-me carousel
Large UK home improvement retailer
Google Shopping landers were exiting PDPs and re-entering
through Shopping multiple times when the specific product didn't fit. Top-50
power-tool SKUs by click volume were the priority.
Built a sticky "follow-me" similar-products carousel
scoped to the product's category. Scrapes the breadcrumb-derived PLP at a
price-bound API call, caches results in sessionStorage so
subsequent PDPs in the same category render instantly. V2 layers a brand
filter for brand-loyal users.
Read the case study →
Trade-price prominence — account-creation lift
UK builders' merchant
Trade prices were rendered separately from retail prices on
PLPs and PDPs, so non-account users couldn't easily compare the two and
weren't motivated to create a trade account. The hypothesis: making trade
prices visually dominant alongside retail prices would lift trade account
creation.
Replaced the host's separate trade-price block with a
unified price banner showing retail and trade side-by-side, gated to
logged-out users via cookie check. Banner click opens an embedded login modal
using the host's own identity SDK, with a separate "I don't have an account"
CTA routing to signup.
Read the case study →
Comparison PLP — bundle product removal
UK home insurance provider
Internal data showed bundled "Gas Combo" products had lower
year-on-year renewal rates than single-category cover, dragging long-term
profitability. Stakeholders wanted to surface them on a separate page so
single-category products got cleaner focus.
Removed bundle items from three comparison PLPs by URL
match, restructured the grid from 3-up to 2-up, destroyed and re-initialised
the Swiper instance to match the new slide count, and dynamically injected a
new nav link to the dedicated bundle page once a user had visited a comparison
PLP.
Read the case study →
Bestsellers carousel — homepage social proof
Global cosmetics retailer (Romania)
Off-boarding from the UK site cut the planned social-proof
test runtime short. The team needed a market that could deliver significance
inside a tight timeframe.
Built a 19-product bestsellers carousel with desktop
dropdown and mobile bottom-sheet variant pickers, integrated with Avon's
internal product API and CartServiceModule for inline add-to-bag,
plus IntersectionObserver-driven impression tracking on both control and
variant.
Read the case study →
Customer gallery homepage integration
UK luxury flooring retailer (Magento)
Customer gallery sat on a separate page where most homepage
visitors never reached it — a high-trust social proof asset locked behind a
navigation step.
Live-fetched the customer gallery into the homepage, built
a product modal that scrapes form data from PDPs to power inline AJAX sample
ordering, and surfaced Magento's native success/error notifications by cloning
the host's existing UI.
Read the case study →
PLP/PDP exit-intent overlay
Large UK home improvement retailer
Plain PLP exit rate of 14.4% across organic and paid
channels — users were leaving without finding a product that matched their
price or criteria. Returning users exited at 15.1%.
Built a custom ExitIntent detection class
(mouse trajectory, inactivity, tab-switch heuristics), an accessible modal
with focus trap, and a V2 carousel that fetches category-relevant clearance
products at price ranges derived from the breadcrumb and SKU scrape.
Read the case study →