app-store-screenshots

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

App Store Screenshots Generator

App Store截图生成器

Overview

概述

Build a Next.js page that renders iOS App Store screenshots as advertisements (not UI showcases) and exports them via
html-to-image
at Apple's required resolutions. Screenshots are the single most important conversion asset on the App Store.
构建一个Next.js页面,将iOS App Store截图渲染为广告(而非UI展示),并通过
html-to-image
按苹果要求的分辨率导出。截图是App Store上最重要的转化资产。

Core Principle

核心原则

Screenshots are advertisements, not documentation. Every screenshot sells one idea. If you're showing UI, you're doing it wrong — you're selling a feeling, an outcome, or killing a pain point.
截图是广告,不是文档。 每张截图只传递一个核心信息。如果你在展示UI,那你就做错了——你要卖的是一种感受、一个结果,或是解决一个痛点

Step 1: Ask the User These Questions

步骤1:向用户询问以下问题

Before writing ANY code, ask the user all of these. Do not proceed until you have answers:
在编写任何代码前,先向用户问清所有这些问题,拿到答案后再继续推进:

Required

必问项

  1. App screenshots — "Where are your app screenshots? (PNG files of actual device captures)"
  2. App icon — "Where is your app icon PNG?"
  3. Brand colors — "What are your brand colors? (accent color, text color, background preference)"
  4. Font — "What font does your app use? (or what font do you want for the screenshots?)"
  5. Feature list — "List your app's features in priority order. What's the #1 thing your app does?"
  6. Number of slides — "How many screenshots do you want? (Apple allows up to 10)"
  7. Style direction — "What style do you want? Examples: warm/organic, dark/moody, clean/minimal, bold/colorful, gradient-heavy, flat. Share App Store screenshot references if you have any."
  1. 应用截图 — 「你的应用截图存放在哪里?(实际设备截图的PNG文件)」
  2. 应用图标 — 「你的应用图标PNG文件存放在哪里?」
  3. 品牌色 — 「你的品牌色是什么?(强调色、文字颜色、背景偏好)」
  4. 字体 — 「你的应用使用什么字体?(或者你希望截图用什么字体?)」
  5. 功能列表 — 「按优先级列出你的应用功能,应用最核心的作用是什么?」
  6. 幻灯片数量 — 「你需要多少张截图?(苹果最多允许10张)」
  7. 风格方向 — 「你想要什么风格?例如:温暖/自然风、暗黑/氛围感、简洁/极简风、醒目/多彩风、重渐变风、扁平风。如果有参考的App Store截图可以分享。」

Optional

可选问项

  1. Component assets — "Do you have any UI element PNGs (cards, widgets, etc.) you want as floating decorations? If not, that's fine — we'll skip them."
  2. Additional instructions — "Any specific requirements, constraints, or preferences?"
  1. 组件素材 — 「你有没有想要作为浮动装饰的UI元素PNG(卡片、小组件等)?如果没有也没关系,我们可以跳过。」
  2. 附加说明 — 「有没有其他特殊要求、限制或偏好?」

Derived from answers (do NOT ask — decide yourself)

根据答案推导(不要问用户,自行决定)

Based on the user's style direction, brand colors, and app aesthetic, decide:
  • Background style: gradient direction, colors, whether light or dark base
  • Decorative elements: blobs, glows, geometric shapes, or none — match the style
  • Dark vs light slides: how many of each, which features suit dark treatment
  • Typography treatment: weight, tracking, line height — match the brand personality
  • Color palette: derive text colors, secondary colors, shadow tints from the brand colors
IMPORTANT: If the user gives additional instructions at any point during the process, follow them. User instructions always override skill defaults.
根据用户的风格方向、品牌色和应用美学,自行决定以下内容:
  • 背景风格:渐变方向、颜色,底色用亮色还是暗色
  • 装饰元素:色块、发光效果、几何图形,或者不用——要和风格匹配
  • 明暗幻灯片占比:明暗版本各多少张,哪些功能适合暗色处理
  • 排版处理:字重、字间距、行高——要匹配品牌调性
  • 调色板:从品牌色中推导文字颜色、辅助色、阴影色调
