diff --git a/apps/mana/apps/web/src/hooks.server.ts b/apps/mana/apps/web/src/hooks.server.ts
index 165d61c25..c5f9c01d5 100644
--- a/apps/mana/apps/web/src/hooks.server.ts
+++ b/apps/mana/apps/web/src/hooks.server.ts
@@ -134,6 +134,8 @@ window.__PUBLIC_GLITCHTIP_DSN__ = ${JSON.stringify(PUBLIC_GLITCHTIP_DSN)};
const isDev = process.env.NODE_ENV !== 'production';
setSecurityHeaders(response, {
+ // Allow mana-media images (localhost in dev, https in prod)
+ imgSrc: isDev ? ['http://localhost:*'] : [],
// @huggingface/transformers (used by @mana/local-llm) lazy-loads the
// onnxruntime-web WASM loader from jsDelivr at backend selection
// time via a dynamic import(). Browsers route dynamic imports
diff --git a/apps/mana/apps/web/src/lib/components/wallpaper/WallpaperLayer.svelte b/apps/mana/apps/web/src/lib/components/wallpaper/WallpaperLayer.svelte
new file mode 100644
index 000000000..3b84b4771
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/components/wallpaper/WallpaperLayer.svelte
@@ -0,0 +1,108 @@
+
+
+{#if isActive}
+
+ {#key bgStyle}
+
+
+
+ {#if hasMultipleScenes}
+
+
+
+
+ {:else}
+
+ {/if}
+
+ {#if currentSource.type !== 'none'}
+
+ {/if}
+
+
+
+
+ {#each tabs as tab}
+
+ {/each}
+
+
+
+ {#if activeTab === 'gradients'}
+
+
+ Empfohlen
+ ({currentVariant})
+
+
+ {#each gradientPresets as gradient}
+
+ {/each}
+
+
+
+ {#each Object.entries(GRADIENT_PRESETS).filter(([v]) => v !== currentVariant) as [variant, presets]}
+
+ {variant}
+
+
+ {#each presets as gradient}
+
+ {/each}
+
+ {/each}
+ {:else if activeTab === 'images'}
+ {#if PREDEFINED_WALLPAPERS.length === 0}
+
+
+
Hintergrundbilder kommen bald
+
+ {:else}
+ {#if variantWallpapers.length > 0}
+
+ Empfohlen
+ ({currentVariant})
+
+
+ {#each variantWallpapers as wp}
+
+ {/each}
+
+ {/if}
+
+ {#if otherWallpapers.length > 0}
+
+ Weitere
+
+
+ {#each otherWallpapers as wp}
+
+ {/each}
+
+ {/if}
+ {/if}
+ {:else if activeTab === 'upload'}
+
+
!uploading && fileInput?.click()}
+ onkeydown={(e) => {
+ if ((e.key === 'Enter' || e.key === ' ') && !uploading) {
+ e.preventDefault();
+ fileInput?.click();
+ }
+ }}
+ ondragover={handleDragOver}
+ ondragleave={handleDragLeave}
+ ondrop={handleDrop}
+ >
+ {#if uploading}
+
+
Wird hochgeladen...
+ {:else}
+
+
+ {isDragging ? 'Hier ablegen' : 'Bild hochladen'}
+
+
JPG, PNG, WebP — Drag & Drop oder Klick
+ {/if}
+
+
+
+
+ {#if uploadError}
+
+ {uploadError}
+
+
+ {/if}
+
+
+ {#if loadingGallery}
+
+
+ Lade Bilder...
+
+ {:else if uploadedWallpapers.length > 0}
+
+ Eigene Bilder
+
+
+ {#each uploadedWallpapers as media (media.id)}
+
+
+
+
+ {/each}
+
+ {/if}
+ {/if}
+
+
+
+
Overlay
+
+
+
+
+ {blur}px
+
+
+
+
+
+
+
+ {Math.round(overlayOpacity * 100)}%
+
+
+
+
+
+
+
diff --git a/apps/mana/apps/web/src/lib/config/wallpapers.ts b/apps/mana/apps/web/src/lib/config/wallpapers.ts
new file mode 100644
index 000000000..f867e5833
--- /dev/null
+++ b/apps/mana/apps/web/src/lib/config/wallpapers.ts
@@ -0,0 +1,85 @@
+/**
+ * Predefined Wallpaper Registry
+ *
+ * Bundled wallpaper images shipped with the app. Each theme variant
+ * gets 2–3 curated images. Images live in /static/wallpapers/ as WebP,
+ * with thumbnails in /static/wallpapers/thumbs/.
+ *
+ * Phase 2 will populate actual images — for now this is the registry structure.
+ */
+
+import type { ThemeVariant, WallpaperGradient } from '@mana/shared-theme';
+
+export interface PredefinedWallpaper {
+ id: string;
+ /** Theme variant this wallpaper was designed for (shown first in that theme). */
+ variant: ThemeVariant;
+ /** Full-size image URL (1920×1080 WebP). */
+ url: string;
+ /** Thumbnail URL (320×180 WebP) for the picker grid. */
+ thumbUrl: string;
+ /** Display label in the picker. */
+ label: string;
+}
+
+/**
+ * All predefined wallpapers. Will be populated in Phase 2 with actual images.
+ * For now, the array is empty so the system works end-to-end without images.
+ */
+export const PREDEFINED_WALLPAPERS: PredefinedWallpaper[] = [
+ // Phase 2: add entries like:
+ // { id: 'ocean-1', variant: 'ocean', url: '/wallpapers/ocean-1.webp', thumbUrl: '/wallpapers/thumbs/ocean-1.webp', label: 'Ocean Waves' },
+];
+
+/**
+ * Look up a predefined wallpaper by ID.
+ */
+export function getPredefinedWallpaper(id: string): PredefinedWallpaper | undefined {
+ return PREDEFINED_WALLPAPERS.find((w) => w.id === id);
+}
+
+/**
+ * Get predefined wallpapers for a specific theme variant.
+ */
+export function getWallpapersForVariant(variant: ThemeVariant): PredefinedWallpaper[] {
+ return PREDEFINED_WALLPAPERS.filter((w) => w.variant === variant);
+}
+
+/**
+ * Theme-aware gradient presets. Each variant gets a few curated gradients
+ * that complement its color palette.
+ */
+export const GRADIENT_PRESETS: Record