cd /news/computer-vision/how-we-built-an-ai-visual-cro-audito… · home topics computer-vision article
[ARTICLE · art-121275] src=dev.to ↗ pub= topic=computer-vision verified=true sentiment=· neutral

How We Built an AI Visual CRO Auditor That Draws Bounding Boxes Over UX Friction

Plyxo Community Edition has released an open-source Visual CRO engine that uses multimodal vision models to audit webpages for UX friction, drawing bounding boxes over problem areas and generating Tailwind CSS fixes. The tool captures high-DPI screenshots via headless Chrome, feeds them to a vision model with a structured prompt, and returns normalized coordinates for an interactive overlay.

read4 min views1 publishedSep 4, 2026

When traditional automated audit tools (like Google Lighthouse) scan a webpage, they test for DOM metrics and performance: Largest Contentful Paint (LCP), missing ARIA labels, image dimensions, or meta tags.

What they CANNOT tell you:

To solve this, we built an open-source Visual CRO (Conversion Rate Optimization) engine for Plyxo Community Edition.

It captures high-DPI full-page screenshots, feeds them into multimodal vision models, returns normalized coordinate bounding boxes [ymin, xmin, ymax, xmax] over friction zones, and generates copy-paste Tailwind CSS fixes.

Here is how the architecture works under the hood.

[Target URL] 
     ↓
[Puppeteer / Headless Chrome] 
     ↓ (High-DPI Screenshot + DOM Heuristics)
[Multimodal Vision Model] 
     ↓ (Normalized 0-1000 Coordinates JSON)
[Interactive Canvas Overlay + Tailwind Code Remediation]

Standard screenshots often miss sticky headers, modals, or hydration popups. We use a headless Chrome pipeline that enforces high-DPI rendering and waits for network idle:

// packages/core/src/scanners/screenshot.ts
import puppeteer from 'puppeteer';

export async function captureViewport(url: string) {
  const browser = await puppeteer.launch({
    headless: 'new',
    args: ['--no-sandbox', '--disable-setuid-sandbox']
  });

  const page = await browser.newPage();
  await page.setViewport({
    width: 1440,
    height: 900,
    deviceScaleFactor: 2 // High DPI for crisp font & badge recognition
  });

  await page.goto(url, { waitUntil: 'networkidle2', timeout: 30000 });

  // Clean scroll to trigger lazy-loaded sections
  await page.evaluate(() => window.scrollTo(0, document.body.scrollHeight / 2));
  await new Promise(r => setTimeout(r, 600));
  await page.evaluate(() => window.scrollTo(0, 0));

  const screenshotBuffer = await page.screenshot({
    fullPage: false, // Hero/above-the-fold is where 80% of CRO friction happens
    encoding: 'base64'
  });

  await browser.close();
  return screenshotBuffer;
}

Standard LLMs return chatty explanations. For an interactive UI overlay, we need structured JSON with normalized visual coordinates (0-1000 scale).

Here is the system prompt and structured schema we pass to the vision model:

const SYSTEM_PROMPT = `
You are an expert Conversion Rate Optimization (CRO) and UX Design Auditor.
Analyze the provided desktop screenshot of a landing page.

Identify top UX/CRO friction points:
1. Contrast/Visibility issues (unclear CTAs)
2. Visual clutter / Cognitive overload
3. Lack of immediate value proposition / hierarchy
4. Trust signal deficiencies

For each issue, you MUST provide:
- 'title': Short descriptive title
- 'severity': 'critical' | 'warning' | 'info'
- 'box_2d': Normalized coordinates [ymin, xmin, ymax, xmax] between 0 and 1000
- 'frictionReason': Why this hurts conversion
- 'proposedCodeFix': Concrete Tailwind CSS / HTML remediation code
`;

Once the backend returns the normalized coordinate array, we render dynamic highlight boxes that scale responsively with any container:

// components/VisualCroOverlay.tsx
import React, { useState } from 'react';

interface FrictionBox {
  id: string;
  title: string;
  severity: 'critical' | 'warning' | 'info';
  box_2d: [number, number, number, number]; // [ymin, xmin, ymax, xmax]
  proposedCodeFix: string;
}

export function VisualCroOverlay({ 
  screenshotUrl, 
  issues 
}: { 
  screenshotUrl: string; 
  issues: FrictionBox[] 
}) {
  const [selectedIssue, setSelectedIssue] = useState<FrictionBox | null>(null);

  return (
    <div className="relative inline-block w-full border border-slate-800 rounded-xl overflow-hidden shadow-2xl">
      <img src={screenshotUrl} alt="Audited Page" className="w-full h-auto block" />

      {/* Visual Bounding Boxes */}
      {issues.map((issue) => {
        const [ymin, xmin, ymax, xmax] = issue.box_2d;
        const top = `${(ymin / 1000) * 100}%`;
        const left = `${(xmin / 1000) * 100}%`;
        const height = `${((ymax - ymin) / 1000) * 100}%`;
        const width = `${((xmax - xmin) / 1000) * 100}%`;

        const colorMap = {
          critical: 'border-rose-500 bg-rose-500/20 text-rose-300',
          warning: 'border-amber-500 bg-amber-500/20 text-amber-300',
          info: 'border-blue-500 bg-blue-500/20 text-blue-300'
        };

        return (
          <div
            key={issue.id}
            onClick={() => setSelectedIssue(issue)}
            style={{ top, left, height, width }}
            className={`absolute border-2 cursor-pointer transition-all hover:scale-[1.02] ${colorMap[issue.severity]}`}
          >
            <span className="absolute -top-6 left-0 text-xs px-1.5 py-0.5 rounded bg-slate-900 border border-slate-700 font-mono">
              {issue.title}
            </span>
          </div>
        );
      })}
    </div>
  );
}

Instead of just telling the developer "your button lacks contrast", the engine generates the replacement JSX:

// Before (Detected Friction)
<button className="bg-indigo-400 text-indigo-100 py-2 px-4 rounded">
  Get Started
</button>

// Recommended Fix (High-Contrast Visual Hierarchy + Micro-interaction)
<button className="bg-gradient-to-r from-emerald-500 to-teal-600 text-white font-semibold py-3 px-6 rounded-lg shadow-lg shadow-emerald-500/20 hover:shadow-emerald-500/40 hover:-translate-y-0.5 transition-all duration-200">
  Start Free Audit →
</button>

We packaged this visual engine into Plyxo Community Edition, a free, MIT-licensed audit platform that combines:

git clone https://github.com/pixelfogg/Plyxo-CRO-SEO-AIO-AEO-GEO.git
cd Plyxo-CRO-SEO-AIO-AEO-GEO
docker compose up -d

Check out the code, run it locally on your own SaaS landing page, or star the repo on GitHub:

GitHub Repo: github.com/pixelfogg/Plyxo-CRO-SEO-AIO-AEO-GEO

🌐 Live Demo: plyxo.org

What techniques are you using to audit landing page conversion rates? Let's discuss in the comments below!

── more in #computer-vision 4 stories · sorted by recency
── more on @plyxo community edition 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/how-we-built-an-ai-v…] indexed:0 read:4min 2026-09-04 ·