skills/screenshots/SKILL.md
Generate marketing screenshots of your app using Playwright. Use when the user wants to create screenshots for Product Hunt, social media, landing pages, or documentation.
npx skillsauth add ruanmalvao-web/lp screenshotsInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Generate marketing-quality screenshots of your app using Playwright directly. Screenshots are captured at true HiDPI (2x retina) resolution using deviceScaleFactor: 2.
Use this skill when:
Playwright must be available. Check for it:
npx playwright --version 2>/dev/null || npm ls playwright 2>/dev/null | grep playwright
If not found, inform the user:
Playwright is required. Install it with:
npm install -D playwrightornpm install -D @playwright/test
If $1 is provided, use it as the app URL.
If no URL is provided:
package.json scriptsAskUserQuestion to ask the user for the URL or offer to help start the dev serverCommon default URLs to suggest:
http://localhost:3000 (Next.js, Create React App, Rails)http://localhost:5173 (Vite)http://localhost:4000 (Phoenix)http://localhost:8080 (Vue CLI, generic)Use AskUserQuestion with the following questions:
Question 1: Screenshot count
Question 2: Purpose
Question 3: Authentication
If user selects "Yes, I'll provide credentials", ask follow-up questions:
/login, /sign-in)The script will automatically detect login form fields using Playwright's smart locators.
Thoroughly explore the codebase to understand the app and identify screenshot opportunities.
Always start by reading these files to understand what the app does:
README.md (and any README files in subdirectories) - Read the full README to understand:
CHANGELOG.md or HISTORY.md - Recent features worth highlighting
docs/ directory - Any additional documentation about features
Read the routing configuration to discover all available pages:
| Framework | File to Read | What to Look For |
|-----------|--------------|------------------|
| Next.js App Router | app/ directory structure | Each folder with page.tsx is a route |
| Next.js Pages Router | pages/ directory | Each file is a route |
| Rails | config/routes.rb | Read the entire file for all routes |
| React Router | Search for createBrowserRouter or <Route | Route definitions with paths |
| Vue Router | src/router/index.js or router.js | Routes array with path definitions |
| SvelteKit | src/routes/ directory | Each folder with +page.svelte is a route |
| Remix | app/routes/ directory | File-based routing |
| Laravel | routes/web.php | Route definitions |
| Django | urls.py files | URL patterns |
| Express | Search for app.get, router.get | Route handlers |
Important: Actually read these files, don't just check if they exist. The route definitions tell you what pages are available for screenshots.
Look for components that represent screenshottable features:
Look for existing marketing content that hints at key features:
components/landing/ or components/marketing/)Create a comprehensive list of discovered features with:
Present the discovered features to the user and ask them to confirm or modify the list.
Use AskUserQuestion:
If user wants specific ones, ask follow-up questions to clarify exactly what to capture.
mkdir -p screenshots
Create a Node.js script that uses Playwright with proper HiDPI settings. The script should:
deviceScaleFactor: 2 for true retina resolutionWrite this script to a temporary file (e.g., screenshot-script.mjs) and execute it:
import { chromium } from 'playwright';
const BASE_URL = '[APP_URL]';
const SCREENSHOTS_DIR = './screenshots';
// Authentication config (if needed)
const AUTH = {
needed: [true|false],
loginUrl: '[LOGIN_URL]',
email: '[EMAIL]',
password: '[PASSWORD]',
};
// Screenshots to capture
const SCREENSHOTS = [
{ name: '01-feature-name', url: '/path', waitFor: '[optional-selector]' },
{ name: '02-another-feature', url: '/another-path' },
// ... add all planned screenshots
];
async function main() {
const browser = await chromium.launch();
// Create context with HiDPI settings
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2, // This is the key for true retina screenshots
});
const page = await context.newPage();
// Handle authentication if needed
if (AUTH.needed) {
console.log('Logging in...');
await page.goto(AUTH.loginUrl);
// Smart login: try multiple common patterns for email/username field
const emailField = page.locator([
'input[type="email"]',
'input[name="email"]',
'input[id="email"]',
'input[placeholder*="email" i]',
'input[name="username"]',
'input[id="username"]',
'input[type="text"]',
].join(', ')).first();
await emailField.fill(AUTH.email);
// Smart login: try multiple common patterns for password field
const passwordField = page.locator([
'input[type="password"]',
'input[name="password"]',
'input[id="password"]',
].join(', ')).first();
await passwordField.fill(AUTH.password);
// Smart login: try multiple common patterns for submit button
const submitButton = page.locator([
'button[type="submit"]',
'input[type="submit"]',
'button:has-text("Sign in")',
'button:has-text("Log in")',
'button:has-text("Login")',
'button:has-text("Submit")',
].join(', ')).first();
await submitButton.click();
await page.waitForLoadState('networkidle');
console.log('Login complete');
}
// Capture each screenshot
for (const shot of SCREENSHOTS) {
console.log(`Capturing: ${shot.name}`);
await page.goto(`${BASE_URL}${shot.url}`);
await page.waitForLoadState('networkidle');
// Optional: wait for specific element
if (shot.waitFor) {
await page.waitForSelector(shot.waitFor);
}
// Optional: perform actions before screenshot
if (shot.actions) {
for (const action of shot.actions) {
if (action.click) await page.click(action.click);
if (action.fill) await page.fill(action.fill.selector, action.fill.value);
if (action.wait) await page.waitForTimeout(action.wait);
}
}
await page.screenshot({
path: `${SCREENSHOTS_DIR}/${shot.name}.png`,
fullPage: shot.fullPage || false,
});
console.log(` Saved: ${shot.name}.png`);
}
await browser.close();
console.log('Done!');
}
main().catch(console.error);
node screenshot-script.mjs
After running, clean up the temporary script:
rm screenshot-script.mjs
To screenshot a specific element instead of the full viewport:
const element = await page.locator('[CSS_SELECTOR]');
await element.screenshot({ path: `${SCREENSHOTS_DIR}/element.png` });
For scrollable content, capture the entire page:
await page.screenshot({
path: `${SCREENSHOTS_DIR}/full-page.png`,
fullPage: true
});
If the page has animations, wait for them to complete:
await page.waitForTimeout(500); // Wait 500ms for animations
To capture a modal, dropdown, or hover state:
await page.click('button.open-modal');
await page.waitForSelector('.modal-content');
await page.screenshot({ path: `${SCREENSHOTS_DIR}/modal.png` });
If the app supports dark mode:
// Set dark mode preference
const context = await browser.newContext({
viewport: { width: 1440, height: 900 },
deviceScaleFactor: 2,
colorScheme: 'dark',
});
Use descriptive, kebab-case filenames with numeric prefixes for ordering:
| Feature | Filename |
|---------|----------|
| Dashboard overview | 01-dashboard-overview.png |
| Link management | 02-link-inbox.png |
| Edition editor | 03-edition-editor.png |
| Analytics | 04-analytics.png |
| Settings | 05-settings.png |
After capturing all screenshots, verify the results:
ls -la screenshots/*.png
sips -g pixelWidth -g pixelHeight screenshots/*.png 2>/dev/null || file screenshots/*.png
Provide a summary to the user:
Example output:
Generated 5 marketing screenshots:
screenshots/
├── 01-dashboard-overview.png (1.2 MB, 2880x1800 @ 2x)
├── 02-link-inbox.png (456 KB, 2880x1800 @ 2x)
├── 03-edition-editor.png (890 KB, 2880x1800 @ 2x)
├── 04-analytics.png (567 KB, 2880x1800 @ 2x)
└── 05-settings.png (234 KB, 2880x1800 @ 2x)
All screenshots are true retina-quality (2x deviceScaleFactor) and ready for marketing use.
npm install -D playwrightwaitForLoadState('networkidle') to ensure all content loadstools
No-code automation democratizes workflow building. Zapier and Make (formerly Integromat) let non-developers automate business processes without writing code. But no-code doesn't mean no-complexity - these platforms have their own patterns, pitfalls, and breaking points. This skill covers when to use which platform, how to build reliable automations, and when to graduate to code-based solutions. Key insight: Zapier optimizes for simplicity and integrations (7000+ apps), Make optimizes for power
tools
This skill should be used when the user asks to "test for XSS vulnerabilities", "perform cross-site scripting attacks", "identify HTML injection flaws", "exploit client-side injection vulnerabilities", "steal cookies via XSS", or "bypass content security policies". It provides comprehensive techniques for detecting, exploiting, and understanding XSS and HTML injection attack vectors in web applications.
development
Comprehensive spreadsheet creation, editing, and analysis with support for formulas, formatting, data analysis, and visualization. When Claude needs to work with spreadsheets (.xlsx, .xlsm, .csv, .tsv, etc) for: (1) Creating new spreadsheets with formulas and formatting, (2) Reading or analyzing data, (3) Modify existing spreadsheets while preserving formulas, (4) Data analysis and visualization in spreadsheets, or (5) Recalculating formulas
tools
Publish articles to X/Twitter