1#!/usr/bin/env node
2
3/**
4 * Generates the Chrome Web Store small promo tile (440x280) from an SVG template.
5 *
6 * Run: node scripts/generate-promo-tile.js
7 */
8
9import puppeteer from 'puppeteer';
10import fs from 'fs';
11import path from 'path';
12import { fileURLToPath } from 'url';
13
14const __dirname = path.dirname(fileURLToPath(import.meta.url));
15const ROOT = path.resolve(__dirname, '..');
16const OUT = path.join(ROOT, 'extension/icons/promo-small.png');
17
18// Brand colors
19const BG = '#0e0d10';
20const BG_TOP = '#161318';
21const MAGENTA = '#cc1b89'; // approximates oklch(55% 0.25 350)
22const TEXT = '#f5f3ef';
23const TEXT_DIM = '#7a7680';
24
25const svg = `
26<svg width="440" height="280" viewBox="0 0 440 280" xmlns="http://www.w3.org/2000/svg">
27 <defs>
28 <linearGradient id="bg" x1="0" y1="0" x2="0" y2="1">
29 <stop offset="0" stop-color="${BG_TOP}"/>
30 <stop offset="1" stop-color="${BG}"/>
31 </linearGradient>
32 <linearGradient id="slop" x1="0" y1="0" x2="1" y2="0">
33 <stop offset="0" stop-color="#a855f7"/>
34 <stop offset="0.5" stop-color="#ec4899"/>
35 <stop offset="1" stop-color="#06b6d4"/>
36 </linearGradient>
37 <radialGradient id="cardGlow" cx="50%" cy="50%" r="60%">
38 <stop offset="0" stop-color="#3a1a4a" stop-opacity="0.6"/>
39 <stop offset="1" stop-color="#1a0f24" stop-opacity="0.3"/>
40 </radialGradient>
41 <filter id="softShadow" x="-20%" y="-20%" width="140%" height="140%">
42 <feGaussianBlur in="SourceAlpha" stdDeviation="6"/>
43 <feOffset dx="0" dy="3" result="offsetblur"/>
44 <feComponentTransfer><feFuncA type="linear" slope="0.4"/></feComponentTransfer>
45 <feMerge><feMergeNode/><feMergeNode in="SourceGraphic"/></feMerge>
46 </filter>
47 </defs>
48
49 <!-- Background -->
50 <rect width="440" height="280" fill="url(#bg)"/>
51
52 <!-- Subtle grid texture -->
53 <g opacity="0.04" stroke="${TEXT}" stroke-width="0.5">
54 <line x1="0" y1="70" x2="440" y2="70"/>
55 <line x1="0" y1="140" x2="440" y2="140"/>
56 <line x1="0" y1="210" x2="440" y2="210"/>
57 <line x1="110" y1="0" x2="110" y2="280"/>
58 <line x1="220" y1="0" x2="220" y2="280"/>
59 <line x1="330" y1="0" x2="330" y2="280"/>
60 </g>
61
62 <!-- Brand wordmark (top-left) -->
63 <g transform="translate(28, 26)">
64 <rect width="24" height="24" rx="4.5" fill="#1a1a1a"/>
65 <!-- Slash matches the brand icon: (76,24)→(52,104) in 128 viewBox, scaled to 24 -->
66 <line x1="14.25" y1="4.5" x2="9.75" y2="19.5" stroke="${TEXT}" stroke-width="1.3" stroke-linecap="round"/>
67 <text x="34" y="17" font-family="-apple-system, BlinkMacSystemFont, 'Segoe UI', system-ui, sans-serif" font-size="16" font-weight="600" fill="${TEXT}" letter-spacing="-0.01em">Impeccable</text>
68 </g>
69
70 <!-- Small badge top-right -->
71 <g transform="translate(338, 28)">
72 <rect width="74" height="20" rx="10" fill="${TEXT}" fill-opacity="0.06"/>
73 <text x="37" y="14" text-anchor="middle" font-family="-apple-system, system-ui, sans-serif" font-size="10" font-weight="500" fill="${TEXT_DIM}" letter-spacing="0.04em">DEVTOOLS</text>
74 </g>
75
76 <!-- Demo card group (centered, slightly offset) -->
77 <g transform="translate(64, 92)">
78 <!-- Faux UI card -->
79 <g filter="url(#softShadow)">
80 <rect x="0" y="0" width="312" height="92" rx="14" fill="#1a1422"/>
81 <rect x="0" y="0" width="312" height="92" rx="14" fill="url(#cardGlow)"/>
82 </g>
83
84 <!-- Sparkles inside the card (more AI slop vibes) -->
85 <text x="32" y="56" font-family="-apple-system, system-ui, sans-serif" font-size="20" fill="#fbbf24">✨</text>
86 <text x="268" y="56" font-family="-apple-system, system-ui, sans-serif" font-size="20" fill="#fbbf24">✨</text>
87
88 <!-- Gradient text headline (the slop being detected) -->
89 <text x="156" y="52" text-anchor="middle"
90 font-family="-apple-system, BlinkMacSystemFont, system-ui, sans-serif"
91 font-size="20" font-weight="700"
92 fill="url(#slop)">AI-Powered Magic</text>
93
94 <!-- Subline -->
95 <text x="156" y="72" text-anchor="middle"
96 font-family="-apple-system, system-ui, sans-serif"
97 font-size="10" fill="#9b94a8" letter-spacing="0.02em">Reimagining the future of everything</text>
98
99 <!-- Impeccable magenta outline (offset by 2px outside the card)
100 Path so top-left corner is square (where label meets it) -->
101 <path d="M -4 -4 L 312 -4 Q 316 -4 316 0 L 316 92 Q 316 96 312 96 L 0 96 Q -4 96 -4 92 L -4 -4 Z"
102 fill="none" stroke="${MAGENTA}" stroke-width="2" stroke-linejoin="round"/>
103
104 <!-- Label tab on top, flush with outline's outer edge.
105 Label extends to x=-5 (matching outline visible left edge) and y=-3 (covering the outline's top stroke). -->
106 <path d="M -5 -3 L -5 -22 Q -5 -26 -1 -26 L 113 -26 Q 117 -26 117 -22 L 117 -3 Z"
107 fill="${MAGENTA}"/>
108 <text x="2" y="-10"
109 font-family="-apple-system, BlinkMacSystemFont, system-ui, sans-serif"
110 font-size="11" font-weight="600" fill="white" letter-spacing="0.01em">
111 ✦ gradient text
112 </text>
113 </g>
114
115 <!-- Tagline at bottom -->
116 <text x="28" y="244"
117 font-family="-apple-system, BlinkMacSystemFont, system-ui, sans-serif"
118 font-size="15" font-weight="600" fill="${TEXT}" letter-spacing="-0.01em">
119 Detect AI slop in any web page.
120 </text>
121 <text x="28" y="262"
122 font-family="-apple-system, system-ui, sans-serif"
123 font-size="11" fill="${TEXT_DIM}" letter-spacing="0.01em">
124 24 detections · Open DevTools and see what needs fixing.
125 </text>
126</svg>
127`;
128
129const browser = await puppeteer.launch({ headless: true });
130const page = await browser.newPage();
131await page.setViewport({ width: 440, height: 280, deviceScaleFactor: 1 });
132await page.setContent(`
133 <!DOCTYPE html>
134 <html>
135 <head><style>
136 * { margin: 0; padding: 0; }
137 body { width: 440px; height: 280px; overflow: hidden; }
138 </style></head>
139 <body>${svg}</body>
140 </html>
141`);
142await page.screenshot({ path: OUT, omitBackground: false });
143await browser.close();
144
145console.log(`Generated ${path.relative(ROOT, OUT)}`);