RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
Never invoke implicitly; use only when explicitly asked for Play Store assets. Builds or updates RIPDPI's marketing screenshots and feature graphic (Next.js generator in play-store-screenshots/).
インストールする前に、エージェントに与えられる指示の中身を確認できます。
Build or update the Next.js page in play-store-screenshots/ that renders Google Play Store screenshots as designed store artwork with accurate app UI and exports them via html-to-image + Puppeteer batch capture at Google Play's required resolutions.
Google Play constraints:
Each screenshot communicates one supported capability. Show the real app experience, and prioritize actual UI in the first three screenshots. Preserve the captured state, warnings and disabled controls. Use short headings to explain the capability without promising an unverified outcome. Do not invent settings, measurements or active connections.
Follow the Google Play preview-asset requirements. They take precedence over older marketing examples in this skill.
The generator already exists at play-store-screenshots/. Check if it needs updating rather than scaffolding from scratch. For the current file layout, asset sources and resolutions, brand/logo state, and the two-stage raw-capture-to-rendered-output pipeline, read play-store-screenshots/AGENTS.md first -- it is the canonical, actively-verified source for those facts; do not re-derive them here.
Screenshot quality floor: only use the high-res Stage-1 captures in public/screenshots/ (see AGENTS.md for the current resolution and file list). The Roborazzi test screenshots under app/src/test/screenshots/ are too low-resolution for Play Store quality (420x900 to 720x920) -- if a screen is only available as a test screenshot, use a text-focused slide instead.
All colors come from the project's design system (DESIGN.md / RipDpiExtendedColors). The Play Store screenshots use these tokens for brand consistency.
Brand-palette note: the repo-root DESIGN.md (alpha) is monochrome-first light (#FAFAFA background, #1A1A1A foreground). Marketing slides honor the design system: light, monochrome-first, restrained semantic color, no decorative gradients. BRAND_LIGHT is the canonical default; BRAND (the strict dark inversion) is kept for at most 1-2 rhythm-break slides per set and uses the same role mapping, not a separate visual identity.
| Item | Default |
|---|---|
| App name | RIPDPI |
| Design philosophy | Monochrome-first, semantic color for status only. Technical, precise, utilitarian. |
| Font families | Geist Sans (Latin copy), Vazirmatn v33.003 for fa, Geist Mono (values/configs); preserve the app fonts in raw captures |
const BRAND_LIGHT = {
bg: "#FAFAFA", // background
card: "#FFFFFF", // card
text: "#1A1A1A", // foreground
muted: "#F5F5F5", // muted
mutedFg: "#575757", // mutedForeground
accent: "#E8E8E8", // accent
border: "#E0E0E0", // border
success: "#047857", // success
warning: "#B45309", // warning
error: "#B91C1C", // destructive
info: "#1D4ED8", // info
restricted: "#6B7280", // restricted
} as const;
Use sparingly for at most 1-2 rhythm-break slides. Status colors are slightly lighter than light-theme variants for dark-surface contrast — never bright.
const BRAND = {
bg: "#1A1A1A", // background
card: "#1F1F1F", // card
text: "#FAFAFA", // foreground
muted: "#262626", // muted
mutedFg: "#A3A3A3", // mutedForeground
accent: "#2A2A2A", // accent
border: "#2A2A2A", // border
success: "#10B981", // success
warning: "#D97706", // warning
error: "#DC2626", // destructive
info: "#3B82F6", // info
restricted: "#6B7280", // restricted
} as const;
RipDpiShapeTokens)| Token | Radius | Usage in screenshots |
|---|---|---|
xl | 16dp | Buttons, cards, text fields |
xlIncreased | 20dp | Expressive interactive surfaces |
xxl | 28dp | Pills, FABs |
full | 50% | Avatars, status dots |
Use these for screenshot frame corners (40px = ~3.7% of 1080px width) and UI element pills.
| Element | Family | Weight | Current full-size text |
|---|---|---|---|
| Phone headline | Geist Sans or pinned locale font | 700 | 100px at 1080w |
| Phone explanation | Same locale font | 400 | At least 48px at 1080w |
| Banner wordmark | Geist Sans | 700 | 64px at 1024w |
| Banner value statement | Locale font | 400 | 56px at 1024w |
M3 Expressive principle: Prefer weight promotion (400->500->700) over size increase for emphasis.
| Feature list | 1. path optimization (proxy + VPN modes) 2. No root required 3. Advanced strategy controls (TCP, QUIC, DNS) 4. Encrypted DNS (DoH/DoT/DNSCrypt) 5. Integrated diagnostics & monitoring 6. Per-network policies 7. Works with AdGuard 8. Session telemetry & export | | Style direction | Light, monochrome-first. Restrained semantic color (use info/success/warning only when the state itself matters). No decorative gradients on any slide — DESIGN.md forbids them in-app and the marketing surface honors the same rule. |
For an existing generator, keep its configured slide count and locales unless the user asks to change them. A request to implement banners includes the feature graphic. Ask only when a missing requirement could change the result; otherwise use the existing project defaults and proceed.
IMPORTANT: If the user says "figure it out" or similar, use the defaults and proceed without asking.
BRAND_LIGHT.bg (#FAFAFA) for the canonical light slides; BRAND.bg (#1A1A1A) for the rare rhythm-break dark slide.Grid pattern is the only allowed decoration. No radial glow orbs, no circuit-board, no shield motifs — these read as consumer-dashboard flourish and violate the design system.top positioning (not bottom + translateY) to precisely control where screenshots start below headlinescd play-store-screenshots
bun install # or npm install
public/screenshots/ holds the raw Stage-1 app captures (see play-store-screenshots/AGENTS.md's Two-Stage Pipeline section). If a screen's UI changed, re-capture it from a device per that file's Stage 1 instructions -- do not copy from docs/screenshots/, which holds the rendered Stage-2 marketing outputs, not a source for raw captures.
Package manager priority: bun > pnpm > yarn > npm
bunx create-next-app@latest play-store-screenshots --typescript --tailwind --app --src-dir --no-eslint --import-alias "@/*"
cd play-store-screenshots
bun add html-to-image
bun add -d puppeteer
bun pm trust puppeteer # allow postinstall to download Chromium
Copy the app icon, then seed public/screenshots/ with fresh Stage-1 device captures per play-store-screenshots/AGENTS.md:
mkdir -p public/screenshots
cp ../app/src/main/ic_launcher-playstore.png public/app-icon.png
# then follow AGENTS.md's Stage 1 capture steps to populate public/screenshots/*.png
Next.js 16 ships Geist fonts natively via next/font/google:
// src/app/layout.tsx
import { Geist, Geist_Mono } from "next/font/google";
const geistSans = Geist({ variable: "--font-geist-sans", subsets: ["latin"] });
const geistMono = Geist_Mono({ variable: "--font-geist-mono", subsets: ["latin"] });
Use var(--font-geist-sans) and var(--font-geist-mono) in slide styles.
useSearchParams requires Suspense: Wrap the main component in <Suspense> to avoid build failures during static prerendering.bun run build && bun run start -- the dev server HMR websocket causes Puppeteer and Chrome DevTools navigation timeouts.| Slot | Purpose | RIPDPI Suggestion |
|---|---|---|
| #1 | Connection | Complete Home frame with real service state |
| #2 | Diagnostics | Completed real run and its actual results |
| #3 | Relay configuration | Complete relay controls with a meaningful viewport |
| #4 | DNS options | Actual DNS settings screen |
| #5 | Packet strategies | Actual packet strategy controls |
| #6 | Backups | Actual backup and restore screen; one clear function |
Rules:
<br />.| Type | What it does | Example |
|---|---|---|
| Paint a moment | You picture yourself doing it | "Open any site. No extra steps." |
| State an outcome | What your life looks like after | "The internet you were promised." |
| Kill a pain | Name a problem and destroy it | "No more blocked pages." |
The entire generator is a single page.tsx file:
page.tsx
├── Constants (PHONE_W/H, FEATURE_GRAPHIC, BRAND tokens)
├── Screenshot component (frameless, 40px border-radius, bgColor prop)
├── Caption component (label + headline)
├── Decorative components (Glow, Grid, Pill)
├── Slide container component
├── Slide1..N components (one per slide)
├── FeatureGraphicSlide component (1024x500)
├── SLIDES array (registry)
├── ScreenshotPreview (ResizeObserver scaling + click-to-export)
├── ScreenshotsPage (grid + export logic)
└── Page wrapper (Suspense boundary)
const PHONE_W = 1080;
const PHONE_H = 1920;
const FEATURE_GRAPHIC = { w: 1024, h: 500 };
function Screenshot({ src, alt, style, bgColor = "#ffffff" }: {
src: string; alt: string; style?: React.CSSProperties; bgColor?: string;
}) {
return (
<div style={{ position: "relative", ...style }}>
<div style={{
width: "100%", height: "100%",
borderRadius: 40, // ~3.7% of 1080px, close to xl (16dp) scaled up
overflow: "hidden",
boxShadow: "0 12px 60px rgba(0,0,0,0.5)",
background: bgColor,
}}>
<img src={src} alt={alt}
style={{ display: "block", width: "100%", height: "100%",
objectFit: "cover", objectPosition: "top" }}
draggable={false} />
</div>
</div>
);
}
The current source frames are 1080x1800. Use their exact aspect ratio and scale the complete frame to fit below the headline. Use top positioning and check both bottom and side margins. Do not crop or stretch the real UI:
// Fit the complete frame below a compact heading
<Screenshot src="/screenshots/home-light.png" alt="Home"
style={{
position: "absolute",
top: 310, // precise control -- no overlap
left: "50%",
transform: "translateX(-50%)",
width: 840,
aspectRatio: "1080/1800",
}}
/>
Never use bottom: 0 + translateY(N%) for complete source screenshots -- the percentage math is hard to get right and leads to overlap or excessive gaps.
Support ?slide=N (1-6) and ?slide=fg for headless capture:
export default function Page() {
return (
<Suspense fallback={<div style={{ background: "#0a0a0a", minHeight: "100vh" }} />}>
<ScreenshotsPage />
</Suspense>
);
}
function ScreenshotsPage() {
const searchParams = useSearchParams();
const slideParam = searchParams.get("slide");
if (slideParam) {
if (slideParam === "fg") return <FeatureGraphicSlide />;
const idx = parseInt(slideParam) - 1;
const slide = SLIDES[idx];
if (slide) { const C = slide.component; return <C />; }
}
// ... grid view with export
}
import { toJpeg } from "html-to-image";
const opts = { width: w, height: h, pixelRatio: 1, quality: 1, cacheBust: true, backgroundColor: "#FAFAFA" };
await toJpeg(el, opts); // warm-up call
const dataUrl = await toJpeg(el, opts); // actual JPEG capture
Prefer bun run capture:prod (see play-store-screenshots/AGENTS.md's Common Workflows table) -- it builds, boots the production server, runs the capture, and always tears the server down. The manual equivalent, against a production build (dev server HMR causes timeouts):
bun run build && bun run start -- -p 3099 &
node capture.mjs
// capture.mjs
import puppeteer from "puppeteer";
const browser = await puppeteer.launch({ headless: true });
const page = await browser.newPage();
for (const slide of SLIDES) {
await page.setViewport({ width: slide.w, height: slide.h, deviceScaleFactor: 1 });
await page.goto(`http://localhost:3099/?slide=${slide.param}`, { waitUntil: "load", timeout: 60000 });
await new Promise(r => setTimeout(r, 2000)); // fonts + images
await page.screenshot({ path: outPath, type: "png", clip: { x: 0, y: 0, width: slide.w, height: slide.h } });
}
Captured images go to docs/screenshots/ for README usage.
01-hero.png, 02-no-root.png, etc.feature-graphic.png#FAFAFA bg, #1A1A1A text, #1D4ED8 info; dark inversion: #1A1A1A bg, #FAFAFA text, #3B82F6 info)| Mistake | Fix |
|---|---|
| Text overlaps phone screenshot | Use top positioning, not bottom + translateY |
| Screenshot clipped at edges | Use left/right: "4%" not negative values |
| Blank Puppeteer captures | Must use production build (bun run build && bun run start) |
| useSearchParams build error | Wrap component in <Suspense> |
| Low-res screenshots look bad | Use only validated Stage-1 captures from play-store-screenshots/public/screenshots/; never use rendered marketing output as raw input |
| Decorative gradients on slides | DESIGN.md forbids them in the app and they also break the marketing brand. Use solid backgrounds plus the Grid utility for subtle texture. No linear-gradient(...) or radial-glow orbs anywhere. |
| Bright saturated status accents | Use restrained DESIGN.md tokens (#1D4ED8 info, #047857 success, #B45309 warning), never bubblegum-bright variants. Reserve status color for slides where the state itself matters. |
| All slides look the same | Vary: centered phone, right-offset, left-offset, text-only, pills-only |
| Copy too complex | "One second at arm's length" test; 3-5 words per line |
まだレビューはありません。使ってみた感想をお寄せください。
概要と使いどころ
RIPDPI's own Compose conventions: ViewModel pattern, Route/Screen split, DataStore->StateFlow, RipDpiThemeTokens. Use for how this app does Compose, not generic Compose API questions (see compose).
日本語の概要は準備中です。原文の説明を表示しています。
ADB-based RIPDPI device/emulator debugging: build/install/launch, logcat filtering, fixture port-forwarding, instrumented tests, crash/ANR triage. Use when debugging on a real device or emulator.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium launch-contract reference: start routes and permission/service/data presets. Use when a test launches to the wrong screen or a new automation route is added. General flakiness: appium-test-debug.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium test authoring: page objects, resource-id locators, assertions, wait tiers. Use when writing a new Appium test or page object, or adding coverage for a new screen.
日本語の概要は準備中です。原文の説明を表示しています。
RIPDPI Appium failure triage: flaky tests, locator/session/wait issues, screenshot and element-tree debugging. Use when an Appium test fails or is flaky.
日本語の概要は準備中です。原文の説明を表示しています。
Add or modify a GitHub Actions job. Use when editing a file under .github/workflows/. Not for running a workflow locally (local-ci-act) or release signing (release-signing).
日本語の概要は準備中です。原文の説明を表示しています。