Brings the working codebase (Next.js app, auth system, Stripe billing, Docker/deploy config, tests, docs) into version control on top of the placeholder initial commit, and adds account self-deletion (Danger Zone in Settings, password + typed-email confirmation, cascading DB cleanup, Stripe cancellation) per GDPR right-to-erasure. Excludes local build caches, node_modules, and internal agent scratch files; .gitignore hardened to keep those out going forward. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
384 lines
16 KiB
JavaScript
384 lines
16 KiB
JavaScript
import fs from "fs";
|
|
import path from "path";
|
|
import sharp from "sharp";
|
|
|
|
const showcaseDir = path.join(process.cwd(), "public", "showcase");
|
|
if (!fs.existsSync(showcaseDir)) {
|
|
fs.mkdirSync(showcaseDir, { recursive: true });
|
|
}
|
|
|
|
// 1. Synthwave / Cyberpunk Sunset (2400 x 1350 - 16:9 2K)
|
|
const synthwaveSvg = `
|
|
<svg width="2400" height="1350" viewBox="0 0 2400 1350" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#0a051b" />
|
|
<stop offset="35%" stop-color="#1b0a3a" />
|
|
<stop offset="65%" stop-color="#4a0e4e" />
|
|
<stop offset="85%" stop-color="#a01a7d" />
|
|
<stop offset="100%" stop-color="#ff5e62" />
|
|
</linearGradient>
|
|
|
|
<linearGradient id="sunGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#ffee55" />
|
|
<stop offset="40%" stop-color="#ff5483" />
|
|
<stop offset="100%" stop-color="#8000ff" />
|
|
</linearGradient>
|
|
|
|
<linearGradient id="gridGrad" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#ff007f" stop-opacity="0.9" />
|
|
<stop offset="100%" stop-color="#00f2fe" stop-opacity="0.1" />
|
|
</linearGradient>
|
|
|
|
<linearGradient id="mountGrad1" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#2d0b4d" />
|
|
<stop offset="100%" stop-color="#0e041d" />
|
|
</linearGradient>
|
|
|
|
<linearGradient id="mountGrad2" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#551268" />
|
|
<stop offset="100%" stop-color="#120420" />
|
|
</linearGradient>
|
|
|
|
<filter id="neonGlow" x="-50%" y="-50%" width="200%" height="200%">
|
|
<feGaussianBlur stdDeviation="25" result="coloredBlur"/>
|
|
<feMerge>
|
|
<feMergeNode in="coloredBlur"/>
|
|
<feMergeNode in="coloredBlur"/>
|
|
<feMergeNode in="SourceGraphic"/>
|
|
</feMerge>
|
|
</filter>
|
|
|
|
<filter id="softGlow" x="-20%" y="-20%" width="140%" height="140%">
|
|
<feGaussianBlur stdDeviation="60" result="blur" />
|
|
<feMerge>
|
|
<feMergeNode in="blur" />
|
|
<feMergeNode in="SourceGraphic" />
|
|
</feMerge>
|
|
</filter>
|
|
</defs>
|
|
|
|
<!-- Sky -->
|
|
<rect width="2400" height="1350" fill="url(#skyGrad)" />
|
|
|
|
<!-- Stars -->
|
|
${Array.from({ length: 120 })
|
|
.map(() => {
|
|
const cx = Math.floor(Math.random() * 2400);
|
|
const cy = Math.floor(Math.random() * 700);
|
|
const r = (Math.random() * 1.8 + 0.5).toFixed(1);
|
|
const op = (Math.random() * 0.7 + 0.3).toFixed(2);
|
|
return `<circle cx="${cx}" cy="${cy}" r="${r}" fill="#fff" opacity="${op}" />`;
|
|
})
|
|
.join("\n ")}
|
|
|
|
<!-- Sun Glow & Sun -->
|
|
<circle cx="1200" cy="720" r="340" fill="url(#sunGrad)" filter="url(#softGlow)" />
|
|
|
|
<!-- Sun Horizontal Blinds (Synthwave Bars) -->
|
|
${[600, 640, 675, 705, 730, 750, 765, 777]
|
|
.map((y, i) => `<rect x="800" y="${y}" width="800" height="${4 + i * 2.2}" fill="#1b0a3a" />`)
|
|
.join("\n ")}
|
|
|
|
<!-- Distant Mountains Layer 1 -->
|
|
<polygon points="0,850 280,680 520,790 850,620 1100,740 1450,590 1780,750 2050,650 2400,820 2400,1350 0,1350" fill="url(#mountGrad1)" opacity="0.95"/>
|
|
|
|
<!-- Foreground Mountains Layer 2 -->
|
|
<polygon points="0,920 380,730 750,880 1200,670 1620,870 1950,710 2400,900 2400,1350 0,1350" fill="url(#mountGrad2)" opacity="0.9"/>
|
|
|
|
<!-- Horizon Glow -->
|
|
<line x1="0" y1="850" x2="2400" y2="850" stroke="#00f2fe" stroke-width="4" filter="url(#neonGlow)" />
|
|
|
|
<!-- 3D Perspective Grid Plane -->
|
|
<rect x="0" y="850" width="2400" height="500" fill="#070210" />
|
|
|
|
<!-- Horizontal Grid Lines with exponential spacing -->
|
|
${[860, 875, 895, 920, 955, 1000, 1060, 1135, 1225, 1335]
|
|
.map((y) => `<line x1="0" y1="${y}" x2="2400" y2="${y}" stroke="url(#gridGrad)" stroke-width="${1 + (y - 850) / 100}" />`)
|
|
.join("\n ")}
|
|
|
|
<!-- Perspective Vanishing Lines radiating from center horizon -->
|
|
${Array.from({ length: 33 })
|
|
.map((_, i) => {
|
|
const bottomX = (i - 16) * 160 + 1200;
|
|
return `<line x1="1200" y1="850" x2="${bottomX}" y2="1350" stroke="#ff00a0" stroke-width="1.8" opacity="0.75" />`;
|
|
})
|
|
.join("\n ")}
|
|
|
|
<!-- Title / Logo Typography -->
|
|
<text x="1200" y="240" font-family="'Inter', 'Montserrat', 'Helvetica', sans-serif" font-size="64" font-weight="900" letter-spacing="16" fill="#ffffff" text-anchor="middle" filter="url(#neonGlow)">NEON HORIZON</text>
|
|
<text x="1200" y="295" font-family="'Courier New', monospace" font-size="20" letter-spacing="10" fill="#00f2fe" text-anchor="middle" opacity="0.9">PROCEDURAL VECTOR GRAPHICS ENGINE</text>
|
|
</svg>
|
|
`;
|
|
|
|
// 2. Premium Dark Mode Glassmorphic FinTech Dashboard (2000 x 2000 - 1:1 Square)
|
|
const glassmorphismSvg = `
|
|
<svg width="2000" height="2000" viewBox="0 0 2000 2000" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<!-- Background Gradient -->
|
|
<radialGradient id="bgDark" cx="50%" cy="30%" r="90%">
|
|
<stop offset="0%" stop-color="#14192b" />
|
|
<stop offset="50%" stop-color="#0b0e17" />
|
|
<stop offset="100%" stop-color="#05070c" />
|
|
</radialGradient>
|
|
|
|
<!-- Orb Gradients -->
|
|
<radialGradient id="orbPurple" cx="35%" cy="35%" r="65%">
|
|
<stop offset="0%" stop-color="#b55fe6" />
|
|
<stop offset="60%" stop-color="#6e24db" />
|
|
<stop offset="100%" stop-color="#2a0873" />
|
|
</radialGradient>
|
|
|
|
<radialGradient id="orbCyan" cx="30%" cy="30%" r="70%">
|
|
<stop offset="0%" stop-color="#5bf0e4" />
|
|
<stop offset="50%" stop-color="#0b9df2" />
|
|
<stop offset="100%" stop-color="#023b82" />
|
|
</radialGradient>
|
|
|
|
<linearGradient id="cardGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="rgba(255, 255, 255, 0.12)" />
|
|
<stop offset="100%" stop-color="rgba(255, 255, 255, 0.02)" />
|
|
</linearGradient>
|
|
|
|
<linearGradient id="accentGrad" x1="0%" y1="0%" x2="100%" y2="0%">
|
|
<stop offset="0%" stop-color="#6366f1" />
|
|
<stop offset="50%" stop-color="#a855f7" />
|
|
<stop offset="100%" stop-color="#ec4899" />
|
|
</linearGradient>
|
|
|
|
<filter id="blurOrb" x="-50%" y="-50%" width="200%" height="200%">
|
|
<feGaussianBlur stdDeviation="80" />
|
|
</filter>
|
|
|
|
<filter id="cardShadow" x="-20%" y="-20%" width="140%" height="140%">
|
|
<feDropShadow dx="0" dy="30" stdDeviation="40" flood-color="#000" flood-opacity="0.6"/>
|
|
</filter>
|
|
</defs>
|
|
|
|
<!-- Background -->
|
|
<rect width="2000" height="2000" fill="url(#bgDark)" />
|
|
|
|
<!-- Background Ambient Glow Orbs -->
|
|
<circle cx="450" cy="500" r="380" fill="url(#orbPurple)" filter="url(#blurOrb)" opacity="0.65" />
|
|
<circle cx="1550" cy="1400" r="440" fill="url(#orbCyan)" filter="url(#blurOrb)" opacity="0.55" />
|
|
<circle cx="1600" cy="400" r="280" fill="#ec4899" filter="url(#blurOrb)" opacity="0.35" />
|
|
|
|
<!-- Subtle Matrix Dot Grid -->
|
|
${Array.from({ length: 20 })
|
|
.map((_, row) =>
|
|
Array.from({ length: 20 })
|
|
.map((_, col) => `<circle cx="${100 + col * 95}" cy="${100 + row * 95}" r="2" fill="#ffffff" opacity="0.08" />`)
|
|
.join("")
|
|
)
|
|
.join("\n ")}
|
|
|
|
<!-- Main Central Glass Card -->
|
|
<g filter="url(#cardShadow)">
|
|
<rect x="300" y="320" width="1400" height="1360" rx="40" fill="url(#cardGrad)" stroke="rgba(255,255,255,0.22)" stroke-width="2" />
|
|
|
|
<!-- Top Bar inside Card -->
|
|
<circle cx="380" cy="400" r="12" fill="#ef4444" opacity="0.8" />
|
|
<circle cx="420" cy="400" r="12" fill="#f59e0b" opacity="0.8" />
|
|
<circle cx="460" cy="400" r="12" fill="#10b981" opacity="0.8" />
|
|
|
|
<text x="1000" y="408" font-family="'Inter', sans-serif" font-size="22" font-weight="600" fill="#a1a1aa" text-anchor="middle" letter-spacing="2">QUANTUM ANALYTICS ENTERPRISE</text>
|
|
|
|
<!-- Header Stats -->
|
|
<text x="380" y="520" font-family="'Inter', sans-serif" font-size="20" font-weight="500" fill="#94a3b8" letter-spacing="1">TOTAL REVENUE (YTD)</text>
|
|
<text x="380" y="590" font-family="'Inter', sans-serif" font-size="64" font-weight="800" fill="#ffffff" letter-spacing="-1">$4,892,340.50</text>
|
|
|
|
<!-- Growth Badge -->
|
|
<rect x="920" y="535" width="140" height="44" rx="22" fill="rgba(16, 185, 129, 0.15)" stroke="#10b981" stroke-width="1.5" />
|
|
<text x="990" y="563" font-family="'Inter', sans-serif" font-size="18" font-weight="700" fill="#10b981" text-anchor="middle">+34.8% ↑</text>
|
|
|
|
<!-- Chart Area -->
|
|
<g transform="translate(380, 680)">
|
|
<!-- Grid lines -->
|
|
<line x1="0" y1="0" x2="1240" y2="0" stroke="rgba(255,255,255,0.08)" stroke-width="1.5" stroke-dasharray="6,6" />
|
|
<line x1="0" y1="120" x2="1240" y2="120" stroke="rgba(255,255,255,0.08)" stroke-width="1.5" stroke-dasharray="6,6" />
|
|
<line x1="0" y1="240" x2="1240" y2="240" stroke="rgba(255,255,255,0.08)" stroke-width="1.5" stroke-dasharray="6,6" />
|
|
<line x1="0" y1="360" x2="1240" y2="360" stroke="rgba(255,255,255,0.08)" stroke-width="1.5" stroke-dasharray="6,6" />
|
|
|
|
<!-- Area Chart Gradient Fill -->
|
|
<defs>
|
|
<linearGradient id="areaGlow" x1="0%" y1="0%" x2="0%" y2="100%">
|
|
<stop offset="0%" stop-color="#8b5cf6" stop-opacity="0.45" />
|
|
<stop offset="100%" stop-color="#8b5cf6" stop-opacity="0.0" />
|
|
</linearGradient>
|
|
</defs>
|
|
|
|
<!-- Smooth Spline Path -->
|
|
<path d="M 0,320 C 150,280 250,180 400,210 C 550,240 700,80 850,110 C 1000,140 1100,30 1240,10 L 1240,400 L 0,400 Z" fill="url(#areaGlow)" />
|
|
<path d="M 0,320 C 150,280 250,180 400,210 C 550,240 700,80 850,110 C 1000,140 1100,30 1240,10" fill="none" stroke="url(#accentGrad)" stroke-width="6" stroke-linecap="round" />
|
|
|
|
<!-- Glowing Data Points -->
|
|
<circle cx="400" cy="210" r="9" fill="#8b5cf6" stroke="#fff" stroke-width="3" />
|
|
<circle cx="850" cy="110" r="9" fill="#ec4899" stroke="#fff" stroke-width="3" />
|
|
<circle cx="1240" cy="10" r="12" fill="#00f2fe" stroke="#fff" stroke-width="4" />
|
|
</g>
|
|
|
|
<!-- Sub Cards (Mini Metrics) -->
|
|
<!-- Card 1 -->
|
|
<g transform="translate(380, 1180)">
|
|
<rect width="380" height="240" rx="24" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.12)" stroke-width="1.5" />
|
|
<text x="40" y="60" font-family="'Inter', sans-serif" font-size="18" font-weight="600" fill="#94a3b8">Active Workflows</text>
|
|
<text x="40" y="130" font-family="'Inter', sans-serif" font-size="44" font-weight="800" fill="#ffffff">18,492</text>
|
|
<text x="40" y="185" font-family="'Inter', sans-serif" font-size="16" font-weight="500" fill="#38bdf8">99.98% uptime</text>
|
|
</g>
|
|
|
|
<!-- Card 2 -->
|
|
<g transform="translate(810, 1180)">
|
|
<rect width="380" height="240" rx="24" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.12)" stroke-width="1.5" />
|
|
<text x="40" y="60" font-family="'Inter', sans-serif" font-size="18" font-weight="600" fill="#94a3b8">AI Inference Latency</text>
|
|
<text x="40" y="130" font-family="'Inter', sans-serif" font-size="44" font-weight="800" fill="#ffffff">14.2 ms</text>
|
|
<text x="40" y="185" font-family="'Inter', sans-serif" font-size="16" font-weight="500" fill="#a855f7">Realtime stream</text>
|
|
</g>
|
|
|
|
<!-- Card 3 -->
|
|
<g transform="translate(1240, 1180)">
|
|
<rect width="380" height="240" rx="24" fill="rgba(255,255,255,0.05)" stroke="rgba(255,255,255,0.12)" stroke-width="1.5" />
|
|
<text x="40" y="60" font-family="'Inter', sans-serif" font-size="18" font-weight="600" fill="#94a3b8">Data Processed</text>
|
|
<text x="40" y="130" font-family="'Inter', sans-serif" font-size="44" font-weight="800" fill="#ffffff">942.6 TB</text>
|
|
<text x="40" y="185" font-family="'Inter', sans-serif" font-size="16" font-weight="500" fill="#10b981">+12.4% vs last week</text>
|
|
</g>
|
|
</g>
|
|
</svg>
|
|
`;
|
|
|
|
// 3. Sacred Geometry / Algorithmic Golden Mandala (2400 x 2400 - Ultra HD)
|
|
function generateMandalaSvg() {
|
|
const size = 2400;
|
|
const center = size / 2;
|
|
const rings = 12;
|
|
const petals = 24;
|
|
|
|
let elements = [];
|
|
|
|
for (let r = 1; r <= rings; r++) {
|
|
const radius = r * 85;
|
|
const strokeWidth = (r % 3 === 0 ? 3 : 1.2);
|
|
const opacity = (0.2 + (r / rings) * 0.7).toFixed(2);
|
|
elements.push(`<circle cx="${center}" cy="${center}" r="${radius}" fill="none" stroke="url(#goldGrad)" stroke-width="${strokeWidth}" opacity="${opacity}" />`);
|
|
}
|
|
|
|
for (let i = 0; i < petals; i++) {
|
|
const angle = (i * 360) / petals;
|
|
elements.push(`
|
|
<g transform="rotate(${angle} ${center} ${center})">
|
|
<ellipse cx="${center}" cy="${center - 380}" rx="90" ry="340" fill="none" stroke="url(#goldGrad)" stroke-width="1.8" opacity="0.6" />
|
|
<circle cx="${center}" cy="${center - 650}" r="25" fill="none" stroke="#ffd700" stroke-width="2.5" />
|
|
<line x1="${center}" y1="${center - 100}" x2="${center}" y2="${center - 950}" stroke="url(#goldGrad)" stroke-width="1" opacity="0.4" stroke-dasharray="5,8" />
|
|
<polygon points="${center},${center - 850} ${center - 30},${center - 780} ${center + 30},${center - 780}" fill="url(#goldGrad)" opacity="0.75" />
|
|
</g>
|
|
`);
|
|
}
|
|
|
|
for (let i = 0; i < 48; i++) {
|
|
const angle = (i * 360) / 48;
|
|
elements.push(`
|
|
<g transform="rotate(${angle} ${center} ${center})">
|
|
<circle cx="${center}" cy="${center - 880}" r="6" fill="#fff5cc" opacity="0.8" />
|
|
</g>
|
|
`);
|
|
}
|
|
|
|
return `
|
|
<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">
|
|
<defs>
|
|
<radialGradient id="darkCosmos" cx="50%" cy="50%" r="70%">
|
|
<stop offset="0%" stop-color="#120c02" />
|
|
<stop offset="60%" stop-color="#070501" />
|
|
<stop offset="100%" stop-color="#000000" />
|
|
</radialGradient>
|
|
|
|
<linearGradient id="goldGrad" x1="0%" y1="0%" x2="100%" y2="100%">
|
|
<stop offset="0%" stop-color="#ffe259" />
|
|
<stop offset="50%" stop-color="#ffa751" />
|
|
<stop offset="100%" stop-color="#e67e22" />
|
|
</linearGradient>
|
|
|
|
<radialGradient id="centerGlow" cx="50%" cy="50%" r="50%">
|
|
<stop offset="0%" stop-color="#ffdd59" stop-opacity="0.8" />
|
|
<stop offset="40%" stop-color="#ff9f43" stop-opacity="0.3" />
|
|
<stop offset="100%" stop-color="#000" stop-opacity="0" />
|
|
</radialGradient>
|
|
|
|
<filter id="goldGlow" x="-30%" y="-30%" width="160%" height="160%">
|
|
<feGaussianBlur stdDeviation="15" result="blur" />
|
|
<feMerge>
|
|
<feMergeNode in="blur" />
|
|
<feMergeNode in="SourceGraphic" />
|
|
</feMerge>
|
|
</filter>
|
|
</defs>
|
|
|
|
<!-- Background -->
|
|
<rect width="${size}" height="${size}" fill="url(#darkCosmos)" />
|
|
|
|
<!-- Center Ambient Sun Glow -->
|
|
<circle cx="${center}" cy="${center}" r="550" fill="url(#centerGlow)" />
|
|
|
|
<!-- Mandala Geometry -->
|
|
<g filter="url(#goldGlow)">
|
|
${elements.join("\n")}
|
|
</g>
|
|
|
|
<!-- Center Focal Core -->
|
|
<circle cx="${center}" cy="${center}" r="60" fill="url(#goldGrad)" />
|
|
<circle cx="${center}" cy="${center}" r="80" fill="none" stroke="#fff" stroke-width="2" opacity="0.8" />
|
|
<circle cx="${center}" cy="${center}" r="110" fill="none" stroke="url(#goldGrad)" stroke-width="4" />
|
|
|
|
<text x="${center}" y="${size - 120}" font-family="'Cinzel', 'Times New Roman', serif" font-size="36" letter-spacing="18" fill="#ffd700" text-anchor="middle" opacity="0.9">SACRED ALGORITHMIC HARMONY</text>
|
|
</svg>
|
|
`;
|
|
}
|
|
|
|
async function runShowcase() {
|
|
console.log("🎨 Generating High-Resolution Showcase Art in public/showcase/...\n");
|
|
|
|
const items = [
|
|
{
|
|
filename: "01_synthwave_cyberpunk_sunset.png",
|
|
svg: synthwaveSvg,
|
|
type: "png",
|
|
},
|
|
{
|
|
filename: "02_glassmorphic_fintech_dashboard.png",
|
|
svg: glassmorphismSvg,
|
|
type: "png",
|
|
},
|
|
{
|
|
filename: "03_sacred_geometry_mandala_2400.png",
|
|
svg: generateMandalaSvg(),
|
|
type: "png",
|
|
},
|
|
];
|
|
|
|
for (const item of items) {
|
|
const destPath = path.join(showcaseDir, item.filename);
|
|
const svgBuffer = Buffer.from(item.svg);
|
|
|
|
console.log(`Rendering ${item.filename}...`);
|
|
|
|
if (item.type === "png") {
|
|
await sharp(svgBuffer)
|
|
.png({ compressionLevel: 9, adaptiveFiltering: true })
|
|
.toFile(destPath);
|
|
} else {
|
|
await sharp(svgBuffer)
|
|
.jpeg({ quality: 98, mozjpeg: true })
|
|
.toFile(destPath);
|
|
}
|
|
|
|
const stat = fs.statSync(destPath);
|
|
console.log(`✓ Created: ${item.filename} (${(stat.size / 1024).toFixed(1)} KB)`);
|
|
}
|
|
|
|
console.log("\n🚀 All showcase art generated successfully in public/showcase/!");
|
|
}
|
|
|
|
runShowcase().catch((err) => {
|
|
console.error("Error generating showcase images:", err);
|
|
process.exit(1);
|
|
});
|