重要提示: 如果用户在过程中任何时候给出附加说明,优先遵循用户要求,用户指令始终高于本技能默认规则。

Step 2: Set Up the Project

步骤2:搭建项目

Detect Package Manager

检测包管理器

Check what's available, use this priority: bun > pnpm > yarn > npm
bash
undefined
检查可用的包管理器,优先级为:bun > pnpm > yarn > npm
bash
undefined

Check in order

按顺序检测

which bun && echo "use bun" || which pnpm && echo "use pnpm" || which yarn && echo "use yarn" || echo "use npm"
undefined
which bun && echo "use bun" || which pnpm && echo "use pnpm" || which yarn && echo "use yarn" || echo "use npm"
undefined

Scaffold (if no existing Next.js project)

项目初始化(如果没有现成的Next.js项目)

bash
undefined
bash
undefined

With bun:

使用bun:

bunx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" bun add html-to-image
bunx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" bun add html-to-image

With pnpm:

使用pnpm:

pnpx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" pnpm add html-to-image
pnpx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" pnpm add html-to-image

With yarn:

使用yarn:

yarn create next-app . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" yarn add html-to-image
yarn create next-app . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" yarn add html-to-image

With npm:

使用npm:

npx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" npm install html-to-image
undefined
npx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*" npm install html-to-image
undefined

Copy the Phone Mockup

复制手机样机

The skill includes a pre-measured iPhone mockup at
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.
本技能包含预测量好的iPhone样机,文件为
mockup.png
(和本SKILL.md文件同目录),将其复制到项目的
public/
目录下。

File Structure

文件结构

project/
├── public/
│   ├── mockup.png              # Phone frame (included with skill)
│   ├── app-icon.png            # User's app icon
│   └── screenshots/            # User's app screenshots
│       ├── home.png
│       ├── feature-1.png
│       └── ...
├── src/app/
│   ├── layout.tsx              # Font setup
│   └── page.tsx                # The screenshot generator (single file)
└── package.json
The entire generator is a single
page.tsx
file.
No routing, no extra layouts, no API routes.
project/
├── public/
│   ├── mockup.png              # 手机边框(本技能自带)
│   ├── app-icon.png            # 用户的应用图标
│   └── screenshots/            # 用户的应用截图
│       ├── home.png
│       ├── feature-1.png
│       └── ...
├── src/app/
│   ├── layout.tsx              # 字体配置
│   └── page.tsx                # 截图生成器(单文件实现)
└── package.json
整个生成器仅需一个
page.tsx
文件
,无需路由、额外布局或API路由。

Font Setup

字体配置

tsx
// 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>;
}
tsx
// src/app/layout.tsx
import { YourFont } from "next/font/google"; // 使用用户指定的字体
const font = YourFont({ subsets: ["latin"] });

export default function Layout({ children }: { children: React.ReactNode }) {
  return <html><body className={font.className}>{children}</body></html>;
}

Step 3: Plan the Slides

步骤3:规划幻灯片

Screenshot Framework (Narrative Arc)

截图框架(叙事弧)

Adapt this framework to the user's requested slide count. Not all slots are required — pick what fits:
SlotPurposeNotes
#1Hero / Main BenefitApp icon + tagline + home screen. This is the ONLY one most people see.
#2DifferentiatorWhat makes this app unique vs competitors
#3EcosystemWidgets, extensions, watch — beyond the main app. Skip if N/A.
#4+Core FeaturesOne feature per slide, most important first
2nd to lastTrust SignalIdentity/craft — "made for people who [X]"
LastMore FeaturesPills listing extras + coming soon. Skip if few features.
Rules:
  • Each slide sells ONE idea. Never two features on one slide.
  • Vary layouts across slides — never repeat the same template structure.
  • Include 1-2 contrast slides (inverted bg) for visual rhythm.
