Conferences
Budapest, Hungary
08-10
July
Budapest, Hungary
09
July
09
July
09
July
09
July
Budapest, Hungary
09
July
Будапешт, Венгрия
09-10
July
10
July
14
July
Tbilisi, Georgia.
15
July
15
July
16
July
Limassol, Cyprus
23-24
July
23
July
Limassol, Cyprus
23
July
26
July
27-28
July
30-31
July
Limassol, Cyprus
07
August
Saint Petersburg, Russia
12-13
August
25
August
Mexico City, Mexico
27-01
August -
September
Мехико, Мексика
31
August
Mexico City, Mexico
01-03
September
Mexico City, Mexico
01-03
September
Mexico City, Mexico
03
September
Cancún, Mexico
07-08
September
Rome, Italy
02-05
November
Rome, Italy
02
November
04
November
05
November
24-25
November
Bangkok, Thailand
30-02
November -
December
Bangkok, Thailand
09-10
December
Ta’ Qali, Malta
03-05
May
Ho Chi Minh City, Vietnam
14
June

Comparison of 8 AI Models for Building PWA Apps


We tested eight AI models, gave each the same prompt to build a PWA app for an offer, and compared the quality of implementation, the need for manual fixes, and task completion time.

Previously, we compared how three neural networks — Kimi, DeepSeek and GLM handle translating and localizing a landing page for a given geo: https://cpa.rip/ai/landing-page-adaptation-test/.

How the testing was conducted

We used: DeepSeek V4 Flash, DeepSeek V4 Pro, Kimi 2.7 Code, the local LLM Qwen 3.6 with 35 billion parameters, GLM 5.2, Claude Sonnet 5, Claude Opus 4.8 and GPT 5.5.

Each model was given the same working prompt from a previous article about building a PWA app using Claude Sonnet 4.6. The prompt was not modified or supplemented:

Pwa prompt english · MDCopyPWA Traffic Arbitrage — Compact Prompt
PARAMETERS
APP_NAME : Generate randomly
SHORT_NAME : Generate randomly
DEVELOPER : SkyNet Entertainment Ltd.
CATEGORY : Casino · Games
LANGUAGE : es
AFFILIATE_LINK : link
RATING : 4.8 | REVIEWS: 12K | DOWNLOADS: 500K+
SIZE : 4.2 MB | AGE: 18+
SPLASH_SECONDS : 10
SCREENSHOT_COUNT: 3

DESCRIPTION: …

REVIEWS (name / date / text):
1: Generate randomly / Generate randomly / Generate randomly
2: Generate randomly / Generate randomly / Generate randomly ?
3: Generate randomly / Generate randomly / Generate randomly

SCREENSHOTS (theme / emoji / bg-color / badge):
1: Slots / ? / dark purple / "⭐️ TOP"
2: Bonus / ? / dark green / "? Bonus"
3: Jackpot / ? / dark orange / "? Jackpot"

ZIP CONTENTS (all files in root, no subfolders)
index.html, app.html, manifest.json, sw.js,
icon-192.png, icon-512.png,
screenshot1.png … screenshot{SCREENSHOT_COUNT}.png

index.html
First lines immediately after <meta charset>:
html<script>
if (window.matchMedia("(display-mode:standalone)").matches || navigator.standalone)
{ document.documentElement.style.cssText="background:#fff;visibility:hidden";
window.location.replace("./app.html"); }
</script>
<style>body{visibility:hidden}html.ready body{visibility:visible}</style>
Page sections (Google Play style):

Sticky header — Google Play logo (4-color SVG) + search/profile icons
App block — 80×80px icon, app name, developer, category, badges
Statistics — star rating ★, downloads, editor's choice medal, age rating
Install button — color #01875f, 100% width, height 40px, border-radius 8px
Progress bar — below the button, hidden by default
Screenshot carousel — 130×230px thumbnails, border-radius 10px, horizontal scroll; show only screenshot1…N
Description — with "More ▾" toggle
Reviews — avatar (first letter), name, date, stars, text
Info grid 2×2 — size / Android version / downloads / developer
Fixed bottom bar — icon + app name + button

Button logic (in-memory variables only, ⛔️ no localStorage/cookies):
jsvar deferredPrompt = null, installed = false, animationPlayed = false;

addEventListener('beforeinstallprompt', e => { e.preventDefault(); deferredPrompt = e; });
addEventListener('appinstalled', () => {
if (animationPlayed) return;
installed = true; deferredPrompt = null;
runInstallAnimation(() => setOpenState());
});

