app-store-screenshots
App StoreやGoogle Play向けのスクリーンショットページの構築、iOS/Androidアプリのマーケティング用スクリーンショットの生成、またはNext.jsを使ったプログラム的なスクリーンショットジェネレーターの作成時に活用します。アプリストア・プレイストア向けのスクリーンショット、マーケティング素材、html-to-image、スマホモックアップ、フィーチャーグラフィックなどに対応します。
description の原文を見る
Use when building App Store or Google Play screenshot pages, generating exportable marketing screenshots for iOS and/or Android apps, or creating programmatic screenshot generators with Next.js. Triggers on app store, play store, screenshots, marketing assets, html-to-image, phone mockup, android screenshots, feature graphic.
SKILL.md 本文
App Store & Google Play スクリーンショットジェネレータ
概要
App Store および Google Play のスクリーンショットを広告(UI のショーケースではなく)としてレンダリングし、html-to-image を使用して Apple および Google の必須解像度で書き出す Next.js ページを構築します。スクリーンショットは両方のストアで最も重要なコンバージョンアセットです。
デフォルトでサポートされているデバイス:
- iPhone(ポートレート) — Apple App Store
- iPad(ポートレート) — Apple App Store
- Android Phone(ポートレート) — Google Play
- Android Tablet 7"(ポートレート + ランドスケープ) — Google Play
- Android Tablet 10"(ポートレート + ランドスケープ) — Google Play
- Feature Graphic(ランドスケープバナー、1024×500) — Google Play ストア一覧ヘッダー
コア原則
スクリーンショットは広告であり、ドキュメントではありません。 すべてのスクリーンショットは 1 つのアイデアを売ります。UI を表示している場合は間違っています。あなたが売っているのは感情、結果、またはペインポイントを取り除くことです。
ステップ 1: ユーザーにこれらの質問をします
コードを書く前に、これらすべてについてユーザーに尋ねてください。回答を得るまで先に進まないでください:
必須
- アプリのスクリーンショット — 「あなたのアプリのスクリーンショットはどこにありますか?(実際のデバイスキャプチャの PNG ファイル)」
- アプリアイコン — 「あなたのアプリアイコン PNG はどこにありますか?」
- ブランドカラー — 「あなたのブランドカラーは何ですか?(アクセントカラー、テキストカラー、背景の好み)」
- フォント — 「あなたのアプリはどのフォントを使用していますか?(またはスクリーンショット用にどのフォントを希望しますか?)」
- 機能リスト — 「優先順に、アプリの機能を挙げてください。あなたのアプリが行う最初のことは何ですか?」
- スライド数 — 「何個のスクリーンショットを希望しますか?(Apple は最大 10、Google Play は最大 8)」
- スタイルの方向 — 「どのようなスタイルが希望ですか?例: warm/organic、dark/moody、clean/minimal、bold/colorful、gradient-heavy、flat。App Store のスクリーンショットリファレンスがあれば共有してください。」
オプション
- ターゲットストア — 「Apple App Store のみ、Google Play のみ、それとも両方をターゲットにしていますか?これはどのデバイスのスクリーンショットを生成するかを決定します。」
- iPad スクリーンショット — 「iPad スクリーンショットもありますか?ある場合は、iPad App Store スクリーンショットも生成します(ユニバーサルアプリではお勧めです)。」
- Android タブレットスクリーンショット — 「Android タブレットのスクリーンショットはありますか?ある場合は、どのタブレットサイズですか - 7"、10"、またはその両方ですか?ポートレート、ランドスケープ、またはその両方の向きですか?」
- Feature Graphic — 「Google Play Feature Graphic(Google Play ストア一覧の上部に表示される 1024×500 バナー)を希望しますか?これは電話のスクリーンショットとは別です。」
- コンポーネントアセット — 「浮いている装飾として使用したい UI 要素 PNG(カード、ウィジェット等)がありますか?ない場合は、スキップして構いません。」
- ローカライズされたスクリーンショット — 「複数の言語でスクリーンショットを希望しますか?これはアプリが英語のみの場合でも、地域の App Store で一覧をランク付けするのに役立ちます。ある場合: どの言語ですか?(例:en、de、es、pt、ja、ar、he)」
- テーマプリセットシステム — 「1 つのアート方向が希望ですか、それとも再利用可能なビジュアルテーマ(例:clean-light、dark-bold、warm-editorial)でスクリーンショットのルックをすばやく切り替えることができるようにしますか?」
- 追加の指示 — 「特定の要件、制約、または好みはありますか?」
回答から導き出されたもの(聞かないこと — 自分で決定してください)
ユーザーのスタイル方向、ブランドカラー、アプリの美学に基づいて、決定してください:
- 背景スタイル: グラデーション方向、色、ライトまたはダークベースかどうか
- 装飾的な要素: blob、glow、幾何学的な形、またはなし — スタイルに合わせる
- ダーク対ライトスライド: 各数、どの機能がダーク処理に適しているか
- タイポグラフィ処理: 太さ、トラッキング、行の高さ — ブランドの個性に合わせる
- カラーパレット: ブランドカラーからテキストカラー、二次色、影の色合いを導き出す
- テーマプリセット名: 漠然としたスタイルリクエストをユーザーが切り替えられる再利用可能なテーマ ID に変換する
- RTL の動作: ロケールが RTL(
ar、he、fa、ur)の場合、テキストを翻訳するだけでなく、意図的にレイアウトをミラーリングする - ランドスケープスライドレイアウト: タブレットランドスケープスライドの場合、caption-left + device-right 構成を使用(横に 2 つのタブレットを配置しようとしないこと — 水平方向に十分なスペースがない)
重要: ユーザーがプロセス中のいつでも追加の指示を与える場合は、それに従ってください。ユーザーの指示は常にスキルのデフォルトを上書きします。
ステップ 2: プロジェクトをセットアップします
パッケージマネージャーを検出
利用可能なものをチェックして、この優先順位を使用してください: bun > pnpm > yarn > npm
# 順番にチェック
which bun && echo "use bun" || which pnpm && echo "use pnpm" || which yarn && echo "use yarn" || echo "use npm"
スキャフォルド(既存の Next.js プロジェクトがない場合)
# bun の場合:
bunx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
bun add html-to-image
# pnpm の場合:
pnpx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
pnpm add html-to-image
# yarn の場合:
yarn create next-app . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
yarn add html-to-image
# npm の場合:
npx create-next-app@latest . --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
npm install html-to-image
iPhone モックアップをコピーします
このスキルには、mockup.png の事前測定済み iPhone モックアップが付属しています(この SKILL.md と同じ場所)。プロジェクトの public/ ディレクトリにコピーしてください。その他すべてのデバイスフレーム(Android Phone、Android Tablet、iPad)は CSS でレンダリングされます — 追加のモックアップ PNG は不要です。
ファイル構造
iPhone のみのアプリ(デフォルト)
project/
├── public/
│ ├── mockup.png # iPhone フレーム(スキルに含まれる)
│ ├── app-icon.png # ユーザーのアプリアイコン
│ └── screenshots/
│ ├── en/
│ │ ├── home.png
│ │ ├── feature-1.png
│ │ └── ...
│ ├── de/
│ └── {locale}/
├── src/app/
│ ├── layout.tsx # フォント設定
│ └── page.tsx # スクリーンショットジェネレータ(単一ファイル)
└── package.json
iPad スクリーンショットもローカライズされている場合は、同じロケール構造をミラーリングします:
└── screenshots-ipad/
├── en/
├── de/
└── {locale}/
単一言語アプリはロケールフォルダ全体を省略できます — パスは screenshots/home.png になります。
マルチプラットフォームアプリ(iOS + Android)
ユーザーが Apple と Android の両方のスクリーンショットを必要とする場合は、プラットフォームベースの構造を使用してください。すべてのデバイスの画像が明確に分離されます:
└── screenshots/
├── apple/
│ ├── iphone/
│ │ ├── en/
│ │ └── {locale}/
│ └── ipad/
│ ├── en/
│ └── {locale}/
└── android/
├── phone/
│ ├── en/
│ └── {locale}/
├── tablet-7/
│ ├── portrait/
│ │ └── {locale}/
│ └── landscape/
│ └── {locale}/
└── tablet-10/
├── portrait/
│ └── {locale}/
└── landscape/
└── {locale}/
実際にスクリーンショットがあるデバイスのサブディレクトリのみを作成してください。 空のディレクトリはジェネレータで壊れた画像プレースホルダーを引き起こします。
デフォルトでは iPhone のみの構造を使用してください。 ユーザーが Android もターゲットにしていることを確認したときだけ、プラットフォームベースの構造に切り替えてください。
ジェネレータ全体は単一の page.tsx ファイルです。 ルーティング、追加レイアウト、API ルートはありません。
マルチ言語: ロケール選択
LOCALES 配列とツールバーに <select> ロケールピッカーを追加します。すべてのスライド src は base 変数を使用します — ハードコードされたロケールパスはありません:
const LOCALES = ["en", "de", "es", "tr"] as const; // 定義されたロケールを使用
type Locale = typeof LOCALES[number];
// ScreenshotsPage で:
const [locale, setLocale] = useState<Locale>("en");
// base はロケールからデバイスごとに導出されます:
const base = (platform: string) => `/screenshots/${platform}/${locale}`;
// ツールバー:
<select value={locale} onChange={e => setLocale(e.target.value as Locale)}>
{LOCALES.map(l => <option key={l} value={l}>{l.toUpperCase()}</option>)}
</select>
// すべてのスライドで — 単一および複数言語の間で変更なし:
<Phone src={`${base("apple/iphone")}/home.png`} alt="Home" />
言語用に <select> を使用してください。インラインタブではなく、多くの言語にスケーリングできても、ツールバーをオーバーフローさせません。
テーマプリセット
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 [themeId, setThemeId] = useState<ThemeId>("clean-light");
const theme = THEMES[themeId];
ハードコードされた色の代わりに、すべての場所でテーマトークンを使用します。
フォント設定
// 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>;
}
ステップ 3: スライドを計画します
スクリーンショットフレームワーク(ナラティブアーク)
このフレームワークをユーザーがリクエストしたスライド数に調整します。すべてのスロットが必須ではありません — 適切なものを選択します:
| スロット | 目的 | 注記 |
|---|---|---|
| #1 | Hero / メイン利点 | アプリアイコン + キャッチコピー + ホーム画面。これはほとんどの人が見る ONLY です。 |
| #2 | 差別化 | このアプリが競合他社と異なる点 |
| #3 | エコシステム | ウィジェット、拡張、ウォッチ — メインアプリを超えて。該当しない場合はスキップします。 |
| #4+ | コア機能 | 1 スライドに 1 機能、最も重要なものが最初 |
| 2 番目から最後 | トラストシグナル | アイデンティティ/クラフト — 「[X] 人向けに作られた」 |
| 最後 | その他の機能 | 追加およびコミングソーンのピルをリストします。機能が少ない場合はスキップします。 |
ルール:
- 各スライドは 1 つのアイデアを売ります。「and」で 2 つの機能を結合しないでください。
- スライド間でレイアウトを変動させます — テンプレートの構造を繰り返さないでください。
- 1~2 のコントラストスライド(反転背景)を視覚的なリズムのために含めます。
- ランドスケープタブレット: caption-left + device-right レイアウトを使用します。広いキャンバスは非対称構成を報酬します。横に 2 つのデバイスを配置しようとしないこと — スペースが足りません。
ステップ 4: まずコピーを書きます
レイアウトを構築する前に、すべてのヘッドラインを承認してもらいます。悪いコピーは良いデザインを台無しにします。
アイアンルール
- ヘッドラインごとに 1 つのアイデア。 「and」で 2 つのものを結合しないでください。
- 短い、一般的な単語。 1~2 音節。ドメイン固有でない限り、専門用語はありません。
- 行ごとに 3~5 語。 App Store のサムネイルサイズで読み取り可能である必要があります。
- 改行は意図的です。
<br />で改行する場所を制御します。
3 つのアプローチ(スライドごとに 1 つ選択)
| タイプ | 機能 | 例 |
|---|---|---|
| 瞬間を描く | あなたはそれをしている自分をイメージします | 「アプリを開かずにコーヒーをチェックしてください。」 |
| 結果を述べる | あなたの人生は後でこのように見えます | 「あなたが買ったすべてのコーヒーのための家。」 |
| 痛みを殺す | 問題に名前を付けて、それを破壊します | 「素晴らしいコーヒー豆を無駄にしないでください。」 |
決して機能しないもの
- ヘッドラインとしての機能リスト: 「タグ、カテゴリ、メモを使用してすべてのアイテムをログに記録する」
- 「and」で結合された 2 つのアイデア: 「X を追跡して Y を決して逃さない」
- 漠然とした志向的: 「すべてのアイテム、追跡」
- マーケティングバズワード: 「AI 搭載のヒント」(実際に AI でない限り)
弱いから良いヘッドライン例
| 弱い | 良い | 勝つ理由 |
|---|---|---|
| 習慣を追跡して動機を保つ | あなたのストリークを生き続けてください | 1 つのアイデア、より速くパース |
| AI 要約でタスクを整理する | メモを次のステップに変える | 結果優先、専門用語が少ない |
| タグとお気に入りでレシピを保存 | 夜ご飯を見つけて速く | 利点を売る、UI ではない |
| 予算を管理して支払いを逃さない | お金がどこに行くかを見る | より清潔な約束、二重要求なし |
コピープロセス
- スライドごとに 3 つのアプローチを使用して 3 つのオプションを書く
- 腕の距離で各オプションを読む — 1 秒で解析できないと、複雑すぎます
- チェック: 各行に 3~5 語がありますか?ない場合は、改行を調整します
- 各オプションの理由を含めてオプションをユーザーに提示します
例のプロンプト形
ユーザーが弱いまたは不十分な要求を与える場合は、内部的に以下のようなものに再形成します:
私の習慣追跡アプリの App Store スクリーンショットを構築してください。
このアプリは、シンプルな日々のルーティンとの一貫性を保つのに役立ちます。
6 スライド、クリーン/最小限のスタイル、ウォーム中立色、落ち着いたプレミアム感が必要です。
個人金融アプリの App Store スクリーンショットを生成してください。
アプリの主な長所は、高速な費用キャプチャ、明確な月間トレンド、共有予算です。
シャープでモダンなスタイル、高コントラスト、7 スライドが必要です。
AI メモ取りアプリの書き出し可能な App Store スクリーンショットを作成してください。
コア価値は、乱雑な音声メモをクリーンなサマリーと実行項目に変えることです。
太字のコピー、暗い背景、ポーランドされたテック先進的なルックが必要です。
パターンは:
- アプリのカテゴリ + コア結果
- 優先順の上位機能
- 希望するスライド数
- スタイルの方向
ローカライズルール
- 結果が長いまたは奇妙になった場合、ヘッドラインを文字通り翻訳しないでください — ターゲット市場向けに再作成します。
- ロケールごとの改行を再度チェックします。ドイツ語、フランス語、ポルトガル語は多くの場合、より短い請求が必要です。
- RTL 言語(
ar、he、fa、ur)の場合、キャンバスにdir="rtl"を設定し、非対称レイアウトを意図的にミラーリングします。
コピースタイルのリファレンスアプリ
- Raycast — 具体的で、説明的で、スライドごとに 1 つの具体的な値
- Turf — 超シンプルなアクション動詞、会話的
- Mela / Notion — ウォーム、最小限、エレガント
ステップ 5: ページを構築します
アーキテクチャ
page.tsx
├── 定数(キャンバス寸法、書き出しサイズ、フレーム比率)
├── 幅計算関数(phoneW、tabletPW、tabletLW、ipadW)
├── LOCALES / RTL_LOCALES / THEMES / COPY_BY_LOCALE
├── 画像プリロードキャッシュ(preloadAllImages + img() ヘルパー)
├── デバイスフレームコンポーネント:
│ ├── Phone — iPhone(mockup.png + 事前測定オーバーレイ)
│ ├── AndroidPhone — Android 電話(CSS のみ)
│ ├── AndroidTabletP — Android タブレットポートレート(CSS のみ)
│ ├── AndroidTabletL — Android タブレットランドスケープ(CSS のみ)
│ └── IPad — iPad(CSS のみ)
├── Caption コンポーネント(ラベル + ヘッドラインから canvasW にスケーリング)
├── 装飾的なコンポーネント(blob、glow — スタイル方向に基づいて)
├── スライドコンポーネント(ポートレート用 makeSlide1..N ファクトリ、
│ ランドスケープ用 makeTabLSlide1..N ファクトリ)
├── スライドレジストリ(IPHONE_SLIDES、ANDROID_SLIDES、ANDROID_7P_SLIDES、
│ ANDROID_7L_SLIDES、ANDROID_10P_SLIDES、ANDROID_10L_SLIDES、IPAD_SLIDES)
├── ScreenshotPreview — ResizeObserver スケーリング + hover 書き出し
└── ScreenshotsPage — グリッド + ツールバー + 書き出しロジック
キャンバス寸法
各デバイスカテゴリの最大必須解像度で設計してください。より小さいサイズは、書き出し時にターゲット解像度で再レンダリングして実現します。
// Apple
const W = 1320; const H = 2868; // iPhone(6.9" — 最大必須)
const IPAD_W = 2064; const IPAD_H = 2752; // iPad 13" — 最大必須
// Android 電話
const AW = 1080; const AH = 1920; // Android 電話
// Android タブレット — ポートレート
const AT7P_W = 1200; const AT7P_H = 1920; // 7" ポートレート
const AT10P_W = 1600; const AT10P_H = 2560; // 10" ポートレート
// Android タブレット — ランドスケープ
const AT7L_W = 1920; const AT7L_H = 1200; // 7" ランドスケープ
const AT10L_W = 2560; const AT10L_H = 1600; // 10" ランドスケープ
// Feature Graphic
const FGW = 1024; const FGH = 500;
書き出しサイズ
iPhone(Apple 必須、ポートレート)
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;
iPad(Apple 必須、ポートレート)
const IPAD_SIZES = [
{ label: '13" iPad', w: 2064, h: 2752 },
{ label: '12.9" iPad Pro', w: 2048, h: 2732 },
] as const;
Android(Google Play 推奨)
const ANDROID_SIZES = [{ label: "Phone", w: 1080, h: 1920 }] as const;
const ANDROID_7P_SIZES = [{ label: '7" Portrait', w: 1200, h: 1920 }] as const;
const ANDROID_7L_SIZES = [{ label: '7" Landscape', w: 1920, h: 1200 }] as const;
const ANDROID_10P_SIZES= [{ label: '10" Portrait', w: 1600, h: 2560 }] as const;
const ANDROID_10L_SIZES= [{ label: '10" Landscape', w: 2560, h: 1600 }] as const;
const FG_SIZES = [{ label: "Feature Graphic", w: 1024, h: 500 }] as const;
デバイスタイプ
type Device = "iphone" | "android" | "android-7" | "android-10" | "ipad" | "feature-graphic";
type Orientation = "portrait" | "landscape";
フレームアスペクト比
const MK_RATIO = 1022 / 2082; // iPhone モックアップ(幅/高さ)
const TAB_P_RATIO = 0.667; // タブレットポートレートフレーム(5:8 画面)
const TAB_L_RATIO = 1.5; // タブレットランドスケープフレーム(8:5 画面)
const IPAD_RATIO = 0.770; // iPad フレーム(770/1000)
幅計算関数
これらの関数は、デバイスフレームをキャンバスに対して相対的にレンダリングする方法を決定します。キャンバスのアスペクト比に関係なく、デバイスがキャンバスに比例して満たされるように自動スケーリングします:
type WidthFn = (cW: number, cH: number) => number;
// キャンバス幅の分数を返す(0–1)
function phoneW(cW: number, cH: number, clamp = 0.84) {
return Math.min(clamp, 0.72 * (cH / cW) * MK_RATIO);
}
function phoneW2(cW: number, cH: number) { return phoneW(cW, cH, 0.66); } // より小さい、2 つ電話スライド用
function tabletPW(cW: number, cH: number, clamp = 0.80) {
return Math.min(clamp, 0.72 * (cH / cW) * TAB_P_RATIO);
}
function tabletPW2(cW: number, cH: number) { return tabletPW(cW, cH, 0.64); }
function tabletLW(cW: number, cH: number, clamp = 0.62) {
return Math.min(clamp, 0.75 * (cH / cW) * TAB_L_RATIO);
}
function ipadW(cW: number, cH: number, clamp = 0.75) {
return Math.min(clamp, 0.72 * (cH / cW) * IPAD_RATIO);
}
function ipadW2(cW: number, cH: number) { return ipadW(cW, cH, 0.60); }
使用: width: \${phoneW(cW, cH) * 100}%``
レンダリング戦略
各スクリーンショットは完全な解像度で設計されます。2 つのコピーが存在します:
- プレビュー:
ResizeObserver経由の CSStransform: scale()をグリッドカードにフィットさせます - 書き出し:
position: absolute; left: -9999pxで真の解像度でオフスクリーン
重要: ページ全体を overflowX: "hidden" でラップして、オフスクリーン書き出し要素が水平スクロールを引き起こすのを防ぎます:
<div style={{ minHeight: "100vh", background: "#f3f4f6", position: "relative", overflowX: "hidden" }}>
デバイスフレームコンポーネント
iPhone(PNG モックアップ)
付属の mockup.png には以下の事前測定値があります:
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; // border-radius x %
const SC_RY = (126 / 1990) * 100; // border-radius y %
function Phone({ src, alt, style }: { src: string; alt: string; style?: React.CSSProperties }) {
return (
<div style={{ position: "relative", aspectRatio: `${MK_W}/${MK_H}`, ...style }}>
<img src={img("/mockup.png")} alt="" style={{ display: "block", width: "100%", height: "100%" }} draggable={false} />
<div style={{
position: "absolute", zIndex: 10, overflow: "hidden",
left: `${SC_L}%`, top: `${SC_T}%`, width: `${SC_W}%`, height: `${SC_H}%`,
borderRadius: `${SC_RX}% / ${SC_RY}%`,
}}>
<img src={src} alt={alt} style={{ display: "block", width: "100%", height: "100%", objectFit: "cover", objectPosition: "top" }} draggable={false} />
</div>
</div>
);
}
Android 電話(CSS のみ)
function AndroidPhone({ src, alt, style }: { src: string; alt: string; style?: React.CSSProperties }) {
return (
<div style={{ position: "relative", aspectRatio: "9/19.5", ...style }}>
<div style={{
width: "100%", height: "100%",
borderRadius: "8% / 4%",
background: "linear-gradient(160deg, #2a2a2e 0%, #18181b 100%)",
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.08), 0 8px 40px rgba(0,0,0,0.55)",
position: "relative", overflow: "hidden",
}}>
{/* パンチホールカメラ */}
<div style={{
position: "absolute", top: "1.5%", left: "50%",
transform: "translateX(-50%)", width: "3%", height: "1.4%",
borderRadius: "50%", background: "#0d0d0f",
border: "1px solid rgba(255,255,255,0.06)", zIndex: 20,
}} />
{/* スクリーン */}
<div style={{
position: "absolute", left: "3.5%", top: "2%",
width: "93%", height: "96%",
borderRadius: "5.5% / 2.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>
);
}
Android タブレット — ポートレート(CSS のみ)
function AndroidTabletP({ src, alt, style }: { src: string; alt: string; style?: React.CSSProperties }) {
return (
<div style={{ position: "relative", aspectRatio: "5/8", ...style }}>
<div style={{
width: "100%", height: "100%",
borderRadius: "4.5% / 2.8%",
background: "linear-gradient(160deg, #2a2a2e 0%, #18181b 100%)",
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.08), 0 8px 48px rgba(0,0,0,0.6)",
position: "relative", overflow: "hidden",
}}>
{/* カメラドット */}
<div style={{
position: "absolute", top: "1.2%", left: "50%",
transform: "translateX(-50%)", width: "1.4%", height: "0.88%",
borderRadius: "50%", background: "#0d0d0f",
border: "1px solid rgba(255,255,255,0.07)", zIndex: 20,
}} />
{/* ベゼルハイライト */}
<div style={{
position: "absolute", inset: 0, borderRadius: "4.5% / 2.8%",
border: "1px solid rgba(255,255,255,0.05)", pointerEvents: "none", zIndex: 15,
}} />
{/* スクリーン */}
<div style={{
position: "absolute", left: "3.5%", top: "2.2%",
width: "93%", height: "95.6%",
borderRadius: "2.5% / 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>
);
}
Android タブレット — ランドスケープ(CSS のみ)
ポートレートと同じですが、回転したアスペクト比とカメラが左側に:
function AndroidTabletL({ src, alt, style }: { src: string; alt: string; style?: React.CSSProperties }) {
return (
<div style={{ position: "relative", aspectRatio: "8/5", ...style }}>
<div style={{
width: "100%", height: "100%",
borderRadius: "2.8% / 4.5%",
background: "linear-gradient(160deg, #2a2a2e 0%, #18181b 100%)",
boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.08), 0 8px 48px rgba(0,0,0,0.6)",
position: "relative", overflow: "hidden",
}}>
{/* カメラドット — ランドスケープの左側 */}
<div style={{
position: "absolute", left: "1.2%", top: "50%",
transform: "translateY(-50%)", width: "0.88%", height: "1.4%",
borderRadius: "50%", background: "#0d0d0f",
border: "1px solid rgba(255,255,255,0.07)", zIndex: 20,
}} />
<div style={{
position: "absolute", inset: 0, borderRadius: "2.8% / 4.5%",
border: "1px solid rgba(255,255,255,0.05)", pointerEvents: "none", zIndex: 15,
}} />
{/* スクリーン */}
<div style={{
position: "absolute", left: "2.2%", top: "3.5%",
width: "95.6%", height: "93%",
borderRadius: "1.6% / 2.5%", 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>
);
}
iPad(CSS のみ)
重要な寸法: フレームアスペクト比は 770/1000 である必要があります。これにより、内側のスクリーン(92% × 94.4%)は iPad スクリーンショットの 3:4 アスペクト比に一致します。
function IPad({ src, alt, style }: { src: string; alt: string; style?: React.CSSProperties }) {
return (
<div style={{ position: "relative", 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)",
}}>
<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,
}} />
<div style={{
position: "absolute", inset: 0, borderRadius: "5% / 3.6%",
border: "1px solid rgba(255,255,255,0.06)", pointerEvents: "none", zIndex: 15,
}} />
<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>
);
}
スライドファクトリーパターン
すべてのデバイス用に別のスライドコンポーネントを書く代わりに、ファクトリー関数を使用します。各ファクトリは、デバイスコンポーネント、その幅関数、スクリーンショットベースパス、フレーム比を受け入れます:
type SlideProps = { cW: number; cH: number; locale: string };
type SlideDef = { id: string; component: (p: SlideProps) => JSX.Element };
type PhoneComp = (p: { src: string; alt: string; style?: React.CSSProperties }) => JSX.Element;
function makeSlide1(
PhoneComp: PhoneComp,
widthFn: WidthFn,
basePath: string,
_frameRatio: number,
): SlideDef {
return {
id: "hero",
component: ({ cW, cH }) => {
const fw = widthFn(cW, cH) * 100;
return (
<div style={{ width: "100%", height: "100%", position: "relative", background: "...", overflow: "hidden" }}>
<Caption cW={cW} label="YOUR APP" headline={<>"Sell one<br />idea here."</>} />
<PhoneComp
src={img(`/${basePath}/home.png`)}
alt="Home"
style={{
position: "absolute", bottom: 0,
width: `${fw}%`,
left: "50%", transform: `translateX(-50%) translateY(13%)`,
}}
/>
</div>
);
},
};
}
同じシグネチャで makeSlide2..N を構築します。次にレジストリを構築します:
const mkTabP = (base: string) => [
makeSlide1(AndroidTabletP, tabletPW, base, TAB_P_RATIO),
makeSlide2(AndroidTabletP, tabletPW, base, TAB_P_RATIO),
// ...
];
const mkTabL = (base: string) => [
makeTabLSlide1(AndroidTabletL, tabletLW, base),
makeTabLSlide2(AndroidTabletL, tabletLW, base),
// ...
];
const IPHONE_SLIDES = [makeSlide1(Phone, phoneW, "screenshots/apple/iphone", MK_RATIO), ...];
const ANDROID_SLIDES = [makeSlide1(AndroidPhone, phoneW, "screenshots/android/phone", MK_RATIO), ...];
const ANDROID_7P_SLIDES = mkTabP("screenshots/android/tablet-7/portrait");
const ANDROID_7L_SLIDES = mkTabL("screenshots/android/tablet-7/landscape");
const ANDROID_10P_SLIDES = mkTabP("screenshots/android/tablet-10/portrait");
const ANDROID_10L_SLIDES = mkTabL("screenshots/android/tablet-10/landscape");
const IPAD_SLIDES = [makeSlide1(IPad, ipadW, "screenshots/apple/ipad", IPAD_RATIO), ...];
ランドスケープスライドレイアウト
ランドスケープタブレットキャンバスは広い(例:2560×1600)。caption-left + device-right レイアウトを使用します。横に 2 つのデバイスを配置しようとしないこと — スペースが足りません。
function makeTabLSlide1(PhoneComp: PhoneComp, widthFn: WidthFn, basePath: string): SlideDef {
return {
id: "hero-landscape",
component: ({ cW, cH }) => {
const fw = widthFn(cW, cH) * 100;
return (
<div style={{ width: "100%", height: "100%", position: "relative", background: "...", overflow: "hidden" }}>
{/* キャプション — キャンバスの左 34% */}
<div style={{ position: "absolute", top: "50%", left: "5%", width: "34%", transform: "translateY(-50%)" }}>
<Caption cW={cW} label="FEATURE" headline={<>"One idea<br />per slide."</>} />
</div>
{/* デバイス — 右側 */}
<PhoneComp
src={img(`/${basePath}/home.png`)}
alt="Home"
style={{
position: "absolute",
right: "-3%",
top: "50%",
width: `${fw}%`,
transform: "translateY(-50%)",
}}
/>
</div>
);
},
};
}
Feature Graphic コンポーネント
Feature Graphic は、Google Play ストア一覧の上部に表示される1024×500 ランドスケープバナーです。デバイスフレームがありません — これはアプリ名、タグライン、アイコン、装飾要素を含む純粋なグラフィックです。
function FeatureGraphicSlide({ cW, cH }: { cW: number; cH: number }) {
return (
<div style={{
width: "100%", height: "100%", position: "relative", overflow: "hidden",
background: "linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%)",
display: "flex", alignItems: "center", justifyContent: "space-between",
padding: `0 ${cW * 0.06}px`,
}}>
{/* 左: アプリアイコン + 名前 + タグライン */}
<div style={{ display: "flex", alignItems: "center", gap: cW * 0.03 }}>
<img src={img("/app-icon.png")} alt="App Icon"
style={{ width: cW * 0.12, height: cW * 0.12, borderRadius: cW * 0.022 }}
draggable={false} />
<div>
<div style={{ fontSize: cW * 0.05, fontWeight: 800, color: "#fff", lineHeight: 1.1 }}>AppName</div>
<div style={{ fontSize: cW * 0.025, color: "rgba(255,255,255,0.7)", marginTop: cW * 0.008 }}>Your tagline here.</div>
</div>
</div>
{/* 右: 装飾的 / サポート画像 */}
</div>
);
}
デバイス解像度ディスパッチ
メインコンポーネントで、現在のデバイス + 向き状態からキャンバス寸法、書き出しサイズ、スライドレジストリを導出します:
const { cW, cH, currentSizes, slides } = (() => {
if (device === "android-7") {
return orientation === "landscape"
? { cW: AT7L_W, cH: AT7L_H, currentSizes: ANDROID_7L_SIZES, slides: ANDROID_7L_SLIDES }
: { cW: AT7P_W, cH: AT7P_H, currentSizes: ANDROID_7P_SIZES, slides: ANDROID_7P_SLIDES };
}
if (device === "android-10") {
return orientation === "landscape"
? { cW: AT10L_W, cH: AT10L_H, currentSizes: ANDROID_10L_SIZES, slides: ANDROID_10L_SLIDES }
: { cW: AT10P_W, cH: AT10P_H, currentSizes: ANDROID_10P_SIZES, slides: ANDROID_10P_SLIDES };
}
if (device === "android") return { cW: AW, cH: AH, currentSizes: ANDROID_SIZES, slides: ANDROID_SLIDES };
if (device === "ipad") return { cW: IPAD_W, cH: IPAD_H, currentSizes: IPAD_SIZES, slides: IPAD_SLIDES };
if (device === "feature-graphic") return { cW: FGW, cH: FGH, currentSizes: FG_SIZES, slides: [FG_SLIDE] };
return { cW: W, cH: H, currentSizes: IPHONE_SIZES, slides: IPHONE_SLIDES };
})();
ツールバーレイアウト
ツールバーには 2 つのセクションがあります: スクロール可能なコントロール領域(左、flex: 1)と固定書き出しボタン(右、常に表示)。それらを 1 つのスクロール可能な行にラップしないでください — ボタンは常に到達可能である必要があります。
{/* ツールバー */}
<div style={{ position: "sticky", top: 0, zIndex: 50, background: "white", borderBottom: "1px solid #e5e7eb", display: "flex", alignItems: "center" }}>
{/* スクロール可能なコントロール */}
<div style={{ flex: 1, display: "flex", alignItems: "center", gap: 10, padding: "10px 16px", overflowX: "auto", minWidth: 0 }}>
<span style={{ fontWeight: 700, fontSize: 14, whiteSpace: "nowrap" }}>My App · Screenshots</span>
{/* ロケール */}
<select value={locale} onChange={e => setLocale(e.target.value as Locale)} style={{ fontSize: 12, border: "1px solid #e5e7eb", borderRadius: 6, padding: "5px 10px" }}>
{LOCALES.map(l => <option key={l} value={l}>{l.toUpperCase()}</option>)}
</select>
{/* デバイスタブ */}
<div style={{ display: "flex", gap: 4, background: "#f3f4f6", borderRadius: 8, padding: 4, flexShrink: 0 }}>
{(["iphone", "android", "ipad", "feature-graphic"] as Device[]).map(d => (
<button key={d} onClick={() => { setDevice(d); setSizeIdx(0); }}
style={{ padding: "4px 14px", borderRadius: 6, border: "none", cursor: "pointer", fontSize: 12, fontWeight: 600, whiteSpace: "nowrap", background: device === d ? "white" : "transparent", color: device === d ? "#2563eb" : "#6b7280" }}>
{d === "iphone" ? "iPhone" : d === "android" ? "Android" : d === "ipad" ? "iPad" : "Feature Graphic"}
</button>
))}
{/* Android タブレットドロップダウン — デバイスタブグループ内 */}
<select
value={isTablet ? device : ""}
onChange={e => { if (e.target.value) { setDevice(e.target.value as Device); setSizeIdx(0); } }}
style={{ fontSize: 12, border: "none", borderRadius: 6, padding: "4px 10px", cursor: "pointer", background: isTablet ? "white" : "transparent", color: isTablet ? "#2563eb" : "#6b7280" }}>
<option value="" disabled>Android Tab.</option>
<option value="android-7">Android 7"</option>
<option value="android-10">Android 10"</option>
</select>
</div>
{/* 向き — タブレットのみ */}
{isTablet && (
<div style={{ display: "flex", gap: 4, background: "#f3f4f6", borderRadius: 8, padding: 4, flexShrink: 0 }}>
{(["portrait", "landscape"] as Orientation[]).map(o => (
<button key={o} onClick={() => { setOrientation(o); setSizeIdx(0); }}
style={{ padding: "4px 12px", borderRadius: 6, border: "none", cursor: "pointer", fontSize: 12, fontWeight: 600, background: orientation === o ? "white" : "transparent", color: orientation === o ? "#2563eb" : "#6b7280" }}>
{o === "portrait" ? "Portrait ↕" : "Landscape ↔"}
</button>
))}
</div>
)}
{/* 書き出しサイズ */}
{device !== "feature-graphic" && (
<select value={sizeIdx} onChange={e => setSizeIdx(Number(e.target.value))} style={{ fontSize: 12, border: "1px solid #e5e7eb", borderRadius: 6, padding: "4px 10px" }}>
{currentSizes.map((s, i) => <option key={i} value={i}>{s.label} — {s.w}×{s.h}</option>)}
</select>
)}
</div>
{/* 書き出しボタン — 常に右端、スクロールしない */}
<div style={{ flexShrink: 0, padding: "10px 16px", borderLeft: "1px solid #e5e7eb" }}>
<button onClick={exportAll} disabled={!!exporting}
style={{ padding: "7px 20px", background: exporting ? "#93c5fd" : "#2563eb", color: "white", border: "none", borderRadius: 8, fontSize: 12, fontWeight: 600, cursor: exporting ? "default" : "pointer", whiteSpace: "nowrap" }}>
{exporting ? `Exporting… ${exporting}` : "Export All"}
</button>
</div>
</div>
isTablet ヘルパー:
const isTablet = device === "android-7" || device === "android-10";
タイポグラフィ(解像度非依存)
キャンバス幅 cW に対して相対的なサイズ:
| 要素 | サイズ | 太さ | 行の高さ |
|---|---|---|---|
| カテゴリラベル | cW * 0.028 | 600 | デフォルト |
| ヘッドライン | cW * 0.09 から cW * 0.1 | 700 | 1.0 |
| ヒーローヘッドライン | cW * 0.1 | 700 | 0.92 |
| Feature Graphic 名 | cW * 0.05 | 800 | 1.1 |
電話配置パターン(ポートレート)
スライド間で変動します — 同じレイアウトを連続して 2 回使用しないでください:
デバイスを中央に配置(ヒーロー、単一機能):
bottom: 0、width: "82-86%"(電話)/ "70-75%"(タブレット)/ "65-70%"(iPad)
left: "50%"、transform: "translateX(-50%) translateY(13%)"
2 つのデバイスが層状(比較):
背面: left: "-8%"、width: "65%"、rotate(-4deg)、opacity: 0.55
前面: right: "-4%"、width: "82%"、translateY(10%)
ランドスケープタブレット(常に caption-left + device-right):
キャプション: position: absolute、top: 50%、left: 5%、width: 34%、transform: translateY(-50%)
デバイス: position: absolute、right: "-3%"、top: 50%、width: fw%、transform: translateY(-50%)
「その他の機能」スライド(オプション)
ダーク/コントラスト背景にアプリアイコン、ヘッドライン(「そしてもっと。」)、機能ピル。「コミングソーン」セクションを含めることができます。より暗いピル。すべてのデバイスタイプで同じに機能します。
ステップ 6: 書き出します
なぜ html2canvas ではなく html-to-image か
html2canvas は CSS フィルター、グラデーション、drop-shadow、backdrop-filter、複雑なクリッピングを破壊します。`html-to-image
ライセンス: MIT(寛容ライセンスのため全文を引用しています) · 原本リポジトリ
詳細情報
- 作者
- parthjadhav
- ライセンス
- MIT
- 最終更新
- 不明
Source: https://github.com/parthjadhav/app-store-screenshots / ライセンス: MIT
関連スキル
doubt-driven-development
重要な判断はすべて、本番環境への展開前に新しい視点から対抗的レビューを実施します。速度より正確性が重要な場合、不慣れなコードを扱う場合、本番環境・セキュリティに関わるロジック・取り消し不可の操作など影響度が高い場合、または後でバグを修正するよりも今検証する方が効率的な場合に活用してください。
apprun-skills
TypeScriptを使用したAppRunアプリケーションのMVU設計に関する総合的なガイダンスが得られます。コンポーネントパターン、イベントハンドリング、状態管理(非同期ジェネレータを含む)、パラメータと保護機能を備えたルーティング・ナビゲーション、vistestを使用したテストに対応しています。AppRunコンポーネントの設計・レビュー、ルートの配線、状態フローの管理、AppRunテストの作成時に活用してください。
desloppify
コードベースのヘルスチェックと技術負債の追跡ツールです。コード品質、技術負債、デッドコード、大規模ファイル、ゴッドクラス、重複関数、コードスメル、命名規則の問題、インポートサイクル、結合度の問題についてユーザーが質問した場合に使用してください。また、ヘルススコアの確認、次の改善項目の提案、クリーンアップ計画の作成をリクエストされた際にも対応します。29言語に対応しています。
debugging-and-error-recovery
テストが失敗したり、ビルドが壊れたり、動作が期待と異なったり、予期しないエラーが発生したりした場合に、体系的な根本原因デバッグをガイドします。推測ではなく、根本原因を見つけて修正するための体系的なアプローチが必要な場合に使用してください。
test-driven-development
テスト駆動開発により実装を進めます。ロジックの実装、バグの修正、動作の変更など、あらゆる場面で活用できます。コードが正常に動作することを証明する必要がある場合、バグ報告を受けた場合、既存機能を修正する予定がある場合に使用してください。
incremental-implementation
変更を段階的に実施します。複数のファイルに影響する機能や変更を実装する場合に使用してください。大量のコードを一度に書こうとしている場合や、タスクが一度では完結できないほど大きい場合に活用します。