diff --git a/public/app.js b/public/app.js
index 2c4c80f..019aa0d 100644
--- a/public/app.js
+++ b/public/app.js
@@ -83,6 +83,22 @@ function featuresForProject(features, projectId) {
return features.filter((f) => !f.projectId || f.projectId === projectId)
}
+const PREVIEW_BASE_WIDTH = 1280
+function setupPreviewFrame(frame) {
+ const view = frame.querySelector('.preview-frame__view')
+ if (!view) return
+ const apply = () => {
+ const w = frame.clientWidth
+ if (w) view.style.transform = `scale(${w / PREVIEW_BASE_WIDTH})`
+ }
+ if (typeof ResizeObserver !== 'undefined') {
+ new ResizeObserver(apply).observe(frame)
+ } else {
+ window.addEventListener('resize', apply)
+ }
+ apply()
+}
+
function renderProjectCard(project, features) {
const li = document.createElement('li')
li.className = 'project-card'
@@ -91,14 +107,25 @@ function renderProjectCard(project, features) {
? `
${projectFeatures.map((f) => `${escapeHtml(f.featureKey)}`).join('')}
`
: ''
+ const ready = isProjectPreviewReady(project)
+ const url = project.previewUrl ? escapeAttr(project.previewUrl) : ''
+ const previewHtml = ready && url
+ ? `
+
+
+ Webseite öffnen ↗
+ `
+ : `${project.previewUrl ? 'Vorschau wird vorbereitet …' : 'Noch keine Webseite veröffentlicht'}
`
+
li.innerHTML = `
+ ${previewHtml}
${escapeHtml(project.projectName || project.subdomain || 'Projekt')}
- Subdomain
- ${escapeHtml(project.subdomain || '–')}
- - Vorschau
- ${project.previewUrl ? `Projekt-Webseite öffnen` : '–'}
- Live-Domain
- ${project.liveDomain ? escapeHtml(project.liveDomain) : '–'}
- Status
- ${escapeHtml(project.status || '–')}
+ ${ready && url ? `Projekt-Webseite öffnen ↗
` : ''}
${featureHtml}
@@ -106,6 +133,9 @@ function renderProjectCard(project, features) {
`
+ const frame = li.querySelector('[data-preview-frame]')
+ if (frame) setupPreviewFrame(frame)
+
const btn = li.querySelector('[data-details-btn]')
const box = li.querySelector('[data-details-box]')
let loaded = false
diff --git a/public/dashboard.html b/public/dashboard.html
index 0c38604..6a8a798 100644
--- a/public/dashboard.html
+++ b/public/dashboard.html
@@ -61,6 +61,31 @@
.commit-meta { font-size: 12px; opacity: 0.6; }
.commit-body { font-size: 13px; opacity: 0.8; white-space: pre-wrap; margin: 2px 0 0 8px; }
+ /* Projekt-Vorschaufenster */
+ .preview-frame {
+ position: relative; display: block; width: 100%; aspect-ratio: 16 / 10;
+ overflow: hidden; border-radius: 12px; border: 1px solid rgba(255,255,255,0.14);
+ background: rgba(255,255,255,0.04); margin-bottom: 1rem; text-decoration: none;
+ transition: border-color 0.2s ease, box-shadow 0.2s ease;
+ }
+ .preview-frame:hover { border-color: rgba(157,61,254,0.7); box-shadow: 0 0 0 1px rgba(157,61,254,0.35); }
+ .preview-frame__view {
+ position: absolute; top: 0; left: 0; width: 1280px; height: 800px; border: 0;
+ transform-origin: 0 0; pointer-events: none; background: #fff;
+ }
+ .preview-frame__hint {
+ position: absolute; left: 0; right: 0; bottom: 0; padding: 10px 14px;
+ font-size: 13px; font-weight: 600; color: #fff;
+ background: linear-gradient(to top, rgba(0,0,0,0.6), rgba(0,0,0,0));
+ opacity: 0; transition: opacity 0.2s ease; pointer-events: none;
+ }
+ .preview-frame:hover .preview-frame__hint { opacity: 1; }
+ .preview-frame--empty {
+ display: flex; align-items: center; justify-content: center;
+ color: rgba(255,255,255,0.5); font-size: 14px; aspect-ratio: 16 / 10;
+ }
+ .preview-open { margin: 0.75rem 0 0; }
+
/* Chat */
.chat-box { display: flex; flex-direction: column; height: 480px; }
.chat-messages { flex: 1; overflow-y: auto; padding: 6px 4px; display: flex; flex-direction: column; gap: 10px; }