# Flux CMS — Content Management für Laravel + Livewire + Flux UI

## Überblick

Flux CMS ist ein modulares, mehrsprachiges Content-Management-System für Laravel-Anwendungen. Es nutzt Livewire Volt (Functional API) und Flux UI Pro als Admin-Oberfläche und speichert alle Inhalte übersetzbar in der Datenbank (`spatie/laravel-translatable`).

**Kernkonzept:** Bestehende Inhalte aus Laravel `lang/`-Dateien werden in die Datenbank migriert. Ein `cms()` Helper mit Fallback auf `__()` sorgt für nahtlose Migration — Seiten funktionieren sofort, auch wenn noch nicht alle Inhalte in der DB sind.

### Tech-Stack

- Laravel 12, Livewire 3, Volt (Functional API)
- Flux UI Pro v2 (Komponenten-Bibliothek)
- Tailwind CSS v4
- `spatie/laravel-translatable` für Mehrsprachigkeit
- `intervention/image` v3 für Bildverarbeitung
- Pest v4 für Tests

---

## Architektur

```
┌──────────────────────────────────────────────────────┐
│  Frontend (Blade/Livewire Views)                     │
│  {{ cms('welcome.hero.heading') }}                   │
│  {{ cms_media_url('welcome.hero.image') }}           │
│  {{ media_url('keyvisual.webp', 'hero') }}           │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Helper-Funktionen                                    │
│  cms()          → CmsContentService (DB + Cache)     │
│  cms_media_url() → CmsContent → CmsMedia → URL      │
│  media_url()     → CmsMedia → URL (mit Cache)        │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Datenbank (flux_cms_* Tabellen)                     │
│  CmsContent, CmsMedia, CmsNewsItem, CmsIndustry,    │
│  CmsFaq, CmsLinkedinPost, CmsDownload                │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Media Library (Storage / public disk)               │
│  cms/media/originals/  → Original-Uploads            │
│  cms/media/conversions/ → Generierte Bildgrößen      │
│  cms/media/thumbnails/  → Auto-generierte Thumbnails │
└──────────────┬───────────────────────────────────────┘
               │
┌──────────────▼───────────────────────────────────────┐
│  Admin UI (/admin/cms/*)                             │
│  Livewire Volt + Flux UI Pro                         │
│  Content-Editor, Medienbibliothek, Downloads,        │
│  Team, News, LinkedIn, FAQs, Industries              │
└──────────────────────────────────────────────────────┘
```

---

## Datenbankmodelle

### CmsContent (Kern)
Flexibler Key-Value-Store für alle Seiteninhalte.

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `group` | string | Seiten-/Dateiname (z.B. `welcome`, `about`, `footer`) |
| `key` | string | Dot-Notation-Pfad (z.B. `hero.heading`, `cta.description`) |
| `type` | enum | `text`, `html`, `image`, `json`, `link` |
| `value` | JSON | Übersetzbar: `{"de": "...", "en": "..."}` |
| `order` | int | Sortierung (chronologisch nach Dateistruktur) |

### CmsMedia (Medienbibliothek)
Zentrale Verwaltung aller Bilder und PDFs.

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `filename` | string | Original-Dateiname (z.B. `keyvisual.webp`) |
| `path` | string | Storage-Pfad (z.B. `cms/media/originals/abc123.webp`) |
| `type` | string | `image`, `pdf`, `document` |
| `mime_type` | string | MIME-Typ |
| `file_size` | int | Dateigröße in Bytes |
| `original_width` | int | Breite bei Bildern |
| `original_height` | int | Höhe bei Bildern |
| `disk` | string | Storage-Disk (default: `public`) |
| `collection` | string | Optionale Sammlung/Ordner |
| `conversions` | JSON | Generierte Bildgrößen-Pfade |
| `title` | JSON | Übersetzbar |
| `alt_text` | JSON | Übersetzbar |
| `is_published` | bool | Veröffentlichungsstatus |

