UX audit — ritfitness.myshopify.com

run 20260907T022252 · 2026-09-07 03:07 · 36 findings

11 Critical25 Needs improvement0 Good
funnel · 2conversion · 3behavior · 1performance · 3merchandising · 0sales · 0heuristics · 0accessibility · 7search · 2voice · 1theme_code · 7design · 5copy · 5

Synthesis · claude

Checkout completion and a slow, error-prone mobile experience are the biggest drags on a mobile-first, high-AOV store
Checkout completion is the single largest revenue leak: Checkout completion sits below the industry band, the dominant monetized finding by an order of magnitude (~$5.25M/mo midpoint, based on ~40,293 begun checkouts × a 35pp completion gap × $921 AOV). The recommended checks — surprise costs shown late, forced account creation, limited payment methods, and mobile form friction — map directly to RitFit's known exit gaps: financing/BNPL/HSA-FSA is the #1 missing info at exit for high-ticket buyers, and this must appear before checkout. Note GA4 over-counts orders at 111%, so treat GA4-derived CVR as slightly overstated when sizing.
Mobile is implicated across funnel, performance, and design pillars: With mobile at 77.1% of sessions (and 76.4% of buyers per VoC), mobile problems compound. Mobile add-to-cart lags desktop by 18.1pp (~$595K/mo). Mobile Core Web Vitals are weak: LCP poor on 6 templates (~$21.7K/mo), FCP poor on 2 (~$10.5K/mo), and INP needs improvement on 2 (~$4.5K/mo) — the INP finding explicitly implicates add-to-cart and variant-selection handlers. Design compounds this with body text too small to read on mobile and tap targets below minimum size. This is a clear cross-pillar mobile theme.
Front-end code health suppresses conversion and slows the site: JavaScript errors affect 26.1% of sessions and can invisibly suppress conversions, especially if they hit PDP/cart/checkout. This is reinforced by heavy theme bloat: 1,593 KB of JS/CSS shipped, 205 KB of inline script/style, 2 oversized bundles (largest 237 KB), legacy jQuery, deprecated img_url filters, 22 images missing lazy-loading, and 19 missing width/height. Together these degrade the same mobile CWV metrics above.
Accessibility gaps carry legal and conversion risk: A cluster of critical WCAG failures — missing image alt text, links and buttons without accessible names, and low-contrast text — plus needs-improvement issues (unlabeled form fields, duplicate IDs, small tap targets). Unlabeled form fields and small tap targets directly overlap the mobile checkout friction that drives the completion gap.
Copy doesn't yet lead with value, convenience, and proof: Headlines make no promise, copy leans on empty superlatives, product copy is feature-led rather than benefit-led, claims lack proof, and brand voice shifts between surfaces. This is squarely off-brand for RitFit, whose winning message is value + all-in-one convenience backed by hard numbers and reviews (reviews are the #1 confidence driver at 45.9%). Concrete specs and social proof should replace superlatives.
Organic and on-site search leave qualified demand uncaptured: Page-1 queries with very low CTR (~$7.3K/mo), high-impression striking-distance queries stuck on page 2 (~$10.2K/mo), unmet on-site search demand (~$4.1K/mo), and missing/incomplete Product structured data that would surface price/stock/stars in results. Underperforming channels (~$56.5K/mo) and high-traffic landing pages (~$52.5K/mo) converting far below site average round out the acquisition-side leaks.

Trends vs 20260831T025814

0 new · 0 regressed · 0 improved · 1 resolved
✓ resolved: Pages overflow the mobile viewport (UX guideline #69 (Responsive / Horizontal Scroll))

Shipped-change outcomes (7)

funnel-mobile-atc-gap — no_baseline: no prior snapshot recorded this finding before it shipped
a11y-images-missing-alt — no_baseline: no prior snapshot recorded this finding before it shipped
39 images without loading="lazy" (in reviewed templates) — no_change: critical → critical
7 images without width/height (in reviewed templates) — no_change: needs_improvement → needs_improvement
11 deprecated | img_url filter(s) — no_change: needs_improvement → needs_improvement
Pinch-zoom disabled in the viewport meta (WCAG 1.4.4) — resolved: needs_improvement → resolved
3 deprecated {% include %} tag(s) — resolved: needs_improvement → resolved
0 shown
Critical Checkout completion below the industry band
conversion · priority 455 · ~$2,623,615–$7,870,845/mo · confidence 65% · effort medium

Why: Only 10% of begun checkouts complete (4,302 of 40,293/30d) vs the 45–65% band (Checkout-begun to purchase completion typically ~45-65% (Baymard-style checkout abandonment ~70% is cart-to-purchase; this band is checkout-step only)). These shoppers already decided to buy — this is the cheapest conversion to recover. Cross-checked vs Shopify: 3,884 actual orders, GA4 captures 111% — numbers shown are corrected.

Fix: Audit the checkout for surprise costs (shipping shown late), forced account creation, limited payment methods, and mobile form friction — the classic completion killers, in that order.

🛠️ Detailed solution
1. In Shopify admin → Settings → Checkout, verify shipping cost is surfaced early: enable a shipping rate estimate and keep 'Customer accounts' set to Optional (never Required) so Marcus and Danielle aren't force-registered mid-flow on their $2,198 Smith-package orders. 2. Expand payment methods to cover the #1 exit gap (financing): enable Shop Pay Installments/Affirm and, given HSA-FSA demand, confirm those cards clear — surface a 'from $X/mo' line and BNPL logos at checkout entry. 3. Because 77% are mobile, test the full mobile checkout on a real phone: reduce required fields, enable address autocomplete, and confirm Shop Pay/Apple Pay/Google Pay accelerated buttons appear first. 4. Cross-reference the 26%-of-sessions JS-error finding — run a live mobile checkout while watching the console to confirm no theme/app script blocks the 'Pay now' step (this can silently drop the 10%→45-65% gap). 5. Add reassurance at the pay step (3-year frame warranty, delivery-damage guarantee) to counter the price/trust/delivery objections that made 41% almost stop. 6. Instrument each step in GA4 checkout funnel to isolate which step (shipping, payment, review) loses the most, then re-audit after fixes.
Evidence (1)
  • ga4_benchmark Checkout completion vs band
    {
      "checkouts": 40293.0,
      "purchases": 4302.0,
      "completion": 0.09639391457573276,
      "bench": {
        "low": 0.45,
        "median": 0.55,
        "good": 0.65,
        "source": "Checkout-begun to purchase completion typically ~45-65% (Baymard-style checkout abandonment ~70% is cart-to-purchase; this band is checkout-step only)"
      },
      "aov": 920.7055230125526,
      "sessions_month": 1512616.0
    }
Needs improvement Mobile add-to-cart rate lags desktop shipped
funnel · device:mobile · priority 438 · ~$595,285/mo · confidence 65% · effort medium

Why: Mobile converts sessions to add-to-cart at 7.08% vs desktop 8.65% — 18% lower — while downstream stages (cart->checkout, checkout->purchase) are similar across devices. With mobile at 77% of sessions, the browse->ATC step is the single largest leak.

Fix: Prioritize the mobile product-browsing-to-ATC experience: make the add-to-cart button prominent and sticky above the fold, tighten the mobile PDP layout (image, price, variant, ATC visible without scrolling), and reduce variant-selection friction.

🛠️ Detailed solution
1. In `sections/main-product.liquid`, the fixed/sticky add-to-cart is gated by `section.settings.show_fixed_add_to_cart` — enable this setting in the theme editor for the default Product template so the ATC stays visible on mobile (mobile ATC is 7.08% vs 8.65% desktop). 2. Tighten above-the-fold order in `snippets/product-info.liquid` so image, price, variant picker and ATC are reachable without scrolling on a phone; move financing ('from $X/mo') directly under price since it's the #1 exit info gap. 3. Reduce variant friction in `snippets/variant-picker.liquid` — use swatches/buttons instead of dropdowns for colorway (pink) and size so selection is one tap. 4. Confirm the `product-quick-add` sticky bar (already coded in main-product.liquid, `sm:hidden` mobile variant) renders on the real live PDP template being served (e.g. `product.blp-gazelle-pro-new.json`), not just the default. 5. Retest mobile addToCarts/sessions after rollout to confirm movement toward the 8.65% desktop rate.
🧪 Hypothesis: A sticky, above-the-fold ATC on mobile PDPs raises mobile ATC rate
Variant: Sticky mobile ATC bar + condensed above-fold PDP
metric add_to_cart_rate
Evidence (1)
  • ga4_metric addToCarts/sessions by deviceCategory
    {
      "mobile": {
        "sessions": 1166683.0,
        "add_to_carts": 82596.0,
        "checkouts": 30640.0,
        "purchases": 3309.0,
        "revenue": 2688487.3500000006,
        "atc_rate": 0.07079558029044736,
        "checkout_rate": 0.37096227420214056,
        "purchase_rate": 0.10799608355091384,
        "overall_cvr": 0.0028362460068416186,
        "aov": 812.4772892112422
      },
      "desktop": {
        "sessions": 267835.0,
        "add_to_carts": 23160.0,
        "checkouts": 8503.0,
        "purchases": 931.0,
        "revenue": 1193244.7399999998,
        "atc_rate": 0.08647114828159128,
        "checkout_rate": 0.36714162348877377,
        "purchase_rate": 0.1094907679642479,
        "overall_cvr": 0.003476020684376575,
        "aov": 1281.6807089151448
      }
    }
Critical High JavaScript errors rate (26% of sessions)
behavior · priority 421 · ~$3,037–$30,367/mo · confidence 90% · effort medium

Why: JavaScript errors in a large share of sessions point to broken front-end behavior — failed handlers, third-party scripts, or theme bugs that can silently break add-to-cart, variant selection, or checkout. Over the last 3d, JavaScript errors appear in 26.1% of sessions (~38,656 of 148,165).

Fix: Triage the top script errors (browser console / error-tracking), prioritizing any on PDP/cart/checkout, and fix or remove the failing scripts — these often suppress conversions invisibly.

🛠️ Detailed solution
1. Open Chrome DevTools console on a real mobile session and reproduce PDP → variant select → add-to-cart → cart-drawer → checkout, logging every error (errors hit 26.1% / ~38,656 of 148,165 sessions over 3 days). 2. Prioritize errors on PDP/cart/checkout paths since those directly suppress the 10% checkout completion; note the theme loads jQuery, vendor.min.js, imagesloaded, masonry, photoswipe and multiple app scripts (`assets/apps.js`, `snippets/mkq_script.liquid`, `snippets/booster-seo.liquid`, speed-booster snippets) — common conflict sources. 3. For each failing third-party/app script, disable it in the app admin or comment its include and confirm the error clears and ATC still fires. 4. Check `assets/theme.js`/`assets/sections.js` custom-element handlers (buy-buttons, variant-picker, cart-drawer) for errors thrown when an app removes/renames DOM nodes. 5. Add an error-tracking beacon (Sentry or GA4 exception event) to quantify error rate by template after fixes and set a regression alert.
🧪 Hypothesis: Eliminating JS errors on the PDP/checkout removes silent add-to-cart and checkout failures
Variant: Ship the JS fix on the affected templates (control = current build)
metric conversion_rate · min sample 8,000 · ~14d
Evidence (1)
  • clarity_metric ScriptErrorCount share of sessions
    {
      "share": 0.2609,
      "affected_sessions": 38656.2485,
      "total_sessions": 148165.0
    }
Critical 22 images without loading="lazy" (in reviewed templates) shipped
theme_code · priority 420 · confidence 70% · effort low

Why: Eager images compete with the hero for bandwidth, inflating LCP on image-heavy pages.

Fix: Add `loading="lazy"` to below-the-fold <img> tags (keep the hero image eager, ideally with `fetchpriority="high"`).

🛠️ Detailed solution
1. Search the reviewed templates for `<img` and `image_tag` calls missing a loading attribute (22 found) — add `loading="lazy"` to every below-the-fold image. 2. Keep the LCP hero eager: on the first hero/product image add `fetchpriority="high"` and do NOT lazy-load it. Example before/after in an image_tag call: before `image_tag: sizes: '80px', widths: '80,160'` → after `image_tag: loading: 'lazy', sizes: '80px', widths: '80,160'`. 3. In `snippets/product-gallery.liquid`, ensure only the first gallery slide is eager and the rest carry `loading="lazy"`. 4. Check homepage/collection sections (e.g. `sections/custom-homepage-bestseller.liquid`, `snippets/product-card.liquid`) since those images repeat below the fold. 5. Re-run Lighthouse mobile on the Product default and Collection templates to confirm the 22 flagged images no longer compete with the hero for bandwidth.
Evidence (1)
  • theme_source 22 images without loading="lazy" (in reviewed templates)
    {
      "count": 22,
      "reviewed_files": 24,
      "samples": [
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ block.settings.image1 | img_url: 'large' }}' class='slider-img active' data-img-index='0' >"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ block.settings.image2 | img_url: 'large' }}' class='slider-img{% if block.settings.image1 == blank %} active{% endif %}' data-img-index='1' >"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ aproduct.featured_image | img_url: 'large' }}' alt='{{ aproduct.title }}' >"
        }
      ]
    }