// On "Install" button click:
// • deferredPrompt exists → prompt() → accepted: run animation → setOpenState; dismissed: restore "Install" label
// • no prompt + iOS → show modal with Safari instruction
// • no prompt + not iOS → wait 4s → prompt()
runInstallAnimation(callback):
jsfunction runInstallAnimation(cb) {
if (animationPlayed) { cb?.(); return; }
animationPlayed = true;
// button → spinner + "Installing…", bg #e8f5e9, color #01875f
// progress bar steps: [10, 25, 45, 65, 80, 92, 100] with pauses between
// after reaching 100% → wait 500ms → call cb()
}
setOpenState / openApp:
jsfunction setOpenState() {
installed = true; // in-memory only — do NOT write to localStorage
// both buttons (main + bottom bar) → "✓ Open", onclick = openApp
}
function openApp() { window.location.href = './app.html'; }
End of script:
jsdocument.documentElement.classList.add('ready');
if ('serviceWorker' in navigator) navigator.serviceWorker.register('./sw.js');
// + add ripple animation to all buttons

app.html — Splash Screen + Redirect
Splash screen (white background, all content centered):

icon-512.png — 112×112px, border-radius 24px, box-shadow
App name — 26px, font-weight 400, color #202124
Spinner — border-top-color #01875f
Entrance animation: scale(0.75→1) + opacity(0→1), duration 0.5s
Progress bar — position: fixed; bottom: 0, height 3px, color #01875f; fills evenly over SPLASH_SECONDS seconds, jumps to 100% 400ms before the end

