Initial commit: Site Builder with PHP API backend

Visual drag-and-drop website builder using GrapesJS with:
- Multi-page editor with live preview
- File-based asset storage via PHP API (no localStorage base64)
- Template library, Docker support, and Playwright test suite

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-02-28 19:25:42 +00:00
commit a71b58c2c7
58 changed files with 14464 additions and 0 deletions

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-app-showcase" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#0f172a"/>
<stop offset="100%" style="stop-color:#06b6d4"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-app-showcase)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#06b6d4" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#06b6d4"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#7c3aed" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-business-agency" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#0f172a"/>
<stop offset="100%" style="stop-color:#0ea5e9"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-business-agency)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#0ea5e9" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#0ea5e9"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#f8fafc" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-coming-soon" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e1b4b"/>
<stop offset="100%" style="stop-color:#8b5cf6"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-coming-soon)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#8b5cf6" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#8b5cf6"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#ec4899" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-event-conference" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#0f172a"/>
<stop offset="100%" style="stop-color:#e11d48"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-event-conference)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#e11d48" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#e11d48"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#fbbf24" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,74 @@
#!/bin/bash
cd "$(dirname "$0")"
# Generate simple SVG thumbnails for each template
for t in landing-saas portfolio-designer business-agency restaurant-cafe resume-cv app-showcase event-conference coming-soon; do
case $t in
landing-saas)
colors='#6366f1 #1e1b4b #8b5cf6'
label='SaaS Landing'
;;
portfolio-designer)
colors='#f97316 #1c1917 #fafaf9'
label='Portfolio'
;;
business-agency)
colors='#0ea5e9 #0f172a #f8fafc'
label='Agency'
;;
restaurant-cafe)
colors='#b45309 #1c1917 #fef3c7'
label='Restaurant'
;;
resume-cv)
colors='#2563eb #1e293b #f1f5f9'
label='Resume/CV'
;;
app-showcase)
colors='#06b6d4 #0f172a #7c3aed'
label='App Landing'
;;
event-conference)
colors='#e11d48 #0f172a #fbbf24'
label='Event'
;;
coming-soon)
colors='#8b5cf6 #1e1b4b #ec4899'
label='Coming Soon'
;;
esac
c1=$(echo $colors | cut -d' ' -f1)
c2=$(echo $colors | cut -d' ' -f2)
c3=$(echo $colors | cut -d' ' -f3)
cat > "${t}.svg" << EOF
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-${t}" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:${c2}"/>
<stop offset="100%" style="stop-color:${c1}"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-${t})"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="${c1}" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="${c1}"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="${c3}" opacity="0.15"/>
</svg>
EOF
done
echo "Generated thumbnails"

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-landing-saas" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e1b4b"/>
<stop offset="100%" style="stop-color:#6366f1"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-landing-saas)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#6366f1" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#6366f1"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#8b5cf6" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-portfolio-designer" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1c1917"/>
<stop offset="100%" style="stop-color:#f97316"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-portfolio-designer)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#f97316" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#f97316"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#fafaf9" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-restaurant-cafe" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1c1917"/>
<stop offset="100%" style="stop-color:#b45309"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-restaurant-cafe)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#b45309" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#b45309"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#fef3c7" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB

View File

@@ -0,0 +1,26 @@
<svg width="400" height="260" xmlns="http://www.w3.org/2000/svg">
<defs>
<linearGradient id="bg-resume-cv" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" style="stop-color:#1e293b"/>
<stop offset="100%" style="stop-color:#2563eb"/>
</linearGradient>
</defs>
<rect width="400" height="260" rx="12" fill="url(#bg-resume-cv)"/>
<!-- Nav bar -->
<rect x="0" y="0" width="400" height="32" rx="12" fill="rgba(0,0,0,0.3)"/>
<rect x="12" y="10" width="60" height="12" rx="3" fill="#2563eb" opacity="0.8"/>
<rect x="280" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<rect x="330" y="10" width="40" height="12" rx="3" fill="rgba(255,255,255,0.2)"/>
<!-- Hero text lines -->
<rect x="40" y="70" width="200" height="18" rx="4" fill="rgba(255,255,255,0.9)"/>
<rect x="40" y="96" width="160" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<rect x="40" y="112" width="180" height="10" rx="3" fill="rgba(255,255,255,0.3)"/>
<!-- CTA button -->
<rect x="40" y="136" width="100" height="28" rx="6" fill="#2563eb"/>
<!-- Content blocks -->
<rect x="40" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="150" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<rect x="260" y="185" width="100" height="50" rx="6" fill="rgba(255,255,255,0.08)"/>
<!-- Accent circle -->
<circle cx="340" cy="90" r="40" fill="#f1f5f9" opacity="0.15"/>
</svg>

After

Width:  |  Height:  |  Size: 1.4 KiB