### CmsDownload (Case Studies, Capabilities, Success Stories)
Vollständiges Download-Management mit sprachspezifischen PDFs.

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `title` | JSON | Übersetzbar |
| `description` | JSON | Übersetzbar |
| `category` | string | `case_study`, `capability`, `success_story` |
| `icon` | string | Heroicon-Name |
| `sub_category` | string | Detail-Kategorie (z.B. "R&D Product Support") |
| `type_label` | JSON | Übersetzbar (z.B. "Case Study" / "Capability") |
| `alt` | JSON | Übersetzbar: Alt-Text für Vorschaubild |
| `file_path` | JSON | Übersetzbar: PDF-Dateiname pro Sprache (DE/EN) |
| `thumbnail` | string | Vorschaubild-Dateiname (CmsMedia) |
| `open_text` | JSON | Übersetzbar: Button-Text "PDF öffnen" |
| `download_text` | JSON | Übersetzbar: Button-Text "PDF downloaden" |
| `highlights` | JSON | Kennzahlen `[{"value": "100%", "label": "..."}]` |
| `checkpoints` | JSON | Checkpunkte `[{"value": "..."}]` |

### Weitere Modelle

| Modell | Tabelle | Zweck |
|--------|---------|-------|
| `CmsNewsItem` | `flux_cms_news_items` | News-Band mit Icon, PDF, Datum, Bild (via CmsMedia) |
| `CmsIndustry` | `flux_cms_industries` | Industries-Band mit Sortierung |
| `CmsFaq` | `flux_cms_faqs` | FAQ-Einträge mit Kategorie und Hilfe-Text |
| `CmsLinkedinPost` | `flux_cms_linkedin_posts` | LinkedIn-Posts mit Bild (via CmsMedia) |
| `CmsSearchIndex` | `flux_cms_search_index` | Seitensuche: Keywords, Titel, Beschreibungen pro Route |

Alle Modelle nutzen `Spatie\Translatable\HasTranslations` für DE/EN (erweiterbar).

### CmsSearchIndex (Seitensuche)
Zentrales Index-Modell für die Frontend-Suche. Jeder Eintrag repräsentiert eine Seite/Route.

| Feld | Typ | Beschreibung |
|------|-----|-------------|
| `item_id` | string | Eindeutige ID (z.B. `home`, `leistungen`) |
| `route` | string | Laravel Named Route (z.B. `home`, `leistungen`) |
| `route_params` | JSON | Route-Parameter als Array |
| `category` | JSON | Übersetzbar: Kategorie (z.B. `Startseite`, `Leistungen`) |
| `title_key` | string | CMS-Key für Titel (löst via `cms()` auf) |
| `title_fallback` | JSON | Übersetzbar: Statischer Fallback-Titel |
| `description_key` | string | CMS-Key für Beschreibung |
| `description_fallback_key` | string | Sekundärer CMS-Key als Fallback |
| `description_fallback_text` | JSON | Übersetzbar: Statischer Fallback-Text |
| `keywords` | JSON | Übersetzbar: Array aus Keywords oder CMS-Keys (Dot-Notation) |
| `is_published` | bool | Veröffentlichungsstatus |

**Keywords:** Können direkte Texte oder CMS-Keys (mit Punkt) sein. CMS-Keys werden automatisch aufgelöst.

```php
// toFrontendArray() löst alle Keys auf
$entries = CmsSearchIndex::published()->ordered()->get()
    ->map(fn ($entry) => $entry->toFrontendArray())
    ->toArray();
```

---

## Medienbibliothek

### Konzept

Alle Bilder und PDFs werden zentral in einer Medienbibliothek verwaltet. Anstatt Dateien direkt an Inhalte hochzuladen, werden sie aus der Bibliothek ausgewählt. Vorteile:

- Zentrale Verwaltung aller Assets
- Automatische Bildoptimierung (Resize, Kompression, Formatkonvertierung)
- Wiederverwendung von Medien über verschiedene Inhalte hinweg
- Sprachunabhängige Bilder, sprachspezifische PDFs

### Bildprofile (Conversions)

Definiert in `config/flux-cms.php`:

```php
'media' => [
    'profiles' => [
        'hero'      => ['width' => 1920, 'height' => 800,  'format' => 'webp', 'quality' => 85],
        'card'      => ['width' => 768,  'height' => 512,  'format' => 'webp', 'quality' => 80],
        'thumbnail' => ['width' => 400,  'height' => 300,  'format' => 'webp', 'quality' => 75],
        'avatar'    => ['width' => 400,  'height' => 400,  'format' => 'webp', 'quality' => 80],
        'news'      => ['width' => 1200, 'height' => 630,  'format' => 'webp', 'quality' => 80],
        'thumb'     => ['width' => 200,  'height' => 200,  'format' => 'webp', 'quality' => 70],
    ],
],
```

