+ );
+}
diff --git a/src/components/huelle/Platzhalterseite.tsx b/src/components/huelle/Platzhalterseite.tsx
new file mode 100644
index 0000000..ec143a8
--- /dev/null
+++ b/src/components/huelle/Platzhalterseite.tsx
@@ -0,0 +1,42 @@
+import { Construction } from "lucide-react";
+import { Leerzustand } from "@/components/ui/Leerzustand";
+import { Seitenkopf } from "@/components/ui/Seitenkopf";
+
+/**
+ * Für Navigationsziele, die erst in späteren Phasen gebaut werden (#002) – damit die
+ * Navigation nie auf eine 404 führt.
+ */
+export function Platzhalterseite({
+ titel,
+ untertitel,
+ phase,
+ issue,
+}: {
+ titel: string;
+ untertitel?: string;
+ phase: number;
+ /** Issue-Nummer, z. B. "#008". */
+ issue?: string;
+}) {
+ return (
+
+
+ }
+ titel={`Folgt in Phase ${phase}`}
+ text={
+ <>
+ Diese Ansicht ist noch nicht gebaut
+ {issue && (
+ <>
+ {" "}
+ – geplant in {issue}
+ >
+ )}
+ .
+ >
+ }
+ />
+
+ );
+}
diff --git a/src/components/huelle/SeitenleistenRahmen.tsx b/src/components/huelle/SeitenleistenRahmen.tsx
new file mode 100644
index 0000000..de7c0a6
--- /dev/null
+++ b/src/components/huelle/SeitenleistenRahmen.tsx
@@ -0,0 +1,91 @@
+"use client";
+
+import { clsx } from "clsx";
+import { Menu, X } from "lucide-react";
+import { useEffect, useRef, useState, type MouseEvent, type ReactNode } from "react";
+
+/**
+ * Schaltet die Seitenleiste unter 1024 px ein und aus (#002). Der Inhalt der Leiste kommt
+ * als Server-gerenderte children; nur das Auf/Zu lebt im Browser.
+ *
+ * Geschlossen ist die Leiste mobil `invisible` und damit auch aus der Tab-Reihenfolge –
+ * nur verschieben würde unsichtbare Links fokussierbar lassen.
+ */
+export function SeitenleistenRahmen({ kopf, children }: { kopf: ReactNode; children: ReactNode }) {
+ const [offen, setOffen] = useState(false);
+ const knopf = useRef(null);
+
+ useEffect(() => {
+ if (!offen) return;
+ function taste(e: KeyboardEvent) {
+ if (e.key === "Escape") {
+ setOffen(false);
+ knopf.current?.focus();
+ }
+ }
+ window.addEventListener("keydown", taste);
+ return () => window.removeEventListener("keydown", taste);
+ }, [offen]);
+
+ // Ein Klick auf einen Link in der Leiste navigiert – danach soll das Menü zu sein.
+ function klickInLeiste(e: MouseEvent) {
+ if ((e.target as HTMLElement).closest("a")) setOffen(false);
+ }
+
+ return (
+ <>
+ {/* Kopfleiste nur auf schmalen Bildschirmen */}
+
+
+ {kopf}
+
+
+ {offen && (
+
setOffen(false)}
+ />
+ )}
+
+ {/* Ab 1024 px eine eigene Spalte über die volle Seitenhöhe; die Leiste darin klebt
+ oben (sticky), damit Hintergrund und Rahmen auch bei langen Seiten durchlaufen. */}
+
+
+
+ >
+ );
+}
diff --git a/src/components/huelle/index.ts b/src/components/huelle/index.ts
new file mode 100644
index 0000000..b7c5ba5
--- /dev/null
+++ b/src/components/huelle/index.ts
@@ -0,0 +1,3 @@
+export { Huelle } from "./Huelle";
+export { Platzhalterseite } from "./Platzhalterseite";
+export { ROLLEN_TEXT, type HuellenBenutzer, type Rolle } from "./typen";
diff --git a/src/components/huelle/nav-punkte.ts b/src/components/huelle/nav-punkte.ts
new file mode 100644
index 0000000..0537ba3
--- /dev/null
+++ b/src/components/huelle/nav-punkte.ts
@@ -0,0 +1,37 @@
+import {
+ Boxes,
+ KeyRound,
+ LayoutDashboard,
+ LayoutTemplate,
+ Network,
+ ScrollText,
+ Server,
+ Settings,
+ Users,
+ type LucideIcon,
+} from "lucide-react";
+
+export type NavPunkt = { href: string; titel: string; icon: LucideIcon };
+
+/** Hauptnavigation (#002). Reihenfolge folgt dem Arbeitsablauf: Hosts → Schlüssel → Zuweisen. */
+export const HAUPTNAVIGATION: NavPunkt[] = [
+ { href: "/", titel: "Übersicht", icon: LayoutDashboard },
+ { href: "/hosts", titel: "Hosts", icon: Server },
+ { href: "/hostgruppen", titel: "Hostgruppen", icon: Boxes },
+ { href: "/schluessel", titel: "Schlüssel", icon: KeyRound },
+ { href: "/personen", titel: "Personen", icon: Users },
+ { href: "/zuweisungen", titel: "Zuweisungen", icon: Network },
+ { href: "/vorlagen", titel: "Vorlagen", icon: LayoutTemplate },
+];
+
+/** Unter dem Trenner: Nachvollziehen und Verwalten statt Tagesgeschäft. */
+export const NEBENNAVIGATION: NavPunkt[] = [
+ { href: "/protokoll", titel: "Audit-Log", icon: ScrollText },
+ { href: "/einstellungen", titel: "Einstellungen", icon: Settings },
+];
+
+/** „/“ nur exakt, sonst zählt auch jede Unterseite (/schluessel/abc → Schlüssel). */
+export function istAktiv(href: string, pfad: string): boolean {
+ if (href === "/") return pfad === "/";
+ return pfad === href || pfad.startsWith(`${href}/`);
+}
diff --git a/src/components/huelle/typen.ts b/src/components/huelle/typen.ts
new file mode 100644
index 0000000..18fef4d
--- /dev/null
+++ b/src/components/huelle/typen.ts
@@ -0,0 +1,13 @@
+export type Rolle = "ADMIN" | "OPERATOR" | "LESER";
+
+/**
+ * Was die Hülle vom angemeldeten Benutzer wissen muss – bewusst nur Anzeigedaten,
+ * damit die Hülle unabhängig von Sitzung und Datenbank bleibt (#002, #004).
+ */
+export type HuellenBenutzer = { name: string; rolle: Rolle; herkunft: string };
+
+export const ROLLEN_TEXT: Record = {
+ ADMIN: "Admin",
+ OPERATOR: "Operator",
+ LESER: "Leser",
+};