Critical Page-1 search queries with very low click-through
search · priority 387 · ~$3,668–$14,670/mo · confidence 60% · effort low

Why: 363 quer(y/ies) rank on Google page 1 (avg position ≤ 10) with ≥ 100 impressions but under 1% CTR — the store appears in results yet isn't earning the click, usually a title/snippet or search-intent mismatch.

Fix: Rewrite the <title> and meta description for these queries to match intent with a compelling hook, and confirm the ranking URL is the most relevant page (PDP/collection, not the homepage).

🛠️ Detailed solution
1. Confirm the ranking URL per query is the best target, not the homepage: 'smith machine' (70,704 impr, 0.31% CTR, pos 8.5) should point to `collection.racks-smithmachine` or the Smith PDP; 'hip abduction machine'/'adductor machine' to `collection.lower-body-machines`; 'best home gym equipment' to `collection.packages`. 2. In each collection/product admin → Search engine listing, rewrite the <title> to lead with value + spec, e.g. 'Smith Machine — Commercial Spec, 25–40% Less | RitFit' and 'Hip Abduction/Adductor Machine for Home Gyms | RitFit'. 3. Write meta descriptions in brand voice with a concrete proof point and hook (3-year frame warranty, 'from $X/mo', footprint fits your garage) to earn the click these 363 page-1 queries aren't converting. 4. Add/verify Product and BreadcrumbList structured data (theme has `snippets/microdata-schema.liquid`) so rich results show price/rating. 5. Reduce keyword cannibalization — ensure one canonical URL per intent so Google surfaces the strongest page. 6. Recheck GSC CTR for these queries after 28 days and iterate on the lowest performers (adductor 0.1%, hip abduction 0.08%).
Evidence (1)
  • search_console Low-CTR page-1 queries (28d)
    {
      "site": "https://www.ritfitsports.com/",
      "count": 363,
      "examples": [
        {
          "query": "smith machine",
          "impressions": 70704,
          "clicks": 218,
          "ctr": 0.0031,
          "position": 8.5
        },
        {
          "query": "hip abduction machine",
          "impressions": 35058,
          "clicks": 28,
          "ctr": 0.0008,
          "position": 7.7
        },
        {
          "query": "smith machine bar weight",
          "impressions": 11825,
          "clicks": 27,
          "ctr": 0.0023,
          "position": 5.4
        },
        {
          "query": "adductor machine",
          "impressions": 11571,
          "clicks": 11,
          "ctr": 0.001,
          "position": 4.1
        },
        {
          "query": "best home gym equipment",
          "impressions": 10902,
          "clicks": 16,
          "ctr": 0.0015,
          "position": 9.1
        }
      ]
    }
Critical LCP poor on 6 template(s) (mobile)
performance · strategy:mobile · priority 379 · ~$5,423–$54,227/mo · confidence 75% · effort medium

Why: Largest Contentful Paint (LCP) is poor on 6 of 6 audited template(s) on mobile: Product · blp-gazelle-pro-new (3,946 ms), Collection · packages-new-dev (2,750 ms), Product · default (4,184 ms), Collection · pink-series (3,406 ms), Product · m2-new (3,156 ms), Collection · smith-new (2,750 ms). Google's 'good' threshold is 2,500 ms. Poor Core Web Vitals both frustrate users and suppress search ranking.

Fix: Speed up the largest above-the-fold element: compress and correctly size the hero/product image, preload it, cut render-blocking CSS/JS, and reduce server response time. On Shopify, audit theme apps and oversized images first.

🛠️ Detailed solution
1. On the two worst templates — Product default (4,184 ms) and Product blp-gazelle-pro-new (3,946 ms) — identify the LCP element (hero product image) and add `fetchpriority="high"` plus a preload; in `snippets/product-gallery.liquid` ensure the first image is NOT lazy-loaded and is sized to the mobile viewport (avoid serving 2000px+ into a ~390px screen). 2. Compress and resize all hero/product images and use responsive `widths`/`sizes` so mobile pulls the smallest adequate render. 3. Cut render-blocking assets: the theme loads jQuery, vendor.min.js, masonry, photoswipe, imagesloaded — defer/async non-critical JS and inline only critical CSS on Product and Collection (packages-new-dev, pink-series, smith-new) templates. 4. Audit installed apps' injected scripts (also implicated in the 26% JS-error finding) and remove any not needed on PDP/collection. 5. Reduce server response by trimming heavy Liquid loops in the served product templates. 6. Re-test each of the 6 templates in PageSpeed mobile until LCP is under 2,500 ms.
🧪 Hypothesis: Cutting mobile LCP below 2.5s reduces bounce and raises mobile conversion
Variant: Preload the LCP hero image, defer non-critical JS, inline critical CSS on PDP/home
metric mobile_conversion_rate · min sample 8,000 · ~14d
Evidence (1)
  • psi_metric LCP (mobile) — 6 template(s)
    {
      "strategy": "mobile",
      "good_bar": "2,500 ms",
      "by_template": {
        "Product \u00b7 blp-gazelle-pro-new": {
          "display": "3,946 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 3946.0
        },
        "Collection \u00b7 packages-new-dev": {
          "display": "2,750 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 2750.0
        },
        "Product \u00b7 default": {
          "display": "4,184 ms",
          "status": "poor",
          "source": "field",
          "value": 4184.0
        },
        "Collection \u00b7 pink-series": {
          "display": "3,406 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 3406.0
        },
        "Product \u00b7 m2-new": {
          "display": "3,156 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 3156.0
        },
        "Collection \u00b7 smith-new": {
          "display": "2,750 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 2750.0
        }
      }
    }
Needs improvement Traffic sources converting far below the site average
conversion · priority 333 · ~$28,258–$98,902/mo · confidence 60% · effort medium

Why: 2 channel(s) with meaningful traffic convert under 50% of the site average (0.28%): Paid Social (596,502 sess, 0.10%); Display (44,769 sess, 0.00%). 2 of them are paid — that portion is directly wasted spend. Gate: ≥300 sessions and ≥5 expected purchases per channel.

Fix: Audit each flagged channel's landing experience and targeting match (query → page). For paid channels, re-point spend at the converting audiences/pages before buying more traffic.