Conversions werden automatisch beim Upload (Thumbnail) oder on-demand im Admin generiert.

### MediaConversionService

```php
$service = app(MediaConversionService::class);

// Einzelne Conversion
$service->convert($media, 'hero');

// Alle Conversions
$service->generateAllConversions($media);

// Thumbnail automatisch
$service->generateThumbnail($media);
```

### Komponenten

| Komponente | Typ | Beschreibung |
|------------|-----|-------------|
| `MediaLibraryUploader` | Class-based Livewire | Multi-File Drag&Drop Upload via `flux:file-upload` |
| `MediaPicker` | Class-based Livewire | Wiederverwendbares Modal zur Medienauswahl |

**MediaPicker-Verwendung:**

```blade
<livewire:admin.cms.media-picker
    :value="$imageMediaId"
    field="news_image"
    type="image"
    profile="news"
    label="Bild wählen"
    :key="'img-' . $editingId" />
```

Feuert `media-selected` Event mit `field`, `mediaId`, `url`:

```php
on(['media-selected' => function (string $field, ?int $mediaId, ?string $url) {
    if ($field === 'news_image') {
        $this->imageMediaId = $mediaId;
        $media = $mediaId ? CmsMedia::find($mediaId) : null;
        $this->image = $media ? $media->filename : '';
    }
}]);
```

---

## Helper-Funktionen

### `cms()` — Content abrufen

```php
$value = cms('welcome.hero.heading');
$value = cms('key', ['highlight' => '<span>...</span>']);
$value = cms('key', locale: 'en');
```

**Fallback-Kette:** DB (mit Cache) → `__()` (Laravel-Translation)

### `cms_media_url()` — Bild über CmsContent-Key

```php
// Holt Dateiname aus CmsContent, löst über CmsMedia auf
$url = cms_media_url('welcome.hero.image');
$url = cms_media_url('welcome.hero.image', 'hero'); // mit Conversion-Profil
```

### `media_url()` — Bild über Dateiname

```php
// Löst CmsMedia-Dateiname direkt auf (mit In-Memory-Cache)
$url = media_url('keyvisual.webp');
$url = media_url('keyvisual.webp', 'hero'); // mit Conversion-Profil
```

**Fallback:** `asset('assets/images/' . $filename)` wenn nicht in CmsMedia.

### `tcms()` — Content mit Tooltip-Verarbeitung

```php
$text = tcms('welcome.hero.heading');
```

---

## Dateien-Übersicht

### Package-Kern (`package/flux-cms/core/`)

```
src/
├── Models/
│   ├── CmsContent.php           # Key-Value Content-Store
│   ├── CmsMedia.php             # Medienbibliothek-Einträge
│   ├── CmsNewsItem.php          # News (mit toFrontendArray())
│   ├── CmsDownload.php          # Downloads (mit toFrontendArray())
│   ├── CmsIndustry.php          # Industries
│   ├── CmsFaq.php               # FAQs
│   └── CmsLinkedinPost.php      # LinkedIn-Posts
├── Services/
│   ├── CmsContentService.php    # Content-Abruf mit Caching + Fallback
│   └── MediaConversionService.php # Bildverarbeitung (intervention/image)
├── Helpers/
│   ├── cms_helpers.php           # cms() und tcms() Helper
│   ├── MediaLibraryUploader.php  # Multi-File Upload Komponente
│   └── MediaPicker.php           # Medienauswahl-Modal
└── FluxCmsServiceProvider.php    # Package-Registrierung

database/
├── migrations/
│   ├── ..._create_flux_cms_contents_table.php
│   ├── ..._create_flux_cms_downloads_table.php
│   ├── ..._create_flux_cms_linkedin_posts_table.php
│   ├── ..._create_flux_cms_faqs_table.php
│   ├── ..._create_flux_cms_news_items_table.php
│   ├── ..._create_flux_cms_industries_table.php
│   └── ..._create_flux_cms_media_table.php
└── seeders-reference/

config/
└── flux-cms.php   # Locales, Cache, Media-Profiles, Auth
```

