关于 appshot-core
供 Appshot 视频和截图技能使用的基础参考技能,定义项目架构、应用信息提取与品牌及界面分析流程。适用于需要基于应用上下文制作营销素材的工作流。
主要功能
- 01说明 Remotion 与 React 架构
- 02提取应用身份和品牌颜色
- 03识别功能与商店元数据
- 04分析导航和页面结构
- 05缓存提取的项目上下文
适用场景
- 01为视频制作准备应用上下文
- 02为截图生成提取品牌样式
- 03分析应用导航与页面清单
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ trunghaiy/ appshot --skill appshot-core安装后在 Claude Code 中输入/appshot-core确认已加载。
详细介绍
Appshot Core — 基础
所有 Appshot 技能的参考技能。在执行 appshot-videos 或 appshot-images 前,请先阅读本文。
架构
- Remotion 4.0 + React 18 + Tailwind CSS 3.4
- 配置仅定义应用元数据和品牌信息——场景是自定义
.tsx文件,而非配置项 - 每个项目都有一个编排组件(例如
FooPreview.tsx),通过 Remotion<Sequence>+<SceneWrap>按顺序排列自定义场景 - 所有基础组件都通过从配置的
BrandColors派生的brand属性适配品牌
项目上下文(自动注入)
- 包清单:!
cat package.json 2>/dev/null || cat pubspec.yaml 2>/dev/null || cat app.json 2>/dev/null - 应用颜色:!
find . -maxdepth 4 \( -name 'colors.xml' -o -name 'Colors.swift' -o -name 'colors.ts' -o -name 'theme.ts' -o -name 'Color.kt' -o -name 'tailwind.config.js' -o -name 'tailwind.config.ts' \) 2>/dev/null | head -5 | xargs head -50 2>/dev/null - 应用图标:!
find . -maxdepth 5 \( -name 'ic_launcher.png' -o -name 'icon.png' \) -type f 2>/dev/null | head -3 - 应用商店元数据:!
cat fastlane/metadata/en-US/full_description.txt 2>/dev/null || cat fastlane/metadata/en-US/description.txt 2>/dev/null || cat fastlane/metadata/android/en-US/full_description.txt 2>/dev/null - README 摘录:!
head -80 README.md 2>/dev/null
提取与上下文缓存
appshot-videos 和 appshot-images 都使用此提取流程。结果会缓存到 .appshot-context.json,因此每个项目只需运行一次提取。
检查缓存的上下文
扫描前,检查项目根目录中是否存在 .appshot-context.json。如果存在:
- 加载并向用户展示摘要
- 询问:“我找到了之前提取的内容。请确认它仍然正确,或重新扫描?”
- 如果用户确认,则跳过提取,继续调用技能的下一阶段
- 如果用户要求重新扫描,则运行下方的提取流程并覆盖该文件
提取步骤
步骤 1:检测框架
按以下顺序检查标记文件。匹配到第一个后即停止。
| 框架 | 标记文件 |
|---|---|
| Expo / React Native | app.json、app.config.ts、app.config.js |
| Flutter | pubspec.yaml |
| iOS(Swift) | *.xcodeproj/、*.xcworkspace/、Package.swift |
| Android(Kotlin/Java) | app/src/main/AndroidManifest.xml、build.gradle.kts、build.gradle |
| Next.js | next.config.js、next.config.ts、next.config.mjs |
| SvelteKit | svelte.config.js |
| Nuxt | nuxt.config.ts、nuxt.config.js |
| Astro | astro.config.mjs、astro.config.ts |
| Remix | remix.config.js、app/root.tsx(含 @remix-run 导入) |
| Vite(React/Vue/Solid) | vite.config.ts、vite.config.js(检查 package.json 中是否包含 react/vue/solid) |
使用 ls 或 find . -maxdepth 3 进行检查。如果都不匹配,请注明未检测到框架,然后继续——用户也可以通过口头描述应用。
步骤 2:提取应用身份信息
Expo / React Native:
- 名称:
app.json->expo.name或name - 描述/标语:
app.json->expo.description或description - Bundle ID:
app.json->expo.ios.bundleIdentifier或expo.android.package - 另请检查:
package.json->name、description
Flutter:
- 名称:
pubspec.yaml->name: - 描述:
pubspec.yaml->description: - 另请检查:
android/app/src/main/AndroidManifest.xml->android:label,ios/Runner/Info.plist->CFBundleDisplayName
iOS(Swift):
- 名称:
Info.plist->CFBundleDisplayName或CFBundleName - Bundle ID:
Info.plist->CFBundleIdentifier - 另请检查:在
project.pbxproj中查找产品名称
Android(Kotlin/Java):
- 名称:
app/src/main/res/values/strings.xml-><string name="app_name"> - Package:
AndroidManifest.xml->package属性 - 另请检查:
build.gradle.kts或build.gradle->applicationId
Next.js:
- 名称:
package.json→name;另请检查next.config.*→env.APP_NAME - 描述/标语:
package.json→description;另请检查app/layout.tsx→metadata.description - URL:检查
.env或.env.local→NEXT_PUBLIC_APP_URL、NEXT_PUBLIC_SITE_URL
SvelteKit:
- 名称:
package.json→name - 描述:
package.json→description;检查src/app.html→<title>、<meta name="description">
Vite(React/Vue):
- 名称:
package.json→name - 描述:
package.json→description;检查index.html→<title>、<meta name="description">
步骤 3:提取品牌颜色
搜索颜色定义。读取每个框架中找到的第一个匹配项。
Expo / React Native — 按顺序搜索以下路径:
src/theme.ts或src/theme.jssrc/colors.ts或src/constants/colors.tstheme/colors.ts或theme/index.tstailwind.config.js或tailwind.config.ts->theme.extend.colorsapp.json->expo.primaryColor、expo.backgroundColor
查找:具名颜色导出(primary、secondary、background、surface、accent、success、danger/error)、十六进制字符串、rgb() 值。
Flutter — 搜索以下路径:
lib/theme.dart或lib/theme/app_theme.dartlib/theme/colors.dart或lib/constants/colors.dartlib/main.dart->ThemeData()
查找:Color(0xFF...) 常量、primaryColor、accentColor、colorScheme 定义。
iOS(Swift) — 搜索以下路径:
*.xcassets/Colors/**/*.colorset/Contents.json— 解析components中的 RGB 值**/Theme.swift、**/Colors.swift或**/ColorScheme.swift
查找:静态 Color 属性、UIColor 十六进制初始化器、资源目录中的颜色定义。
Android — 搜索以下路径:
app/src/main/res/values/colors.xml—<color name="primary">#RRGGBB</color>app/src/main/res/values/themes.xml—<item name="colorPrimary">**/ui/theme/Color.kt— Jetpack ComposeColor()定义
Web(所有框架) — 按顺序搜索以下路径:
tailwind.config.js或tailwind.config.ts→theme.extend.colorssrc/styles/globals.css或src/app/globals.css→ CSS 自定义属性(--primary、--background等)src/theme.*或src/lib/theme.*src/styles/variables.css
步骤 4:查找应用图标
| 框架 | 查找位置 |
|---|---|
| Expo/RN | app.json -> expo.icon 字段(通常为 ./assets/icon.png) |
| Flutter | android/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png 或 ios/Runner/Assets.xcassets/AppIcon.appiconset/ |
| iOS | *.xcassets/AppIcon.appiconset/ -> 查找最大的 .png |
| Android | app/src/main/res/mipmap-xxxhdpi/ic_launcher.png |
| Web(全部) | public/favicon.svg、public/favicon.ico、public/logo.svg、public/logo.png、app/favicon.ico、src/assets/logo.* |
步骤 5:提取功能并存储元数据
这些来源适用于所有框架:
| 来源 | 路径 | 提取内容 |
|---|---|---|
| Fastlane(iOS) | fastlane/metadata/en-US/description.txt 或 full_description.txt | 完整的商店描述 |
| Fastlane(iOS) | fastlane/metadata/en-US/subtitle.txt | App Store 副标题 |
| Fastlane(iOS) | fastlane/metadata/en-US/keywords.txt | 搜索关键词 |
| Fastlane(Android) | fastlane/metadata/android/en-US/full_description.txt | Play Store 描述 |
| Fastlane(Android) | fastlane/metadata/android/en-US/short_description.txt | 简短描述 |
| README | README.md | 前 80 行——应用描述、功能要点 |
| CLAUDE.md | CLAUDE.md | 项目描述(如有) |
步骤 5b:提取 ASO 关键词
Apple 和 Google 现在可以通过视觉智能读取截图和视频中的文字。提取关键词列表,以便叠加文字自然地融入高价值搜索词。
主要来源: fastlane/metadata/en-US/keywords.txt(以逗号分隔)。这是开发者自己的关键词策略,应将其作为权威列表。
备用来源(若不存在 Fastlane 关键词):从以下来源推导出 8-12 个候选关键词:
- App Store 副标题(
fastlane/metadata/en-US/subtitle.txt)——拆分为单个词 - 商店描述——提取重复次数最多的名词和动作动词
- 提取出的功能名称——将功能名称对应到面向用户的搜索词
- 步骤 6 信号表中的应用类别术语
输出: 按重要性从高到低排列的关键词列表。包含单个词(“习惯”“连续记录”)和短语(“阅读追踪器”“读书记录”)。最多 12 个关键词。
步骤 6:推断应用类别
根据提取出的关键词、描述和功能名称,推断应用类别:
| 类别 | 信号 |
|---|---|
| 习惯追踪 | 连续记录、习惯、每日、日常、坚持、追踪天数 |
| 健身 | 锻炼、运动、健身房、次数、组数、卡路里、步数 |
| 金融 | 预算、支出、金钱、储蓄、交易、消费 |
| 生产力 | 任务、待办事项、笔记、项目、整理、日历 |
| 社交 | 聊天、消息、朋友、关注、帖子、分享 |
| 教育 | 学习、测验、复习、课程、抽认卡、练习 |
| 健康 | 冥想、睡眠、情绪、日记、健康、正念 |
| 旅行 | 旅行、行程、预订、目的地、探索 |
| 美食 | 食谱、餐食、餐厅、烹饪、食材 |
| SaaS | 仪表盘、订阅、方案、定价、引导流程、工作区、团队、分析 |
| 电子商务 | 购物车、结账、产品、购物、订单、支付、库存 |
| 开发者工具 | API、SDK、CLI、文档、体验场、沙盒、webhook |
| 内容/出版 | 博客、文章、帖子、发布、编辑器、CMS、新闻简报 |
| AI/ML | 模型、推理、提示词、生成、嵌入、向量、聊天、补全 |
| 社区 | 论坛、讨论、话题、社区、成员、个人资料 |
如果无法明确归类,请注明“未分类”——用户会进一步说明。
深度分析
不止了解应用的身份和颜色,还要弄清它实际能做什么。
屏幕清单。 扫描导航文件,找出所有屏幕:
- React Native / Expo:读取
App.tsx、所有*Navigator.tsx、*Router.tsx、标签栏配置、react-navigation设置 - Flutter:读取
lib/main.dart、路由配置、GoRouter或MaterialApp路由 - iOS:读取 storyboard 引用、
UITabBarController设置、SwiftUINavigationStack/TabView - 查找:屏幕名称、标签名称、导航结构(标签、堆栈、抽屉) 导航栏提取。 为了制作逼真的模拟屏幕,提取用户实际看到的导航模式:
- 导航类型:标签页、堆栈、抽屉或组合
- 标签栏:每个标签的文本、图标描述(例如“房屋图标”“放大镜”“圆形人像”),以及默认/首页标签
- 导航栏标题:标题样式(大标题或行内标题)、是否有返回按钮、右侧栏按钮项(例如“齿轮图标”“加号”“铃铛”)
- 状态栏样式:浅色内容(白色文本,适用于深色背景)或深色内容(黑色文本,适用于浅色背景)
- 主屏幕指示条:所有现代 iPhone 上都有(底部横条),旧款设备上没有
查找位置:
- React Native / Expo:导航器文件中的
screenOptions、tabBarLabel、tabBarIcon、headerTitle、headerRight、navigationOptions - Flutter:
BottomNavigationBar项、AppBar标题/操作项、Scaffold配置 - iOS:
UITabBarItem设置、navigationItem.title、navigationItem.rightBarButtonItems、SwiftUI 的.tabItem/.navigationTitle/.toolbar - Android:
BottomNavigationView菜单 XML、Toolbar/TopAppBar设置、Jetpack Compose 的NavigationBar项
页面/路由清单(Web)。 扫描路由配置,找出所有页面:
- Next.js:查看
app/目录结构(每个包含page.tsx的文件夹都是一个路由),或查看pages/目录 - SvelteKit:查看
src/routes/目录结构(每个包含+page.svelte的文件夹都是一个路由) - Vite/React:查看路由配置(
react-router、tanstack-router),或扫描路由定义 - 查找:页面名称、布局结构、受保护路由与公开路由
着陆页分析。 如果存在着陆页/营销页:
- 提取主视觉标题和副标题
- 提取功能介绍板块
- 记录 CTA 文案(“开始使用”“免费注册”等)
- 记录可见的定价结构
功能分析。 对发现的每个屏幕,阅读组件文件以了解:
- 它显示哪些数据
- 用户可以执行哪些操作
- 它在视觉上有何特色或独特之处
UI 模式提取。 扫描组件样式,了解应用的视觉设计语言。与真实应用外观不符的模拟屏幕会削弱可信度——这些信息可以弥合差距。
要提取的内容:
- 圆角半径:按钮、卡片和输入框的圆角半径(例如“全圆角按钮”“8px 卡片圆角”“胶囊形输入框”)
- 间距节奏:容器内及元素之间使用的内边距/外边距值(例如“水平内边距 16px”“板块间距 24px”)
- 字体排版:字体系列(自定义或系统字体)、标题字重(例如 700 或 800)、正文大小、应用是否使用全大写的板块标题
- 按钮样式:形状(圆角矩形、胶囊形、圆形)、尺寸、实心或描边、仅含图标的按钮
- 卡片样式:背景色、边框、阴影深度、玻璃/模糊效果
- 图标库:使用的图标集(SF Symbols、Material Icons、Lucide、自定义 SVG、Ionicons)
- 特殊 UI 元素:波形可视化器、进度环、渐变背景、标签/芯片样式、徽标指示器
查找位置:
- React Native / Expo:
StyleSheet.create()代码块、内联style={}、NativeWind/Tailwind 类、组件库导入项(react-native-paper、tamagui、gluestack) - Flutter:组件文件和主题数据中的
BoxDecoration、BorderRadius、TextStyle、ButtonStyle - iOS(Swift):SwiftUI/UIKit 中的
.cornerRadius、.font()、.padding()、UIFont描述符、SF Symbol 名称 - Android:
shapes.xml、styles.xml、Compose 的RoundedCornerShape()、MaterialTheme.typography、Material 图标导入项
价值主张/差异化优势。 从 README、商店描述、CLAUDE.md、营销文案或着陆页中提取:
- 此应用解决什么问题?
- 它与竞争产品有何不同?
- 它带来怎样的情感承诺? 核心操作循环。 确定用户最常执行的单一操作:
- 用户每次打开应用都会做什么?
- 需要点击或执行多少步?
- 完成操作后会收到什么反馈?
主题检测。 检查应用是否支持浅色/深色模式:
- 查找配置中的主题切换选项、
useColorScheme、Appearance、ThemeProvider、darkMode - 记录默认或主要主题
- 记录应用是否仅支持深色主题(某些品牌,如 Spotify、电影应用)
保存上下文
提取并获得用户确认后,将结果保存到项目根目录下的 .appshot-context.json:
{
"version": 2,
"extractedAt": "ISO timestamp",
"app": { "name": "", "tagline": "", "icon": "", "platform": "", "framework": "", "url": "string or null" },
"brand": { "primary": "", "primaryLight": "", "background": "", "surface": "", "textPrimary": "", "textSecondary": "", "success": "", "danger": "", "accent": "" },
"category": "string",
"theme": "light | dark | both",
"themeDefault": "light | dark",
"navigation": {
"type": "tabs | stack | drawer | tabs+stack",
"tabs": [{ "label": "", "iconDescription": "", "isDefault": false }],
"headerStyle": "largeTitle | inline",
"statusBarStyle": "light | dark"
},
"screens": [{ "name": "", "tab": "", "description": "" }],
"pages": [{ "route": "", "name": "", "description": "", "layout": "" }],
"landingPage": { "heroHeadline": "string or null", "heroSubheadline": "string or null", "cta": "string or null", "featureSections": ["string"] },
"uiPatterns": {
"borderRadius": { "button": "string", "card": "string", "input": "string" },
"spacing": { "horizontal": "string", "sectionGap": "string" },
"typography": { "fontFamily": "string or 'system'", "headingWeight": "number", "bodySize": "string", "capsHeaders": "boolean" },
"buttonStyle": "string — e.g., 'pill filled, 56px height' or 'rounded-lg outline'",
"cardStyle": "string — e.g., 'dark surface, 16px radius, no border, subtle shadow'",
"iconLibrary": "string — e.g., 'SF Symbols' or 'Ionicons' or 'custom SVG'",
"specialElements": ["string — e.g., 'waveform visualizer', 'gradient background', 'pill-shaped tags'"]
},
"features": ["string"],
"coreAction": "string",
"valueProps": ["string"],
"keywords": ["string — ranked ASO keywords, most important first, max 12"],
"screenshots": [{
"referenceFile": "original file path (for re-reading if needed)",
"screen": "matched screen name",
"description": "what this screenshot shows",
"visualSpec": "structured visual analysis — layout, colors, components, typography, special elements (the actual styling reference for mock UI)"
}],
"storeDescription": "string or null",
"sources": { "name": "source file", "colors": "source file", "keywords": "source file or 'derived'" }
}
架构版本 2 说明:
screenshots— 如果用户选择自动模拟,则为空数组;如果用户提供了真实截图,则填入截图app.framework有效值:"expo"|"flutter"|"ios"|"android"|"nextjs"|"sveltekit"|"nuxt"|"astro"|"remix"|"vite"|"web"app.platform有效值:"ios"|"android"|"both"|"web"pages— Web 路由清单(移动应用使用空数组)landingPage— 提取的落地页数据(移动应用为 null)
展示发现结果
简洁地总结发现。务必注明源文件,以便用户核实。
I scanned your project and found:
- **App:** [name] (from [source file])
- **Tagline:** [text] (from [source file])
- **Platform:** [iOS/Android/both] (from [evidence])
- **Colors:** primary [hex], background [hex], surface [hex], ... (from [source file])
- **Icon:** [file path]
- **Category:** [category] based on [keywords/description]
- **Store description:** [found at path / not found]
- **Theme:** [light only / dark only / both — default is X]
**Navigation:** [tabs / stack / drawer] — [header style: large title / inline]
**Tab bar:** [Tab1 (icon)] | [Tab2 (icon)] | [Tab3 (icon)] — default: [Tab1]
**Status bar:** [dark content / light content]
**Screen inventory:**
- [Tab 1]: [ScreenName] — [what it shows]
- [Tab 2]: [ScreenName] — [what it shows]
- [Modal/Detail]: [ScreenName] — [what it shows]
- ...
**Page inventory (web):**
- `/` — [Landing page] — [hero headline, feature sections]
- `/dashboard` — [Dashboard] — [what it shows]
- `/settings` — [Settings] — [configuration options]
- ...
**Landing page:** [hero headline] / [CTA text] / [number of feature sections]
**URL:** [extracted URL or "not found"]
**Key features:** [bullet list of 3-5 main features extracted from code/docs]
**Core action loop:** [what users do most — e.g., "tap Log -> enter page number -> save (3 taps, ~5 seconds)"]
**Value props:**
- [Primary differentiator]
- [Secondary differentiator]
- [Emotional promise]
**UI patterns:** [font family], [heading weight], [button style], [card style], [border radius], [icon library]
**Special elements:** [waveform, gradient bg, pill tags, ...]
**ASO keywords:** [keyword1], [keyword2], [keyword3], ... (from [fastlane/keywords.txt / derived])
**Pre-filled config:**
[Show the AppConfig object with app, brand, and video sections filled in]
Does this look right? Anything to correct before I proceed?
如果有字段缺失,请注明:“我找不到品牌配色——你有主题文件吗?还是我们一起挑选配色?”
只有获得确认后,才继续下面的截图收集步骤。
收集截图(可选)
确认提取结果后,询问用户是否想提供真实应用截图作为视觉参考。截图是参考材料,而非内容——绝不会嵌入最终输出。相反,视觉参考分析会从截图中提取准确的颜色、形状、间距和字体样式,供此技能用于构建看起来像真实应用的动态模拟 UI。
AskUserQuestion({
questions: [{
question: "Do you have screenshots of your app to use as screen content?",
header: "Screenshots",
options: [
{ label: "Yes, I'll provide screenshots", description: "Highest fidelity — your screenshots are analyzed and the mock UI is built to match exactly." },
{ label: "No, generate mock screens", description: "Skill builds UI from extracted code patterns. Good for most cases." }
],
multiSelect: false
}]
})
如果用户提供截图:
- 请用户提供截图文件路径,或将截图拖入对话
- 阅读每张截图图片,了解其展示的内容
- 将每张截图映射到提取结果中的一个屏幕:
I mapped your screenshots to app screens: - recording.png → Voice Recording screen - transcription.png → Transcription Detail screen - scan-preview.png → Document Scan screen - home.png → Home / Notes List screen Missing: Settings screen (will use mock UI for this one) Does this mapping look right? - 对每张截图运行视觉参考分析(见下文)
- 记录任何缺失项——没有截图的屏幕。这些屏幕将回退到基于
uiPatterns构建的模拟 UI。 - 将映射结果和视觉分析保存到
.appshot-context.json中的screenshots
视觉参考分析
重要: 提供截图时,此步骤为必做步骤。否则,模拟场景会显得千篇一律,无法匹配真实应用。
对于提供的每张截图,请仔细查看图片并编写结构化视觉规范。此规范有两种用途:
- 直接使用场景:通过
<Img>原样使用截图,因此规范用于确认截图展示的内容 - 模拟场景和补充缺失内容:当场景需要模拟 UI(用于动画、缺失的截图或修改后的内容)时,规范是样式的唯一依据
对于每张截图,请记录:
## [filename] → [Screen Name]
**Layout:**
- [top-to-bottom description of what appears: nav bar, content sections, bottom bar]
- [approximate vertical proportions: "nav bar 8%, content 78%, tab bar 8%, home indicator 6%"]
**Colors (sample from the image):**
- Background: [exact color, e.g., "#0A1628 — very dark navy, NOT pure black"]
- Cards/surfaces: [e.g., "#1A2940 — slightly lighter navy with subtle border"]
- Accent: [e.g., "#3BB8E0 — teal/cyan for CTAs and active states"]
- Text primary: [e.g., "#E8ECF1 — off-white"]
- Text secondary: [e.g., "#6B7A8D — muted blue-grey"]
- Highlight/badge: [e.g., "#E5C044 — warm yellow for 'Voice Recording' badge"]
**Components (describe exactly what you see):**
- Back button: [e.g., "48px rounded-square (#1A2940 bg), white chevron, no text"]
- Nav actions: [e.g., "three icon buttons: more (⋯), share (↑), delete (🗑), same rounded-square style"]
- Type badge: [e.g., "'Voice Recording' in yellow text on dark pill, microphone emoji prefix"]
- Content cards: [e.g., "rounded-xl (~16px), #1A2940 bg, 1px border #2A3A50, 20px padding"]
- Buttons: [e.g., "'Enhance · 2 credits' — outlined, teal border, rounded-full, 48px height"]
- Tab bar: [e.g., "3 tabs with SF Symbol icons above labels, active=teal, inactive=grey, dark bg"]
**Typography:**
- Headings: [e.g., "32px, weight 700, white — 'May 28 at 11:03 PM'"]
- Section headers: [e.g., "ALL CAPS, 13px, weight 600, letter-spacing 1px, teal — 'TRANSCRIPTION'"]
- Body: [e.g., "16px, weight 400, off-white, line-height ~1.5"]
- Labels: [e.g., "12px, weight 500, muted grey — metadata, tab labels"]
**Special elements:**
- [e.g., "Waveform: grey rounded-rect container, horizontal bars, blue play button 48px circle"]
- [e.g., "Speed pills: row of 4 (0.75x, 1x, 1.5x, 2x), active has teal bg, others have dark bg"]
- [e.g., "Coral/red horizontal divider line below title, 2px height"]
- [e.g., "Vietnamese flag emoji + 'VI' in language selector pill"]
分析规则:
- 详尽无遗。 列出截图中可见的每一个不同 UI 元素,并统计数量。如果看到 15 个元素,就记录 15 个元素。常被遗漏的元素包括:导航栏中的所有操作按钮(不只是返回按钮)、标签栏图标、速度/播放控件、CTA、徽章指示器、额度计数器、语言选择器、分隔线、主屏幕指示条。
- 精确描述颜色——从实际图片中取样,不要猜测。“#0A1628”和“#000000”相差很大。
- 精确描述组件形状:“48px 圆角方形,圆角半径为 12px”,而不是“圆角按钮”。
- 记录间距:“卡片内边距为 20px”“元素之间间隔 12px”。
- 记录不存在的元素:“卡片没有阴影”“按钮没有边框”“列表项之间没有分隔线”。
- 当视觉规范与代码提取的
uiPatterns冲突时,以本分析为准——截图才是视觉真实依据。 - 视觉规范中的尺寸以手机逻辑像素计。 886px 的 App Store Preview 画布宽度是 iPhone 16 Pro(393px)的 2.25 倍。编写场景代码时,所有尺寸都必须乘以 2.25:48px 的按钮变为 108px,16px 的文字变为 36px,11px 的标签文字变为 25px。代码指南中有完整的缩放表。
截图要求(告知用户):
- 任意分辨率均可——这些是参考图片,不会嵌入输出
- 画面干净——不要有通知或调试横幅(这样分析才能捕捉到真实设计)
- 每种不同的屏幕/功能提供一张截图。同一屏幕的多个状态也欢迎提供。
- 此技能会分析你的截图,并构建与应用外观精准匹配的动画模拟 UI。
如果用户拒绝,设置 screenshots: [] 并继续。此技能会像之前一样,根据提取的模式生成模拟 UI。
完成此步骤后,继续执行调用此技能的下一个阶段。
项目结构
src/
app-config.ts # App metadata, brand colors, video settings
Root.tsx # Remotion entry — registers the composition
[AppName]Preview.tsx # Orchestrator — sequences scenes with <Sequence> + <SceneWrap>
scenes/
S1_[Name].tsx # Custom scene components (one per scene)
S2_[Name].tsx
...
components/ # Shared primitives library (imported from template)
PhoneFrame.tsx # Device frame with bezel, Dynamic Island, spring entrance
AmbientBackground.tsx # Animated gradient bg with floating orbs
Caption.tsx # Word-by-word animated caption with frosted pill
FadeIn.tsx # Directional spring fade-in wrapper
SceneWrap.tsx # Fade envelope for scene transitions
AppIcon.tsx # App icon with optional glow pulse
TypeWriter.tsx # Character-by-character text reveal
BrowserFrame.tsx # macOS browser chrome (light/dark)
AnimatedCursor.tsx # Bezier-interpolated cursor with click ripple
index.ts
config.ts # Type definitions, device dimensions, defaults
public/
icon.png # App icon
*.png # Screenshots, assets
配置架构
配置包含应用标识、品牌颜色和视频设置,不包含场景——场景是自定义组件。
interface AppConfig {
app: {
name: string; // Display name
tagline: string; // One-line value prop
icon: string; // Filename in public/
platform: "ios" | "android" | "both";
};
brand: BrandColors;
video: {
fps: number; // 30
width: number; // 886
height: number; // 1920
device: DevicePreset;
backgroundMusic?: string; // Filename in public/
backgroundMusicVolume?: number; // 0-1
};
}
interface BrandColors {
primary: string; // Main brand color (hex)
primaryLight: string; // Tinted background
background: string; // App background
surface: string; // Card/container background
textPrimary: string; // Main text
textSecondary: string; // Secondary/muted text
success: string; // Positive states
danger: string; // Destructive/error states
accent?: string; // Optional secondary brand color
}
基础组件库
以下每个组件都可在 src/components/ 中使用。请从 "../components" 导入。
PhoneFrame
设备模型包含边框、侧边按钮和刘海/灵动岛。可包裹任意内容,使其看起来像真实的手机屏幕。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
children | ReactNode | 必填 | 屏幕内容 |
device | DevicePreset | "iphone-15" | 设备型号 |
delay | number | 0 | 入场动画延迟(帧) |
scale | number | 1 | 缩放倍数 |
screenBackground | string | "#FAFAF8" | 屏幕内的背景颜色 |
适用场景: 在手机中展示应用 UI 的场景。是移动应用视频的核心视觉锚点。
AmbientBackground
带有浮动模糊光球的动态渐变背景,为每个场景营造氛围。
| 属性 | 类型 | 默认值 | 描述 |
|---|---|---|---|
brand | BrandColors | 必填 | 渐变和光球的颜色 |
variant | "light" | "medium" | "deep" | "dark" | "light" | 强度/氛围 |
适用场景: 用于每个场景的背景。在功能/解决方案场景中使用 "light" 或 "medium";在问题/对比场景中使用 "dark";在情感/证明场景中使用 "deep"。
Caption
锚定在底部的文字叠加层,带有逐词入场动画。深色胶囊背景,便于阅读。
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | required | 字幕文本(最多 8 个词) |
delay | number | 0 | 入场延迟(帧) |
fontSize | number | 44 | 字体大小(px) |
maxWidth | number | 820 | 容器最大宽度 |
使用场景: 每个场景都应包含一个字幕。字幕能为关闭声音的观众讲述故事。
FadeIn
使用 Remotion 弹簧动画,为任意元素添加带方向的淡入动画。
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | 要添加动画的内容 |
delay | number | 0 | 开始帧 |
direction | "up" | "down" | "left" | "right" | "none" | "up" | 滑入方向 |
distance | number | 40 | 滑动距离(px) |
className | string | — | Tailwind 类 |
style | CSSProperties | — | 内联样式 |
使用场景: 依次显示卡片、标题入场,以及任何需要播放入场动画的元素。
SceneWrap
为每个场景添加淡入和淡出过渡(各 12 帧)。由编排器使用。
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | 场景内容 |
durationInFrames | number | required | 场景总时长 |
fadeIn | boolean | true | 启用淡入 |
fadeOut | boolean | true | 启用淡出 |
使用场景: 在编排器组件中,将每个场景包裹在 <Sequence> + <SceneWrap> 中。
AppIcon
渲染应用图标,并可选添加动画发光效果。
| Prop | Type | Default | Description |
|---|---|---|---|
src | string | required | public/ 中的文件名或 URL |
size | number | 120 | 图标大小(px) |
glow | boolean | false | 启用脉冲发光效果 |
glowColor | string | "rgba(0,122,255,0.4)" | 发光颜色 |
使用场景: CTA/收尾场景。提升品牌辨识度。
TypeWriter
逐字符显示文本,并可选显示闪烁光标。
| Prop | Type | Default | Description |
|---|---|---|---|
text | string | required | 要逐字输入的完整文本 |
startFrame | number | required | 开始输入的帧 |
charsPerFrame | number | 2 | 输入速度 |
className | string | — | Tailwind 类 |
cursor | boolean | true | 显示闪烁光标 |
cursorColor | string | "#2196F3" | 光标颜色 |
使用场景: 搜索栏、文本输入框、聊天消息,以及任何需要模拟用户输入的场景。
BrowserFrame
macOS 风格的浏览器外框,包含红黄绿按钮、地址栏和内容区域。提供浅色和深色版本。
| Prop | Type | Default | Description |
|---|---|---|---|
children | ReactNode | required | 页面内容 |
url | string | "app.example.com" | 地址栏文本 |
delay | number | 0 | 入场动画延迟(帧) |
scale | number | 1 | 缩放倍率 |
variant | "light" | "dark" | "light" | 浏览器外观主题 |
AnimatedCursor
使用贝塞尔插值在关键帧位置之间移动的光标,带有点击涟漪效果。
| Prop | Type | Default | Description |
|---|---|---|---|
keyframes | CursorKeyframe[] | required | { frame, x, y, click? } 位置 |
visible | boolean | true | 显示/隐藏光标 |
动画模式
已移除的原语(FloatingCard、StatCard、HeatMap、ProgressBar、AppStoreBadge、IconSet)只是简单的包装器,会通过硬编码尺寸限制 Agent。请改为直接内联编写这些模式,并根据画布适当缩放:
动画计数器(前后对比数据):
const counter = spring({ frame, fps, delay: 10, config: { mass: 0.6, damping: 18, stiffness: 80 } });
const value = Math.round(interpolate(counter, [0, 1], [beforeValue, afterValue]));
逐格填充网格(热力图/贡献网格):
{cells.map((cell, i) => {
const cellEnter = spring({ frame, fps, delay: baseDelay + i * 0.4, config: { mass: 0.2, damping: 10, stiffness: 180 } });
return <div key={i} style={{ opacity: 0.3 + cellEnter * 0.7, transform: `scale(${0.6 + cellEnter * 0.4})` }} />;
})}
进度条填充:
const progress = interpolate(frame, [delay, delay + 30], [0, targetPercent], { extrapolateRight: "clamp" });
<div style={{ width: `${progress}%`, height, backgroundColor: color, borderRadius }} />
弹簧入场卡片(替代 FloatingCard):
const entrance = spring({ frame, fps, delay, config: { mass: 0.8, damping: 14, stiffness: 120 } });
<div style={{ opacity: entrance, transform: `translateY(${(1 - entrance) * 20}px)`, borderRadius, padding, background }}>
App Store/Play Store 徽章(用于营销 CTA 场景):
// iOS badge — black rounded rect with Apple SVG
<div style={{ background: "#000", borderRadius: 14, padding: "14px 28px", display: "flex", alignItems: "center", gap: 12 }}>
<svg width="28" height="34" viewBox="0 0 28 34" fill="white">
<path d="M23.2 17.8c0-3.6 2.9-5.3 3-5.4-1.6-2.4-4.2-2.7-5.1-2.8-2.1-.2-4.2 1.3-5.3 1.3-1.1 0-2.8-1.2-4.6-1.2C8.5 9.8 6 11.2 4.6 13.5c-2.8 4.9-.7 12.1 2 16.1 1.3 1.9 2.9 4.1 5 4 2-.1 2.8-1.3 5.2-1.3 2.4 0 3.1 1.3 5.2 1.3 2.2 0 3.5-2 4.8-3.9 1.5-2.2 2.1-4.3 2.2-4.4 0-.1-4.2-1.6-4.2-6.4zM19.3 7.5c1.1-1.3 1.8-3.2 1.6-5-1.6.1-3.4 1-4.6 2.3-1 1.2-1.9 3-1.6 4.8 1.7.2 3.5-.9 4.6-2.1z" />
</svg>
<div>
<div style={{ fontSize: 16, color: "rgba(255,255,255,0.8)" }}>Download on the</div>
<div style={{ fontSize: 28, fontWeight: 600, color: "#FFF" }}>App Store</div>
</div>
</div>
设备预设
| 预设 | 屏幕 | 刘海 | 商店 |
|---|---|---|---|
iphone-16-pro | 393x852 | 灵动岛 | App Store |
iphone-15 | 375x812 | 灵动岛 | App Store |
ipad-pro-13 | 1024x1366 | 无 | App Store(iPad) |
pixel-9 | 412x915 | 打孔 | Play Store |
商店与设备的对应关系
| 商店 | Composition ID 后缀 | 设备预设 |
|---|---|---|
| App Store | -AppStore | iphone-16-pro |
| Play Store | -PlayStore | pixel-9 |
对于多商店项目,Root.tsx 会为每个商店注册一个 <Composition>。场景共用,只有设备边框(以及导航栏样式)不同。两者的画布均为 886×1920。注意:App Store Preview 视频的 CTA 中不包含商店徽章(在商店列表中重复显示);营销视频的 CTA 中会内嵌商店徽章(SVG 代码片段见上文的动画模式)。
输出目标
AppShot 支持两种输出目标,视觉规则各不相同:
| 目标 | 用途 | 设备边框 | 叠加文字 | 屏幕内容 |
|---|---|---|---|---|
| App Store Preview | App Store/Play Store 视频预览和屏幕截图 | 不带设备边框。 应用屏幕铺满整个画布,延伸至边缘。 | 文字叠加在应用屏幕上方(使用半透明胶囊背景以便阅读)。 | 必须包含导航栏(状态栏、导航栏、标签栏),看起来像真实截图。 |
| Marketing | 社交媒体、网站、演示文稿、广告 | 使用 PhoneFrame 包裹应用屏幕的设备边框。 | 文字和卡片显示在设备边框的旁边或周围。 | 导航栏可选(PhoneFrame 已提供“设备”场景)。 |
**为何这很重要:**如果 App Store 预览中包含设备边框,或未充分展示应用的实际使用情况,Apple 会拒绝该预览。App Store Preview 目标可确保符合要求。Marketing 目标则保留了当前精致的外观,适用于非商店场景。
默认值:appshot-videos 和 appshot-images 使用 App Store Preview。社交媒体/网页场景使用 Marketing。
用户选择 App Store Preview 目标时:
- 不要在任何场景中使用 PhoneFrame
- 应用 UI 填满 886×1920 画布(视频)或商店要求的尺寸(屏幕截图)
- 每个模拟屏幕都应包含状态栏、导航栏、标签栏和主屏幕指示条
- 字幕/叠加文字使用半透明胶囊背景,以便在应用 UI 上清晰可读
- 最终效果应类似带有文字叠加的屏幕录制
用户选择 Marketing 目标时:
- 像之前一样,以 1.5 的缩放比例使用 PhoneFrame
- 文字、卡片和徽章显示在设备边框的外部/周围
- 手机内的导航栏有则更好,但不是必需的
商店要求
视频
| 平台 | 尺寸 | 时长 | 格式 |
|---|---|---|---|
| iPhone 6.7" | 886x1920 | 15-30s | H.264, MP4/MOV |
| iPhone 6.1" | 886x1920 | 15-30s | H.264, MP4/MOV |
| iPad 13" | 1200x1600 | 15-30s | H.264, MP4/MOV |
| Google Play | 886x1920 | 15-30s | H.264, MP4 |
默认输出为 886×1920。此画布适用于 App Store(iPhone 6.7″ 原生尺寸)和 Google Play。appshot-videos 中的所有尺寸规则均按此画布宽度进行校准。
截图
iOS App Store:
| 设备 | 尺寸(纵向) | 必需 |
|---|---|---|
| iPhone 6.9" | 1320x2868 | 是(主要尺寸,必须提供) |
| iPhone 6.7" | 1290x2796 | 可选(Apple 会根据 6.9" 尺寸缩放) |
| iPhone 6.5" | 1242x2688 | 可选(旧款) |
| iPad 13" | 2064x2752 | 应用支持 iPad 时必需 |
Google Play:
| 设备 | 尺寸(纵向) | 备注 |
|---|---|---|
| 手机 | 1080x1920 | 推荐标准尺寸 |
| 7" 平板电脑 | 1200x1920 | 应用面向 7" 平板电脑时使用 |
| 10" 平板电脑 | 1600x2560 | 应用面向 10" 平板电脑时使用 |
项目设置
该技能会在第 3 阶段代码生成期间,在目标项目中搭建 appshot-video/ 目录。完整的搭建步骤请参阅 code-guide.md。
cd appshot-video
npm run dev # Preview at localhost:3000
npm run build # Render to out/AppPreview.mp4
编排器模式
编排器使用 Remotion <Sequence> 按顺序编排场景,并使用 <SceneWrap> 包裹每个场景,以实现淡入淡出过渡。
import { Composition, Sequence } from "remotion";
import { SceneWrap } from "./components";
import { S1_Hook } from "./scenes/S1_Hook";
import { S2_Feature } from "./scenes/S2_Feature";
import { S3_CTA } from "./scenes/S3_CTA";
const scenes = [
{ component: S1_Hook, duration: 120 },
{ component: S2_Feature, duration: 150 },
{ component: S3_CTA, duration: 120 },
];
export const AppPreview: React.FC = () => {
let offset = 0;
return (
<>
{scenes.map(({ component: Scene, duration }, i) => {
const from = offset;
offset += duration;
return (
<Sequence key={i} from={from} durationInFrames={duration}>
<SceneWrap durationInFrames={duration}>
<Scene />
</SceneWrap>
</Sequence>
);
})}
</>
);
};
共享资源
- 文案原则:copy-principles.md
- 提取逻辑已内置于此技能中(请参阅上文的 Extraction & Context Caching)