🛠️ Detailed solution
1. Paid Social (596,502 sessions, 0.10% CVR = ~$ wasted on 605 purchases) is far below the 0.28% site average — pull its landing URLs and confirm ads point to the specific PDP/collection matching the ad creative (e.g. Pink Series ads → `collection.pink-series`, package ads → `collection.packages`), not the homepage. 2. Display (44,769 sessions, 0.00% CVR, 0 purchases) is converting to nothing — pause the campaign immediately and reallocate budget until landing pages and audiences are fixed. 3. For Paid Social, tighten targeting to the validated buyer personas (Marcus garage-builders, Danielle first-year starters) and lead creative with value+convenience and 'from $X/mo' financing, the top exit gap. 4. Build dedicated landing pages that preempt price/trust/space/financing objections and carry UGC (reviews are the #1 confidence driver, 45.9%). 5. Set a minimum CVR/ROAS threshold and re-audit after 2–4 weeks before scaling spend.
Evidence (1)
  • ga4_benchmark Channel CVR vs site average
    {
      "examples": [
        {
          "channel": "Paid Social",
          "sessions": 596502.0,
          "purchases": 605.0,
          "cvr": 0.0010142463897857845
        },
        {
          "channel": "Display",
          "sessions": 44769.0,
          "purchases": 0.0,
          "cvr": 0.0
        }
      ],
      "site_cvr": 0.0028440793962248184,
      "aov": 920.7055230125526,
      "count": 2
    }
Needs improvement High-demand on-site searches with no catalog match
voice · priority 332 · ~$1,381–$8,286/mo · confidence 55% · effort low

Why: 6 on-site search term(s) with ≥ 15 sessions return no catalog match — a product gap or a findability/synonym problem on high-intent traffic (these shoppers told you exactly what they want). Top: "p9" (52), "dumbells" (31), "treadmill" (20), "P9" (16), "kettlebells" (16).

Fix: For each term, either add the product/collection shoppers expect, or — if it already exists — add search synonyms/redirects so it's findable. Start with the highest-volume terms.

🛠️ Detailed solution
1. For 'p9'/'P9' (52+16 sessions) — the theme has `templates/product.p9.json` and `product.p9-pkg.json`, so this is a findability gap: in your search app (or Shopify Search & Discovery) add 'p9' and 'P9' as synonyms pointing to the P9 product, and add a redirect for the raw query. 2. For 'dumbells'/'dumbell' (31+15) and 'kettlebells' (16) add synonyms mapping the misspellings/plurals to the correct dumbbell and kettlebell products/collections; if no kettlebell SKU exists, flag as a catalog gap for merchandising. 3. For 'treadmill' (20) — RitFit is strength-first, so either confirm no product exists and add a redirect to the closest relevant collection (or a 'not carried' note), rather than returning a dead no-results page. 4. Install/configure Shopify Search & Discovery app to manage synonyms and redirects centrally. 5. Recheck on-site search no-result terms after 28 days and add the next tier of high-session terms.
Evidence (1)
  • ga4_search On-site search terms with no catalog match (28d)
    {
      "count": 6,
      "examples": [
        {
          "term": "p9",
          "sessions": 52,
          "searches": 123
        },
        {
          "term": "dumbells",
          "sessions": 31,
          "searches": 84
        },
        {
          "term": "treadmill",
          "sessions": 20,
          "searches": 42
        },
        {
          "term": "P9",
          "sessions": 16,
          "searches": 41
        },
        {
          "term": "kettlebells",
          "sessions": 16,
          "searches": 44
        },
        {
          "term": "dumbell",
          "sessions": 15,
          "searches": 38
        }
      ]
    }
Needs improvement High-traffic landing pages converting far below site average
conversion · priority 303 · ~$26,227–$91,796/mo · confidence 55% · effort medium

Why: 14 commercial landing page(s) convert under 40% of the site average: /products/ritfit-blp01-full-leg-workout- (103,209 sess, 0.00%); /collections/racks-package (79,292 sess, 0.06%); /collections/ritfit-pink-series (60,646 sess, 0.01%); /products/m3-pro-power-rack-cable-crosso (25,428 sess, 0.04%). Content pages are excluded (low landing CVR is normal for blogs); gate: ≥300 sessions and ≥5 expected purchases.

Fix: Treat each flagged page as its own funnel entry: check message match with its traffic source, above-the-fold clarity, and load speed. Run a page audit (page_audit) on each for specifics.

🛠️ Detailed solution
1. Start with the two worst leaks by traffic: /products/ritfit-blp01-full-leg-workout-package... (103,209 sess, 0.00% CVR, only 5 purchases) and /collections/racks-package (79,292 sess, 0.06%) — pull their traffic source in GA4/Shopify to confirm ad/query-to-page message match, since near-zero CVR at this volume usually signals a mismatch or broken buy path. 2. On the BLP01 leg-workout PDP, verify the buy path actually works: this product uses `sections/main-product.liquid`/`main-product2.liquid` rendering `snippets/buy-buttons.liquid` — confirm the variant is in stock and the ATC form isn't disabled; a 0.00% CVR on 103K sessions strongly implies a sold-out/hidden ATC. 3. Add Marcus/Fresh-Starter above-the-fold proof to each flagged PDP: footprint + ceiling-clearance specs (space fit is the #1 constraint at 38.1%), a 'from $X/mo' Affirm line, and the 3-year frame warranty — financing is the #1 exit info gap. 4. For the collection leaks (/collections/racks-package, /collections/ritfit-pink-series 60,646 sess/0.01%), edit their templates (`templates/collection.packages.json`, `templates/collection.pink-series.json`) to lead with a value+convenience banner ('Your whole gym. One smart buy.') and ensure product cards show price and review stars, not just imagery. 5. Run the page_audit tool on each of the 14 flagged URLs for load-speed and above-the-fold specifics, prioritizing by session volume. 6. For /products/ritfit-water-bottle (13,839 sess, 0 purchases) confirm it's a genuine commercial page vs. accessory/giveaway; if non-commercial, exclude it from paid traffic rather than optimizing.
Evidence (1)
  • ga4_benchmark Landing-page CVR vs site average
    {
      "examples": [
        {
          "path": "/products/ritfit-blp01-full-leg-workout-package-3-in-1-leg-press-hack-squat-and-calf-raise-set",
          "sessions": 103209.0,
          "purchases": 5.0,
          "cvr": 4.844538751465473e-05
        },
        {
          "path": "/collections/racks-package",
          "sessions": 79292.0,
          "purchases": 48.0,
          "cvr": 0.000605357413105988
        },
        {
          "path": "/collections/ritfit-pink-series",
          "sessions": 60646.0,
          "purchases": 8.0,
          "cvr": 0.00013191306928733964
        },
        {
          "path": "/products/m3-pro-power-rack-cable-crossover-smith-machine",
          "sessions": 25428.0,
          "purchases": 10.0,
          "cvr": 0.0003932672644329086
        },
        {
          "path": "",
          "sessions": 19116.0,
          "purchases": 16.0,
          "cvr": 0.0008369951872776732
        },
        {
          "path": "/products/ritfit-pink-bumper-plates-olympic-rubber-weight-plates-2-inch",
          "sessions": 17597.0,
          "purchases": 5.0,
          "cvr": 0.0002841393419332841
        },
        {
          "path": "/products/ritfit-a-frame-dumbbell-storage-rack-stand-5-pairs",
          "sessions": 14779.0,
          "purchases": 3.0,
          "cvr": 0.00020299073008999256
        },
        {
          "path": "/products/ritfit-water-bottle",
          "sessions": 13839.0,
          "purchases": 0.0,
          "cvr": 0.0
        }
      ],
      "site_cvr": 0.0028440793962248184,
      "aov": 920.7055230125526,
      "count": 14
    }
Critical Images missing alt text (WCAG 1.1.1) shipped
accessibility · priority 294 · confidence 70% · effort medium

Why: Screen readers can't convey an image without alt text, and it's an SEO signal. Found 41 occurrence(s) across Home, Collection · packages-new-dev, Product · m3, Collection · pink-series, Product · m2-new, Collection · smith-new, +1 more (rendered DOM).

Fix: Add descriptive alt text to meaningful images (alt="" for purely decorative ones).

🛠️ Detailed solution
1. Prioritize by count: Collection · pink-series has 15 of the 41 missing-alt images — audit `templates/collection.pink-series.json` and the sections it renders (e.g. `sections/pink-collection.liquid`, `sections/custom-pinkoct-*`) for `image_tag`/`<img>` calls lacking an `alt`. 2. For product/collection media rendered through theme snippets, ensure the `alt` pulls the image's set alt: e.g. `{{ image | image_tag: alt: image.alt | escape }}` — in Shopify admin set alt text on each product/collection image via Products → [product] → media → Edit alt text. 3. For the 4 Cart occurrences, check `sections/cart-drawer.liquid`/`snippets/line-item.liquid` thumbnails and add `alt: line_item.title`. 4. For the 4 Home occurrences, open the sections in `templates/index.json` (hero/banner/media-grid) and give merchandising images descriptive alt like 'RitFit M3 power rack set up in a home garage gym'. 5. Mark purely decorative images (dividers, background flags) with `alt=""` so screen readers skip them. 6. Re-scan the affected templates (Home, packages-new-dev, m3, pink-series, m2-new, smith-new, Cart) to confirm all 41 are resolved.
Evidence (1)
  • a11y_rule Images missing alt text — WCAG 1.1.1
    {
      "total": 41,
      "by_template": {
        "Home": 4,
        "Collection \u00b7 packages-new-dev": 5,
        "Product \u00b7 m3": 4,
        "Collection \u00b7 pink-series": 15,
        "Product \u00b7 m2-new": 5,
        "Collection \u00b7 smith-new": 4,
        "Cart": 4
      }
    }
Critical Links without discernible text (WCAG 4.1.2 / 2.4.4)
accessibility · priority 294 · confidence 70% · effort medium

Why: A link with no name is announced as just 'link', leaving users no idea where it goes. Found 50 occurrence(s) across Home, Product · new-product, Product · blp-gazelle-pro-new, Collection · packages-new-dev, Product · default, Product · m3, +6 more (rendered DOM).

Fix: Give every link visible text or an aria-label (icon-only links need one).

🛠️ Detailed solution
1. Target the highest-count template first: Home has 10 of the 50 unnamed links — inspect the sections in `templates/index.json` for icon-only or image-wrapped `<a>` tags with no text. 2. For social/icon links, add an `aria-label`: `sections/footer.liquid` and `snippets/social-media.liquid` icon anchors should read e.g. `<a href="..." aria-label="RitFit on Instagram">`. 3. For image-only product/collection card links (Product and Collection templates each show 3–4 occurrences), ensure the card anchor carries the product title — in `snippets/product-card.liquid` add `aria-label="{{ product.title | escape }}"` on the wrapping `<a>`. 4. In `sections/cart-drawer.liquid` (Cart: 3), the close/remove anchors already use `sr-only` spans in some spots — replicate that pattern (`<span class="sr-only">{{ 'cart.order.remove' | t }}</span>`) on any bare link. 5. Re-audit each affected template to confirm every link exposes visible text or an aria-label.
Evidence (1)
  • a11y_rule Links without discernible text — WCAG 4.1.2 / 2.4.4
    {
      "total": 50,
      "by_template": {
        "Home": 10,
        "Product \u00b7 new-product": 4,
        "Product \u00b7 blp-gazelle-pro-new": 4,
        "Collection \u00b7 packages-new-dev": 3,
        "Product \u00b7 default": 4,
        "Product \u00b7 m3": 4,
        "Product \u00b7 m1-buffalo-new": 4,
        "Collection \u00b7 pink-series": 3,
        "Product \u00b7 m2-new": 4,
        "Collection \u00b7 smith-new": 4,
        "Product \u00b7 p9": 3,
        "Cart": 3
      }
    }
Critical Buttons without accessible names (WCAG 4.1.2)
accessibility · priority 294 · confidence 70% · effort medium

Why: An unnamed button can't be understood or actioned by screen-reader users. Found 50 occurrence(s) across Product · new-product, Product · blp-gazelle-pro-new, Collection · packages-new-dev, Product · m3, Product · m1-buffalo-new, Product · m2-new, +2 more (rendered DOM).

Fix: Give icon-only buttons an aria-label or visible text.

🛠️ Detailed solution
1. Fix the biggest offender first: Product · m1-buffalo-new has 16 of the 50 unnamed buttons — open its template (`templates/product.m1-buffalo-new.json`) and the custom sections it loads, looking for icon-only `<button>` (carousel arrows, quantity, swatches) with no label. 2. Follow the theme's existing pattern from `sections/cart-drawer.liquid`: add a hidden label + aria-label, e.g. `<button is="prev-button" aria-label="{{ 'general.accessibility.previous' | t | escape }}">`. 3. For variant/swatch buttons in `snippets/variant-picker.liquid` and quantity steppers, add `aria-label` describing the action (e.g. 'Select color: Pink', 'Increase quantity'). 4. For the ATC and quick-add buttons rendered via `snippets/buy-buttons.liquid` / `sections/main-product.liquid`, confirm `button_content` (the translated 'Add to cart' text) is present and not replaced by an icon-only variant. 5. Apply the same fixes across the other flagged product templates (new-product, blp-gazelle-pro-new, m3, m2-new, p9) and the smith-new collection, then re-scan to confirm all 50 resolve.
Evidence (1)
  • a11y_rule Buttons without accessible names — WCAG 4.1.2
    {
      "total": 50,
      "by_template": {
        "Product \u00b7 new-product": 5,
        "Product \u00b7 blp-gazelle-pro-new": 6,
        "Collection \u00b7 packages-new-dev": 2,
        "Product \u00b7 m3": 6,
        "Product \u00b7 m1-buffalo-new": 16,
        "Product \u00b7 m2-new": 4,
        "Collection \u00b7 smith-new": 5,
        "Product \u00b7 p9": 6
      }
    }
Critical 205 KB of inline script/style in reviewed templates
theme_code · priority 294 · confidence 70% · effort medium

Why: Inline code re-downloads with every page view and can't be cached, inflating HTML weight and parse time on each navigation.

Fix: Move large inline <script>/<style> blocks into .js/.css assets (served from the CDN with long cache lifetimes); keep only tiny critical CSS inline.

🛠️ Detailed solution
1. Audit the reviewed templates for large inline `<style>`/`<script>` blocks — the product sections (`sections/main-product.liquid`, `main-product2.liquid`, `main-product-features.liquid`) and `sections/header.liquid` each embed sizeable inline `<style>` blocks that re-download on every page view. 2. Move static, non-Liquid CSS out of these inline blocks into a cacheable asset (the theme already ships `assets/theme.css`); create/extend a `.css` file and reference it via `{{ 'your-file.css' | asset_url | stylesheet_tag }}`. 3. Keep only the truly dynamic, per-section rules inline (e.g. the `--product-grid` values in `main-product.liquid` that depend on `section.settings`) — those must stay Liquid-driven, so scope them tightly and remove the static portions. 4. For inline `<script>` blocks, relocate logic into `assets/*.js` files (theme has `assets/sections.js`, `assets/theme.js`) and load with `defer`. 5. Re-measure inline payload after extraction to confirm it drops well below the flagged 205 KB, and verify long-cache CDN headers apply to the new assets.
Evidence (1)
  • theme_source 205 KB of inline script/style in reviewed templates
    {
      "inline_bytes": 209424,
      "reviewed_files": 24
    }
Needs improvement FCP poor on 2 template(s) (mobile)
performance · strategy:mobile · priority 281 · ~$2,623–$26,231/mo · confidence 60% · effort medium

Why: First Contentful Paint (FCP) is poor on 2 of 6 audited template(s) on mobile: Product · blp-gazelle-pro-new (3,230 ms), Product · default (3,437 ms). Google's 'good' threshold is 1,800 ms. Poor Core Web Vitals both frustrate users and suppress search ranking.

Fix: Render something sooner: inline critical CSS, remove render-blocking resources, and cut server response time.

🛠️ Detailed solution
1. Focus on the two poor templates: Product · blp-gazelle-pro-new (3,230 ms) and Product · default (3,437 ms), both far above the 1,800 ms 'good' threshold on mobile — critical since 76.4% of buyers are mobile. 2. Inline only critical above-the-fold CSS and defer the rest: the `main-product` sections emit inline `<style>` and load `assets/theme.css` render-blocking; add `media="print" onload="this.media='all'"` style deferral to non-critical stylesheets. 3. Ensure the LCP/hero product image loads eagerly with `fetchpriority="high"` while gallery images below the fold use `loading="lazy"` (the theme already uses `loading: 'lazy'` in `snippets/product-gallery.liquid` — confirm the first image is NOT lazy). 4. Defer non-critical third-party and app scripts (review `sections/apps.liquid` / `snippets/booster-seo.liquid`) so they don't block first paint. 5. Cut server response time by trimming heavy Liquid loops in the product-info snippets. 6. Re-test both templates in PageSpeed Insights (mobile) to confirm FCP moves under 1,800 ms.
🧪 Hypothesis: Faster first paint reduces early mobile bounce
Variant: Inline critical CSS and remove render-blocking resources above the fold
metric mobile_conversion_rate · min sample 8,000 · ~14d
Evidence (1)
  • psi_metric FCP (mobile) — 2 template(s)
    {
      "strategy": "mobile",
      "good_bar": "1,800 ms",
      "by_template": {
        "Product \u00b7 blp-gazelle-pro-new": {
          "display": "3,230 ms",
          "status": "poor",
          "source": "field",
          "value": 3230.0
        },
        "Product \u00b7 default": {
          "display": "3,437 ms",
          "status": "poor",
          "source": "field",
          "value": 3437.0
        }
      }
    }
Critical High-impression queries stuck on page 2
search · priority 257 · ~$4,077–$20,387/mo · confidence 55% · effort medium

Why: 85 quer(y/ies) sit in striking distance (avg position ~11–20) with ≥ 100 impressions — modest on-page improvements could move them to page 1, where the large majority of clicks happen.

Fix: Strengthen the ranking pages for these near-miss queries (on-page copy, internal links, a matching collection/PDP) to push them onto page 1.

🛠️ Detailed solution
1. Map each of the 85 page-2 queries to its best-fit landing page: 'dumbbells' (46,784 impr, pos 13.7) and 'dumbbell' (7,725 impr, pos 11.2) → the dumbbells collection; 'weight bench'/'adjustable weight bench' (pos 12.8/11.6) → `templates/collection.benches.json` or `collection.strength-benches.json`; 'best home gym machine' → a packages/power-rack page. 2. Strengthen on-page copy on those collection templates with the target query in the H1, intro paragraph, and meta title/description — lean on value+convenience ('best spec-per-dollar', 'your whole gym, one order') in the brand voice. 3. Add internal links from high-authority pages (home `templates/index.json`, main nav in `sections/header.liquid`, footer) to each target collection using the exact query as anchor text. 4. Enrich the ranking pages with unique content the query implies (footprint/spec tables, reviews/UGC — the #1 confidence driver at 45.9%) so they earn depth over page-1 rivals. 5. Ensure each target collection has a clean, indexable template and unique title tag (avoid duplicate meta across the collection.* variants). 6. Re-check Search Console positions after 4 weeks and double down on queries that moved to page 1.
Evidence (1)
  • search_console Striking-distance queries (28d)
    {
      "site": "https://www.ritfitsports.com/",
      "count": 85,
      "examples": [
        {
          "query": "dumbbells",
          "impressions": 46784,
          "clicks": 4,
          "ctr": 0.0001,
          "position": 13.7
        },
        {
          "query": "weight bench",
          "impressions": 10315,
          "clicks": 5,
          "ctr": 0.0005,
          "position": 12.8
        },
        {
          "query": "dumbbell",
          "impressions": 7725,
          "clicks": 10,
          "ctr": 0.0013,
          "position": 11.2
        },
        {
          "query": "adjustable weight bench",
          "impressions": 6739,
          "clicks": 15,
          "ctr": 0.0022,
          "position": 11.6
        },
        {
          "query": "best home gym machine",
          "impressions": 6293,
          "clicks": 5,
          "ctr": 0.0008,
          "position": 11.1
        }
      ]
    }
Needs improvement INP needs improvement on 2 template(s) (mobile)
performance · strategy:mobile · priority 255 · ~$1,113–$11,129/mo · confidence 60% · effort medium

Why: Interaction to Next Paint (INP) is needs improvement on 2 of 6 audited template(s) on mobile: Collection · pink-series (287 ms), Product · m2-new (245 ms). Google's 'good' threshold is 200 ms. Poor Core Web Vitals both frustrate users and suppress search ranking.

Fix: Make interactions feel responsive: break up long JavaScript tasks, defer non-critical third-party scripts, and minimize main-thread work — especially around the add-to-cart and variant-selection handlers users tap most.

🛠️ Detailed solution
1. Target the two flagged templates: Collection · pink-series (287 ms INP) and Product · m2-new (245 ms), both above the 200 ms 'good' bar on mobile. 2. On pink-series, the custom sections (`sections/custom-pinkoct-*`, `sections/pink-collection.liquid`) plus vendor libraries (`assets/jquery-3.7.1.min.js`, `masonry.pkgd.min.js`, `imagesloaded.pkgd.min.js`) add main-thread work — defer or `async` these and remove any unused on this template. 3. Break up long tasks in the variant-selection and add-to-cart handlers (`snippets/variant-picker.liquid`, `snippets/buy-buttons.liquid`, `assets/theme.js`) — these are the interactions users tap most on m2-new; use event delegation and avoid synchronous layout thrash. 4. Defer non-critical third-party/app scripts (`sections/apps.liquid`, tracking snippets) until after first interaction. 5. Confirm large inline scripts (see the inline-code finding) are moved to deferred assets so parsing doesn't block interactions. 6. Re-measure INP on both templates via field data / PSI mobile until each is under 200 ms.
🧪 Hypothesis: Lower mobile INP makes interactions feel instant and lifts add-to-cart rate
Variant: Break up long tasks and defer third-party scripts on the PDP
metric add_to_cart_rate · min sample 8,000 · ~14d
Evidence (1)
  • psi_metric INP (mobile) — 2 template(s)
    {
      "strategy": "mobile",
      "good_bar": "200 ms",
      "by_template": {
        "Collection \u00b7 pink-series": {
          "display": "287 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 287.0
        },
        "Product \u00b7 m2-new": {
          "display": "245 ms",
          "status": "needs_improvement",
          "source": "field",
          "value": 245.0
        }
      }
    }
Critical Low-contrast text (WCAG 1.4.3)
accessibility · priority 252 · confidence 60% · effort medium

Why: Text below the WCAG AA contrast ratio is hard to read for low-vision users and in glare. Found 69 text element(s) under the threshold across Home, Product · new-product, Product · blp-gazelle-pro-new, Collection · packages-new-dev, Product · default, Product · m3, +6 more (rendered DOM; judged against solid backgrounds only — text over images is skipped).

Fix: Raise text-to-background contrast to at least 4.5:1 (3:1 for large text ≥24px or ≥18.7px bold).

🛠️ Detailed solution
1. Fix the highest-frequency, lowest-ratio offenders first: `span.region-divider` (1.54:1, the "|" separator on Home, Product·default, m1-buffalo-new, m2-new, Cart) and `div.productBadges.Bestseller` (1.85:1, ×14 on Collection·packages-new-dev) — darken the text/badge color in the relevant CSS (e.g. `assets/theme.css` or the badge snippet `snippets/product-badges.liquid`) until each hits ≥4.5:1. 2. On `templates/collection.pink-series.json` (32 failures) raise `a.button.button--xl` "Explore the Collection" from 3.18:1 and `h2.h2` "GEAR UP WITH OUR PINK SERIES!" from 2.58:1 — for Alicia's Pink Series page use a darker pink or add a dark overlay behind the text so large-heading text clears 3:1 and button text clears 4.5:1; keep the same pink brand feel. 3. Nudge the borderline tab/download controls that just miss 4.5 (`button.tab-button.active` 4.41, `button.tab-button` 4.37, `a.download-btn` 4.41, `span.download-size` 2.85) — bump those foreground colors one shade darker in their component CSS; these recur across new-product, m3, blp-gazelle-pro-new, p9, smith-new, m2-new. 4. Fix `div.custom-homepage-small-title` "PINK SERIES" (3.17:1) in its homepage section CSS. 5. Re-audit each edited template with a contrast checker (Home, the 6 product templates, 3 collection templates, Cart) confirming 69→0 before shipping.
🧪 Hypothesis: Raising text, price, and CTA contrast improves scannability and lifts conversion
Variant: Apply WCAG-AA contrast to body, price, and CTA text
metric conversion_rate · min sample 8,000 · ~14d
Evidence (1)
  • a11y_contrast Low-contrast text — WCAG 1.4.3
    {
      "total": 69,
      "by_template": {
        "Home": 2,
        "Product \u00b7 new-product": 3,
        "Product \u00b7 blp-gazelle-pro-new": 3,
        "Collection \u00b7 packages-new-dev": 14,
        "Product \u00b7 default": 1,
        "Product \u00b7 m3": 3,
        "Product \u00b7 m1-buffalo-new": 2,
        "Collection \u00b7 pink-series": 32,
        "Product \u00b7 m2-new": 2,
        "Collection \u00b7 smith-new": 3,
        "Product \u00b7 p9": 3,
        "Cart": 1
      },
      "examples": [
        {
          "selector": "div.custom-homepage-small-title",
          "ratio": 3.17,
          "need": 4.5,
          "text": "PINK SERIES",
          "template": "Home"
        },
        {
          "selector": "span.region-divider",
          "ratio": 1.54,
          "need": 4.5,
          "text": "|",
          "template": "Home"
        },
        {
          "selector": "a.download-btn",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Download PDF",
          "template": "Product \u00b7 new-product"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Product \u00b7 new-product"
        },
        {
          "selector": "button.tab-button",
          "ratio": 4.37,
          "need": 4.5,
          "text": "Resource",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "div.productBadges.Bestseller",
          "ratio": 1.85,
          "need": 4.5,
          "text": "Bestseller",
          "template": "Collection \u00b7 packages-new-dev"
        },
        {
          "selector": "div.productBadges.Bestseller",
          "ratio": 1.85,
          "need": 4.5,
          "text": "Bestseller",
          "template": "Collection \u00b7 packages-new-dev"
        },
        {
          "selector": "span.region-divider",
          "ratio": 1.54,
          "need": 4.5,
          "text": "|",
          "template": "Product \u00b7 default"
        },
        {
          "selector": "button.tab-button",
          "ratio": 4.37,
          "need": 4.5,
          "text": "Resource",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Product \u00b7 m1-buffalo-new"
        },
        {
          "selector": "span.region-divider",
          "ratio": 1.54,
          "need": 4.5,
          "text": "|",
          "template": "Product \u00b7 m1-buffalo-new"
        },
        {
          "selector": "a.button.button--xl",
          "ratio": 3.18,
          "need": 4.5,
          "text": "Explore the Collection",
          "template": "Collection \u00b7 pink-series"
        },
        {
          "selector": "h2.h2",
          "ratio": 2.58,
          "need": 3,
          "text": "GEAR UP WITH OUR PINK SERIES!",
          "template": "Collection \u00b7 pink-series"
        },
        {
          "selector": "span.download-size",
          "ratio": 2.85,
          "need": 4.5,
          "text": "PDF \u2022 15.04 MB",
          "template": "Product \u00b7 m2-new"
        },
        {
          "selector": "span.region-divider",
          "ratio": 1.54,
          "need": 4.5,
          "text": "|",
          "template": "Product \u00b7 m2-new"
        },
        {
          "selector": "a.download-btn",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Download PDF",
          "template": "Collection \u00b7 smith-new"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Collection \u00b7 smith-new"
        },
        {
          "selector": "a.download-btn",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Download PDF",
          "template": "Product \u00b7 p9"
        },
        {
          "selector": "button.tab-button.active",
          "ratio": 4.41,
          "need": 4.5,
          "text": "Features",
          "template": "Product \u00b7 p9"
        },
        {
          "selector": "span.region-divider",
          "ratio": 1.54,
          "need": 4.5,
          "text": "|",
          "template": "Cart"
        }
      ]
    }
Needs improvement GA4 over-counts orders vs Shopify (111% of actual)
funnel · device:all · priority 225 · confidence 75% · effort low

Why: Over the last 30d Shopify recorded 3884 orders (EXACT) but GA4 counted 4302 purchases — GA4 captures 111% of true order volume, a 11% gap. CVR and revenue-opportunity sizing inherit this error, so reconcile tracking before trusting magnitudes.

Fix: GA4 is over-counting purchases — check for the purchase event double-firing (back/refresh on the thank-you page, duplicate tags) or inflated revenue. Until fixed, treat GA4 CVR as an overstatement.

🛠️ Detailed solution
1. In GA4 (Admin → Data streams → your web stream → Events) confirm whether `purchase` is firing from both Shopify's native GA4 integration AND a second source (e.g. a GTM tag or app pixel) — duplicate tags are the most common cause of the 4302 vs 3884 (111%) gap. 2. Test the thank-you/order-status page: reload and hit back to see if `purchase` re-fires; if so, gate it on `transaction_id` (Shopify `checkout.order_id`) so GA4 dedupes repeat fires. 3. Remove the redundant tag/pixel so only one `purchase` event fires per order, passing the Shopify order name as `transaction_id`. 4. After the fix, compare a fresh 30-day window of GA4 `ecommercePurchases` against Shopify `ordersCount` and confirm it lands near 100%. 5. Until reconciled, annotate dashboards that GA4 CVR/revenue are ~11% overstated so opportunity sizing isn't over-trusted.
Evidence (1)
  • shopify_orders ordersCount vs GA4 ecommercePurchases
    {
      "window": {
        "since": "2026-08-08",
        "days": 30
      },
      "shopify_orders": 3884.0,
      "shopify_precision": "EXACT",
      "ga4_purchases": 4302.0,
      "ga4_minus_shopify": 418.0,
      "ga4_capture_rate": 1.107621009268795,
      "source": "shopify_orders_count"
    }
Needs improvement 19 images without width/height (in reviewed templates) shipped
theme_code · priority 210 · confidence 70% · effort low

Why: Missing dimensions cause layout shift (CLS) as images pop in, a Core Web Vital and a mis-tap source on mobile.

Fix: Add explicit `width`/`height` (or an aspect-ratio wrapper) to <img> tags so the browser reserves space before the image loads.

🛠️ Detailed solution
1. Locate the 19 `<img>` tags lacking width/height in the reviewed templates (start with product/collection sections since those dominated other findings). 2. Where images render via Liquid, switch to `image_tag`, which auto-emits width/height: e.g. `{{ image | image_url: width: 1200 | image_tag: loading: 'lazy', widths: '400,800,1200' }}`. 3. For hardcoded `<img src=...>` in custom sections (e.g. download/feature blocks), add explicit `width` and `height` attributes matching the asset's aspect ratio. 4. For any image whose size is fluid, wrap it in an aspect-ratio container (`style="aspect-ratio: 16/9"`) so space is reserved before load. 5. Re-run Lighthouse/CLS on the affected product and collection templates to confirm no layout shift from image pop-in — critical since 76.4% of buyers are on mobile.
Evidence (1)
  • theme_source 19 images without width/height (in reviewed templates)
    {
      "count": 19,
      "reviewed_files": 24,
      "samples": [
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ block.settings.image1 | img_url: 'large' }}' class='slider-img active' data-img-index='0' >"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ block.settings.image2 | img_url: 'large' }}' class='slider-img{% if block.settings.image1 == blank %} active{% endif %}' data-img-index='1' >"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "<img src='{{ aproduct.featured_image | img_url: 'large' }}' alt='{{ aproduct.title }}' >"
        }
      ]
    }