### Projekt-Integration (Host-App)

```
app/
├── helpers.php                     # cms(), tcms(), cms_media_url(), media_url()
└── Livewire/Admin/Cms/
    ├── MediaLibraryUploader.php    # Multi-File Upload
    └── MediaPicker.php             # Medienauswahl-Modal

resources/views/
├── livewire/admin/cms/             # Volt-Admin-Komponenten
│   ├── content-index.blade.php     # Inhalte-Editor (text/html/image/json)
│   ├── news-index.blade.php        # News-Verwaltung
│   ├── downloads-index.blade.php   # Downloads (Case Studies/Capabilities/Stories)
│   ├── team-index.blade.php        # Team-Verwaltung
│   ├── linkedin-index.blade.php    # LinkedIn-Posts
│   ├── industries-index.blade.php  # Industries
│   ├── faqs-index.blade.php        # FAQ-Verwaltung
│   ├── media-index.blade.php       # Medienbibliothek
│   ├── media-library-uploader.blade.php
│   ├── media-picker.blade.php
│   └── dashboard-index.blade.php   # CMS-Dashboard
└── components/layouts/
    └── cms.blade.php               # CMS-Admin-Layout mit Sidebar

database/
├── migrations/
│   ├── ..._add_detail_columns_to_flux_cms_downloads_table.php
│   └── ..._change_file_path_to_json_on_flux_cms_downloads_table.php
└── seeders/
    ├── CmsContentSeeder.php        # Lang → DB Migration
    ├── CmsMediaSeeder.php          # Medien-Einträge + CmsContent image-Typen
    ├── CmsDownloadSeeder.php       # Case Studies, Capabilities, Success Stories
    ├── CmsNewsItemSeeder.php       # News-Band (mit Media-Filename-Mapping)
    ├── CmsLinkedinPostSeeder.php   # LinkedIn-Posts (mit Media-Filenames)
    ├── CmsIndustrySeeder.php
    └── CmsFaqSeeder.php

routes/web.php                      # CMS-Routes unter /admin/cms/*
```

---

## Admin-Bereiche

### Inhalte (`/admin/cms/content`)
- Alle `CmsContent`-Einträge nach Gruppen
- **Text/HTML**: Inline-Editor (Flux Editor) mit konfigurierbaren Toolbars
- **Image**: MediaPicker mit Vorschau
- **JSON**: Modal-Editor mit strukturierter Bearbeitung (Icons, Editor, Inputs)
- Sprachumschaltung DE/EN

### Medienbibliothek (`/admin/cms/media`)
- Grid- und Listenansicht (Toggle)
- Filter nach Typ (Bilder/PDFs/Dokumente), Sammlung, Dateiname
- Multi-File Drag&Drop Upload
- Detail-Sidebar: Metadaten, Alt-Text, Titel, Sammlung
- Bildgrößen-Management: Einzelne oder alle Conversions generieren
- **PDF-Vorschau**: Eingebettete PDF-Darstellung via iframe
- **Typ-Icons**: Farbige Icons (blau=Bild, rot=PDF) neben Dateinamen

### Downloads (`/admin/cms/downloads`)
- **Drei Kategorien**: Case Studies, Capabilities, Success Stories
- Kategoriefilter-Buttons
- Vollständige Bearbeitung: Titel, Unterkategorie, Typ-Label, Alt-Text, Icon, Beschreibung
- **Vorschaubild**: MediaPicker mit Bildvorschau
- **Sprachspezifische PDFs**: Separater PDF-Upload pro Sprache (DE/EN) mit Vorschau
- **Highlights-Editor** (Case Studies/Success Stories): Dynamische Kennzahl-Paare
- **Checkpoints-Editor** (Capabilities): Dynamische Checkpunkte
- Sortierung mit Pfeiltasten
- `toFrontendArray()` für nahtlose Frontend-Integration

### News-Band (`/admin/cms/news`)
- CRUD mit Icon (Heroicon-Select + Vorschau), Datum, Autor
- **Bild**: MediaPicker mit Vorschau in Liste und Formular
- **PDF**: MediaPicker mit eingebetteter PDF-Vorschau
- Rich-Text-Editor für Kurztext und Inhalt
- Sprachumschaltung

