generate-promo-tile.js

  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)}`);