Needs improvement 19 deprecated | img_url filter(s) shipped
theme_code · priority 210 · confidence 70% · effort low

Why: `img_url` is deprecated and typically ships one fixed size to every device — oversized downloads on mobile, where LCP is worst.

Fix: Migrate `| img_url` to `| image_url: width:` plus `| image_tag` (which emits srcset/width/height automatically) so devices download right-sized images.

🛠️ Detailed solution
1. Grep the theme for the 19 `| img_url` usages (likely in custom sections such as `sections/main-product-features.liquid`, download/feature blocks, and product cards). 2. Replace each `| img_url: '600x'` pattern with `| image_url: width: 600` and render through `| image_tag`, which auto-generates srcset, sizes, and width/height. Before: `<img src="{{ img | img_url: '600x' }}">` After: `{{ img | image_url: width: 600 | image_tag: widths: '300,600,900', loading: 'lazy', sizes: '(max-width:749px) 100vw, 600px' }}`. 3. Set a correct `sizes` attribute per placement so mobile downloads a small file (LCP is worst on mobile, and 76.4% of buyers are mobile). 4. Verify each converted image still renders at correct dimensions in the theme editor. 5. Confirm mobile payload dropped via DevTools Network on the affected templates.
Evidence (1)
  • theme_source 19 deprecated | img_url filter(s)
    {
      "total": 19,
      "by_file": {
        "sections/card-carousel.liquid": 7,
        "sections/product-card-carousel.liquid": 5,
        "sections/shop-by-single-category.liquid": 2,
        "sections/sticky-footer.liquid": 3,
        "snippets/booster-seo.liquid": 2
      },
      "samples": [
        {
          "file": "sections/card-carousel.liquid",
          "code": "{{ block.settings.image1 | img_url: 'large' }}"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "{{ block.settings.image2 | img_url: 'large' }}"
        },
        {
          "file": "sections/card-carousel.liquid",
          "code": "{{ aproduct.featured_image | img_url: 'large' }}"
        }
      ]
    }