### Team (`/admin/cms/team`)
- CRUD für Teammitglieder (JSON in CmsContent)
- **Profilbild**: MediaPicker mit Vorschau (rund)
- Name, Position, Kürzel, LinkedIn-URL, Kurzvorstellung
- Profil-Text mit Rich-Text-Editor

### LinkedIn-Posts (`/admin/cms/linkedin`)
- CRUD mit **Bild über MediaPicker** (nicht mehr Text-Input)
- Titel, Autor, Datum, URL, Tags
- Rich-Text-Editor für Kurztext und Inhalt
- Bildvorschau in Liste und Formular

### Industries (`/admin/cms/industries`)
- CRUD mit Sortierung (Pfeiltasten + numerisches Order-Feld)

### FAQs (`/admin/cms/faqs`)
- CRUD nach Kategorien
- Rich-Text-Editor für Frage und Antwort

### Suchindex (`/admin/cms/search-index`)
- Zwei-Spalten-Layout: Liste links, Editor rechts
- Jeder Eintrag = eine Seite/Route (z.B. `home`, `leistungen`)
- **Keywords** können direkte Texte oder CMS-Keys (Dot-Notation) sein
- **Reindexieren**-Button löst `search:extract-keywords` Artisan-Befehl aus
- Vorschau zeigt aufgelöste Titel/Beschreibung/Keywords pro Sprache
- Sprachumschaltung DE/EN

---

## Content-Typen & Editor-Konfiguration

### Text-Felder
Standard-Editor mit `bold italic` Toolbar. Im Admin automatisch erkannt.

### HTML-Felder (Datenschutz, Impressum)
Voller Editor mit `heading | bold italic underline strike | bullet ordered blockquote | link`.

### Image-Felder
MediaPicker mit Vorschau. Speichert CmsMedia-Dateiname als Wert.

### JSON-Felder
Öffnen ein Modal mit strukturierter Bearbeitung:
- Erkennung ob String-Array oder Objekt-Array
- Icon-Felder → Searchable Heroicon-Select (nur Vorschau des gewählten Icons)
- Text-Felder (description, content, quote) → Rich-Text-Editor
- Verschachtelte JSON → Textarea
- Standard-Felder → Input

### :highlight Pattern
Für gradient-gestylte Textteile in Headings:

```php
// Template
{!! cms('welcome.solutions.heading', [
    'highlight' => '<span class="text-gradient-premium">' . cms('welcome.solutions.heading_highlight') . '</span>',
]) !!}
```

---

## Medien-Integration im Frontend

### Bilder über CmsContent-Keys (dynamisch austauschbar)

```blade
<img src="{{ cms_media_url('welcome.hero.image') }}" />
<img src="{{ cms_media_url('welcome.hero.image', 'hero') }}" /> {{-- mit Conversion --}}
```

### Bilder über Dateiname (direkt)

```blade
<img src="{{ media_url('keyvisual.webp') }}" />
<img src="{{ media_url($profile['image']) }}" /> {{-- z.B. Team-Profil --}}
```

### Downloads (toFrontendArray)

```blade
@foreach (CmsDownload::published()->byCategory('case_study')->ordered()->get() as $dl)
    <x-download-article-card :article="$dl->toFrontendArray()" :index="$loop->index" />
@endforeach
```

### News-Band (toFrontendArray)

```blade
@php
    $newsItems = CmsNewsItem::published()->ordered()->get()
        ->map(fn ($item) => $item->toFrontendArray())
        ->toArray();
@endphp
```

Die `toFrontendArray()` Methoden lösen `media_url()` automatisch auf — Bilder und PDFs sind sofort verlinkt.

---

## Seeders

### Ausführungsreihenfolge

```php
$this->call([
    CmsContentSeeder::class,      // Hauptinhalte aus lang/ Dateien
    CmsMediaSeeder::class,         // Medien-Einträge + Image-Content
    CmsNewsItemSeeder::class,      // News-Band
    CmsIndustrySeeder::class,      // Industries
    CmsFaqSeeder::class,           // FAQs
    CmsLinkedinPostSeeder::class,  // LinkedIn-Posts
    CmsDownloadSeeder::class,      // Case Studies, Capabilities, Stories
]);
```