Redirect (fires 300ms after page load):
jsvar TARGET = 'AFFILIATE_LINK';
var host = new URL(TARGET).host, path = TARGET.replace(/^https?://[^/]+/, '');
var intent = 'intent://' + host + path
+ '#Intent;scheme=https;package=com.android.chrome'
+ ';S.browser_fallback_url=' + encodeURIComponent(TARGET) + ';end';
window.location.href = intent;
setTimeout(() => window.location.replace(TARGET), 1500); // fallback for non-Android

manifest.json
json{
"name": "APP_NAME",
"short_name": "SHORT_NAME",
"start_url": "./index.html",
"scope": "./",
"display": "standalone",
"orientation": "portrait",
"background_color": "#ffffff",
"theme_color": "#01875f",
"icons": [
{"src":"icon-192.png","sizes":"192x192","type":"image/png","purpose":"any"},
{"src":"icon-512.png","sizes":"512x512","type":"image/png","purpose":"maskable"}
]
}
⛔️ Do NOT add a "screenshots" field — Chrome renders them inside the install dialog, which breaks UX.

sw.js
jsconst CACHE = 'app-v1';
const STATIC = [
'./manifest.json', './icon-192.png', './icon-512.png',
...Array.from({ length: SCREENSHOT_COUNT }, (_, i) => `./screenshot${i + 1}.png`)
];

// install → caches.open(CACHE).addAll(STATIC); self.skipWaiting()
// activate → delete old caches; self.clients.claim()
// fetch rules:
// cross-origin request → pass through (do not intercept)
// index.html / app.html / "/"→ network-first; fallback: cache(index.html)
// everything else → cache-first → on miss: fetch → store in cache

Icons (Python Pillow)

icon-192.png — 192×192px
icon-512.png — 512×512px
Background: vertical gradient #1a0a3e → #0d0628
First letter of SHORT_NAME, white, bold, centered


Screenshots (Playwright Chromium)

Generate exactly SCREENSHOT_COUNT files (screenshot1.png … screenshotN.png)
Viewport: 260×460px, save as PNG
Each screenshot structure:

Header — app name + colored badge (from SCREENSHOT_N params)
Center — large emoji ~80px with CSS drop-shadow filter
Bottom panel — background: rgba(0,0,0,0.65) + backdrop-filter: blur; title in #ffd700; description text; 3 decorative emojis; "Play Now" button in #01875f

After the generation was complete, we checked:

  • whether the app worked;
  • the completeness of the implemented functionality;
  • whether the interface matched the requirements;
  • task completion time.

Key takeaways

Fully completed the task: DeepSeek V4 Flash, DeepSeek V4 Pro, Claude Sonnet 5, Claude Opus 4.8 and Kimi 2.7 Code — all these models created a working PWA app that required no further fixes.

Partially completed: GLM 5.2 generated the main code but did not create the project files and skipped the WebView installation step.

Failed the task: Qwen 3.6 generated only an app shell, while GPT 5.5 refused to perform the task, citing safety restrictions.

The fastest model was DeepSeek V4 Flash, which needed about 3 minutes to generate the finished app.

The interface closest to the original was produced by Claude Sonnet 5 and Claude Opus 4.8.

ModelTimeFunctionalityFixes neededInterface
DeepSeek V4 Flash3 minNo⭐⭐⭐
DeepSeek V4 Pro22 minNo⭐⭐⭐⭐
Claude Sonnet 59 minNo⭐⭐⭐⭐⭐
Kimi 2.7 Code15 minNo⭐⭐⭐⭐
GLM 5.230 min*⚠️ PartialYes⭐⭐⭐⭐
Claude Opus 4.841 minNo⭐⭐⭐⭐⭐
Qwen 3.65 minRework required
GPT 5.5🚫 Refused

GLM 5.2

Completion time: about 20 minutes + roughly 10 minutes of manual fixes.

The model wrote the app code but did not create the necessary project files. They had to be created manually before launching, per the instructions.

During review, it also turned out that GLM skipped the step of installing the WebView app on the device, meaning the app could not be considered fully ready for use without additional work.

Final result

Kimi 2.7 Code

Completion time: about 15 minutes.

Kimi fully completed the assigned task. The app launched and worked successfully.

Final result

Qwen 3.6

The model failed to complete the assigned task.

Instead of a full-fledged PWA app, only an interface shell was created. After building, the Install button did not perform any action, so the app cannot be considered functional.

Final result

Claude Sonnet 5 and Opus 4.8

Both Sonnet 5 and Opus 4.8 successfully completed the assigned task, creating fully functional PWA apps that required no manual fixes. They also produced the most successful interfaces, closely matching the original app.

The main difference between the models was generation speed. Claude Sonnet 5 completed the task in about 9 minutes, while Claude Opus 4.8 took about 41 minutes — the longest time among all the models tested.

Final result — Sonnet 5

Final result — Opus 4.8

DeepSeek V4 Flash and V4 Pro

Comparing DeepSeek V4 Flash and V4 Pro showed the difference between fast and more powerful models. Flash finished generation in just three minutes, but the interface quality was the weakest among the successfully completed projects. V4 Pro took almost seven times longer, but the result ended up significantly closer to the original app.

Final result — V4 Flash

Final result — V4 Pro


Like it? Share with your friends!
0 Comments
Affiliate - Our assessment
Verticals
Min. sum
Site
It’s an affiliate program of the eponymous online casino. A direct advertiser as of 2017. They work by RevShare (up to 50%) partnership strategy.
1,000 rubles/$20/€20 pay
фото
фото
фото
фото
фото
фото
фото
фото
фото
фото
фото
фото
фото
фото
Affiliate program in the gambling vertical, a direct advertiser. It accepts traffic from CIS. It cooperates on the CPA, RevShare, Hybrid, Fix, and Flat models.
€100 pay
фото
фото
фото
фото
фото
фото
фото
It’s our affiliate program with gambling and betting offers. We are currently in beta but we accept traffic.
$20 pay
фото
860
Go to offers
SpinBetter Partners is an affiliate program in the Gambling and Betting verticals and a direct advertiser of the SpinBetter project. The company has been operating since 2019. It co-operates on CPA, Hybrid, RevShare, Fix, and Fix + RevShare models. SpinBet accepts SEO, ASO, PPC, UAC, Facebook, social, stream, email, messaging apps, and other types of traffic, except motive, schemes, and fraud.
50 pay
фото
фото
фото
фото
фото
Traffic Cake is a direct advertiser offering iGaming vertical offers. Exclusive offers include VOX and Winnita. GEOs: Tier-1 and Tier-2. An authorized partner of well-known brands such as Vulkan Vegas, IceCasino, and Verde Casino. Works on CPA (up to €600), RevShare, and Hybrid models. Affiliates can earn up to 60% RevShare. Payments are made weekly with no delays. All major traffic sources are accepted.
€100 pay
фото
фото
Direct advertiser of popular European casino and sportsbook brands V.Vegas, ICE Casino, VERDE Casino, FS Casino, Fiery Play, Slotoro and Hit’N’Spin. Founded in 2016, they operate on CPA, RevShare, and Hybrid models, accepting traffic from over 35 countries. They provide all the necessary tools for success.
€100 pay
фото
фото
фото
фото
фото