diff --git a/app/src/app.html b/app/src/app.html
index 6a2bb58..60b13e8 100644
--- a/app/src/app.html
+++ b/app/src/app.html
@@ -1,9 +1,51 @@
-
+
-
+
+ Jörg Lohrer
+
%sveltekit.head%
diff --git a/app/src/lib/components/LoadingOrError.svelte b/app/src/lib/components/LoadingOrError.svelte
new file mode 100644
index 0000000..9d1226f
--- /dev/null
+++ b/app/src/lib/components/LoadingOrError.svelte
@@ -0,0 +1,40 @@
+
+
+{#if loading && !error}
+ Lade von Nostr-Relays …
+{:else if error}
+
+ {error}
+ {#if hablaLink}
+
+ In Habla.news öffnen
+ {/if}
+
+{/if}
+
+
diff --git a/app/src/lib/components/PostCard.svelte b/app/src/lib/components/PostCard.svelte
new file mode 100644
index 0000000..9531ac7
--- /dev/null
+++ b/app/src/lib/components/PostCard.svelte
@@ -0,0 +1,94 @@
+
+
+
+
+
+
{date}
+
{title}
+ {#if summary}
{summary}
{/if}
+
+
+
+
diff --git a/app/src/lib/components/PostView.svelte b/app/src/lib/components/PostView.svelte
new file mode 100644
index 0000000..0b06b2a
--- /dev/null
+++ b/app/src/lib/components/PostView.svelte
@@ -0,0 +1,148 @@
+
+
+{title}
+
+
+{#if image}
+ 
+{/if}
+
+{#if summary}
+ {summary}
+{/if}
+
+{@html bodyHtml}
+
+
diff --git a/app/src/lib/components/ProfileCard.svelte b/app/src/lib/components/ProfileCard.svelte
new file mode 100644
index 0000000..8d25ec0
--- /dev/null
+++ b/app/src/lib/components/ProfileCard.svelte
@@ -0,0 +1,82 @@
+
+
+{#if profile}
+
+ {#if profile.picture}
+

+ {:else}
+
+ {/if}
+
+
{profile.display_name ?? profile.name ?? ''}
+ {#if profile.about}
+
{profile.about}
+ {/if}
+ {#if profile.nip05 || profile.website}
+
+ {/if}
+
+
+{/if}
+
+
diff --git a/app/src/routes/+layout.svelte b/app/src/routes/+layout.svelte
index 9cebde5..1b26365 100644
--- a/app/src/routes/+layout.svelte
+++ b/app/src/routes/+layout.svelte
@@ -1,11 +1,32 @@
-{@render children()}
+
+ {@render children()}
+
+
+
diff --git a/app/src/routes/+page.svelte b/app/src/routes/+page.svelte
index 962cbde..13e33dd 100644
--- a/app/src/routes/+page.svelte
+++ b/app/src/routes/+page.svelte
@@ -1,2 +1,50 @@
-SvelteKit-SPA bootet
-Wird Stück für Stück mit Nostr-Funktionalität gefüllt.
+
+
+
+
+Beiträge
+
+
+
+{#each posts as post (post.id)}
+
+{/each}
+
+
diff --git a/app/src/routes/[...slug]/+page.svelte b/app/src/routes/[...slug]/+page.svelte
new file mode 100644
index 0000000..3b47ef6
--- /dev/null
+++ b/app/src/routes/[...slug]/+page.svelte
@@ -0,0 +1,61 @@
+
+
+
+
+
+
+{#if post}
+
+{/if}
+
+
diff --git a/app/src/routes/[...slug]/+page.ts b/app/src/routes/[...slug]/+page.ts
new file mode 100644
index 0000000..c7b2f51
--- /dev/null
+++ b/app/src/routes/[...slug]/+page.ts
@@ -0,0 +1,21 @@
+import { error, redirect } from '@sveltejs/kit';
+import { parseLegacyUrl, canonicalPostPath } from '$lib/url/legacy';
+import type { PageLoad } from './$types';
+
+export const load: PageLoad = async ({ url }) => {
+ const pathname = url.pathname;
+
+ // Legacy-Form /YYYY/MM/DD/.html/ → Redirect auf //
+ const legacyDtag = parseLegacyUrl(pathname);
+ if (legacyDtag) {
+ throw redirect(301, canonicalPostPath(legacyDtag));
+ }
+
+ // Kanonisch: // — erster Segment des Pfades.
+ const segments = pathname.replace(/^\/+|\/+$/g, '').split('/');
+ if (segments.length !== 1 || !segments[0]) {
+ throw error(404, 'Seite nicht gefunden');
+ }
+
+ return { dtag: decodeURIComponent(segments[0]) };
+};