### Medien-Seeder

Der `CmsMediaSeeder` erfüllt zwei Aufgaben:
1. Erstellt `CmsMedia`-Einträge für alle hochgeladenen Dateien (Bilder + PDFs)
2. Erstellt `CmsContent`-Einträge vom Typ `image` (z.B. `welcome.hero.image` → `keyvisual-small.webp`)

### Download-Seeder

Enthält inline alle 12 Einträge (4 Case Studies, 5 Capabilities, 3 Success Stories) mit:
- Bilingualen Texten (DE/EN)
- Sprachspezifischen PDF-Dateinamen (DE/EN)
- Media-Dateinamen (statt alter Pfade)
- Highlights / Checkpoints

### Dateiname-Mapping

Die Seeder für News und LinkedIn mappen alte Pfade auf Media-Dateinamen:

```php
// Alt: '/assets/images/capability-global-player.jpg'
// Neu: 'capability-global-player.webp' (CmsMedia-Dateiname)
```

---

## Installation

→ Siehe [SETUP.md](SETUP.md) für eine vollständige Schritt-für-Schritt-Anleitung. Für die Migration in ein bestehendes Projekt: [MIGRATION.md](MIGRATION.md).

### Kurzfassung

1. Package einbinden (`composer.json` Repository + require)
2. `composer update flux-cms/core`
3. `php artisan vendor:publish --tag=flux-cms-config`
4. `php artisan migrate`
5. Helper-Funktionen in `app/helpers.php` + `composer.json` autoload
6. Admin-Views aus `admin-reference/` kopieren
7. Seeders kopieren & ausführen
8. Routes registrieren
9. `intervention/image` installieren: `composer require intervention/image`

---

## Features-Übersicht

| Feature | Status | Beschreibung |
|---------|--------|-------------|
| Mehrsprachige Inhalte | ✅ | DE/EN, erweiterbar |
| Content-Editor (Text/HTML/Image) | ✅ | Flux Editor mit konfigurierbaren Toolbars + MediaPicker |
| JSON-Modal-Editor | ✅ | Strukturierte Bearbeitung von Arrays |
| Heroicon-Select | ✅ | Searchable mit Vorschau (performant) |
| **Medienbibliothek** | ✅ | Zentrale Bild/PDF-Verwaltung mit Grid+Listenansicht |
| **Bildoptimierung** | ✅ | Automatische Conversions (Resize/Format/Qualität) |
| **MediaPicker** | ✅ | Wiederverwendbares Modal für Medienauswahl |
| **PDF-Vorschau** | ✅ | Eingebettete PDF-Darstellung im Admin |
| **Downloads (erweitert)** | ✅ | Case Studies, Capabilities, Success Stories mit Highlights/Checkpoints |
| **Sprachspezifische PDFs** | ✅ | Separate DE/EN PDFs pro Download |
| News-Verwaltung | ✅ | CRUD mit MediaPicker für Bild + PDF |
| Team-Verwaltung | ✅ | CRUD mit MediaPicker für Profilbilder |
| LinkedIn-Posts | ✅ | CRUD mit MediaPicker für Bilder |
| Industries-Verwaltung | ✅ | CRUD mit Sortierung |
| FAQ-Verwaltung | ✅ | CRUD nach Kategorien |
| Toast-Benachrichtigungen | ✅ | Flux Toast bei allen Aktionen |
| Fallback auf lang/ | ✅ | cms() → __() wenn nicht in DB |
| Cache | ✅ | Pro Gruppe, auto-invalidiert |
| Seeder mit cleanHtml() | ✅ | Automatische HTML-Bereinigung |
| **Media-Seeder** | ✅ | Wiederherstellung aller Medien nach DB-Reset |
| **Suchindex** | ✅ | Keywords, Titel, Beschreibungen pro Route mit CMS-Key-Auflösung |

---

## Offene Punkte / Roadmap

- [ ] Drag & Drop Sortierung (Alternative zu Pfeilen)
- [ ] Versionierung von Content-Änderungen
- [ ] Rollen-basierter Zugriff auf CMS-Bereiche
- [ ] Import/Export von Inhalten
- [ ] Automatischer LinkedIn-API-Import
- [ ] Bildbearbeitung (Crop/Rotate) im Admin