Needs improvement Video autoplays on page load (UX guideline #96 (Sustainability / Auto-Play Video))
design · priority 210 · confidence 70% · effort low

Why: Video consumes massive data and energy. Found 11 autoplaying video(s) across Product · m3, Product · m2-new — heavy on data and battery, and distracting. Avoid: auto-play high-res video loops.

Fix: Click-to-play or pause when off-screen (e.g. playsInline muted preload='none').

🛠️ Detailed solution
1. Find the 11 autoplaying videos on `templates/product.m3.json` (6) and `templates/product.m2-new.json` (5) — likely in `sections/video-product-feature.liquid`, `sections/video-responsive.liquid`, or the athlete-feature section (`reus__eyebrow` appears on m3). 2. Remove `autoplay loop` from those `<video>`/embeds and switch to click-to-play with a poster image, or set `muted playsinline preload="none"` so nothing streams until tapped. 3. For any hero-style loop you want to keep moving, add an IntersectionObserver to pause when the video scrolls off-screen. 4. Ensure a visible play button/poster so buyers (mobile-first) control playback and save data/battery. 5. Reload m3 and m2-new to confirm zero videos auto-start on load.
Evidence (1)
  • ux_guideline UX guideline #96 (Sustainability / Auto-Play Video)
    {
      "by_template": {
        "Product \u00b7 m3": 6,
        "Product \u00b7 m2-new": 5
      },
      "total": 11,
      "unmuted": 0,
      "rule": {
        "no": 96,
        "category": "Sustainability",
        "issue": "Auto-Play Video",
        "do": "Click-to-play or pause when off-screen",
        "dont": "Auto-play high-res video loops",
        "good_example": "playsInline muted preload='none'",
        "bad_example": "autoplay loop"
      }
    }
Needs improvement Body text too small to read on mobile (UX guideline #67 (Responsive / Readable Font Size))
design · device:mobile · priority 180 · confidence 60% · effort low

Why: Text must be readable on all devices. Found 14 text element(s) under 12px across Product · new-product, Product · blp-gazelle-pro-new, Product · m3, Product · m1-buffalo-new, Collection · smith-new, Product · p9 at a mobile viewport (the rulebook floor is 16px body text). Avoid: tiny text on mobile.

Fix: Minimum 16px body text on mobile (e.g. text-base or larger).

🛠️ Detailed solution
1. Raise the recurring `span.badge.badge--primary` from 9px to at least 12–14px (it fails at 9px on new-product, blp-gazelle-pro-new ×2, m3, m1-buffalo-new for "Space Saver", "Patented", "Pre-order", "Bestseller") — edit the badge font-size in `snippets/product-badges.liquid` / `assets/theme.css` at the mobile breakpoint. 2. Bump `span.reus__eyebrow` "At home by Marco Reus" from 11px in the athlete-feature section CSS. 3. For genuine body copy (not compact labels), target the rulebook's 16px floor on mobile so the 14 flagged elements on m3 (5), p9 (3), blp-gazelle-pro-new (2), m1-buffalo-new (2), new-product (1), smith-new (1) are legible. 4. Since these are strength specs Marcus scrutinizes (e.g. "Space Saver" footprint cue), keep them readable — legible specs sell. 5. Re-test each template at a 375px viewport confirming no text under the floor.
Evidence (1)
  • ux_guideline UX guideline #67 (Responsive / Readable Font Size)
    {
      "total": 14,
      "by_template": {
        "Product \u00b7 new-product": 1,
        "Product \u00b7 blp-gazelle-pro-new": 2,
        "Product \u00b7 m3": 5,
        "Product \u00b7 m1-buffalo-new": 2,
        "Collection \u00b7 smith-new": 1,
        "Product \u00b7 p9": 3
      },
      "examples": [
        {
          "selector": "span.badge.badge--primary",
          "px": 9,
          "text": "Space Saver",
          "template": "Product \u00b7 new-product"
        },
        {
          "selector": "span.badge.badge--primary",
          "px": 9,
          "text": "Patented",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "span.badge.badge--primary",
          "px": 9,
          "text": "Patented",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "span.badge.badge--primary",
          "px": 9,
          "text": "Pre-order",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "span.reus__eyebrow",
          "px": 11,
          "text": "At home by Marco Reus",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "span.badge.badge--primary",
          "px": 9,
          "text": "Bestseller",
          "template": "Product \u00b7 m1-buffalo-new"
        }
      ],
      "rule": {
        "no": 67,
        "category": "Responsive",
        "issue": "Readable Font Size",
        "do": "Minimum 16px body text on mobile",
        "dont": "Tiny text on mobile",
        "good_example": "text-base or larger",
        "bad_example": "text-xs for body text"
      }
    }
Needs improvement Anchor links jump instead of scrolling (UX guideline #1 (Navigation / Smooth Scroll))
design · priority 165 · confidence 55% · effort low

Why: Anchor links should scroll smoothly to target section. Product · new-product, Product · blp-gazelle-pro-new, Product · default, Product · m3, Product · m1-buffalo-new, Collection · pink-series, +3 more use in-page anchor links without `scroll-behavior: smooth` — one CSS line on `html`. Avoid: jump directly without transition.

Fix: Use scroll-behavior: smooth on html element (e.g. html { scroll-behavior: smooth; }).

🛠️ Detailed solution
1. Add a single global rule to the theme's base stylesheet (`assets/theme.css`): `html { scroll-behavior: smooth; }`. 2. This covers all 9 affected templates at once (new-product, blp-gazelle-pro-new, default, m3, m1-buffalo-new, pink-series, m2-new, smith-new, p9), each of which uses in-page anchor links (e.g. Features/Resource spec tabs). 3. Guard motion-sensitive users: wrap in `@media (prefers-reduced-motion: no-preference) { html { scroll-behavior: smooth; } }`. 4. Verify tapping a spec/tab anchor on a product template scrolls smoothly rather than jumping. 5. Confirm no conflicting JS scroll handler overrides it on those templates.
Evidence (1)
  • ux_guideline UX guideline #1 (Navigation / Smooth Scroll)
    {
      "by_template": {
        "Product \u00b7 new-product": 1,
        "Product \u00b7 blp-gazelle-pro-new": 1,
        "Product \u00b7 default": 1,
        "Product \u00b7 m3": 1,
        "Product \u00b7 m1-buffalo-new": 1,
        "Collection \u00b7 pink-series": 1,
        "Product \u00b7 m2-new": 1,
        "Collection \u00b7 smith-new": 1,
        "Product \u00b7 p9": 1
      },
      "rule": {
        "no": 1,
        "category": "Navigation",
        "issue": "Smooth Scroll",
        "do": "Use scroll-behavior: smooth on html element",
        "dont": "Jump directly without transition",
        "good_example": "html { scroll-behavior: smooth; }",
        "bad_example": "<a href='#section'> without CSS"
      }
    }
Needs improvement Headlines don't promise the buyer anything
copy · priority 165 · confidence 55% · effort low

Why: The homepage H1 is just the brand name and the H2 offers no benefit. Five times as many people read the headline as the body, and this one carries no promise, no audience, and no news. The brand line 'Your whole gym. One smart buy.' already exists but isn't used where it matters. For a mobile-first store (76.4% of buyers) the first line above the fold has to answer what this is and who it's for. Affects 1 surface(s): home. e.g. "RitFit" (home). (Judged against the Ogilvy rubric; quotes verified verbatim.)

Fix: Rework the flagged headlines to carry one specific benefit (6–12 plain words); informative beats clever.

🛠️ Detailed solution
1. On the homepage template (`templates/index.json`) locate the hero section holding the H1 "RitFit" and H2 "Find Your Perfect Fit" (likely `sections/slideshow.liquid`, `sections/hero-fitness-athletes.liquid`, or an image-with-text-overlay section). 2. Replace the bare-brand H1 with a promise-carrying headline that names the buyer and benefit for mobile-first shoppers — lead with the existing brand line and value+convenience: e.g. H1 "Your whole gym. One smart buy." 3. Rewrite the H2 into a specific sub-promise for Marcus the Garage Builder: e.g. "Commercial-gym strength for your garage — at 25–40% less than buying piece by piece." 4. Keep it 6–12 plain, spec-backed words in the brand voice (numbers over adjectives; no cheapest-price or premium-prestige language). 5. Save and preview above the fold on a 375px viewport to confirm the promise and audience are visible without scrolling.
Evidence (1)
  • copy_judge copy rubric — no-promise-headline
    {
      "examples": [
        {
          "surface": "home",
          "quote": "RitFit"
        },
        {
          "surface": "home",
          "quote": "Find Your Perfect Fit"
        }
      ],
      "rewrite": {
        "surface": "home",
        "before": "RitFit",
        "after": "Your whole gym. One smart buy. Commercial-gym strength for your garage, at 25\u201340% less than buying piece by piece."
      },
      "count": 2,
      "surfaces": [
        "home"
      ]
    }
Needs improvement Copy leans on empty superlatives
copy · priority 165 · confidence 55% · effort low

Why: The M2 pages lean on marketer filler — 'next level', 'sleek', 'ultra-smooth', 'premium' — instead of numbers. The rubric and this brand's voice call for specifics over adjectives (1,500 lb rating beats 'heavy-duty'), and 'take your workouts to the next level' is exactly the elevate/unleash filler to cut. Affects 2 surface(s): product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels, product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system. e.g. "Take your home workouts to the next level with the new RitFit M2 Smith Machine, our most v" (product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels). (Judged against the Ogilvy rubric; quotes verified verbatim.)

Fix: Replace each 'premium/amazing/high-quality' with a number, material, or concrete detail that proves the claim.

🛠️ Detailed solution
1. Open the M2 PDP body (edit via Shopify admin → Products → RitFit M2 → Description, which renders in `sections/main-product.liquid`/`product-info`) and rewrite the opener: replace "Take your home workouts to the next level with the new RitFit M2 Smith Machine, our most versatile, space-efficient strength system yet." with a spec-led line for Marcus — e.g. "The RitFit M2 Smith Machine does what a commercial rack does in a [X]\" × [Y]\" footprint — rated to [ ] lb, with [ ] built-in stations." Pull the real footprint, weight rating, and station count from the product's own spec sheet. 2. Replace "combines power, safety, and functionality in one sleek, compact unit" with the concrete safety + capacity numbers: name the safety-catch/spotter arm rating in lb and the exact overall dimensions (footprint + height for the #1 space-fit objection). 3. On the P9 PDP, replace "a cleaner, stronger, and more premium alternative to traditional concrete-filled stacks" with the measured advantage — e.g. the actual weight-stack capacity in lb and the cable pulley ratio — stating why the material choice performs better, not that it's "premium." 4. Sweep both descriptions for "next level / sleek / ultra-smooth / premium" and swap each for a number, material, or dimension (bar travel, steel gauge, warranty term). 5. Reinforce with the 3-year frame warranty and per-month framing ("from $X/mo with Affirm") near the top, keeping the plain, spec-backed garage-gym-friend voice.
Evidence (1)
  • copy_judge copy rubric — vague-superlatives
    {
      "examples": [
        {
          "surface": "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels",
          "quote": "Take your home workouts to the next level with the new RitFit M2 Smith Machine, our most versatile, space\u2011efficient strength system yet."
        },
        {
          "surface": "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels",
          "quote": "the M2 combines power, safety, and functionality in one sleek, compact unit"
        },
        {
          "surface": "product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system",
          "quote": "a cleaner, stronger, and more premium alternative to traditional concrete-filled stacks"
        }
      ],
      "rewrite": {
        "surface": "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels",
        "before": "Take your home workouts to the next level with the new RitFit M2 Smith Machine, our most versatile, space\u2011efficient strength system yet.",
        "after": "A power cage, cable crossover, pull-up station and Smith machine in one frame \u2014 four stations in the floor space most trainers give one, with 16 height settings. Insert the M2 footprint in [sq ft] and ceiling clearance in [inches]."
      },
      "count": 3,
      "surfaces": [
        "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels",
        "product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system"
      ]
    }
Needs improvement Multiple animations never stop (UX guideline #12 (Animation / Continuous Animation))
design · priority 150 · confidence 50% · effort low

Why: Infinite animations are distracting. Found Product · m1-buffalo-new running 15+ infinite animation(s) at once (marquees/carousels compete for attention and CPU). Avoid: use for decorative elements.

Fix: Use for loading indicators only (e.g. animate-spin on loader).

🛠️ Detailed solution
1. Load the Product · m1-buffalo-new template (`templates/product.m1-buffalo-new.json`) in the theme editor and list its sections — the 15 infinite animations come from stacked marquee/carousel sections (e.g. `sections/marquee.liquid`, `sections/scrolling-text.liquid`, auto-advancing `card-carousel`/`product-scroll` blocks). 2. For each marquee/scrolling-text section, open its settings and disable auto-scroll/loop where the section offers a toggle; keep at most one moving marquee on the page. 3. For carousels that auto-advance, turn off autoplay so they only move on user swipe/click (the theme's `scroll-carousel` already supports the prev/next buttons). 4. For any purely decorative CSS loop, gate it behind reduced-motion by wrapping the animation in `@media (prefers-reduced-motion: no-preference)` in the section's `<style>` block so it never runs for motion-sensitive users. 5. Reload the PDP and confirm only intentional motion remains (ideally none continuous), preserving the static real garage/basement imagery that converts for Marcus.
Evidence (1)
  • ux_guideline UX guideline #12 (Animation / Continuous Animation)
    {
      "by_template": {
        "Product \u00b7 m1-buffalo-new": 15
      },
      "total": 15,
      "rule": {
        "no": 12,
        "category": "Animation",
        "issue": "Continuous Animation",
        "do": "Use for loading indicators only",
        "dont": "Use for decorative elements",
        "good_example": "animate-spin on loader",
        "bad_example": "animate-bounce on icons"
      }
    }
Needs improvement 2 oversized JS/CSS asset(s) (largest 237 KB)
theme_code · priority 147 · confidence 70% · effort medium

Why: Assets over 244 KB (JS) / 146 KB (CSS) dominate download and parse time on mid-range phones.

Fix: Minify and split the flagged bundles, drop dead code (audit with coverage in DevTools), and lazy-load feature JS (reviews, sliders) only on templates that use it.

🛠️ Detailed solution
1. In Shopify admin → Online Store → Themes → Edit code → Assets, sort by size and locate the two flagged bundles (the 237 KB file plus the next largest) — likely `assets/theme.js`/`theme.sbold.js` and `assets/vendor.min.js` or `assets/theme.css`. 2. Run Chrome DevTools → Coverage on a PDP and the Home page to see unused bytes in each bundle, and record the dead ranges. 3. Split feature JS (reviews widget, PhotoSwipe gallery via `assets/photoswipe.min.js`, Masonry, Covet-pics) out of the global bundle and load it only on templates that use it — e.g. gate the gallery/lightbox script with `{% if template contains 'product' %}` in `layout/theme.liquid`. 4. Ensure both flagged files are minified (`.min` variants) and served with `defer`; remove any `.liquid`-suffixed duplicate assets that are no longer referenced. 5. Re-test on a throttled mid-range mobile profile (76.4% of buyers are mobile) and confirm each bundle is under the 244 KB JS / 146 KB CSS threshold.
Evidence (1)
  • theme_source 2 oversized JS/CSS asset(s) (largest 237 KB)
    {
      "assets": [
        {
          "file": "assets/theme.sbold.css",
          "bytes": 242414
        },
        {
          "file": "assets/theme.css",
          "bytes": 195687
        }
      ]
    }
Needs improvement jQuery is shipped with the theme
theme_code · priority 147 · confidence 70% · effort medium

Why: jQuery adds ~30 KB compressed plus parse cost on every page for APIs the platform now provides natively.

Fix: Migrate the remaining usages to vanilla JS (querySelector/fetch/classList cover nearly all theme needs) and drop the library.

🛠️ Detailed solution
1. Search the codebase (Edit code → search) for `jquery-3.7.1.min.js` and `$(`/`jQuery(` to inventory every file that depends on it — check `layout/theme.liquid`, `snippets/vendor.liquid`, and app snippets. 2. Rewrite each usage in vanilla JS: `$('.x')`→`document.querySelectorAll('.x')`, `.on('click')`→`addEventListener('click')`, `$.ajax`→`fetch`, `.addClass/removeClass`→`classList`, `.attr`→`setAttribute`. 3. Confirm no third-party/app snippet still requires jQuery (e.g. `assets/imagesloaded.pkgd.min.js`, `masonry.pkgd.min.js` can run standalone); refactor or replace any that do. 4. Remove the `<script src="{{ 'jquery-3.7.1.min.js' | asset_url }}">` tag from `layout/theme.liquid` and delete the asset. 5. Regression-test cart drawer, gallery, carousels, and any custom sections on mobile before publishing.
Evidence (1)
  • theme_source jQuery is shipped with the theme
    {
      "assets": [
        "assets/jquery-3.7.1.min.js"
      ]
    }
Needs improvement Product structured data missing or incomplete
design · priority 147 · confidence 70% · effort medium

Why: 6 of 7 product template(s) checked lack a rich-results-eligible Product schema: Product · new-product — Product schema missing offers.price/availability; Product · blp-gazelle-pro-new — Product schema missing offers.price/availability; Product · default — Product schema missing offers.price/availability; Product · m3 — no Product JSON-LD. Missing structured data forfeits price/stock/star snippets in search.

Fix: Emit a schema.org Product JSON-LD block on every PDP with offers (price, priceCurrency, availability) and aggregateRating when reviews exist — it's what makes Google show price/stock/stars in the result.

🛠️ Detailed solution
1. Confirm the theme already has `snippets/microdata-schema.liquid`; open it and verify it emits schema.org Product JSON-LD with `offers` (price, priceCurrency, availability). 2. In `sections/main-product.liquid`, add the render near the closing of the product wrapper so it fires on every PDP template that uses this section: `{%- render 'microdata-schema', product: product -%}`. 3. Ensure the offers block outputs live values, e.g. `"price": "{{ product.selected_or_first_available_variant.price | divided_by: 100.0 }}", "priceCurrency": "{{ cart.currency.iso_code }}", "availability": "https://schema.org/{% if product.available %}InStock{% else %}OutOfStock{% endif %}"`. 4. Add `aggregateRating` conditionally when reviews exist (the theme already reads `product.metafields.spr.reviews`), outputting ratingValue and reviewCount so stars can appear. 5. Because the flagged templates (`product.new-product`, `product.blp-gazelle-pro-new`, `product.default`/`product.json`, `product.m3`, `product.m1-buffalo-new`, `product.m2-new`) use different `main-product*` sections, replicate the same render in `sections/main-product2.liquid` and `sections/main-product-features.liquid`, then validate each URL in Google's Rich Results Test.
Evidence (1)
  • structured_data Product JSON-LD (rich results)
    {
      "by_template": {
        "Product \u00b7 new-product": 1,
        "Product \u00b7 blp-gazelle-pro-new": 1,
        "Product \u00b7 default": 1,
        "Product \u00b7 m3": 1,
        "Product \u00b7 m1-buffalo-new": 1,
        "Product \u00b7 m2-new": 1
      },
      "gaps": {
        "Product \u00b7 new-product": "Product schema missing offers.price/availability",
        "Product \u00b7 blp-gazelle-pro-new": "Product schema missing offers.price/availability",
        "Product \u00b7 default": "Product schema missing offers.price/availability",
        "Product \u00b7 m3": "no Product JSON-LD",
        "Product \u00b7 m1-buffalo-new": "Product schema missing offers.price/availability",
        "Product \u00b7 m2-new": "Product schema missing offers.price/availability"
      },
      "count": 7
    }
Needs improvement Tap targets below the minimum size (WCAG 2.5.8)
accessibility · priority 136 · confidence 65% · effort medium

Why: Small targets are hard to hit accurately on touch screens and for users with motor impairments. Found 373 of 1199 interactive target(s) under 24×24px across Home, Product · new-product, Product · blp-gazelle-pro-new, Collection · packages-new-dev, Product · default, Product · m3, +6 more (31%, rendered DOM).

Fix: Give interactive controls at least a 24×24 CSS-px hit area (48×48 is the comfortable mobile size); pad icon-only buttons.

🛠️ Detailed solution
1. The bulk of the 373 hits are the carousel `button.tap-area` Previous/Next controls rendering at 7×24px (seen across Home, all PDPs, collections, and Cart) — these use `circle-chevron`/`tap-area` classes from snippets like `sections/cart-drawer.liquid`. In `assets/theme.css` add a global rule: `.tap-area, .circle-chevron { min-width: 24px; min-height: 24px; }` (aim for 48×48 on the mobile breakpoint since 76.4% of buyers are mobile). 2. For the prev/next carousel buttons specifically, give them explicit padding so the SVG chevron has a full hit area: `button[is='prev-button'], button[is='next-button'] { padding: 12px; }`. 3. Audit icon-only header controls in `sections/header.liquid` (`.tap-area` hamburger/search) and confirm they also meet 24px minimum. 4. Re-measure the flagged selectors on each template in DevTools device mode to confirm ≥24×24 CSS px. 5. Keep visual chevron size unchanged — only expand the clickable padding — so the design is unaffected.
🧪 Hypothesis: Larger mobile tap targets cut mis-taps and raise mobile add-to-cart rate
Variant: Enlarge primary controls (ATC, variant pickers, qty) to >=44px on mobile
metric add_to_cart_rate · min sample 8,000 · ~14d
Evidence (1)
  • a11y_tap_target Small tap targets — WCAG 2.5.8
    {
      "total": 373,
      "targets_total": 1199,
      "by_template": {
        "Home": 32,
        "Product \u00b7 new-product": 29,
        "Product \u00b7 blp-gazelle-pro-new": 30,
        "Collection \u00b7 packages-new-dev": 47,
        "Product \u00b7 default": 28,
        "Product \u00b7 m3": 32,
        "Product \u00b7 m1-buffalo-new": 30,
        "Collection \u00b7 pink-series": 28,
        "Product \u00b7 m2-new": 28,
        "Collection \u00b7 smith-new": 29,
        "Product \u00b7 p9": 31,
        "Cart": 29
      },
      "examples": [
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Home"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Home"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 new-product"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 new-product"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 blp-gazelle-pro-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Collection \u00b7 packages-new-dev"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Collection \u00b7 packages-new-dev"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 default"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 default"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 m3"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 m1-buffalo-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 m1-buffalo-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Collection \u00b7 pink-series"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Collection \u00b7 pink-series"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 m2-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 m2-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Collection \u00b7 smith-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Collection \u00b7 smith-new"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Product \u00b7 p9"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Product \u00b7 p9"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Previous",
          "template": "Cart"
        },
        {
          "selector": "button.tap-area",
          "w": 7,
          "h": 24,
          "text": "Next",
          "template": "Cart"
        }
      ]
    }
Needs improvement Form fields without labels (WCAG 1.3.1 / 4.1.2)
accessibility · priority 126 · confidence 60% · effort medium

Why: Unlabeled fields are unusable with assistive tech and depress form completion. Found 2 occurrence(s) across Home, Collection · packages-new-dev (rendered DOM).

Fix: Associate every input/select/textarea with a <label for> (or an aria-label).

🛠️ Detailed solution
1. Identify the 2 unlabeled fields — one on Home and one on Collection · packages-new-dev (`templates/collection.packages-new-dev.json`); likely a newsletter/email input and a filter or quantity control. 2. For the Home field, open the relevant newsletter section (e.g. `sections/newsletter.liquid`/`newletter-new.liquid`) and add an associated `<label for="...">` or `aria-label` to the input using the theme's `snippets/input.liquid` (which supports a `label` param) — pass a `label` value rather than leaving it blank. 3. For the collection field, locate the input in the collection template's section (facets or a quantity/select control) and add `aria-label="{{ '...' | t }}"` describing its purpose. 4. Prefer a visible `<label>` where space allows for the beginner Fresh Starter persona; use `aria-label` only for icon/compact controls. 5. Re-run an accessibility scan on both templates to confirm zero unlabeled fields remain.
Evidence (1)
  • a11y_rule Form fields without labels — WCAG 1.3.1 / 4.1.2
    {
      "total": 2,
      "by_template": {
        "Home": 1,
        "Collection \u00b7 packages-new-dev": 1
      }
    }
Needs improvement Duplicate element IDs (WCAG 4.1.1)
accessibility · priority 126 · confidence 60% · effort medium

Why: Duplicate ids cause label/ARIA references to resolve to the wrong element. Found 127 occurrence(s) across Home, Product · new-product, Product · blp-gazelle-pro-new, Collection · packages-new-dev, Product · default, Product · m3, +6 more (rendered DOM).

Fix: Make IDs unique — duplicates break label associations and scripts.

🛠️ Detailed solution
1. The 127 duplicate IDs cluster where product sections/snippets are rendered more than once per page — note `sections/main-product.liquid` hardcodes `id="quick-buy-content"` and `id="shopify-product-reviews"`, and `main-product2.liquid`/`main-product-features.liquid` repeat the same literals, so any page using two of these collides. 2. Suffix these with the section id: change `<template id="quick-buy-content">` to `<template id="quick-buy-content-{{ section.id }}">` and `<div id="shopify-product-reviews">` to `id="shopify-product-reviews-{{ section.id }}"`, updating the JS selectors that read them. 3. On Collection · packages-new-dev (37 dupes), audit repeated product cards/facets — ensure IDs generated in `snippets/product-card*.liquid` and facet snippets append the product/loop index (e.g. `{{ id }}-{{ forloop.index }}`). 4. For carousels rendered multiple times, confirm each `scroll-carousel`/accordion instance derives its id from `section.id` or a unique `block.id` rather than a static string. 5. Reload each flagged template and run `document.querySelectorAll('[id]')` dedupe check in the console to confirm all IDs are now unique so label/ARIA references resolve correctly.
Evidence (1)
  • a11y_rule Duplicate element IDs — WCAG 4.1.1
    {
      "total": 127,
      "by_template": {
        "Home": 4,
        "Product \u00b7 new-product": 8,
        "Product \u00b7 blp-gazelle-pro-new": 8,
        "Collection \u00b7 packages-new-dev": 37,
        "Product \u00b7 default": 8,
        "Product \u00b7 m3": 8,
        "Product \u00b7 m1-buffalo-new": 14,
        "Collection \u00b7 pink-series": 8,
        "Product \u00b7 m2-new": 9,
        "Collection \u00b7 smith-new": 8,
        "Product \u00b7 p9": 9,
        "Cart": 6
      }
    }
Needs improvement Brand voice shifts between surfaces
copy · priority 115 · confidence 55% · effort medium

Why: The meta description positions RitFit as cheap ('shoestring budget'), which directly contradicts the store positioning: NOT the cheapest steel, always value-plus-proof, never cheapest-price language. 'Best value for the price' is the #1 purchase reason (35.4%) and should be paired with all-in-one convenience, not framed as bargain-basement. Affects 1 surface(s): home. e.g. "RitFit provides affordable and sturdy fitness equipment, helping everyone to create the id" (home). (Judged against the Ogilvy rubric; quotes verified verbatim.)

Fix: Pick one register (who we are, how we talk) and align the flagged surfaces to it.

🛠️ Detailed solution
1. In Shopify admin go to Online Store → Preferences (or Search & Discovery / the home page's SEO fields) and locate the homepage meta description reading "RitFit provides affordable and sturdy fitness equipment, helping everyone to create the ideal home gym design on a shoestring budget." 2. Rewrite it in the value-plus-proof register, e.g. "RitFit builds all-in-one strength equipment with the best spec-per-dollar in the category — commercial-gym capability for your garage, proven by thousands of real customer gyms. Your whole gym. One smart buy." — never use 'shoestring budget', 'affordable', or 'cheapest'. 3. Replace the second flagged phrase 'leading manufacturer of home gym fitness equipment' with a value+convenience line that pairs the #1 (best value, 35.4%) and #2 (all-in-one, 29.4%) purchase reasons, e.g. 'the all-in-one strength brand — one order, your whole gym'. 4. Audit any homepage hero/rich-text sections in templates/index.json for the same 'affordable/budget' language and align them to the same register. 5. Confirm the new meta description stays ≤155 characters and reads confidently and spec-backed, then save and re-crawl via Google Search Console.
Evidence (1)
  • copy_judge copy rubric — inconsistent-voice
    {
      "examples": [
        {
          "surface": "home",
          "quote": "RitFit provides affordable and sturdy fitness equipment, helping everyone to create the ideal home gym design on a shoestring budget."
        },
        {
          "surface": "home",
          "quote": "leading manufacturer of home gym fitness equipment"
        }
      ],
      "rewrite": {
        "surface": "home",
        "before": "RitFit provides affordable and sturdy fitness equipment, helping everyone to create the ideal home gym design on a shoestring budget.",
        "after": "RitFit builds all-in-one strength equipment with the best spec-per-dollar in the category \u2014 a complete home gym in one order, for 25\u201340% less than buying the same stations piecemeal. Insert real proof: [number] customer gyms, [satisfaction %] satisfaction."
      },
      "count": 2,
      "surfaces": [
        "home"
      ]
    }
Needs improvement Product copy lists features instead of benefits
copy · priority 115 · confidence 55% · effort medium

Why: Product pages open with 'Features', 'Feature 1', 'What's Included' and spec dumps before any benefit lead. The rubric wants benefit first, then the fact that proves it. Marcus (research-heavy, ~50% of revenue) will read the specs, but the lead should tell him what he gets before how it's built. Affects 2 surface(s): product:ritfit-m3-pro-home-gym-smith-machine-with-cable-crossover-package, product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system. e.g. "Features Feature 1: Uniform 3"×3" Frame, 11-Gauge Front-to-Back" (product:ritfit-m3-pro-home-gym-smith-machine-with-cable-crossover-package). (Judged against the Ogilvy rubric; quotes verified verbatim.)

Fix: Rewrite the opening of each flagged description to lead with the specific benefit the buyer gets, then support it with the feature as proof.

🛠️ Detailed solution
1. In admin open Products → RitFit M3 Pro Home Gym Smith Machine w/ Cable Crossover and edit the description; the body currently opens with 'Features Feature 1: Uniform 3"×3" Frame, 11-Gauge Front-to-Back'. 2. Add a benefit-first lead sentence before the spec block, e.g. 'Everything a commercial gym does — Smith, cable crossover, and rack — in one garage footprint, then keep the specs as the proof:' followed by the existing 3"×3" / 11-gauge frame line reframed as 'built on a uniform 3"×3", 11-gauge frame so it won't flex under heavy loads.' 3. Repeat for the RitFit P9 Modular Power Cage product: replace the bare 'Features: Train Beyond Traditional Smith Machines' heading with a benefit lead for Marcus, e.g. 'Train past what a fixed Smith allows — the 3D Smith and adjustable dual-cable system let you press, row, and crossover from any angle,' then list the specs beneath as supporting facts. 4. Keep the full spec list intact below the lead (Marcus reads specs) — only reorder so each section states the benefit, then the number that proves it (e.g. '1,500 lb rating' not 'heavy-duty'). 5. Save and preview on mobile (76.4% of buyers) to confirm the benefit line appears above the fold before the spec dump.
Evidence (1)
  • copy_judge copy rubric — feature-led
    {
      "examples": [
        {
          "surface": "product:ritfit-m3-pro-home-gym-smith-machine-with-cable-crossover-package",
          "quote": "Features Feature 1: Uniform 3\"\u00d73\" Frame, 11-Gauge Front-to-Back"
        },
        {
          "surface": "product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system",
          "quote": "Features: Train Beyond Traditional Smith Machines"
        }
      ],
      "rewrite": {
        "surface": "product:ritfit-m3-pro-home-gym-smith-machine-with-cable-crossover-package",
        "before": "Features Feature 1: Uniform 3\"\u00d73\" Frame, 11-Gauge Front-to-Back",
        "after": "Three strength stations that stay planted under heavy squats, presses and deadlifts. The proof: uniform 75\u00d775\u00d73 mm A36 steel uprights front-to-back (many racks step down to 2\"\u00d72\" on the middle and rear posts), 1,093 lb of total steel mass, and safety arms rated 650 lb."
      },
      "count": 2,
      "surfaces": [
        "product:ritfit-m3-pro-home-gym-smith-machine-with-cable-crossover-package",
        "product:ritfit-p9-multi-functional-modular-power-cage-with-3d-smith-machine-and-adjustable-dual-cable-system"
      ]
    }
Needs improvement Claims are made without proof
copy · priority 115 · confidence 55% · effort medium

Why: Not one page shows a rating, review, warranty, or financing option. Reviews/UGC are the #1 confidence driver (45.9%), the 3-year frame warranty is a proven differentiator, and financing is the #1 missing info at exit (22.9%) — critical for Danielle on big-ticket AOVs. Claims like '1500 lbs' and 'commercial-grade' read as puffery without support, and per-month framing is absent from every high-AOV product. Affects 2 surface(s): product:ritfit-m1-pro-smith-machine-weight-stack-home-gym-package, product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels. e.g. "With a high weight capacity of up to 1500 lbs and 11 adjustable height settings" (product:ritfit-m1-pro-smith-machine-weight-stack-home-gym-package). (Judged against the Ogilvy rubric; quotes verified verbatim.)

Fix: Back the flagged claims with ratings, testimonials, guarantees, or demo media near the claim.

🛠️ Detailed solution
1. On the RitFit M1 Pro and M2 product pages, surface the existing review data: main-product.liquid already renders `{{ product.metafields.spr.reviews }}` and a rating block is available in the Product page schema — add the 'Rating' block via Theme editor → Product template so a star rating shows next to the price supporting claims like 'up to 1500 lbs' (reviews/UGC are the #1 confidence driver, 45.9%). 2. Add the 'Payment installments' block (already defined in main-product.liquid schema, requires Shop Pay Installments) directly under the price so a 'from $X/mo' appears above the fold — financing is the #1 missing info at exit (22.9%) and both are high-AOV packages critical to Danielle. 3. Place the 3-year frame warranty next to the durability claim: add a short trust line in the product description or a text block (e.g. via sections/text-with-icons.liquid) reading '3-year frame warranty' beside 'commercial-grade uprights' so the claim is backed, not puffery. 4. Add real garage/basement customer photos or a testimonial near the '1500 lbs' and 'commercial-grade' claims using a UGC section (sections/covet-pics.liquid or testimonials_v2.liquid) — real setups outperform studio shots. 5. Save and QA on mobile to confirm rating, per-month price, and warranty all render above the fold on both pages.
Evidence (1)
  • copy_judge copy rubric — no-proof
    {
      "examples": [
        {
          "surface": "product:ritfit-m1-pro-smith-machine-weight-stack-home-gym-package",
          "quote": "With a high weight capacity of up to 1500 lbs and 11 adjustable height settings"
        },
        {
          "surface": "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels",
          "quote": "Engineered with commercial-grade uprights and an upgraded carriage and pulley system"
        }
      ],
      "rewrite": {
        "surface": "product:ritfit-m1-pro-smith-machine-weight-stack-home-gym-package",
        "before": "With a high weight capacity of up to 1500 lbs and 11 adjustable height settings",
        "after": "Load it up: the Smith system is rated to 1,500 lbs with 11 height settings, backed by our [3-year frame warranty] and [rating/\u2605 from N reviews]. From [$X/mo] with Affirm, or pay with HSA/FSA. Insert the real warranty term, review count and financing figure."
      },
      "count": 2,
      "surfaces": [
        "product:ritfit-m1-pro-smith-machine-weight-stack-home-gym-package",
        "product:ritfit-m2-multi-functional-modular-home-gym-smith-machine-tailored-to-all-fitness-levels"
      ]
    }
Needs improvement Theme ships 1,593 KB of JS/CSS
theme_code · priority 84 · confidence 70% · effort high

Why: Total JS/CSS weight is the ceiling on how fast any page can get — every page pays for it regardless of what it actually uses.

Fix: Set a weight budget (≤1.5 MB JS+CSS), remove unused app leftovers from assets/, and split per-template so each page loads only what it uses.

🛠️ Detailed solution
1. Set a hard budget of ≤1.5 MB combined JS+CSS and audit assets/ for leftovers from removed apps and one-off campaigns (e.g. rfm-*, worldcup, 10th, pinkoct, valentines, black-friday assets) that are no longer referenced by any live template. 2. Use the theme's built-in speed helpers already present — snippets/sb-speed-booster.liquid, snippets/sb-js-lazyload.liquid, snippets/sb-preload-booster.liquid, and assets/lazysizes.min.js — to defer and lazy-load non-critical scripts rather than loading everything globally in layout/theme.liquid. 3. Split asset loading per-template: only enqueue section-specific CSS/JS (e.g. rfm-membership.css, rfm-encore.css, ritfit-about.css) on the pages that use them instead of theme-wide, so product and collection pages don't pay for membership/about styles. 4. Confirm jquery-3.7.1.min.js, masonry, imagesloaded, and photoswipe are each still required; remove any that only served retired sections, since they add significant weight to every page. 5. After each removal run Lighthouse on mobile (76.4% of buyers) for the home, product, and collection templates to verify total JS+CSS stays under budget and no section breaks.
Evidence (1)
  • theme_source Theme ships 1,593 KB of JS/CSS
    {
      "asset_weight_bytes": 1631453,
      "asset_count": 25
    }

A/B backlog (7) · 7 proposed

proposed Mobile add-to-cart rate lags desktop — A sticky, above-the-fold ATC on mobile PDPs raises mobile ATC rate
proposed High JavaScript errors rate (26% of sessions) — Eliminating JS errors on the PDP/checkout removes silent add-to-cart and checkout failures
proposed LCP poor on 6 template(s) (mobile) — Cutting mobile LCP below 2.5s reduces bounce and raises mobile conversion
proposed FCP poor on 2 template(s) (mobile) — Faster first paint reduces early mobile bounce
proposed INP needs improvement on 2 template(s) (mobile) — Lower mobile INP makes interactions feel instant and lifts add-to-cart rate
proposed Low-contrast text (WCAG 1.4.3) — Raising text, price, and CTA contrast improves scannability and lifts conversion
proposed Tap targets below the minimum size (WCAG 2.5.8) — Larger mobile tap targets cut mis-taps and raise mobile add-to-cart rate

2 on-demand page audits → separate reports

Generated by uiuxbx · 2026-09-07 03:07