diff --git a/public/preview.html b/public/preview.html
index 31f762d..304cbde 100644
--- a/public/preview.html
+++ b/public/preview.html
@@ -202,9 +202,12 @@
const color = TYPE_COLORS[data.type] ?? 0xffffff;
let mesh;
- // API field names from rowToGhost: webm / webp / image (URLs or null).
- const webm = data.webm || null;
- const image = data.image || data.webp || null;
+ // The admin endpoint (/api/admin/ghosts) returns raw DB rows with
+ // snake_case *_path fields holding bare filenames. Map them to /uploads
+ // URLs. (Also tolerate the public API shape: webm / webp / image.)
+ const up = (f) => (f ? (f.startsWith('/') || f.startsWith('http') ? f : `/uploads/${f}`) : null);
+ const webm = up(data.webm_path || data.webm);
+ const image = up(data.image_path || data.image || data.webp_path || data.webp);
if (webm && SUPPORTS_WEBM_ALPHA) {
const vid = document.createElement('video');
@@ -259,7 +262,12 @@
current = buildGhost(data);
applyTransform();
scene.add(current);
- $('#pv-hint').textContent = `${data.name}${data.webm ? ' · WebM' + (SUPPORTS_WEBM_ALPHA ? '' : ' (no VP9-alpha -> fallback)') : data.image ? ' · image' : ' · procedural wisp'}`;
+ const hasWebm = !!(data.webm_path || data.webm);
+ const hasImg = !!(data.image_path || data.image || data.webp_path || data.webp);
+ const kind = hasWebm
+ ? ' · WebM' + (SUPPORTS_WEBM_ALPHA ? '' : ' (no VP9-alpha fallback)')
+ : hasImg ? ' · image' : ' · procedural wisp';
+ $('#pv-hint').textContent = `${data.name}${kind}`;
}
function clearGhost() {