diff --git a/apps/manacore/apps/web/src/lib/modules/calc/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/calc/AppView.svelte
new file mode 100644
index 000000000..834e3b1fb
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/calc/AppView.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+
+
+
{expression || ' '}
+
{result || '0'}
+
+
+
+
+
+
+
+ {#each ['7', '8', '9', '/', '4', '5', '6', '*', '1', '2', '3', '-', '0', '.', '=', '+'] as key}
+
+ {/each}
+
+
+
+ {#if recent.length > 0}
+
+
Verlauf
+ {#each recent as calc (calc.id)}
+
+ {calc.expression}
+ = {calc.result}
+
+ {/each}
+
+ {/if}
+
diff --git a/apps/manacore/apps/web/src/lib/modules/calendar/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/calendar/AppView.svelte
new file mode 100644
index 000000000..8c7f42993
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/calendar/AppView.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+
+ {#each weekDays() as day, i}
+ {@const isToday = day.toISOString().split('T')[0] === todayStr}
+
+ {dayNames[i]}
+
+ {day.getDate()}
+
+
+ {/each}
+
+
+
+
+
Heute
+ {#each todayEvents as event (event.id)}
+
+
{event.title}
+
+ {#if event.allDay}
+ Ganztägig
+ {:else}
+ {formatTime(event.startDate)} — {formatTime(event.endDate)}
+ {/if}
+
+ {#if event.location}
+
{event.location}
+ {/if}
+
+ {/each}
+
+ {#if todayEvents.length === 0}
+
Keine Termine heute
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/cards/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/cards/AppView.svelte
new file mode 100644
index 000000000..e07ec4dd7
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/cards/AppView.svelte
@@ -0,0 +1,73 @@
+
+
+
+
+
+
{decks.length} Decks
+
{dueForReview()} fällig
+
+
+
+ {#each decks as deck (deck.id)}
+
+
+
+
{deck.name}
+
{cardsInDeck(deck.id)}
+
+ {#if deck.description}
+
{deck.description}
+ {/if}
+
+ {/each}
+
+ {#if decks.length === 0}
+
Keine Decks
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/chat/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/chat/AppView.svelte
new file mode 100644
index 000000000..8b36b28ef
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/chat/AppView.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
{conversations.length} Unterhaltungen
+
+
+ {#each sorted as conv (conv.id)}
+ {@const lastMsg = lastMessages.get(conv.id)}
+
+
+
+ {conv.title || 'Neue Unterhaltung'}
+
+ {#if conv.isPinned}
+
📌
+ {/if}
+
+ {#if lastMsg}
+
+ {lastMsg.sender === 'user' ? 'Du: ' : ''}{truncate(lastMsg.messageText)}
+
+ {/if}
+
+ {/each}
+
+ {#if sorted.length === 0}
+
Keine Unterhaltungen
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/citycorners/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/citycorners/AppView.svelte
new file mode 100644
index 000000000..39c741d99
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/citycorners/AppView.svelte
@@ -0,0 +1,86 @@
+
+
+
+
+
+ {locations.length} Orte
+ {favorites.length} Favoriten
+
+
+
+ {#each locations as location (location.id)}
+
+
+
+
+
{location.name}
+ {#if favoriteIds.has(location.id)}
+
★
+ {/if}
+
+
+ {categoryLabels[location.category] ?? location.category}
+
+
+
+ {/each}
+
+ {#if locations.length === 0}
+
Keine Orte
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/clock/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/clock/AppView.svelte
new file mode 100644
index 000000000..b36c2397d
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/clock/AppView.svelte
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+ {now.toLocaleTimeString('de', { hour: '2-digit', minute: '2-digit', second: '2-digit' })}
+
+
+ {now.toLocaleDateString('de', { weekday: 'long', day: 'numeric', month: 'long' })}
+
+
+
+
+ {#if worldClocks.length > 0}
+
+
Weltuhr
+ {#each worldClocks as wc (wc.id)}
+
+ {wc.cityName}
+ {timeInZone(wc.timezone)}
+
+ {/each}
+
+ {/if}
+
+
+ {#if activeTimers.length > 0}
+
+
Timer
+ {#each activeTimers as timer (timer.id)}
+
+ {timer.label ?? 'Timer'}
+
+ {formatDuration(timer.remainingSeconds ?? timer.durationSeconds)}
+
+
+ {/each}
+
+ {/if}
+
+
+ {#if enabledAlarms.length > 0}
+
+
Wecker ({enabledAlarms.length})
+ {#each enabledAlarms.slice(0, 3) as alarm (alarm.id)}
+
+ {alarm.label ?? 'Wecker'}
+ {alarm.time}
+
+ {/each}
+
+ {/if}
+
diff --git a/apps/manacore/apps/web/src/lib/modules/contacts/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/contacts/AppView.svelte
new file mode 100644
index 000000000..ef78c42a2
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/contacts/AppView.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
{filtered().length} Kontakte
+
+
+ {#each filtered() as contact (contact.id)}
+
+
+ {initials(contact)}
+
+
+
{displayName(contact)}
+ {#if contact.company}
+
{contact.company}
+ {/if}
+
+ {#if contact.isFavorite}
+
★
+ {/if}
+
+ {/each}
+
+ {#if filtered().length === 0}
+
Keine Kontakte gefunden
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/context/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/context/AppView.svelte
new file mode 100644
index 000000000..3539b4341
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/context/AppView.svelte
@@ -0,0 +1,88 @@
+
+
+
+
+
+ {spaces.length} Spaces
+ {documents.length} Dokumente
+
+
+
+
+ {#if spaces.filter((s) => s.pinned).length > 0}
+
Angepinnte Spaces
+ {#each spaces.filter((s) => s.pinned) as space (space.id)}
+
+
{space.name}
+ {#if space.description}
+
{space.description}
+ {/if}
+
+ {/each}
+ {/if}
+
+
+
Zuletzt bearbeitet
+ {#each recentDocs as doc (doc.id)}
+
+
{@html typeIcons[doc.type] ?? '📄'}
+
+
{doc.title || 'Unbenannt'}
+
+ {#if doc.pinned}
+
📌
+ {/if}
+
+ {/each}
+
+ {#if recentDocs.length === 0}
+
Keine Dokumente
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/inventar/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/inventar/AppView.svelte
new file mode 100644
index 000000000..39bf5321e
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/inventar/AppView.svelte
@@ -0,0 +1,77 @@
+
+
+
+
+
+ {items.length} Gegenstände
+ {#if totalValue() > 0}
+ ~{totalValue().toFixed(0)} EUR
+ {/if}
+
+
+
+ {#each collections as collection (collection.id)}
+
+
+ {#if collection.icon}
+
{collection.icon}
+ {/if}
+
{collection.name}
+
{itemsInCollection(collection.id)}
+
+ {#if collection.description}
+
{collection.description}
+ {/if}
+
+ {/each}
+
+ {#if collections.length === 0}
+
Keine Sammlungen
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/memoro/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/memoro/AppView.svelte
new file mode 100644
index 000000000..a8a30d091
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/memoro/AppView.svelte
@@ -0,0 +1,88 @@
+
+
+
+
+
+ {memos.length} Memos
+ {pinned.length} angepinnt
+
+
+
+ {#each sorted as memo (memo.id)}
+
+
+
+
+ {#if memo.isPinned}
+
📌
+ {/if}
+
+ {memo.title || 'Unbenanntes Memo'}
+
+
+ {#if memo.intro}
+
{memo.intro}
+ {/if}
+
+
+ {memo.processingStatus === 'completed'
+ ? formatDuration(memo.audioDurationMs)
+ : memo.processingStatus}
+
+
+
+ {/each}
+
+ {#if sorted.length === 0}
+
Keine Memos
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/moodlit/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/moodlit/AppView.svelte
new file mode 100644
index 000000000..0ba9a6003
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/moodlit/AppView.svelte
@@ -0,0 +1,68 @@
+
+
+
+
+
+ {#if activeMood}
+
+ {:else}
+
+ {/if}
+
+
+
+
+ {#each moods as mood (mood.id)}
+
+ {/each}
+
+
+ {#if moods.length === 0}
+
Keine Moods
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/mukke/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/mukke/AppView.svelte
new file mode 100644
index 000000000..fd64b790f
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/mukke/AppView.svelte
@@ -0,0 +1,84 @@
+
+
+
+
+
+ {songs.length} Songs
+ {playlists.length} Playlists
+ {favorites.length} Favoriten
+
+
+
+
Zuletzt gehört
+ {#each recentlyPlayed as song (song.id)}
+
+
+ ♫
+
+
+
{song.title}
+
{song.artist ?? 'Unbekannt'}
+
+
{formatDuration(song.duration)}
+
+ {/each}
+
+ {#if recentlyPlayed.length === 0}
+
Noch nichts gehört
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/nutriphi/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/nutriphi/AppView.svelte
new file mode 100644
index 000000000..7c9ab6d81
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/nutriphi/AppView.svelte
@@ -0,0 +1,109 @@
+
+
+
+
+
+
+
{Math.round(totalCalories)}
+
+ {#if goal}
+ von {goal.dailyCalories} kcal
+ {:else}
+ kcal heute
+ {/if}
+
+ {#if goal}
+
+ {/if}
+
+
+
+
+ {Math.round(totalProtein)}g Protein
+ {todayMeals.length} Mahlzeiten
+
+
+
+
+ {#each todayMeals as meal (meal.id)}
+
+
+ {mealTypeLabels[meal.mealType] ?? meal.mealType}
+ {#if meal.nutrition}
+ {Math.round(meal.nutrition.calories)} kcal
+ {/if}
+
+
{meal.description}
+
+ {/each}
+
+ {#if todayMeals.length === 0}
+
Noch keine Mahlzeiten heute
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/photos/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/photos/AppView.svelte
new file mode 100644
index 000000000..f469dee84
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/photos/AppView.svelte
@@ -0,0 +1,64 @@
+
+
+
+
+
+ {albums.length} Alben
+ {favorites.length} Favoriten
+
+
+
+
Alben
+ {#each albums as album (album.id)}
+
+
{album.name}
+ {#if album.description}
+
{album.description}
+ {/if}
+
+ {album.isAutoGenerated ? 'Auto-generiert' : 'Manuell'}
+
+
+ {/each}
+
+ {#if albums.length === 0}
+
Keine Alben
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/picture/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/picture/AppView.svelte
new file mode 100644
index 000000000..050429e28
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/picture/AppView.svelte
@@ -0,0 +1,62 @@
+
+
+
+
+
+
{images.length} Bilder
+
{favoriteCount} Favoriten
+
+
+
+
+ {#each sorted as image (image.id)}
+
+ {#if image.publicUrl}
+

+ {:else}
+
+ {image.format ?? 'img'}
+
+ {/if}
+ {#if image.isFavorite}
+
★
+ {/if}
+
+ {/each}
+
+
+ {#if sorted.length === 0}
+
Keine Bilder
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/planta/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/planta/AppView.svelte
new file mode 100644
index 000000000..b1db32674
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/planta/AppView.svelte
@@ -0,0 +1,102 @@
+
+
+
+
+
+ {plants.length} Pflanzen
+ {#if dueForWatering.length > 0}
+ {dueForWatering.length} giessen
+ {/if}
+ {#if needsAttention.length > 0}
+ {needsAttention.length} brauchen Pflege
+ {/if}
+
+
+
+ {#each plants as plant (plant.id)}
+ {@const schedule = getSchedule(plant.id)}
+ {@const waterDue = needsWater(schedule)}
+
+
+
{@html healthIcons[plant.healthStatus ?? 'healthy'] ?? '🌱'}
+
+
{plant.name}
+ {#if plant.scientificName}
+
{plant.scientificName}
+ {/if}
+
+ {#if waterDue}
+
💧
+ {/if}
+
+ {#if schedule}
+
+ Alle {schedule.frequencyDays} Tage giessen
+
+ {/if}
+
+ {/each}
+
+ {#if plants.length === 0}
+
Keine Pflanzen
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/playground/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/playground/AppView.svelte
new file mode 100644
index 000000000..d432ef7c0
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/playground/AppView.svelte
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+ {#each messages as msg, i}
+
+
{msg.role === 'user' ? 'Du' : modelLabel}
+
{msg.content}
+
+ {/each}
+
+ {#if messages.length === 0}
+
+
Schreib einen Prompt...
+
+ {/if}
+
+
+
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/presi/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/presi/AppView.svelte
new file mode 100644
index 000000000..2e1a30dae
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/presi/AppView.svelte
@@ -0,0 +1,67 @@
+
+
+
+
+
{decks.length} Präsentationen
+
+
+ {#each decks as deck (deck.id)}
+
+
{deck.title}
+
+ {slideCount(deck.id)} Folien
+ {#if deck.isPublic}
+ Öffentlich
+ {/if}
+
+ {#if deck.description}
+
{deck.description}
+ {/if}
+
+ {/each}
+
+ {#if decks.length === 0}
+
Keine Präsentationen
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/questions/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/questions/AppView.svelte
new file mode 100644
index 000000000..ad87ff28f
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/questions/AppView.svelte
@@ -0,0 +1,94 @@
+
+
+
+
+
+ {questions.length} Fragen
+ {collections.length} Sammlungen
+
+
+
+ {#each sorted as question (question.id)}
+
+
+
{question.title}
+
+ {statusLabels[question.status] ?? question.status}
+
+
+ {#if question.description}
+
{question.description}
+ {/if}
+ {#if question.tags.length > 0}
+
+ {#each question.tags.slice(0, 3) as tag}
+ {tag}
+ {/each}
+
+ {/if}
+
+ {/each}
+
+ {#if sorted.length === 0}
+
Keine offenen Fragen
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/skilltree/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/skilltree/AppView.svelte
new file mode 100644
index 000000000..282cb9326
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/skilltree/AppView.svelte
@@ -0,0 +1,81 @@
+
+
+
+
+
+
+ {totalXp} XP
+ Level {highestLevel}
+ {skills.length} Skills
+
+
+
+
+ {#each skills as skill (skill.id)}
+ {@const branch = BRANCH_INFO[skill.branch as SkillBranch]}
+ {@const progress = xpProgress(skill.currentXp, skill.level)}
+
+
+
+
{skill.icon}
+
{skill.name}
+
+
Lv. {skill.level}
+
+
+
+
{skill.currentXp} XP
+
+
+ {branch?.name ?? skill.branch} — {LEVEL_NAMES[skill.level] ?? 'Unbekannt'}
+
+
+ {/each}
+
+ {#if skills.length === 0}
+
Keine Skills angelegt
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/storage/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/storage/AppView.svelte
new file mode 100644
index 000000000..3ebce74ce
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/storage/AppView.svelte
@@ -0,0 +1,92 @@
+
+
+
+
+
+ {folders.length} Ordner
+ {files.length} Dateien
+
+
+
+
+ {#if folders.filter((f) => !f.parentFolderId).length > 0}
+
Ordner
+ {#each folders.filter((f) => !f.parentFolderId) as folder (folder.id)}
+
+ 📁
+ {folder.name}
+
+ {/each}
+ {/if}
+
+
+
Zuletzt
+ {#each recentFiles as file (file.id)}
+
+ {@html fileIcon(file.mimeType)}
+ {file.name}
+ {formatSize(file.size)}
+
+ {/each}
+
+ {#if recentFiles.length === 0}
+
Keine Dateien
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/times/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/times/AppView.svelte
new file mode 100644
index 000000000..0be427377
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/times/AppView.svelte
@@ -0,0 +1,95 @@
+
+
+
+
+
+ {#if running}
+
+
+
+
{running.description || 'Timer läuft'}
+
+
{projectName(running.projectId)}
+
+ {/if}
+
+
+
+ Heute: {todayEntries.length} Einträge
+ {formatDuration(totalToday)}
+
+
+
+
+ {#each todayEntries as entry (entry.id)}
+
+
+
{entry.description || 'Ohne Beschreibung'}
+
{formatDuration(entry.duration)}
+
+
{projectName(entry.projectId)}
+
+ {/each}
+
+ {#if todayEntries.length === 0 && !running}
+
Noch keine Zeiteinträge heute
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/todo/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/todo/AppView.svelte
new file mode 100644
index 000000000..cfba65f35
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/todo/AppView.svelte
@@ -0,0 +1,127 @@
+
+
+
+
+
+
+ {stats.total} gesamt
+ {stats.today} heute
+ 0}>{stats.overdue} überfällig
+
+
+
+
+ {#each ['inbox', 'today', 'overdue'] as f}
+
+ {/each}
+
+
+
+
+
+
+
+ {#each filtered() as task (task.id)}
+
+ {/each}
+
+ {#if filtered().length === 0}
+
Keine Aufgaben
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/uload/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/uload/AppView.svelte
new file mode 100644
index 000000000..7d2663780
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/uload/AppView.svelte
@@ -0,0 +1,79 @@
+
+
+
+
+
+ {links.length} Links
+ {totalClicks} Klicks
+ {folders.length} Ordner
+
+
+
+ {#each sorted as link (link.id)}
+
+
+
+ {link.title || link.shortCode}
+
+
{link.clickCount}
+
+
{hostname(link.originalUrl)}
+ {#if link.customCode}
+
/{link.customCode}
+ {/if}
+
+ {/each}
+
+ {#if sorted.length === 0}
+
Keine Links
+ {/if}
+
+
diff --git a/apps/manacore/apps/web/src/lib/modules/zitare/AppView.svelte b/apps/manacore/apps/web/src/lib/modules/zitare/AppView.svelte
new file mode 100644
index 000000000..393f109de
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/modules/zitare/AppView.svelte
@@ -0,0 +1,58 @@
+
+
+
+
+
+
+ «{todayQuote.text}»
+
+
— {todayQuote.author}
+
+
+
+ {favorites.length} gespeicherte Zitate
+
+
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/PanelHeader.svelte b/apps/manacore/apps/web/src/lib/splitscreen/PanelHeader.svelte
new file mode 100644
index 000000000..030610999
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/PanelHeader.svelte
@@ -0,0 +1,65 @@
+
+
+
+
+
{label}
+
+
+ {#if onSwap}
+
+ {/if}
+
+
+
+
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/ResizeHandle.svelte b/apps/manacore/apps/web/src/lib/splitscreen/ResizeHandle.svelte
new file mode 100644
index 000000000..ce52f48e5
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/ResizeHandle.svelte
@@ -0,0 +1,69 @@
+
+
+
+
+
+
+{#if isDragging}
+
+
+{/if}
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/SplitPaneLayout.svelte b/apps/manacore/apps/web/src/lib/splitscreen/SplitPaneLayout.svelte
new file mode 100644
index 000000000..34460ea3a
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/SplitPaneLayout.svelte
@@ -0,0 +1,49 @@
+
+
+
+
+
+
+ {@render children()}
+
+
+ {#if splitStore.isActive && splitStore.SplitComponent}
+
splitStore.setDividerPosition(pos)} />
+
+
+
+
splitStore.closeSplit()} />
+
+
+
+
+ {/if}
+
+ {#if splitStore.isLoading}
+
+ {/if}
+
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/index.ts b/apps/manacore/apps/web/src/lib/splitscreen/index.ts
new file mode 100644
index 000000000..f8a6d214d
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/index.ts
@@ -0,0 +1,10 @@
+/**
+ * Split-Screen — barrel exports.
+ */
+
+export { splitStore } from './store.svelte';
+export { loadAppComponent, SPLIT_APP_IDS, SPLIT_APP_LABELS } from './registry';
+export type { SplitAppId } from './registry';
+export { default as SplitPaneLayout } from './SplitPaneLayout.svelte';
+export { default as ResizeHandle } from './ResizeHandle.svelte';
+export { default as PanelHeader } from './PanelHeader.svelte';
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/registry.ts b/apps/manacore/apps/web/src/lib/splitscreen/registry.ts
new file mode 100644
index 000000000..069815430
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/registry.ts
@@ -0,0 +1,74 @@
+/**
+ * Split-Screen App Registry
+ *
+ * Lazy-import registry for all app modules.
+ * Each app has an AppView.svelte component that renders in split-screen.
+ */
+
+const APP_COMPONENTS = {
+ todo: () => import('$lib/modules/todo/AppView.svelte'),
+ calendar: () => import('$lib/modules/calendar/AppView.svelte'),
+ contacts: () => import('$lib/modules/contacts/AppView.svelte'),
+ chat: () => import('$lib/modules/chat/AppView.svelte'),
+ picture: () => import('$lib/modules/picture/AppView.svelte'),
+ cards: () => import('$lib/modules/cards/AppView.svelte'),
+ zitare: () => import('$lib/modules/zitare/AppView.svelte'),
+ clock: () => import('$lib/modules/clock/AppView.svelte'),
+ mukke: () => import('$lib/modules/mukke/AppView.svelte'),
+ storage: () => import('$lib/modules/storage/AppView.svelte'),
+ presi: () => import('$lib/modules/presi/AppView.svelte'),
+ inventar: () => import('$lib/modules/inventar/AppView.svelte'),
+ photos: () => import('$lib/modules/photos/AppView.svelte'),
+ skilltree: () => import('$lib/modules/skilltree/AppView.svelte'),
+ citycorners: () => import('$lib/modules/citycorners/AppView.svelte'),
+ times: () => import('$lib/modules/times/AppView.svelte'),
+ context: () => import('$lib/modules/context/AppView.svelte'),
+ questions: () => import('$lib/modules/questions/AppView.svelte'),
+ nutriphi: () => import('$lib/modules/nutriphi/AppView.svelte'),
+ planta: () => import('$lib/modules/planta/AppView.svelte'),
+ uload: () => import('$lib/modules/uload/AppView.svelte'),
+ calc: () => import('$lib/modules/calc/AppView.svelte'),
+ moodlit: () => import('$lib/modules/moodlit/AppView.svelte'),
+ memoro: () => import('$lib/modules/memoro/AppView.svelte'),
+ playground: () => import('$lib/modules/playground/AppView.svelte'),
+};
+
+export type SplitAppId = keyof typeof APP_COMPONENTS;
+
+export const SPLIT_APP_IDS = Object.keys(APP_COMPONENTS) as SplitAppId[];
+
+/** Display names for each app (German UI). */
+export const SPLIT_APP_LABELS: Record = {
+ todo: 'Todo',
+ calendar: 'Kalender',
+ contacts: 'Kontakte',
+ chat: 'Chat',
+ picture: 'Picture',
+ cards: 'Cards',
+ zitare: 'Zitare',
+ clock: 'Uhr',
+ mukke: 'Mukke',
+ storage: 'Storage',
+ presi: 'Presi',
+ inventar: 'Inventar',
+ photos: 'Fotos',
+ skilltree: 'SkillTree',
+ citycorners: 'CityCorners',
+ times: 'Times',
+ context: 'Context',
+ questions: 'Questions',
+ nutriphi: 'NutriPhi',
+ planta: 'Planta',
+ uload: 'uLoad',
+ calc: 'Calc',
+ moodlit: 'Moodlit',
+ memoro: 'Memoro',
+ playground: 'Playground',
+};
+
+export async function loadAppComponent(appId: string) {
+ const loader = APP_COMPONENTS[appId as SplitAppId];
+ if (!loader) return null;
+ const module = await loader();
+ return module.default;
+}
diff --git a/apps/manacore/apps/web/src/lib/splitscreen/store.svelte.ts b/apps/manacore/apps/web/src/lib/splitscreen/store.svelte.ts
new file mode 100644
index 000000000..a9ccb4db2
--- /dev/null
+++ b/apps/manacore/apps/web/src/lib/splitscreen/store.svelte.ts
@@ -0,0 +1,133 @@
+/**
+ * Split-Screen Store — Svelte 5 runes store
+ *
+ * Manages split-screen panel state: which app is shown, divider position,
+ * component loading, and localStorage persistence.
+ */
+
+import type { Component } from 'svelte';
+import { loadAppComponent, type SplitAppId } from './registry';
+
+const STORAGE_KEY = 'manacore:split-screen';
+const MIN_POSITION = 20;
+const MAX_POSITION = 80;
+const DEFAULT_POSITION = 50;
+const MIN_SCREEN_WIDTH = 1024;
+
+interface PersistedState {
+ splitApp: SplitAppId | null;
+ dividerPosition: number;
+}
+
+function loadPersisted(): PersistedState {
+ try {
+ const raw = localStorage.getItem(STORAGE_KEY);
+ if (!raw) return { splitApp: null, dividerPosition: DEFAULT_POSITION };
+ const parsed = JSON.parse(raw) as PersistedState;
+ return {
+ splitApp: parsed.splitApp ?? null,
+ dividerPosition: clamp(parsed.dividerPosition ?? DEFAULT_POSITION),
+ };
+ } catch {
+ return { splitApp: null, dividerPosition: DEFAULT_POSITION };
+ }
+}
+
+function savePersisted(state: PersistedState) {
+ try {
+ localStorage.setItem(STORAGE_KEY, JSON.stringify(state));
+ } catch {
+ // Storage full or unavailable — ignore
+ }
+}
+
+function clamp(pos: number): number {
+ return Math.min(MAX_POSITION, Math.max(MIN_POSITION, pos));
+}
+
+function createSplitStore() {
+ const persisted =
+ typeof window !== 'undefined'
+ ? loadPersisted()
+ : { splitApp: null, dividerPosition: DEFAULT_POSITION };
+
+ let splitApp = $state(null);
+ let SplitComponent = $state(null);
+ let dividerPosition = $state(persisted.dividerPosition);
+ let isLoading = $state(false);
+ let isMobile = $state(
+ typeof window !== 'undefined' ? window.innerWidth < MIN_SCREEN_WIDTH : false
+ );
+
+ const isActive = $derived(!isMobile && splitApp !== null && SplitComponent !== null);
+
+ // Listen for resize
+ if (typeof window !== 'undefined') {
+ window.addEventListener('resize', () => {
+ isMobile = window.innerWidth < MIN_SCREEN_WIDTH;
+ });
+
+ // Restore persisted app on load
+ if (persisted.splitApp && !isMobile) {
+ isLoading = true;
+ loadAppComponent(persisted.splitApp).then((component) => {
+ if (component) {
+ SplitComponent = component;
+ splitApp = persisted.splitApp;
+ }
+ isLoading = false;
+ });
+ }
+ }
+
+ return {
+ get splitApp() {
+ return splitApp;
+ },
+ get SplitComponent() {
+ return SplitComponent;
+ },
+ get dividerPosition() {
+ return dividerPosition;
+ },
+ get isLoading() {
+ return isLoading;
+ },
+ get isActive() {
+ return isActive;
+ },
+ get isMobile() {
+ return isMobile;
+ },
+
+ async openSplit(appId: SplitAppId) {
+ if (isMobile) return;
+ if (splitApp === appId) return;
+
+ isLoading = true;
+ const component = await loadAppComponent(appId);
+ if (component) {
+ SplitComponent = component;
+ splitApp = appId;
+ savePersisted({ splitApp: appId, dividerPosition });
+ }
+ isLoading = false;
+ },
+
+ closeSplit() {
+ splitApp = null;
+ SplitComponent = null;
+ isLoading = false;
+ savePersisted({ splitApp: null, dividerPosition });
+ },
+
+ setDividerPosition(pos: number) {
+ dividerPosition = clamp(pos);
+ if (splitApp) {
+ savePersisted({ splitApp, dividerPosition });
+ }
+ },
+ };
+}
+
+export const splitStore = createSplitStore();