Screenshot
clawhub2 platformsScreen Capture Very Easy
3.7
Capture, inspect, and compare screenshots of screens, windows, regions, web pages, simulators, and CI runs with the right tool, wait strategy, viewport, and...
1.2K
clawhub install screenshotname: app-store-screenshots
# Install Skill npx skills add parthjadhav/app-store-screenshots@app-store-screenshots # Claude Code will auto-detect and use it after installation
# Same install command — works with all SKILL.md-compatible AI coding tools npx skills add parthjadhav/app-store-screenshots@app-store-screenshots
html-to-image at Apple's required resolutions. Screenshots are the single most important conversion asset on the App Store.ar, he, fa, ur), mirror layout intentionally instead of just translating the text# Check in order
which bun && echo "use bun" || which pnpm && echo "use pnpm" || which yarn && echo "use yarn" || echo "use npm"
# With bun:
bunx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
bun add html-to-image
# With pnpm:
pnpx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
pnpm add html-to-image
# With yarn:
yarn create next-app . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
yarn add html-to-image
# With npm:
npx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
npm install html-to-image
mockup.png (co-located with this SKILL.md). Copy it to the project's public/ directory. The mockup file is in the same directory as this skill file. No iPad mockup is needed — the iPad frame is CSS-only.project/
├── public/
│ ├── mockup.png # iPhone frame (included with skill)
│ ├── app-icon.png # User's app icon
│ ├── screenshots/ # iPhone app screenshots
│ │ ├── home.png
│ │ ├── feature-1.png
│ │ └── ...
│ └── screenshots-ipad/ # iPad app screenshots (optional)
│ ├── home.png
│ ├── feature-1.png
│ └── ...
├── src/app/
│ ├── layout.tsx # Font setup
│ └── page.tsx # The screenshot generator (single file)
└── package.json
base path; all slide image srcs stay identical.└── screenshots/
├── en/
│ ├── home.png
│ ├── feature-1.png
│ └── ...
├── de/
│ └── ...
└── {locale}/
└── screenshots-ipad/
├── en/
├── de/
└── {locale}/
page.tsx file. No routing, no extra layouts, no API routes.LOCALES array and locale tabs to the toolbar. Every slide src uses base — no hardcoded paths:const LOCALES = ["en", "de", "es"] as const; // use whatever langs were defined
type Locale = typeof LOCALES[number];
// In ScreenshotsPage:
const [locale, setLocale] = useState<Locale>("en");
const base = /screenshots/${locale};
// Toolbar tabs:
{LOCALES.map(l => (
<button key={l} onClick={() => setLocale(l)}
style={{ fontWeight: locale === l ? 700 : 400 }}>
{l.toUpperCase()}
</button>
))}
// In every slide — unchanged between single and multi-language:
<Phone src={${base}/home.png} alt="Home" />
const LOCALES = ["en", "de", "ar"] as const;
type Locale = typeof LOCALES[number];
const RTL_LOCALES = new Set<Locale>(["ar"]);
const THEMES = {
"clean-light": {
bg: "#F6F1EA",
fg: "#171717",
accent: "#5B7CFA",
muted: "#6B7280",
},
"dark-bold": {
bg: "#0B1020",
fg: "#F8FAFC",
accent: "#8B5CF6",
muted: "#94A3B8",
},
"warm-editorial": {
bg: "#F7E8DA",
fg: "#2B1D17",
accent: "#D97706",
muted: "#7C5A47",
},
} as const;
type ThemeId = keyof typeof THEMES;
const COPY_BY_LOCALE = {
en: { hero: "Build better habits" },
de: { hero: "Baue bessere Gewohnheiten auf" },
ar: { hero: "ابنِ عادات أفضل" },
} satisfies Record<Locale, { hero: string }>;
const [themeId, setThemeId] = useState<ThemeId>("clean-light");
const [locale, setLocale] = useState<Locale>("en");
const theme = THEMES[themeId];
const copy = COPY_BY_LOCALE[locale];
const isRtl = RTL_LOCALES.has(locale);
dir={isRtl ? "rtl" : "ltr"} on the screenshot canvas and mirror asymmetric layouts intentionally.// ?locale=de&theme=dark-bold&device=ipad
// src/app/layout.tsx
import { YourFont } from "next/font/google"; // Use whatever font the user specified
const font = YourFont({ subsets: ["latin"] });
export default function Layout({ children }: { children: React.ReactNode }) {
return <html><body className={font.className}>{children}</body></html>;
}
<br />.Build App Store screenshots for my habit tracker.
The app helps people stay consistent with simple daily routines.
I want 6 slides, clean/minimal style, warm neutrals, and a calm premium feel.
Generate App Store screenshots for my personal finance app.
The app's main strengths are fast expense capture, clear monthly trends, and shared budgets.
I want a sharp, modern style with high contrast and 7 slides.
Create exportable App Store screenshots for my AI note-taking app.
The core value is turning messy voice notes into clean summaries and action items.
I want bold copy, dark backgrounds, and a polished tech-forward look.
page.tsx
├── Constants (IPHONE_W/H, IPAD_W/H, SIZES, design tokens)
├── LOCALES / RTL_LOCALES / THEMES / COPY_BY_LOCALE
├── Phone component (mockup PNG with screen overlay)
├── IPad component (CSS-only frame with screen overlay)
├── Caption component (label + headline, accepts canvasW for scaling)
├── Decorative components (blobs, glows, shapes — based on style direction)
├── iPhoneSlide1..N components (one per slide)
├── iPadSlide1..N components (same designs, adjusted for iPad proportions)
├── IPHONE_SCREENSHOTS / IPAD_SCREENSHOTS arrays (registries)
├── ScreenshotPreview (ResizeObserver scaling + hover export)
└── ScreenshotsPage (grid + locale tabs + theme tabs + device toggle + export logic)
const IPHONE_SIZES = [
{ label: '6.9"', w: 1320, h: 2868 },
{ label: '6.5"', w: 1284, h: 2778 },
{ label: '6.3"', w: 1206, h: 2622 },
{ label: '6.1"', w: 1125, h: 2436 },
] as const;
const IPAD_SIZES = [
{ label: '13" iPad', w: 2064, h: 2752 },
{ label: '12.9" iPad Pro', w: 2048, h: 2732 },
] as const;
?device=ipad URL parameter for headless/automated capture workflows.locale switches screenshot folders and copy dictionariestheme switches design tokens onlydevice switches iPhone/iPad slide registriessize switches export resolution onlytransform: scale() via ResizeObserver to fit a grid cardposition: absolute; left: -9999px at true resolutionmockup.png has these pre-measured values:const MK_W = 1022; // mockup image width
const MK_H = 2082; // mockup image height
const SC_L = (52 / MK_W) * 100; // screen left offset %
const SC_T = (46 / MK_H) * 100; // screen top offset %
const SC_W = (918 / MK_W) * 100; // screen width %
const SC_H = (1990 / MK_H) * 100; // screen height %
const SC_RX = (126 / 918) * 100; // border-radius x %
const SC_RY = (126 / 1990) * 100; // border-radius y %
function Phone({ src, alt, style, className = "" }: {
src: string; alt: string; style?: React.CSSProperties; className?: string;
}) {
return (
<div className={relative ${className}}
style={{ aspectRatio: ${MK_W}/${MK_H}, ...style }}>
<img src="/mockup.png" alt=""
className="block w-full h-full" draggable={false} />
<div className="absolute z-10 overflow-hidden"
style={{
left: ${SC_L}%, top: ${SC_T}%,
width: ${SC_W}%, height: ${SC_H}%,
borderRadius: ${SC_RX}% / ${SC_RY}%,
}}>
<img src={src} alt={alt}
className="block w-full h-full object-cover object-top"
draggable={false} />
</div>
</div>
);
}
770/1000 so the inner screen area (92% width × 94.4% height) matches the 3:4 aspect ratio of iPad screenshots. Using incorrect proportions causes black bars or stretched screenshots.function IPad({ src, alt, style, className = "" }: {
src: string; alt: string; style?: React.CSSProperties; className?: string;
}) {
return (
<div className={relative ${className}}
style={{ aspectRatio: "770/1000", ...style }}>
<div style={{
width: "100%", height: "100%", borderRadius: "5% / 3.6%",
background: "linear-gradient(180deg, #2C2C2E 0%, #1C1C1E 100%)",
position: "relative", overflow: "hidden",
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.1), 0 8px 40px rgba(0,0,0,0.6)",
}}>
{/* Front camera dot */}
<div style={{
position: "absolute", top: "1.2%", left: "50%",
transform: "translateX(-50%)", width: "0.9%", height: "0.65%",
borderRadius: "50%", background: "#111113",
border: "1px solid rgba(255,255,255,0.08)", zIndex: 20,
}} />
{/* Bezel edge highlight */}
<div style={{
position: "absolute", inset: 0, borderRadius: "5% / 3.6%",
border: "1px solid rgba(255,255,255,0.06)",
pointerEvents: "none", zIndex: 15,
}} />
{/* Screen area */}
<div style={{
position: "absolute", left: "4%", top: "2.8%",
width: "92%", height: "94.4%",
borderRadius: "2.2% / 1.6%", overflow: "hidden", background: "#000",
}}>
<img src={src} alt={alt}
style={{ display: "block", width: "100%", height: "100%",
objectFit: "cover", objectPosition: "top" }}
draggable={false} />
</div>
</div>
</div>
);
}
width: "65-70%" for iPad mockups (vs 82-86% for iPhone) — iPad is wider relative to its heightcanvasW (which is 2064 for iPad vs 1320 for iPhone)W * 0.028 | 600 (semibold) | default |
| Headline | W * 0.09 to W * 0.1 | 700 (bold) | 1.0 |
| Hero headline | W * 0.1 | 700 (bold) | 0.92 |bottom: 0, width: "82-86%", translateX(-50%) translateY(12-14%)
Back: left: "-8%", width: "65%", rotate(-4deg), opacity: 0.55
Front: right: "-4%", width: "82%", translateY(10%)
Cards should NOT block the phone's main content.
Position at edges, slight rotation (2-5deg), drop shadows.
If distracting, push partially off-screen or make smaller.
html2canvas breaks on CSS filters, gradients, drop-shadow, backdrop-filter, and complex clipping. html-to-image uses native browser SVG serialization — handles all CSS faithfully.import { toPng } from "html-to-image";
// Before capture: move element on-screen
el.style.left = "0px";
el.style.opacity = "1";
el.style.zIndex = "-1";
const opts = { width: W, height: H, pixelRatio: 1, cacheBust: true };
// CRITICAL: Double-call trick — first warms up fonts/images, second produces clean output
await toPng(el, opts);
const dataUrl = await toPng(el, opts);
// After capture: move back off-screen
el.style.left = "-9999px";
el.style.opacity = "";
el.style.zIndex = "";
const jobs = LOCALES.flatMap(locale =>
ACTIVE_THEME_IDS.flatMap(themeId =>
ACTIVE_DEVICES.flatMap(device =>
getSlidesFor(device).map((slide, index) => ({
locale,
themeId,
device,
index,
slide,
})),
),
),
);
01-hero-en-clean-light-iphone-1320x2868.png
01-hero-ar-dark-bold-ipad-2064x2752.png
toPng() loads fonts/images lazily. Second produces clean output. Without this, exports are blank.left: 0 before calling toPng.position: absolute; left: -9999px (not fixed).fontFamily on the offscreen container.01-hero-1320x2868.png, 02-freshness-1320x2868.png, etc. Use String(index + 1).padStart(2, "0").```bash
npx skills add parthjadhav/app-store-screenshots@app-store-screenshots
skills run app-store-screenshots --app-name "MyApp" --device "iphone-14" --language "zh-CN" --output-dir ./app-store-assets
```Capture, inspect, and compare screenshots of screens, windows, regions, web pages, simulators, and CI runs with the right tool, wait strategy, viewport, and...
clawhub install screenshot