<template x-for="app in installedApps" :key="app.id">
<div class="dock-icon" @click="openApp(app)" :title="app.name">
<span x-text="app.icon"></span>
+ <span class="dock-dot" x-show="app.running"></span>
</div>
</template>
</div>
},
// ── Installed apps ────────────────────────────────────────────────────
- // Loaded from crimata.json manifests on the server
- installedApps: [
- { id: 'bio', name: 'Bio', icon: '🪪', url: '/apps/bio' },
- { id: 'contacts', name: 'Contacts', icon: '👥', url: '/apps/contacts' },
- { id: 'blog', name: 'Blog', icon: '📝', url: '/apps/blog' },
- ],
+ installedApps: [],
+
+ async loadApps() {
+ try {
+ const res = await fetch('/dock/apps')
+ const data = await res.json()
+ this.installedApps = data.map(app => ({
+ ...app,
+ url: `/apps/${app.id}`,
+ }))
+ } catch (e) {
+ console.error('dock unreachable:', e)
+ }
+ },
// ── Init ──────────────────────────────────────────────────────────────
- init() {
+ async init() {
+ await this.loadApps()
+
+ // Refresh running status every 10s
+ setInterval(() => this.loadApps(), 10_000)
+
// Open bio full-screen on load
const bio = this.installedApps.find(a => a.id === 'bio')
if (bio) this.openApp(bio)
}
.dock-icon {
+ position: relative;
width: 52px;
height: 52px;
border-radius: 12px;
user-select: none;
}
+.dock-dot {
+ position: absolute;
+ bottom: -6px;
+ left: 50%;
+ transform: translateX(-50%);
+ width: 4px;
+ height: 4px;
+ border-radius: 50%;
+ background: rgba(255,255,255,0.9);
+}
+
.dock-icon:hover {
transform: translateY(-8px) scale(1.1);
background: rgba(255,255,255,0.35);