根据用户要求的幻灯片数量适配该框架,不需要用到所有模块,选择合适的即可:
位置用途说明
#1首屏/核心优势应用图标+宣传语+首页截图,大部分用户只会看到这张
#2差异化优势这款应用和竞品相比的独特之处
#3生态能力小组件、扩展、手表端等主应用之外的能力,不适用则跳过
#4+核心功能每张幻灯片讲一个功能,按优先级排序
倒数第二张信任信号身份/工艺定位——「为[某类人群]打造」
最后一张更多功能用标签列出额外功能+即将上线内容,功能少则跳过
规则:
  • 每张幻灯片只传递一个核心信息,绝对不要在一张幻灯片里放两个功能
  • 不同幻灯片的布局要多样化,不要重复使用相同的模板结构
  • 加入1-2张对比幻灯片(反转背景)增加视觉节奏

Step 4: Write Copy FIRST

步骤4:先写文案

Get all headlines approved before building layouts. Bad copy ruins good design.
搭建布局前先确认所有标题的文案,糟糕的文案会毁掉优秀的设计。

The Iron Rules

铁则

  1. One idea per headline. Never join two things with "and."
  2. Short, common words. 1-2 syllables. No jargon unless it's domain-specific.
  3. 3-5 words per line. Must be readable at thumbnail size in the App Store.
  4. Line breaks are intentional. Control where lines break with
    <br />
    .
  1. 每个标题只讲一个点,绝对不要用「和」连接两个内容
  2. 使用简短常用的词,1-2个音节,除非是领域专属术语,否则不要用行话
  3. 每行3-5个词,要保证在App Store的缩略图尺寸下也能清晰阅读
  4. 换行是故意设计的,用
    <br />
    控制换行位置

Three Approaches (pick one per slide)

三种文案思路(每张幻灯片选一种)

TypeWhat it doesExample
Paint a momentYou picture yourself doing it"Check your coffee without opening the app."
State an outcomeWhat your life looks like after"A home for every coffee you buy."
Kill a painName a problem and destroy it"Never waste a great bag of coffee."
类型作用示例
描绘场景让用户能代入使用场景「不用打开应用就能查看咖啡状态」
陈述结果展示使用后生活的变化「你买的每杯咖啡都有专属记录」
解决痛点点明问题并给出解决方案「再也不浪费好咖啡豆」

What NEVER Works

