Skip to content

ilder-as/pwa-guide-image-generator

Repository files navigation

PWA Guide Image Generator

A utility for generating polished, print-ready PDF installation guides for Progressive Web Apps (PWAs). It renders step-by-step iOS Safari and Android Chrome installation flows inside realistic phone frames, with full internationalisation support, then exports them as high-resolution PNGs and combines them into a PDF.


Output

Each locale produces a 3-page PDF (exports/<locale>/<locale>-pwa-guide.pdf):

Page Content
1 Cover — app icon, title, locale badge
2 iOS · Safari — steps 1–4 inside an iPhone frame
3 Android · Chrome — steps 1–4 inside an Android frame

Cover page

Cover page

iOS · Safari

iOS install steps

Android · Chrome

Android install steps

PNGs are also saved individually in exports/<locale>/ for use in other documents or presentations.


Quick Start

Prerequisites

  • Node.js 18+
  • npm 9+

Install

npm install
npx playwright install chromium

Generate PDFs

# English only
npm run capture

# All languages (en, no, pl)
npm run capture:all

Output lands in exports/:

exports/
├── en/
│   ├── cover.png
│   ├── ios.png
│   ├── android.png
│   └── en-pwa-guide.pdf
├── no/
│   └── no-pwa-guide.pdf
└── pl/
    └── pl-pwa-guide.pdf

Preview in the browser

npm run dev
# Open http://localhost:5173/en

Supported routes: /en, /no, /pl


How It Works

Architecture

React SPA (Vite)
    │
    ├── /:locale              ← dev/preview UI (both platforms side-by-side)
    └── /export/:locale/
            ├── cover         ← cover page
            ├── ios           ← iOS steps only, scale 0.27
            └── android       ← Android steps only, scale 0.26

Playwright (headless Chromium)
    └── Screenshots each export route at 1400×750 × 2× DPR → 2800×1500 PNG

pdf-lib
    └── Embeds the 3 PNGs into a single PDF

Phone frame rendering

Each phone is rendered at native resolution inside a CSS transform: scale() wrapper:

  • iOS frame (frame-dark.svg) — 1079×2178 px native, scaled to ~291×588 px on the export page
  • Android frame (frame-light.svg) — 1148×2318 px native, scaled to ~298×603 px on the export page

The screenshot image is clipped to the transparent screen opening in the SVG frame using overflow: hidden with precise pixel insets derived from the frame geometry.

Step overlays

Each step renders a different overlay on top of the screenshot:

Step iOS Android
1 Safari address bar (floating pills, red ring on ⋯) Chrome address bar (red ring on ⋮)
2 Safari share popover Chrome three-dots dropdown (Add to home screen highlighted)
3 iOS Share Sheet (Add to Home Screen highlighted) "Add to home screen" bottom sheet (Install highlighted)
4 "Add to Home Screen" dialog + keyboard PWA install prompt (Install button highlighted)

iOS overlays use the iOS 26 liquid glass aesthetic — frosted white pills and sheets with backdrop-filter: blur(). Android overlays use plain white cards matching Material You Chrome UI.

Glass style tokens are centralised in src/lib/glass.ts and shared across all components.

Internationalisation

Translations live in src/i18n/:

src/i18n/
├── en.json   ← English
├── no.json   ← Norwegian (Bokmål)
├── pl.json   ← Polish
└── index.ts  ← LocaleContext, useT() hook, getTranslations()

Every UI string in the overlay components is pulled from the active translation via useT(). The app name and URL (My App, example.com) are intentionally not translated — replace these with your own brand strings.

The locale is set from the URL path (/:locale or /export/:locale/...) and injected via React Context, so no prop drilling is needed.

Screenshots per locale

Background screenshots are locale-specific (different language in the app UI):

Locale iOS screenshot Android screenshot
en screen-ios.jpg screen-en.png
no screen-ios.jpg screen-no.png
pl screen-ios.jpg screen-pl.png

Screenshot paths are defined in src/lib/screens.ts.


Adapting for a Different App

1. Replace screenshots

Drop your own screenshots into public/:

public/
├── screen-en.png     ← Android, English UI
├── screen-no.png     ← Android, Norwegian UI
├── screen-pl.png     ← Android, Polish UI
└── screen-ios.jpg    ← iOS (used for all locales)

Update src/lib/screens.ts if you use different filenames.

2. Replace the app icon and name

Set VITE_APP_ICON in .env.local to point to your icon (local path under public/local/ or a remote URL).

Update the hardcoded brand strings "My App" and "example.com" in ShareActionSheet.tsx, AddToHomeScreen.tsx, AndroidAddToHome.tsx, AndroidInstallPrompt.tsx, SafariBar.tsx, ChromeBar.tsx, and ExportCover.tsx.

3. Add or remove languages

  1. Add a new JSON file in src/i18n/ (e.g. de.json) using en.json as a template.
  2. Register it in src/i18n/index.ts:
    import de from "./de.json";
    export const LOCALES = ["en", "no", "pl", "de"] as const;
    const translations = { en, no, pl, de };
  3. Add an Android screenshot for the new locale and update src/lib/screens.ts.
  4. Run npm run capture:all — it will pick up the new locale automatically.

4. Adjust export resolution

In scripts/capture.ts:

const SCALE = 2; // increase to 3 for 4200×2250 output

Higher values produce sharper PNGs at the cost of larger file sizes.


Project Structure

├── public/
│   ├── frame-dark.svg          iPhone frame (iOS)
│   ├── frame-light.svg         Android frame
│   ├── icon-placeholder.png    Placeholder app icon (default)
│   ├── screen-placeholder.png  Placeholder screenshot (default)
│   └── local/                  Your real assets (git-ignored)
│
├── src/
│   ├── i18n/                   Translation files + context
│   ├── lib/
│   │   ├── glass.ts            iOS 26 glassmorphism style tokens
│   │   └── screens.ts          Locale → screenshot mapping
│   ├── components/
│   │   ├── PhoneFrame.tsx      iOS frame wrapper
│   │   ├── PhoneFrameAndroid.tsx  Android frame wrapper
│   │   ├── SafariBar.tsx       iOS 26 floating address bar
│   │   ├── ShareSheet.tsx      iOS ⋯ popover menu
│   │   ├── ShareActionSheet.tsx   iOS full share sheet
│   │   ├── AddToHomeScreen.tsx    iOS "Add to Home Screen" dialog
│   │   └── android/
│   │       ├── ChromeBar.tsx      Chrome address bar + nav
│   │       ├── ChromeMenu.tsx     Chrome three-dots dropdown
│   │       ├── AndroidAddToHome.tsx   "Add to home screen" sheet
│   │       └── AndroidInstallPrompt.tsx  PWA install prompt
│   ├── pages/
│   │   ├── ExportCover.tsx     Cover page (export route)
│   │   ├── ExportIOS.tsx       iOS steps page (export route)
│   │   └── ExportAndroid.tsx   Android steps page (export route)
│   ├── App.tsx                 Main dev/preview layout
│   └── main.tsx                Router setup
│
├── scripts/
│   └── capture.ts              Playwright + pdf-lib export script
│
└── exports/                    Generated output (git-ignored)
    ├── en/
    ├── no/
    └── pl/

Tech Stack

Tool Purpose
Vite + React + TypeScript App framework
Tailwind CSS v4 Utility classes for the dev preview shell
React Router v7 Locale-based routing (/en, /no, /pl)
Playwright Headless Chromium screenshot capture
pdf-lib PNG → PDF assembly
tsx Zero-config TypeScript script runner

About

Will take a png screenshot as a upload file and overlay with proper share steps for iOS

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages