@appscreenshotstudio/mcp 0.4.3 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -148,6 +148,21 @@ Generate an AI background for a specific card. Uses project metadata (brand colo
148
148
 
149
149
  **Costs 6 credits.**
150
150
 
151
+ ### `generate-panoramic-background`
152
+
153
+ Generate one wide image and slice it across multiple cards so they read as a continuous scene in the App Store gallery. Use after a panoramic chat edit ("pano the background across cards 1-3") to fulfill the pending panoramic, or pass an explicit `prompt` / `pexels_query`.
154
+
155
+ | Parameter | Type | Required | Description |
156
+ |---|---|---|---|
157
+ | `project_id` | string | Yes | Project containing the cards |
158
+ | `prompt` | string | No | AI scene description (Gemini). Costs 6 credits. |
159
+ | `pexels_query` | string | No | Stock photo search query (Pexels). Free. |
160
+ | `card_indices` | number[] | No | Cards sharing the panoramic (0-based, min 2). Default: first 3. |
161
+
162
+ Omit both `prompt` and `pexels_query` to fulfill a panoramic already set up by `generate-screenshots` / `edit-screenshots`.
163
+
164
+ **Costs 6 credits (AI prompt) or free (Pexels query).**
165
+
151
166
  ### `list-devices`
152
167
 
153
168
  Show all supported device specs. No API call needed.
@@ -169,6 +184,7 @@ Once installed, use `/appscreenshotstudio` in Claude Code or just ask "generate
169
184
  | ID | Name | Size | Required |
170
185
  |---|---|---|---|
171
186
  | `iphone-6.9` | iPhone 16 Pro Max | 1260x2736 | App Store |
187
+ | `iphone-6.3` | iPhone 17 Pro | 1206x2622 | Optional |
172
188
  | `ipad-13` | iPad Pro 13" | 2064x2752 | App Store |
173
189
  | `android-phone` | Android Phone | 1080x2340 | Play Store |
174
190
  | `android-tablet-10` | Android Tablet 7" | 1200x1920 | Play Store |
@@ -178,12 +194,13 @@ Once installed, use `/appscreenshotstudio` in Claude Code or just ask "generate
178
194
 
179
195
  The AI generates professional screenshots using:
180
196
 
181
- - **78+ decorative shapes** across 13 categories: nature (leaf, flower, tree), weather (cloud, sun, snowflake), celebration (sparkle, trophy, crown, confetti), social (chat-bubble, music-note), tech (rocket, code-bracket), health (dumbbell, flame), food (coffee-cup, pizza), travel (airplane, compass), finance (dollar-sign, piggy-bank), education (graduation-cap, lightbulb), pets (paw-print, cat-face), emoji (smiley, fire-emoji), abstract (swirl, infinity, gem)
197
+ - **94 shape types** (17 core + 77 decorative across 13 categories): nature (leaf, flower, tree), weather (cloud, sun, snowflake), celebration (sparkle, trophy, crown, confetti), social (chat-bubble, music-note), tech (rocket, code-bracket), health (dumbbell, flame), food (coffee-cup, pizza), travel (airplane, compass), finance (dollar-sign, piggy-bank), education (graduation-cap, lightbulb), pets (paw-print, cat-face), emoji (smiley, fire-emoji), abstract (swirl, infinity, gem)
182
198
  - **Rich text**: per-word color, bold, italic, underline, highlight pills (colored backgrounds behind words), gradient fills, text stroke outlines, emoji
183
- - **Laurel stats**: wing-left + wing-right shapes flanking stats like "4.9 Rating" or "1M+ Users"
184
- - **Floating UI snippets**: rounded-rect panels with text overlapping device edges
199
+ - **Compound trust signals**: statRow, laurelStat, starRating, quote, pressBanner, featureGrid, beforeAfterSplit, stepList auto-positioned per layout
200
+ - **Frame color**: recolor the device frame `natural` (default), `black`, `white`, `gold`. Requires Growth plan or higher.
201
+ - **Panoramics**: slice one wide image across cards (chat tags the cards, then `generate-panoramic-background` creates and slices the image) or stretch a foreground element across adjacent cards (`apply_element_span`). Gallery gutters are accounted for.
185
202
  - **9 device perspectives**: flat, left-15, left-30, right-15, right-30, isometric, top-down, landscape-left, landscape-right
186
- - **7 layouts**: Text Top + Device Center (A), Text Top + Device Offset (B), Social Proof (C), Marketing Title (M1), Feature Callout (M2), CTA (M3), Testimonial (M4)
203
+ - **13 layouts**: `text-top-device-bottom`, `text-top-device-tilted`, `device-hero`, `social-proof`, `review-clip`, `screen-hero`, `lifestyle-hero`, `feature-grid`, `before-after`, `stats-hero`, `metric-badge`, `annotated-feature`, `step-flow`. The AI varies `deviceScale`, `deviceSide`, `textPosition`, and `textAlign` per card so a set never looks like duplicates.
187
204
 
188
205
  ## Workflow
189
206
 
package/dist/index.js CHANGED
@@ -31,12 +31,40 @@ const API_KEY = process.env.APPSCREENSHOTSTUDIO_API_KEY;
31
31
  // ⚠️ Keep in sync: lib/device-specs.ts, mcp-server/README.md, docs/api page, docs/mcp page
32
32
  const DEVICES = [
33
33
  { id: 'iphone-6.9', name: 'iPhone 16 Pro Max', width: 1260, height: 2736, category: 'iphone', required: true },
34
+ { id: 'iphone-6.3', name: 'iPhone 17 Pro', width: 1206, height: 2622, category: 'iphone', required: false },
34
35
  { id: 'ipad-13', name: 'iPad Pro 13"', width: 2064, height: 2752, category: 'ipad', required: true },
35
36
  { id: 'android-phone', name: 'Android Phone', width: 1080, height: 2340, category: 'android-phone', required: true },
36
37
  { id: 'android-tablet-10', name: 'Android Tablet 7"', width: 1200, height: 1920, category: 'android-tablet', required: true },
37
38
  { id: 'apple-watch-ultra', name: 'Apple Watch Ultra 2', width: 410, height: 502, category: 'apple-watch', required: true },
38
39
  ];
39
40
  const VALID_DEVICE_IDS = DEVICES.map(d => d.id);
41
+ // Aliases callers commonly pass instead of the canonical id: display-name
42
+ // derivations ("iPhone 16 Pro Max" -> iphone-16-pro-max) and legacy slugs.
43
+ // Mirrors MARKETING_TO_SPEC in lib/device-specs.ts — keep the two in sync.
44
+ const DEVICE_ALIASES = {
45
+ 'iphone-16-pro': 'iphone-6.9',
46
+ 'iphone-16-pro-max': 'iphone-6.9',
47
+ 'iphone-17-pro': 'iphone-6.3',
48
+ 'samsung-galaxy-s25-ultra': 'android-phone',
49
+ 'ipad-pro-13': 'ipad-13',
50
+ 'ipad-12.9': 'ipad-13',
51
+ 'ipad-pro-12.9': 'ipad-13',
52
+ 'android-tablet': 'android-tablet-10',
53
+ 'apple-watch': 'apple-watch-ultra',
54
+ };
55
+ /**
56
+ * Resolve a caller-supplied device id to a canonical one, or return the list of
57
+ * valid devices for a helpful error. Runs at the tool boundary so an unknown
58
+ * device fails fast (before a project is created and generation credits are
59
+ * spent) instead of surfacing late as a render-time "Unknown device".
60
+ */
61
+ function resolveDeviceId(input) {
62
+ const normalized = DEVICE_ALIASES[input] ?? input;
63
+ if (VALID_DEVICE_IDS.includes(normalized))
64
+ return { id: normalized };
65
+ const options = DEVICES.map(d => `${d.id} (${d.name})`).join(', ');
66
+ return { error: `Unknown device "${input}". Valid devices: ${options}.` };
67
+ }
40
68
  // ─── API helper ─────────────────────────────────────────────────────────────────
41
69
  async function apiCall(method, path, body) {
42
70
  if (!API_KEY) {
@@ -126,7 +154,7 @@ function buildDesignMessage(input) {
126
154
  // ─── MCP Server ─────────────────────────────────────────────────────────────────
127
155
  const server = new McpServer({
128
156
  name: 'appscreenshotstudio',
129
- version: '0.4.3',
157
+ version: '0.4.4',
130
158
  });
131
159
  // Tool 1: generate-screenshots
132
160
  server.registerTool('generate-screenshots', {
@@ -137,20 +165,24 @@ IMPORTANT: Before calling this tool, research the user's codebase to populate th
137
165
 
138
166
  The AI picks a narrative arc that fits the app's sell angle (trust-first for finance/health, visual-first for creative/lifestyle, problem-first for pain-relief apps, numbers-led for SaaS/analytics, community-first for social, feature-forward for multi-mode apps) and assigns a job to each card: HOOK → EDUCATE → PROVE → CONVERT.
139
167
 
140
- 9 layout types available per card:
168
+ 13 layout types available per card:
141
169
  - text-top-device-bottom: headline top, flat device bottom (feature/educate)
142
170
  - text-top-device-tilted: headline left, tilted device right (or mirrored)
143
171
  - device-hero: headline + large centered device, product-forward
144
- - social-proof: stars + quote + laurel stat, device optional
145
- - feature-callout: headline + features list, no device
146
- - lifestyle-hero: full-bleed photo background + text overlay
172
+ - social-proof: stars + quote + laurel stat, no device (testimonial-led)
173
+ - review-clip: rating-statement headline + stars + quote + bottom-clipped device
174
+ - screen-hero: top-clipped device + centered headline + optional trust stat
175
+ - lifestyle-hero: full-bleed photo background + text overlay (no device)
176
+ - feature-grid: headline + 2×2 or 2×3 icon+label grid, no device
147
177
  - before-after: dark before-half + bright after-half (transformation apps)
148
- - text-bottom: headline anchored to lower third, cinematic
149
178
  - stats-hero: giant centered stat(s), no device, numbers-led
179
+ - metric-badge: centered device + chunky "achievement card" floating over its screen
180
+ - annotated-feature: tilted device + side callout chip linked by a connector line
181
+ - step-flow: headline + 2-4 numbered step rows ("how it works"), no device
150
182
 
151
- Each card can carry auto-positioned compound fields: statRow, laurelStat, pressBanner, starRating, quote, featureGrid, beforeAfterSplit.
183
+ Each card can carry auto-positioned compound fields: statRow, laurelStat, pressBanner, starRating, quote, featureGrid, beforeAfterSplit, stepList.
152
184
 
153
- Panoramic backgrounds slice one wide AI-generated or Pexels image across multiple cards for cohesion. Panoramic element spans stretch a foreground image or device across adjacent cards.
185
+ Panoramic backgrounds slice one wide AI-generated or Pexels image across multiple cards for cohesion. Panoramic element spans stretch a foreground image or device across adjacent cards. Note: if the design comes back with a panoramic background, the chat only tags the cards; call generate-panoramic-background afterwards to actually create and slice the wide image.
154
186
 
155
187
  Design features:
156
188
  - Rich text with per-word color, weight, italic, underline, highlight pills, gradient fills, stroke outlines, and emoji
@@ -204,10 +236,16 @@ Costs 5 credits per generation.`,
204
236
  .describe('Context gathered from researching the app codebase. Dramatically improves screenshot quality — the more detail here, the better the output.'),
205
237
  }),
206
238
  }, async (input) => {
239
+ // Resolve/validate the device up front so an unknown id fails fast here
240
+ // instead of after a project (and its 5 generation credits) is spent.
241
+ const resolved = resolveDeviceId(input.device_id);
242
+ if ('error' in resolved) {
243
+ return { content: [{ type: 'text', text: resolved.error }] };
244
+ }
207
245
  // Step 1: Create project (with codebase context if provided)
208
246
  const projectName = `${input.app_name} Screenshots`;
209
247
  const createBody = {
210
- device_id: input.device_id,
248
+ device_id: resolved.id,
211
249
  name: projectName,
212
250
  };
213
251
  if (input.codebase_context) {
@@ -268,14 +306,14 @@ What you can change:
268
306
  - Text: headlines, subtitles, badge text, font size, font family (Inter, Poppins, Montserrat, DM Sans, Space Grotesk, etc.)
269
307
  - Text styling: per-word color, bold, italic, underline, highlight pills (colored background behind words), gradient text, text stroke outlines
270
308
  - Colors: brand palette, gradient backgrounds, accent colors, text colors
271
- - Layouts (9 types): text-top-device-bottom, text-top-device-tilted, device-hero, social-proof, feature-callout, lifestyle-hero, before-after, text-bottom, stats-hero. The AI picks a narrative arc (HOOK → EDUCATE → PROVE → CONVERT) across the set.
272
- - Layout params: deviceScale (small/medium/large), deviceSide (left/right), textPosition (above/below), compoundPlacement (below-text/below-device)
309
+ - Layouts (13 types): text-top-device-bottom, text-top-device-tilted, device-hero, social-proof, review-clip, screen-hero, lifestyle-hero, feature-grid, before-after, stats-hero, metric-badge, annotated-feature, step-flow. The AI picks a narrative arc (HOOK → EDUCATE → PROVE → CONVERT) across the set.
310
+ - Layout params: deviceScale (small/medium/large), deviceSide (left/right), textPosition (above/below), textAlign (left/center)
273
311
  - Device mockups: perspective variants (flat, left-15, right-15, left-30, right-30, isometric, top-down, landscape-left, landscape-right), 2D rotation, resize, reposition
274
312
  - Frame color: recolor the device frame — "natural" (default), "black", "white", "gold". Examples: "make the iPhone gold", "black titanium finish", "white iPhone". Requires Growth plan or higher.
275
313
  - Switch devices: "make this for Apple Watch" / "duplicate for Android tablet" clones the project at the target device's canvas size
276
314
  - Add/remove cards: add a social proof card, remove card 3, add a marketing title card
277
315
  - Compound fields (auto-positioned): statRow, laurelStat, pressBanner, starRating, quote, featureGrid, beforeAfterSplit
278
- - Panoramic backgrounds: one wide AI or Pexels image sliced across multiple cards ("pano the background across cards 1-3")
316
+ - Panoramic backgrounds: one wide AI or Pexels image sliced across multiple cards ("pano the background across cards 1-3"). The edit only tags the cards; call generate-panoramic-background afterwards to create and slice the wide image.
279
317
  - Panoramic element spans: stretch a foreground image, device-mockup, or shape across adjacent cards ("pano the device across cards 1-2")
280
318
  - Floating elements: add/edit badges, star ratings
281
319
  - Shapes: glow orbs, waves, blobs, rounded rectangles, circles, custom SVG paths
@@ -371,7 +409,7 @@ Example edit messages:
371
409
  // Tool 3: render-screenshots
372
410
  server.registerTool('render-screenshots', {
373
411
  title: 'Render Screenshots to PNG',
374
- description: 'Export a screenshot project to high-resolution PNG files at exact App Store dimensions. Returns download URLs for each card. Free no credit cost. Note: device mockups will show empty frames unless app screenshots have been uploaded via upload-screenshots first.',
412
+ description: 'Export a screenshot project to high-resolution PNG files at exact App Store dimensions. Returns download URLs for each card. Free (no credit cost). Rendering blocks until the PNGs are ready: expect roughly 20-40s for iPhone sets and 60-120s for iPad (the larger canvas renders slower), so allow up to ~2 minutes before treating it as failed. Note: device mockups will show empty frames unless app screenshots have been uploaded via upload-screenshots first.',
375
413
  inputSchema: z.object({
376
414
  project_id: z.string().describe('Project ID to render'),
377
415
  }),
@@ -383,16 +421,34 @@ server.registerTool('render-screenshots', {
383
421
  };
384
422
  }
385
423
  const data = res.data.data;
386
- return {
387
- content: [{
388
- type: 'text',
389
- text: [
390
- `Rendered ${data.images?.length || 0} screenshots:`,
391
- '',
392
- ...(data.images || []).map((img, i) => `Card ${i + 1}: ${img.url} (${img.width}×${img.height})`),
393
- ].join('\n'),
394
- }],
395
- };
424
+ const images = data.images || [];
425
+ // Text block: full-resolution download URLs (for export / saving to disk).
426
+ const content = [
427
+ {
428
+ type: 'text',
429
+ text: [
430
+ `Rendered ${images.length} screenshot${images.length === 1 ? '' : 's'}:`,
431
+ '',
432
+ ...images.map((img, i) => `Card ${i + 1}: ${img.url} (${img.width}×${img.height})`),
433
+ '',
434
+ 'Previews are shown below. To compare all cards side by side at full size,',
435
+ 'refine by hand, or export, open the project URL in the builder.',
436
+ ].join('\n'),
437
+ },
438
+ ];
439
+ // Image blocks: downscaled inline previews so image-capable agents
440
+ // (Claude Code, Cursor) display the screenshots in-chat. The API returns
441
+ // each preview as a `data:<mime>;base64,<data>` URL; MCP image content
442
+ // needs the raw base64 + mimeType, so split it out.
443
+ for (const img of images) {
444
+ if (!img.preview)
445
+ continue;
446
+ const match = /^data:(image\/[a-zA-Z+]+);base64,(.+)$/.exec(img.preview);
447
+ if (!match)
448
+ continue;
449
+ content.push({ type: 'image', mimeType: match[1], data: match[2] });
450
+ }
451
+ return { content };
396
452
  });
397
453
  // Tool 4: list-devices
398
454
  server.registerTool('list-devices', {
@@ -506,7 +562,55 @@ The generated image is cropped to exact device dimensions and set as the card's
506
562
  }],
507
563
  };
508
564
  });
509
- // Tool 7: prepare-screenshot-brief
565
+ // Tool 7: generate-panoramic-background
566
+ server.registerTool('generate-panoramic-background', {
567
+ title: 'Generate Panoramic Background',
568
+ description: `Generate one wide background image and slice it across multiple cards so they read as a continuous scene when the App Store gallery scrolls. The store gallery gap is accounted for, so slices line up after Apple's gutter.
569
+
570
+ Two image sources:
571
+ - prompt: AI-generated via Gemini. Costs 6 credits.
572
+ - pexels_query: stock landscape photo from Pexels. Free.
573
+
574
+ If you omit both, the tool fulfills a panoramic the chat already set up: when generate-screenshots or edit-screenshots returns a design with a panoramic background (e.g. after "pano the background across cards 1-3"), the cards are tagged but the wide image isn't created yet. Calling this tool with just the project_id picks up that pending panoramic and generates it.
575
+
576
+ Good prompts describe a continuous scene, not objects or text:
577
+ - "Misty mountain range at dawn, soft pink-to-blue gradient sky"
578
+ - "Calm ocean horizon at golden hour with gentle waves"
579
+ - "Abstract flowing emerald and teal silk waves"`,
580
+ inputSchema: z.object({
581
+ project_id: z.string().describe('Project ID'),
582
+ prompt: z.string().optional()
583
+ .describe('AI scene description (Gemini, 6 credits). Describe one continuous landscape scene: no text, devices, or UI.'),
584
+ pexels_query: z.string().optional()
585
+ .describe('Stock photo search query (Pexels, free). e.g. "mountain sunrise", "ocean horizon". Provide either this or prompt, not both.'),
586
+ card_indices: z.array(z.number().int().min(0)).max(10).optional()
587
+ .describe('Which cards share the panoramic (0-based, min 2). Default: first 3 cards (the ones visible in the App Store gallery without scrolling).'),
588
+ }),
589
+ }, async ({ project_id, prompt, pexels_query, card_indices }) => {
590
+ const res = await apiCall('POST', `/api/v1/projects/${project_id}/generate-panoramic-background`, {
591
+ ...(prompt ? { prompt } : {}),
592
+ ...(pexels_query ? { pexels_query } : {}),
593
+ ...(card_indices ? { card_indices } : {}),
594
+ });
595
+ if (!res.ok) {
596
+ return {
597
+ content: [{ type: 'text', text: `Panoramic background generation failed: ${JSON.stringify(res.data)}` }],
598
+ };
599
+ }
600
+ const data = res.data.data;
601
+ const creditsRemaining = res.data.credits_remaining;
602
+ return {
603
+ content: [{
604
+ type: 'text',
605
+ text: [
606
+ `Panoramic background generated and sliced across cards ${data.card_indices.map(i => i + 1).join(', ')}.`,
607
+ `Credits remaining: ${creditsRemaining}`,
608
+ `Project URL: ${API_BASE}/builder/${project_id}`,
609
+ ].join('\n'),
610
+ }],
611
+ };
612
+ });
613
+ // Tool 8: prepare-screenshot-brief
510
614
  server.registerTool('prepare-screenshot-brief', {
511
615
  title: 'Prepare Screenshot Brief',
512
616
  description: `Get a research checklist and strategy guide to prepare for screenshot generation. Call this BEFORE generate-screenshots to know what to look for in the codebase. Free — no API call or credits needed.
@@ -746,7 +850,7 @@ This tool helps you gather the right information so generate-screenshots produce
746
850
  }],
747
851
  };
748
852
  });
749
- // Tool 8: upload-screenshots
853
+ // Tool 9: upload-screenshots
750
854
  server.registerTool('upload-screenshots', {
751
855
  title: 'Upload App Screenshots',
752
856
  description: `Upload local app screenshots (from Simulator, emulator, or screen captures) into the device mockups of an existing project. This lets you add real app UI into the device frames without leaving the terminal.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@appscreenshotstudio/mcp",
3
- "version": "0.4.3",
3
+ "version": "0.5.1",
4
4
  "description": "MCP server for generating App Store screenshots via AppScreenshotStudio",
5
5
  "type": "module",
6
6
  "license": "MIT",
@@ -2,7 +2,7 @@
2
2
  name: appscreenshotstudio
3
3
  version: 1.0.0
4
4
  description: Generate App Store screenshots by researching the codebase and calling AppScreenshotStudio MCP tools. Use when the user mentions "screenshots", "App Store", "Play Store", "store listing", or wants to create marketing screenshots for their app.
5
- allowed_tools:
5
+ allowed-tools:
6
6
  - Read
7
7
  - Glob
8
8
  - Grep
@@ -64,7 +64,7 @@ Call the `generate-screenshots` MCP tool with:
64
64
  - `features`: top 3-5 features as array
65
65
  - `brand_colors`: `{ primary, secondary?, accent? }` from theme files
66
66
  - `mood`: user's chosen mood
67
- - `device_id`: `"iphone-6.9"` (default), `"ipad-13"`, `"android-phone"`, `"android-tablet-10"`
67
+ - `device_id`: `"iphone-6.9"` (default), `"ipad-13"`, `"android-phone"`, `"android-tablet-10"`, `"apple-watch-ultra"`
68
68
  - `count`: number of cards (3-10)
69
69
  - `story_flow`: `"auto"` (default), `"hero-intro"`, `"problem-solution"`, `"benefit-first"`, etc.
70
70
  - `codebase_context`: the full context object from Step 1
@@ -76,16 +76,22 @@ If the user has actual app screenshots (from Simulator, emulator, or screen capt
76
76
  - Fills the empty device mockups with real app UI
77
77
  - Free — no credit cost
78
78
 
79
- ### Step 5: Review and Iterate
79
+ ### Step 5: Show and Iterate (the core loop)
80
80
 
81
- Share the project URL and offer refinements using `edit-screenshots`:
82
- - "Want to change any headlines or colors?"
83
- - "Should I add a social proof card with ratings?"
84
- - "Want to try a different layout style?"
81
+ This is conversational refinement, so iterate right here in the chat. Don't send the user away to look at links.
82
+
83
+ 1. **Show.** Call `render-screenshots` (free) after generating. It returns inline image previews, so the screenshots display directly in the conversation. Show them.
84
+ 2. **Refine.** Let the user react in plain language and apply changes with `edit-screenshots`:
85
+ - "Want any headlines or colors changed?"
86
+ - "Should I add a social proof card with ratings?"
87
+ - "Different layout style?"
88
+ 3. **Re-show.** Call `render-screenshots` again to display the updated set inline. Repeat the show → refine → re-show loop until the user is happy.
89
+
90
+ Hand off to the builder only when it genuinely helps: share the project URL and frame it as the place to **compare all cards side by side at full size, nudge things by hand, or export**. It is the compare-and-export step, not the required destination. Coarse, language-expressible edits ("warmer background", "swap the headline") are faster to do right here in chat.
85
91
 
86
92
  ### Step 6: Export
87
93
 
88
- Call `render-screenshots` to export PNGs at exact App Store dimensions. This is free.
94
+ `render-screenshots` is also the export: it returns download URLs for the final PNGs at exact App Store dimensions, and it is free. Give the user those URLs. The builder project URL is the alternative when they want to compare the full set visually or hand-tweak before downloading.
89
95
 
90
96
  ## Headline Rules
91
97