绝对不要用的文案

  • Feature lists as headlines: "Log every item with tags, categories, and notes"
  • Two ideas joined by "and": "Track X and never miss Y"
  • Compound clauses: "Save and customize X for every Y you own"
  • Vague aspirational: "Every item, tracked"
  • Marketing buzzwords: "AI-powered tips" (unless it's actually AI)
  • 把功能列表当标题:「用标签、分类和笔记记录每一项」
  • 用「和」连接两个想法:「跟踪X且永远不会错过Y」
  • 复合从句:「为你拥有的每个Y保存并自定义X」
  • 模糊的 aspirational 表述:「记录每一件物品」
  • 营销流行词:「AI驱动的建议」(除非真的用了AI)

Copy Process

文案流程

  1. Write 3 options per slide using the three approaches
  2. Read each at arm's length — if you can't parse it in 1 second, it's too complex
  3. Check: does each line have 3-5 words? If not, adjust line breaks
  4. Present options to the user with reasoning for each
  1. 每张幻灯片用三种思路各写3个版本
  2. 一臂距离阅读文案——如果1秒内看不懂,说明太复杂
  3. 检查:每行是不是3-5个词?如果不是,调整换行
  4. 把选项交给用户,并说明每个选项的理由

Reference Apps for Copy Style

文案风格参考应用

  • Raycast — specific, descriptive, one concrete value per slide
  • Turf — ultra-simple action verbs, conversational
  • Mela / Notion — warm, minimal, elegant
  • Raycast — 具体、描述性强,每张幻灯片传递一个明确价值
  • Turf — 极简动作动词,口语化
  • Mela / Notion — 温暖、极简、优雅

Step 5: Build the Page

步骤5:搭建页面

Architecture

架构

page.tsx
├── Constants (W, H, SIZES, design tokens from user's brand)
├── Phone component (mockup with screen overlay)
├── Caption component (label + headline)
├── Decorative components (blobs, glows, shapes — based on style direction)
├── Screenshot1..N components (one per slide)
├── SCREENSHOTS array (registry)
├── ScreenshotPreview (ResizeObserver scaling + hover export)
└── ScreenshotsPage (grid + toolbar + export logic)
page.tsx
├── 常量(W, H, SIZES, 来自用户品牌的设计token)
├── Phone组件(带屏幕覆盖层的样机)
├── Caption组件(标签+标题)
├── 装饰组件(色块、发光效果、形状——根据风格方向决定)
├── Screenshot1..N组件(每张幻灯片对应一个)
├── SCREENSHOTS数组(注册表)
├── ScreenshotPreview(ResizeObserver缩放+hover导出)
└── ScreenshotsPage(网格+工具栏+导出逻辑)

Export Sizes (Apple Required — iPhone only, portrait)

导出尺寸(苹果要求——仅iPhone竖屏)

typescript
const 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;
Design at the LARGEST size (1320x2868) and scale down for export.
typescript
const 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;
按最大尺寸(1320x2868)设计,导出时缩放即可。

Rendering Strategy

渲染策略

Each screenshot is designed at full resolution (1320x2868px). Two copies exist:
  1. Preview: CSS
    transform: scale()
    via ResizeObserver to fit a grid card
  2. Export: Offscreen at
    position: absolute; left: -9999px
    at true resolution
每张截图都按全分辨率(1320x2868px)设计,存在两个副本:
  1. 预览版:通过ResizeObserver使用CSS
    transform: scale()
    适配网格卡片大小
  2. 导出版:放在屏幕外
    position: absolute; left: -9999px
    的位置,保持真实分辨率

Phone Mockup Component

手机样机组件

The included
mockup.png
has these pre-measured values:
typescript
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 %
tsx
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>
  );
}
自带的
mockup.png
有以下预测量数值:
typescript
const MK_W = 1022;  // 样机图片宽度
const MK_H = 2082;  // 样机图片高度
const SC_L = (52 / MK_W) * 100;   // 屏幕左偏移百分比
const SC_T = (46 / MK_H) * 100;   // 屏幕上偏移百分比
const SC_W = (918 / MK_W) * 100;  // 屏幕宽度百分比
const SC_H = (1990 / MK_H) * 100; // 屏幕高度百分比
const SC_RX = (126 / 918) * 100;  // 圆角x百分比
const SC_RY = (126 / 1990) * 100; // 圆角y百分比
tsx
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>
  );
}

Typography (Resolution-Independent)

排版(分辨率无关)

All sizing relative to canvas width W:
ElementSizeWeightLine Height
Category label
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
所有尺寸都相对于画布宽度W:
元素尺寸字重行高
分类标签
W * 0.028
600(半粗体)默认
标题
W * 0.09
W * 0.1
700(粗体)1.0
首屏标题
W * 0.1
700(粗体)0.92

Phone Placement Patterns

手机放置模式

Vary across slides — NEVER use the same layout twice in a row:
Centered phone (hero, single-feature):
bottom: 0, width: "82-86%", translateX(-50%) translateY(12-14%)
Two phones layered (comparison):
Back: left: "-8%", width: "65%", rotate(-4deg), opacity: 0.55
Front: right: "-4%", width: "82%", translateY(10%)
Phone + floating elements (only if user provided component PNGs):
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.
不同幻灯片要变化,绝对不要连续使用相同布局:
手机居中(首屏、单功能页):
bottom: 0, width: "82-86%", translateX(-50%) translateY(12-14%)
两台手机层叠(对比场景):
后层:left: "-8%", width: "65%", rotate(-4deg), opacity: 0.55
前层:right: "-4%", width: "82%", translateY(10%)
手机+浮动元素(仅当用户提供了组件PNG时使用):
卡片不要遮挡手机的核心内容
放在边缘,轻微旋转(2-5度),加阴影
如果太分散注意力,就部分移出屏幕或者缩小尺寸

"More Features" Slide (Optional)

「更多功能」幻灯片(可选)

Dark/contrast background with app icon, headline ("And so much more."), and feature pills. Can include a "Coming Soon" section with dimmer pills.
深色/对比背景,搭配应用图标、标题(「还有更多功能」)和功能标签,可以加「即将上线」板块,用更浅的标签展示。

Step 6: Export

步骤6:导出

Why html-to-image, NOT html2canvas

为什么用html-to-image而不是html2canvas

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.
html2canvas
对CSS滤镜、渐变、drop-shadow、backdrop-filter和复杂裁剪的支持有问题,
html-to-image
使用原生浏览器SVG序列化,可以完美适配所有CSS效果。

Export Implementation

导出实现

typescript
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 = "";
typescript
import { toPng } from "html-to-image";

// 截图前:把元素移到屏幕内
el.style.left = "0px";
el.style.opacity = "1";
el.style.zIndex = "-1";

const opts = { width: W, height: H, pixelRatio: 1, cacheBust: true };

// 关键:两次调用技巧——第一次预加载字体/图片,第二次生成干净的输出
await toPng(el, opts);
const dataUrl = await toPng(el, opts);

// 截图后:移回屏幕外
el.style.left = "-9999px";
el.style.opacity = "";
el.style.zIndex = "";

Key Rules

核心规则

  • Double-call trick: First
    toPng()
    loads fonts/images lazily. Second produces clean output. Without this, exports are blank.
  • On-screen for capture: Temporarily move to
    left: 0
    before calling
    toPng
    .
  • Offscreen container: Use
    position: absolute; left: -9999px
    (not
    fixed
    ).
  • Resizing: Load data URL into Image, draw onto canvas at target size.
  • 300ms delay between sequential exports.
  • Set
    fontFamily
    on the offscreen container.
  • Numbered filenames: Prefix exports with zero-padded index so they sort correctly:
    01-hero-1320x2868.png
    ,
    02-freshness-1320x2868.png
    , etc. Use
    String(index + 1).padStart(2, "0")
    .
  • 两次调用技巧:第一次
    toPng()
    懒加载字体/图片,第二次生成干净的输出,没有这一步导出会是空白的
  • 截图时放在屏幕内:调用
    toPng
    前临时移动到
    left: 0
    位置
  • 屏幕外容器:使用
    position: absolute; left: -9999px
    (不要用
    fixed
  • 调整大小:把数据URL加载到Image对象,在canvas上绘制到目标尺寸
  • 连续导出之间间隔300ms
  • 给屏幕外容器设置
    fontFamily
  • 文件名编号:导出文件前缀加补零的序号,保证排序正确:
    01-hero-1320x2868.png
    02-freshness-1320x2868.png
    等,使用
    String(index + 1).padStart(2, "0")
    实现

Common Mistakes

常见问题

MistakeFix
All slides look the sameVary phone position (center, left, right, two-phone, no-phone)
Decorative elements invisibleIncrease size and opacity — better too visible than invisible
Copy is too complex"One second at arm's length" test
Floating elements block the phoneMove off-screen edges or above the phone
Plain white/black backgroundUse gradients — even subtle ones add depth
Too clutteredRemove floating elements, simplify to phone + caption
Too simple/emptyAdd larger decorative elements, floating items at edges
Headlines use "and"Split into two slides or pick one idea
No visual contrast across slidesMix light and dark backgrounds
Export is blankUse double-call trick; move element on-screen before capture
问题解决方法
所有幻灯片看起来都一样变化手机位置(居中、左、右、双手机、无手机)
装饰元素看不见增大尺寸和透明度——太显眼也比看不见好
文案太复杂用「一臂距离1秒可读」测试
浮动元素遮挡手机移到屏幕边缘或者手机上方
纯白色/黑色背景太单调用渐变——哪怕是很淡的渐变也能增加层次感
太拥挤移除浮动元素,简化为手机+标题
太空旷加更大的装饰元素,在边缘放浮动元素
标题里用了「和」拆成两张幻灯片或者只选一个点
幻灯片之间没有视觉对比混合使用亮暗背景
导出是空白的使用两次调用技巧,截图前把元素移到屏幕内