22-05-2026 Optimierung der User und Admin Panels
Some checks failed
linter / quality (push) Has been cancelled
tests / ci (push) Has been cancelled

This commit is contained in:
Kevin Adametz 2026-05-22 11:18:59 +02:00
parent d2ba22c0cf
commit e8c47b7553
73 changed files with 10282 additions and 1546 deletions

View file

@ -5,7 +5,7 @@
> Bekommt deshalb eine eigene Phase außerhalb der bisherigen
> `hub-flux`-Roadmap (Phase 06 sind dort abgeschlossen).
**Status**: 🟡 in Planung · **Aufwand**: 23 Tage · **Risiko**: mittel
**Status**: ✅ abgeschlossen · **Aufwand**: 23 Tage · **Risiko**: mittel
(Datenmodell-Erweiterung, Editor-Format-Migration, Composer-Dependency)
---
@ -187,25 +187,46 @@ Untertitel, Editor, Medien, Anhänge, Boilerplate.
- [ ] Reorder-Test
- [ ] Datei-Type-Validation grün
### 7F — Scheduling + Embargo (Schema vorhanden, UI aktivieren)
### 7F — Scheduling + Embargo
**Scope:**
- UI-Elemente aus 7C (Veröffentlichung-RadioGroup,
Embargo-Checkbox + Date-Picker) aktivieren
- `bald`-Badges entfernen
- Service-Logik in `PressReleaseService`:
- bei `submitForReview` mit `scheduled_at` → Status bleibt
`review`, beim `publish` durch Admin/Job wird `published_at`
auf `scheduled_at` gesetzt
- bei `embargo_at` → öffentliche Anzeige erst ab `embargo_at`
- Background-Job (`PublishScheduledPressReleases`) für die
Veröffentlichung um `scheduled_at`
- Test: Geplante PM wird nicht vor Termin öffentlich
- Test: Embargo-Filter im Portal-Index
**Was umgesetzt wurde:**
- **UI**: Customer Create + Edit haben eine zweite Radio-Option
„Geplanter Termin" mit `<flux:input type="datetime-local">` und
einen separaten Embargo-Switch + Date-Picker.
`bald`-Badge im Veröffentlichungs-Block entfernt.
- **Validation**: `scheduled_at` muss min. 5 Min in der Zukunft
liegen (passend zum Scheduler-Intervall), `embargo_at` muss in
der Zukunft liegen. Beide Felder werden nur validiert, wenn der
jeweilige Toggle aktiv ist.
- **Save**: `scheduled_at` + `embargo_at` werden in den Forms bei
Create + Update persistiert (Customer Variante; Admin bleibt
vorerst ohne UI).
- **Service**: `PressReleaseService::publish()` nutzt jetzt
`resolvePublishedAt()`:
1. bereits gesetztes `published_at` bleibt unangetastet
2. sonst `scheduled_at` (falls vorhanden)
3. `embargo_at` verschiebt zusätzlich nach hinten
4. Fallback `now()`
Damit greift der vorhandene Sichtbarkeits-Filter
`where('published_at', '<=', now())` in `routes/web.php`
automatisch — keine zusätzlichen Embargo-Filter nötig.
- **Background-Command** `press-releases:publish-scheduled`
(`App\Console\Commands\PublishScheduledPressReleases`) findet
alle Review-PRs mit `scheduled_at <= now`, publisht sie via
Service (`source: 'scheduler'` im Status-Log) und respektiert
weiterhin Blacklist + Mail-Benachrichtigung. Optionen:
`--dry-run`, `--limit=N`.
- **Scheduler-Eintrag** in `routes/console.php`: alle 5 Min,
`withoutOverlapping()`, `runInBackground()`.
**Hinweis:** 7F ist optional und kann nach 7C/D/E in eine eigene
kleine Sub-Phase ausgelagert werden, weil es einen Background-Job
einführt und der Test-Umfang separat sauber bleibt.
**Tests:**
- `tests/Feature/PressReleaseSchedulingTest.php` — 11 Tests für
Service + Command (resolvePublishedAt-Matrix, Source-Log,
Command-Dry-Run, Command-Limit).
- `tests/Feature/CustomerPressReleaseSchedulingFormTest.php`
5 Tests für die Form (Persistierung, Past-Date-Validation für
beide Felder, Now-Mode setzt scheduled_at zurück,
Edit-Hydration).
---

View file

@ -0,0 +1,866 @@
<!doctype html>
<html lang="de">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>presseportale.com — Meine Firmen</title>
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Inter+Tight:wght@400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet" />
<script src="https://cdn.tailwindcss.com?plugins=forms"></script>
<script>
tailwind.config = {
theme: {
extend: {
colors: {
bg:"#F6F4EF","bg-elev":"#FBFAF6","bg-rule":"#E2DDD0","bg-rule-2":"#EDE7D7",
"bg-card":"#FFFFFF","bg-card-warm":"#EFEADC",
hub:"#1A2540","hub-2":"#243152","hub-3":"#2E3D66",
"hub-soft":"#E5E9F1","hub-soft-2":"#CFD6E4","hub-line":"#7B8FBF",
accent:"#B07A3A","accent-deep":"#8A5E27","accent-soft":"#F1E6D3",
ink:"#1A1F1C","ink-2":"#3A413D","ink-3":"#5A6360","ink-4":"#8A918D",
"ink-on-dark":"#F6F4EF","ink-on-dark-2":"#B2B9C7","ink-on-dark-3":"#7B8FBF",
ok:"#2E8540","ok-soft":"#E2F1E5",
warn:"#A87A1F","warn-soft":"#F6EAC8",
err:"#A8331F","err-soft":"#F4DAD2",
},
fontFamily:{
sans:['"Inter Tight"','Inter','system-ui','sans-serif'],
mono:['"JetBrains Mono"','"SF Mono"','ui-monospace','monospace'],
},
}
}
};
</script>
<style>
html,body{margin:0;padding:0;}
body{background:#E8E4DA;font-family:"Inter Tight",system-ui,sans-serif;}
.eyebrow{font-size:10.5px;font-weight:700;letter-spacing:.20em;text-transform:uppercase;color:#1A2540;}
.eyebrow.muted{color:#5A6360;letter-spacing:.16em;font-weight:600;font-size:10px;}
.eyebrow.accent{color:#8A5E27;}
.eyebrow.on-dark{color:#7B8FBF;}
.section-eyebrow{display:inline-flex;align-items:center;gap:10px;font-size:10.5px;font-weight:700;letter-spacing:.22em;text-transform:uppercase;color:#1A2540;}
.section-eyebrow::after{content:"";display:block;width:30px;height:1px;background:#1A2540;opacity:.45;}
.rule{height:1px;background:#E2DDD0;border:0;margin:0;}
/* Sidebar */
.nav-item{display:flex;align-items:center;gap:11px;padding:8px 12px;border-radius:4px;font-size:13px;font-weight:500;color:#3A413D;transition:background .12s,color .12s;position:relative;}
.nav-item:hover{background:#F6F4EF;color:#1A2540;}
.nav-item.active{background:#E5E9F1;color:#1A2540;font-weight:600;}
.nav-item.active::before{content:"";position:absolute;left:-1px;top:6px;bottom:6px;width:2px;background:#1A2540;border-radius:0 2px 2px 0;}
.nav-item.disabled{color:#8A918D;cursor:default;}
.nav-item.disabled:hover{background:transparent;color:#8A918D;}
.nav-item .ico{width:16px;height:16px;flex-shrink:0;color:currentColor;opacity:.8;}
.nav-item.active .ico{opacity:1;}
.nav-section{font-size:10px;font-weight:700;letter-spacing:.18em;text-transform:uppercase;color:#8A918D;padding:0 12px 6px;}
/* Panels */
.panel{background:#FFFFFF;border:1px solid #E2DDD0;border-radius:6px;}
.panel-warm{background:#FBFAF6;border:1px solid #E2DDD0;border-radius:6px;}
/* Buttons */
.btn-primary{display:inline-flex;align-items:center;gap:8px;justify-content:center;padding:9px 16px;background:#1A2540;color:#fff;border-radius:4px;font-size:13px;font-weight:600;transition:background .15s;}
.btn-primary:hover{background:#243152;}
.btn-secondary{display:inline-flex;align-items:center;gap:8px;justify-content:center;padding:8px 14px;background:#fff;color:#1A2540;border:1px solid #CFD6E4;border-radius:4px;font-size:12.5px;font-weight:600;transition:border-color .15s,background .15s;}
.btn-secondary:hover{border-color:#1A2540;background:#F6F4EF;}
.btn-ghost{display:inline-flex;align-items:center;gap:6px;padding:6px 10px;color:#3A413D;border-radius:4px;font-size:12px;font-weight:500;transition:background .12s,color .12s;}
.btn-ghost:hover{background:#F6F4EF;color:#1A2540;}
/* Badges */
.badge{display:inline-flex;align-items:center;gap:6px;padding:3px 9px;border-radius:99px;font-size:10.5px;font-weight:700;letter-spacing:.10em;text-transform:uppercase;}
.badge.warn{background:#F6EAC8;color:#8A5E27;}
.badge.ok{background:#E2F1E5;color:#1F5E2E;}
.badge.err{background:#F4DAD2;color:#8E2A19;}
.badge.hub{background:#E5E9F1;color:#1A2540;}
.badge.muted{background:#EFEADC;color:#5A6360;}
.badge.dot::before{content:"";width:6px;height:6px;border-radius:99px;background:currentColor;}
/* Filter chips */
.filter-chip{display:inline-flex;align-items:center;gap:6px;padding:6px 12px 6px 14px;background:#fff;border:1px solid #CFD6E4;border-radius:4px;font-size:12.5px;color:#1A2540;font-weight:500;transition:border-color .15s,background .15s;white-space:nowrap;}
.filter-chip:hover{border-color:#1A2540;background:#F6F4EF;}
.filter-chip.is-active{background:#1A2540;color:#fff;border-color:#1A2540;}
.filter-chip.is-active:hover{background:#243152;}
.filter-chip .caret{opacity:.55;}
.active-chip{display:inline-flex;align-items:center;gap:7px;padding:4px 6px 4px 11px;background:#FBFAF6;border:1px solid #E2DDD0;border-radius:99px;font-size:11.5px;color:#3A413D;font-weight:500;}
.active-chip strong{color:#1A2540;font-weight:600;}
.active-chip .x{width:16px;height:16px;border-radius:99px;display:inline-flex;align-items:center;justify-content:center;color:#5A6360;background:#EDE7D7;transition:background .12s,color .12s;}
.active-chip .x:hover{background:#1A2540;color:#fff;}
/* Search */
.search-wrap{position:relative;flex:1;min-width:220px;}
.search-wrap input{
width:100%;padding:8px 12px 8px 34px;background:#fff;border:1px solid #CFD6E4;border-radius:4px;
font-size:12.5px;color:#1A1F1C;transition:border-color .15s;
}
.search-wrap input:focus{outline:none;border-color:#1A2540;}
.search-wrap input::placeholder{color:#8A918D;}
.search-wrap .ico{position:absolute;left:11px;top:50%;transform:translateY(-50%);color:#5A6360;}
.search-wrap kbd{position:absolute;right:8px;top:50%;transform:translateY(-50%);font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10px;color:#8A918D;border:1px solid #E2DDD0;border-radius:3px;padding:1px 4px;background:#FBFAF6;}
/* Saved view tabs */
.view-tab{display:inline-flex;align-items:center;gap:8px;padding:7px 12px 9px;font-size:12.5px;font-weight:500;color:#5A6360;border-bottom:2px solid transparent;margin-bottom:-1px;transition:color .12s,border-color .12s;}
.view-tab:hover{color:#1A2540;}
.view-tab.is-active{color:#1A2540;font-weight:600;border-bottom-color:#1A2540;}
.view-tab .cnt{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10.5px;color:#8A918D;background:#EDE7D7;padding:1px 6px;border-radius:99px;font-weight:600;letter-spacing:.04em;}
.view-tab.is-active .cnt{background:#1A2540;color:#fff;}
/* Counter strip */
.counter-strip{display:inline-flex;align-items:center;gap:14px;padding:6px 0;}
.counter-strip .seg{display:inline-flex;align-items:baseline;gap:6px;font-size:12.5px;color:#3A413D;}
.counter-strip .seg b{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:13.5px;font-weight:600;color:#1A1F1C;letter-spacing:-.2px;}
.counter-strip .sep{width:1px;height:11px;background:#CFD6E4;}
/* View-mode toggle (Karten/Liste) */
.seg-toggle{display:inline-flex;background:#fff;border:1px solid #CFD6E4;border-radius:4px;padding:2px;gap:0;}
.seg-toggle button{display:inline-flex;align-items:center;gap:6px;padding:5px 11px;font-size:12px;font-weight:600;color:#5A6360;border-radius:3px;transition:background .12s,color .12s;}
.seg-toggle button:hover{color:#1A2540;}
.seg-toggle button.is-active{background:#1A2540;color:#fff;}
.seg-toggle button.is-active svg{opacity:1;}
.seg-toggle button svg{opacity:.7;}
/* Bridge ribbon */
.bridge-row{display:inline-flex;align-items:center;gap:6px;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:10.5px;letter-spacing:.10em;text-transform:uppercase;color:#5A6360;}
.dot-pe{width:6px;height:6px;border-radius:99px;background:#1F4D3A;}
.dot-bp{width:6px;height:6px;border-radius:99px;background:#C84A1E;}
/* Portal pills */
.portal-pill{display:inline-flex;align-items:center;gap:5px;padding:3px 8px;border-radius:99px;font-size:10.5px;font-weight:600;letter-spacing:.06em;background:#FBFAF6;border:1px solid #E2DDD0;color:#3A413D;white-space:nowrap;}
.portal-pill .pdot{width:6px;height:6px;border-radius:99px;}
.portal-pill.pe .pdot{background:#1F4D3A;}
.portal-pill.bp .pdot{background:#C84A1E;}
/* ============ FIRMA-KARTEN ============ */
.firm-card{background:#fff;border:1px solid #E2DDD0;border-radius:6px;padding:18px;transition:border-color .15s,box-shadow .15s;display:flex;flex-direction:column;gap:14px;min-height:266px;}
.firm-card:hover{border-color:#7B8FBF;box-shadow:0 1px 0 rgba(26,37,64,.04),0 8px 24px -16px rgba(26,37,64,.18);}
.firm-card.is-self{border-color:#1A2540;box-shadow:inset 0 0 0 1px #1A2540;}
.firm-card .logo{width:56px;height:56px;border-radius:6px;display:flex;align-items:center;justify-content:center;font-family:"JetBrains Mono",ui-monospace,monospace;font-weight:700;font-size:18px;letter-spacing:-.5px;flex-shrink:0;}
.firm-card .name{font-size:16px;font-weight:700;letter-spacing:-.3px;color:#1A1F1C;line-height:1.2;margin:0;}
.firm-card .meta-line{font-size:11.5px;color:#5A6360;margin-top:3px;line-height:1.4;}
.firm-card .kpis{display:grid;grid-template-columns:repeat(3,1fr);gap:0;border-top:1px solid #EDE7D7;padding-top:11px;margin-top:auto;}
.firm-card .kpi{display:flex;flex-direction:column;gap:2px;padding:0 4px;border-right:1px solid #EDE7D7;}
.firm-card .kpi:last-child{border-right:0;}
.firm-card .kpi .k{font-family:"JetBrains Mono",ui-monospace,monospace;font-size:15.5px;font-weight:600;color:#1A1F1C;font-variant-numeric:tabular-nums;line-height:1.1;letter-spacing:-.3px;}
.firm-card .kpi .l{font-size:10px;font-weight:700;letter-spacing:.14em;text-transform:uppercase;color:#8A918D;}
.firm-card .role-pill{display:inline-flex;align-items:center;gap:5px;padding:2px 8px 2px 7px;background:#FBFAF6;border:1px dashed #CFD6E4;border-radius:99px;font-size:10.5px;color:#5A6360;font-weight:600;letter-spacing:.04em;}
.firm-card .role-pill::before{content:"";width:5px;height:5px;border-radius:99px;background:#B07A3A;}
.firm-card .role-pill.admin::before{background:#1A2540;}
.firm-card .role-pill.admin{color:#1A2540;}
/* Logo color tokens */
.lg-brew{background:linear-gradient(135deg,#3A4D2F 0%,#1F2E1A 100%);color:#F1E6D3;}
.lg-mv{background:linear-gradient(135deg,#1A2540 0%,#243152 100%);color:#fff;}
.lg-soft{background:#F1E6D3;color:#8A5E27;border:1px solid #E1C883;}
.lg-warm{background:linear-gradient(135deg,#B07A3A 0%,#8A5E27 100%);color:#fff;}
.lg-blank{background:repeating-linear-gradient(135deg,#FBFAF6 0 6px,#F1ECDD 6px 12px);color:#8A918D;border:1px dashed #CFD6E4;}
/* Table */
table.list{width:100%;border-collapse:separate;border-spacing:0;font-size:13px;}
table.list thead th{
text-align:left;font-weight:700;font-size:10px;letter-spacing:.18em;text-transform:uppercase;color:#5A6360;
padding:11px 14px;background:#FBFAF6;border-bottom:1px solid #E2DDD0;white-space:nowrap;
}
table.list thead th:first-child{padding-left:18px;}
table.list thead th:last-child{padding-right:18px;}
table.list tbody td{padding:14px;border-bottom:1px solid #EDE7D7;vertical-align:middle;}
table.list tbody td:first-child{padding-left:18px;}
table.list tbody td:last-child{padding-right:18px;text-align:right;}
table.list tbody tr:last-child td{border-bottom:0;}
table.list tbody tr{transition:background .1s;}
table.list tbody tr:hover{background:#FBFAF6;}
.row-title{font-weight:600;color:#1A1F1C;font-size:13.5px;line-height:1.35;letter-spacing:-.1px;display:inline-flex;align-items:center;gap:9px;}
.row-title:hover{color:#1A2540;}
.row-sub{font-size:11.5px;color:#5A6360;margin-top:3px;line-height:1.4;}
.row-num{font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;font-size:13px;color:#1A1F1C;font-weight:600;}
.row-num .sub{font-family:"Inter Tight",sans-serif;font-weight:400;font-size:11px;color:#8A918D;margin-left:4px;letter-spacing:.02em;}
/* mini-logo for table rows */
.mini-logo{width:30px;height:30px;border-radius:4px;display:inline-flex;align-items:center;justify-content:center;font-family:"JetBrains Mono",ui-monospace,monospace;font-size:11px;font-weight:700;letter-spacing:-.3px;flex-shrink:0;}
/* Menu trigger */
.menu-trigger{display:inline-flex;align-items:center;justify-content:center;width:30px;height:30px;border-radius:4px;border:1px solid transparent;color:#5A6360;transition:background .12s,border-color .12s,color .12s;}
.menu-trigger:hover{background:#F6F4EF;border-color:#CFD6E4;color:#1A2540;}
/* Card-style action button on the card itself */
.card-action{display:inline-flex;align-items:center;gap:6px;padding:6px 12px;border:1px solid #CFD6E4;background:#fff;color:#1A2540;border-radius:4px;font-size:12px;font-weight:600;transition:border-color .12s,background .12s;}
.card-action:hover{border-color:#1A2540;background:#F6F4EF;}
.card-action.primary{background:#1A2540;color:#fff;border-color:#1A2540;}
.card-action.primary:hover{background:#243152;}
/* Empty state */
.empty-stage{padding:80px 24px;text-align:center;display:flex;flex-direction:column;align-items:center;}
.empty-ico{width:64px;height:64px;border-radius:6px;background:#E5E9F1;border:1px solid #CFD6E4;color:#1A2540;display:flex;align-items:center;justify-content:center;margin-bottom:20px;}
.empty-ico.warm{background:#F1E6D3;border-color:#E1C883;color:#8A5E27;}
.empty-title{font-size:17px;font-weight:600;color:#1A1F1C;margin:0;letter-spacing:-.2px;}
.empty-sub{font-size:13px;color:#5A6360;line-height:1.55;margin:8px 0 0;max-width:440px;}
/* Pagination */
.page-btn{display:inline-flex;align-items:center;justify-content:center;min-width:30px;height:30px;padding:0 9px;border-radius:4px;border:1px solid #CFD6E4;background:#fff;font-size:12px;font-weight:600;color:#3A413D;font-family:"JetBrains Mono",ui-monospace,monospace;font-variant-numeric:tabular-nums;transition:border-color .12s,background .12s,color .12s;}
.page-btn:hover{border-color:#1A2540;color:#1A2540;}
.page-btn.is-current{background:#1A2540;border-color:#1A2540;color:#fff;}
.page-btn.is-disabled{color:#B5BCB9;border-color:#EDE7D7;background:#FBFAF6;cursor:default;}
.page-btn.is-disabled:hover{color:#B5BCB9;border-color:#EDE7D7;}
/* "Neue Firma anlegen" Karte (Add tile) */
.add-tile{border:1.5px dashed #CFD6E4;background:#FBFAF6;border-radius:6px;display:flex;flex-direction:column;align-items:center;justify-content:center;text-align:center;padding:24px;min-height:266px;transition:border-color .15s,background .15s,color .15s;cursor:pointer;color:#3A413D;}
.add-tile:hover{border-color:#1A2540;border-style:solid;background:#fff;color:#1A2540;}
.add-tile .ico{width:48px;height:48px;border-radius:6px;background:#E5E9F1;color:#1A2540;display:flex;align-items:center;justify-content:center;margin-bottom:14px;}
.add-tile:hover .ico{background:#1A2540;color:#fff;}
.add-tile .lbl{font-size:14px;font-weight:600;}
.add-tile .sub{font-size:11.5px;color:#5A6360;margin-top:6px;line-height:1.5;max-width:200px;}
</style>
</head>
<body class="bg-bg text-ink font-sans antialiased">
<!-- ============== ARTBOARD ============== -->
<div class="mx-auto bg-bg" style="width:1440px;">
<div class="flex" style="min-height:1100px;">
<!-- ============== SIDEBAR ============== -->
<aside class="bg-bg-elev border-r border-bg-rule flex flex-col" style="width:260px;">
<div class="px-5 pt-6 pb-5">
<a href="Hub Landing presseportale.html" class="flex items-baseline gap-2">
<span class="text-[19px] font-bold tracking-[-0.4px] text-hub leading-none">presseportale<span class="text-accent">.com</span></span>
</a>
<div class="eyebrow muted mt-2">Publisher · Hub</div>
<button class="mt-4 w-full grid items-center gap-2.5 px-3 py-2.5 bg-white border border-bg-rule rounded-[4px] hover:border-hub/40 text-left" style="grid-template-columns:auto 1fr auto;">
<span class="w-7 h-7 rounded-[3px] bg-hub-soft border border-hub-soft-2 flex items-center justify-center text-hub text-[11px] font-bold">TU</span>
<span class="min-w-0">
<span class="block text-[12.5px] font-semibold text-ink leading-tight truncate">Test User</span>
<span class="block text-[10.5px] text-ink-3 leading-tight mt-0.5 truncate">Tegernseer Brauerei AG +1</span>
</span>
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="text-ink-3">
<path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M3 7.5l3-3 3 3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round" opacity="0.4"/>
</svg>
</button>
</div>
<nav class="px-3 flex-1">
<div class="nav-section">Mein Bereich</div>
<div class="space-y-0.5 mb-5">
<a class="nav-item" href="User Dashboard presseportale.html">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M2 7l6-5 6 5v7H2z" stroke="currentColor" stroke-width="1.4"/><path d="M6 14V9h4v5" stroke="currentColor" stroke-width="1.4"/></svg>
Übersicht
</a>
<a class="nav-item" href="User Pressemitteilungen presseportale.html">
<svg class="ico" viewBox="0 0 16 16" fill="none"><rect x="2.5" y="2.5" width="9" height="11" stroke="currentColor" stroke-width="1.4"/><path d="M11.5 5h2v8.5H4" stroke="currentColor" stroke-width="1.4"/><path d="M5 5.5h4M5 8h4M5 10.5h2.5" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
Meine Pressemitteilungen
<span class="badge hub ml-auto" style="font-size:9.5px;padding:1px 6px;letter-spacing:0.08em;">24</span>
</a>
<a class="nav-item active" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><rect x="2.5" y="3.5" width="11" height="10" stroke="currentColor" stroke-width="1.4"/><path d="M2.5 6h11" stroke="currentColor" stroke-width="1.4"/><path d="M6 9h1M9 9h1M6 11.5h1M9 11.5h1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Firmen
<span class="badge hub ml-auto" style="font-size:9.5px;padding:1px 6px;letter-spacing:0.08em;">2</span>
</a>
<a class="nav-item" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 5h10l-1 9H4z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M6 5V3.5a2 2 0 014 0V5" stroke="currentColor" stroke-width="1.4"/></svg>
Buchungen &amp; Add-ons
</a>
<span class="nav-item disabled">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 13V8M7 13V5M11 13V9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Statistiken
<span class="ml-auto text-[9.5px] tracking-[0.14em] uppercase font-semibold text-ink-4">bald</span>
</span>
</div>
<div class="nav-section">Finanzen</div>
<div class="space-y-0.5 mb-5">
<span class="nav-item disabled">
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="8" r="5" stroke="currentColor" stroke-width="1.4"/><path d="M8 5.5v5M6 8h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Credits &amp; Tarif
<span class="ml-auto text-[9.5px] tracking-[0.14em] uppercase font-semibold text-ink-4">bald</span>
</span>
<a class="nav-item" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M3 2.5h7l3 3v8H3z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M10 2.5V5.5h3" stroke="currentColor" stroke-width="1.4"/><path d="M5.5 8h5M5.5 10.5h5M5.5 6h2" stroke="currentColor" stroke-width="1.2" stroke-linecap="round"/></svg>
Rechnungen
</a>
</div>
<div class="nav-section">Konto</div>
<div class="space-y-0.5 mb-5">
<a class="nav-item" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="6" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M3 13.5c.7-2.4 2.7-4 5-4s4.3 1.6 5 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Profil
</a>
<a class="nav-item" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><path d="M8 2l5 2v4c0 3-2 5-5 6-3-1-5-3-5-6V4z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/><path d="M6 8l1.5 1.5L10.5 6" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
Sicherheit
</a>
<a class="nav-item" href="#">
<svg class="ico" viewBox="0 0 16 16" fill="none"><circle cx="6" cy="8" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M8.5 8h5M11 8v2.5" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
API &amp; Integrationen
</a>
</div>
</nav>
<div class="px-4 pb-4">
<div class="bg-hub text-ink-on-dark rounded-[5px] p-4 relative overflow-hidden">
<div class="absolute -top-6 -right-6 w-16 h-16 rounded-full bg-hub-3 opacity-50"></div>
<div class="absolute -bottom-8 -left-8 w-20 h-20 rounded-full bg-hub-3 opacity-30"></div>
<div class="relative">
<div class="flex items-center gap-2 mb-2">
<span class="w-1.5 h-1.5 rounded-full bg-accent animate-pulse"></span>
<span class="eyebrow on-dark" style="color:#F4D89C;">Testmodus aktiv</span>
</div>
<div class="text-[12px] leading-[1.5] text-ink-on-dark-2">
Angemeldet als <strong class="text-white font-semibold">Test User</strong>.<br/>
Admin: <strong class="text-white font-semibold">Portal Admin</strong>
</div>
<button class="mt-3 w-full px-3 py-2 bg-white text-hub text-[12px] font-semibold rounded-[3px] hover:bg-bg transition-colors flex items-center justify-center gap-1.5">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none">
<path d="M9 3L3 9M3 9H8M3 9V4" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
Zurück zum Admin
</button>
</div>
</div>
</div>
</aside>
<!-- ============== MAIN ============== -->
<main class="flex-1 min-w-0" data-screen-label="01 Meine Firmen">
<!-- Topbar -->
<div class="bg-bg-elev border-b border-bg-rule">
<div class="px-10 py-3 flex items-center gap-6">
<div class="flex items-center gap-2 text-[12px] text-ink-3 font-medium">
<a href="Hub Landing presseportale.html" class="hover:text-hub">Hub</a>
<span class="text-ink-4">/</span>
<a href="User Dashboard presseportale.html" class="hover:text-hub">User Backend</a>
<span class="text-ink-4">/</span>
<span class="text-hub font-semibold">Firmen</span>
</div>
<span class="flex-1"></span>
<span class="bridge-row">
<span class="dot-pe"></span> presseecho
<span class="text-ink-4 mx-1">·</span>
<span class="dot-bp"></span> businessportal24
</span>
<span class="w-px h-5 bg-bg-rule"></span>
<div class="relative">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none" class="absolute left-2.5 top-1/2 -translate-y-1/2 text-ink-3">
<circle cx="7" cy="7" r="4.5" stroke="currentColor" stroke-width="1.3"/>
<path d="M10.5 10.5L13 13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
</svg>
<input class="pl-8 pr-3 py-1.5 w-[220px] bg-white border border-bg-rule rounded-[4px] text-[12.5px] placeholder:text-ink-4 focus:outline-none focus:border-hub" placeholder="Suchen…" />
<span class="absolute right-2 top-1/2 -translate-y-1/2 text-[10px] font-mono text-ink-4 border border-bg-rule rounded px-1">⌘K</span>
</div>
<button class="relative w-8 h-8 flex items-center justify-center rounded-[4px] hover:bg-bg border border-transparent hover:border-bg-rule">
<svg width="15" height="15" viewBox="0 0 16 16" fill="none" class="text-ink-2">
<path d="M3.5 7a4.5 4.5 0 119 0v3.5l1 1.5H2.5l1-1.5z" stroke="currentColor" stroke-width="1.3" stroke-linejoin="round"/>
<path d="M6.5 13a1.5 1.5 0 003 0" stroke="currentColor" stroke-width="1.3"/>
</svg>
<span class="absolute top-1 right-1 w-1.5 h-1.5 rounded-full bg-accent"></span>
</button>
<a href="User Firma Bearbeiten presseportale.html?mode=new" class="btn-primary text-[12px] py-1.5 px-3.5">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none">
<path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/>
</svg>
Firma anlegen
</a>
</div>
</div>
<!-- Inhalt -->
<div class="px-10 py-8 space-y-6">
<!-- ============== PAGE HEADER ============== -->
<header class="grid items-end gap-8" style="grid-template-columns:1fr auto;">
<div class="min-w-0">
<div class="flex items-center gap-3 mb-3 flex-nowrap whitespace-nowrap">
<span class="badge hub dot">User Backend</span>
<span class="eyebrow muted">Mein Bereich · A · 03</span>
</div>
<h1 class="text-[34px] font-bold tracking-[-0.7px] text-ink leading-[1.1] m-0">Meine Firmen</h1>
<div class="counter-strip mt-3">
<span class="seg"><b>2</b> Firmen</span>
<span class="sep"></span>
<span class="seg"><b style="color:#1F5E2E;">2</b> aktiv</span>
<span class="sep"></span>
<span class="seg"><b>24</b> Pressemitteilungen gesamt</span>
<span class="sep"></span>
<span class="seg"><b>5</b> Pressekontakte hinterlegt</span>
</div>
<p class="mt-3 text-[12.5px] text-ink-3 leading-[1.55] max-w-[640px] m-0">
Eine Firma ist der Container für Pressemitteilungen: Stammdaten, Boilerplate, Pressekontakte.
Anlage ohne separate Freigabe — die redaktionelle Prüfung erfolgt erst bei der Pressemitteilung.
</p>
</div>
<div class="flex items-center gap-2 flex-shrink-0">
<button class="btn-secondary whitespace-nowrap">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M3 8h7M3 12h4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/><path d="M11 12l2 2 2-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
Export
<span class="badge muted" style="font-size:9px;padding:0px 5px;letter-spacing:0.06em;">bald</span>
</button>
<a href="User Firma Bearbeiten presseportale.html?mode=new" class="btn-primary whitespace-nowrap">
<svg width="12" height="12" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
Neue Firma anlegen
</a>
</div>
</header>
<!-- ============== SAVED VIEWS (TABS) ============== -->
<nav class="border-b border-bg-rule flex items-center gap-1" aria-label="Gespeicherte Ansichten">
<button class="view-tab is-active" data-view="all">Alle <span class="cnt">2</span></button>
<button class="view-tab" data-view="active">Aktiv <span class="cnt">2</span></button>
<button class="view-tab" data-view="drafts">In Anlage <span class="cnt">0</span></button>
<button class="view-tab" data-view="inactive">Inaktiv <span class="cnt">0</span></button>
<button class="view-tab" data-view="shared">Mit mir geteilt <span class="cnt">1</span></button>
<span class="flex-1"></span>
<button class="btn-ghost" title="Eigene Ansicht speichern">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M3 2.5h8l2 2v9H3z" stroke="currentColor" stroke-width="1.3"/><path d="M5 2.5v3h5v-3M5 13.5v-4h6v4" stroke="currentColor" stroke-width="1.3"/></svg>
Ansicht speichern
</button>
</nav>
<!-- ============== FILTER + SUCHE ============== -->
<section class="space-y-3">
<div class="flex items-center gap-2 flex-wrap">
<button class="filter-chip">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none" class="opacity-70"><circle cx="6" cy="6" r="2" fill="currentColor"/><circle cx="6" cy="6" r="4.5" stroke="currentColor" stroke-width="1.2"/></svg>
Status: <strong class="font-semibold">Alle</strong>
<svg class="caret" width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="filter-chip">
<span class="dot-pe inline-block" style="margin-right:1px;"></span><span class="dot-bp inline-block" style="margin-left:-2px;"></span>
Portal: <strong class="font-semibold">Alle</strong>
<svg class="caret" width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="filter-chip">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" class="opacity-70"><circle cx="8" cy="6" r="2.5" stroke="currentColor" stroke-width="1.4"/><path d="M3 13.5c.7-2.4 2.7-4 5-4s4.3 1.6 5 4" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Rolle: <strong class="font-semibold">Alle</strong>
<svg class="caret" width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="filter-chip">
<svg width="11" height="11" viewBox="0 0 16 16" fill="none" class="opacity-70"><path d="M3 13V8M7 13V5M11 13V9" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Branche: <strong class="font-semibold">Alle</strong>
<svg class="caret" width="9" height="9" viewBox="0 0 12 12" fill="none"><path d="M3 4.5l3 3 3-3" stroke="currentColor" stroke-width="1.4" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<span class="w-px h-6 bg-bg-rule mx-1"></span>
<div class="search-wrap" style="max-width:340px;">
<svg class="ico" width="13" height="13" viewBox="0 0 16 16" fill="none">
<circle cx="7" cy="7" r="4.5" stroke="currentColor" stroke-width="1.3"/>
<path d="M10.5 10.5L13 13" stroke="currentColor" stroke-width="1.3" stroke-linecap="round"/>
</svg>
<input type="text" placeholder="Firmenname, Stadt oder Branche…" />
<kbd>/</kbd>
</div>
<span class="flex-1"></span>
<!-- View-Toggle Karten/Liste -->
<div class="seg-toggle" role="tablist" aria-label="Ansicht umschalten">
<button class="is-active" data-viewmode="cards" aria-label="Kartenansicht">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><rect x="2.5" y="2.5" width="4.5" height="4.5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="2.5" width="4.5" height="4.5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="2.5" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" stroke-width="1.4"/><rect x="9" y="9" width="4.5" height="4.5" rx="1" stroke="currentColor" stroke-width="1.4"/></svg>
Karten
</button>
<button data-viewmode="list" aria-label="Listenansicht">
<svg width="13" height="13" viewBox="0 0 16 16" fill="none"><path d="M3 4h10M3 8h10M3 12h10" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
Liste
</button>
</div>
</div>
</section>
<!-- ============== HOST: Karten ODER Liste ============== -->
<article data-state-host>
<!-- ============== KARTEN-ANSICHT (DEFAULT) ============== -->
<div data-state="cards">
<div class="grid gap-4" style="grid-template-columns:repeat(3,1fr);">
<!-- 1) Tegernseer Brauerei -->
<div class="firm-card is-self">
<div class="flex items-start justify-between gap-3">
<div class="logo lg-brew">TB</div>
<div class="flex items-center gap-1">
<span class="badge ok dot">Aktiv</span>
<button class="menu-trigger" aria-label="Aktionen">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="3.5" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="12.5" r="1.2" fill="currentColor"/></svg>
</button>
</div>
</div>
<div class="min-w-0">
<h3 class="name">Tegernseer Brauerei AG</h3>
<div class="meta-line">Tegernsee · Brauerei &amp; Getränke · 142 MA</div>
</div>
<div class="flex items-center gap-2 flex-wrap">
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
<span class="role-pill admin">Admin</span>
</div>
<div class="kpis">
<div class="kpi"><span class="k">16</span><span class="l">PMs</span></div>
<div class="kpi"><span class="k">2</span><span class="l">Kontakte</span></div>
<div class="kpi"><span class="k">14.05.</span><span class="l">letzte PM</span></div>
</div>
<div class="flex items-center gap-2 pt-1">
<a href="User Firma Bearbeiten presseportale.html" class="card-action primary" style="flex:1;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M11 3l2 2-8 8H3v-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
Bearbeiten
</a>
<a href="Veroeffentlichen.html" class="card-action">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
Neue PM
</a>
</div>
</div>
<!-- 2) Mittelstandsverband Süd -->
<div class="firm-card">
<div class="flex items-start justify-between gap-3">
<div class="logo lg-mv">MV</div>
<div class="flex items-center gap-1">
<span class="badge ok dot">Aktiv</span>
<button class="menu-trigger" aria-label="Aktionen">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="3.5" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="12.5" r="1.2" fill="currentColor"/></svg>
</button>
</div>
</div>
<div class="min-w-0">
<h3 class="name">Mittelstandsverband Süd e. V.</h3>
<div class="meta-line">München · Verband · 38 MA</div>
</div>
<div class="flex items-center gap-2 flex-wrap">
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
<span class="portal-pill bp"><span class="pdot"></span>businessportal24</span>
<span class="role-pill">Redakteur</span>
</div>
<div class="kpis">
<div class="kpi"><span class="k">8</span><span class="l">PMs</span></div>
<div class="kpi"><span class="k">3</span><span class="l">Kontakte</span></div>
<div class="kpi"><span class="k">12.05.</span><span class="l">letzte PM</span></div>
</div>
<div class="flex items-center gap-2 pt-1">
<a href="User Firma Bearbeiten presseportale.html" class="card-action primary" style="flex:1;">
<svg width="12" height="12" viewBox="0 0 16 16" fill="none"><path d="M11 3l2 2-8 8H3v-2z" stroke="currentColor" stroke-width="1.4" stroke-linejoin="round"/></svg>
Bearbeiten
</a>
<a href="Veroeffentlichen.html" class="card-action">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
Neue PM
</a>
</div>
</div>
<!-- 3) Add tile -->
<a href="User Firma Bearbeiten presseportale.html?mode=new" class="add-tile">
<span class="ico">
<svg width="22" height="22" viewBox="0 0 24 24" fill="none"><path d="M12 5v14M5 12h14" stroke="currentColor" stroke-width="2" stroke-linecap="round"/></svg>
</span>
<span class="lbl">Neue Firma anlegen</span>
<span class="sub">Stammdaten und Boilerplate. Die Anlage benötigt keine separate Freigabe.</span>
</a>
</div>
</div>
<!-- ============== LISTEN-ANSICHT ============== -->
<div data-state="list" style="display:none;">
<div class="panel overflow-hidden">
<table class="list">
<thead>
<tr>
<th style="width:32px;"></th>
<th>Firma</th>
<th style="width:170px;">Portal</th>
<th style="width:130px;">Rolle</th>
<th style="width:110px;">Status</th>
<th style="width:90px;text-align:right;">PMs</th>
<th style="width:110px;text-align:right;">Kontakte</th>
<th style="width:130px;">Letzte PM</th>
<th style="width:40px;"></th>
</tr>
</thead>
<tbody>
<tr>
<td><span class="mini-logo lg-brew">TB</span></td>
<td>
<a href="User Firma Bearbeiten presseportale.html" class="row-title">Tegernseer Brauerei AG</a>
<div class="row-sub">Tegernsee · Brauerei &amp; Getränke · 142 MA</div>
</td>
<td>
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
<span class="portal-pill bp" style="margin-left:4px;"><span class="pdot"></span>businessportal24</span>
</td>
<td><span class="role-pill admin">Admin</span></td>
<td><span class="badge ok dot">Aktiv</span></td>
<td style="text-align:right;"><span class="row-num">16</span></td>
<td style="text-align:right;"><span class="row-num">2</span></td>
<td><span class="row-num">14.05.2026</span></td>
<td>
<button class="menu-trigger" aria-label="Aktionen">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="3.5" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="12.5" r="1.2" fill="currentColor"/></svg>
</button>
</td>
</tr>
<tr>
<td><span class="mini-logo lg-mv">MV</span></td>
<td>
<a href="User Firma Bearbeiten presseportale.html" class="row-title">Mittelstandsverband Süd e. V.</a>
<div class="row-sub">München · Verband · 38 MA</div>
</td>
<td>
<span class="portal-pill pe"><span class="pdot"></span>presseecho</span>
<span class="portal-pill bp" style="margin-left:4px;"><span class="pdot"></span>businessportal24</span>
</td>
<td><span class="role-pill">Redakteur</span></td>
<td><span class="badge ok dot">Aktiv</span></td>
<td style="text-align:right;"><span class="row-num">8</span></td>
<td style="text-align:right;"><span class="row-num">3</span></td>
<td><span class="row-num">12.05.2026</span></td>
<td>
<button class="menu-trigger" aria-label="Aktionen">
<svg width="16" height="16" viewBox="0 0 16 16" fill="none"><circle cx="8" cy="3.5" r="1.2" fill="currentColor"/><circle cx="8" cy="8" r="1.2" fill="currentColor"/><circle cx="8" cy="12.5" r="1.2" fill="currentColor"/></svg>
</button>
</td>
</tr>
</tbody>
</table>
<div class="px-5 py-3 bg-bg-elev border-t border-bg-rule flex items-center justify-between flex-wrap gap-3">
<div class="flex items-center gap-3 text-[12px] text-ink-3">
<span><strong class="text-ink-2 font-semibold">12</strong> von <strong class="text-ink-2 font-semibold">2</strong></span>
<span class="text-ink-4">·</span>
<label class="flex items-center gap-2">
<span>Pro Seite</span>
<select class="bg-white border border-bg-rule rounded-[3px] text-[12px] py-1 px-2 pr-6 text-ink-2 font-medium focus:outline-none focus:border-hub">
<option>12</option><option selected="">25</option><option>50</option>
</select>
</label>
</div>
<div class="flex items-center gap-1.5">
<button class="page-btn is-disabled" aria-label="Vorherige Seite">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M7.5 3L4.5 6l3 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
<button class="page-btn is-current">1</button>
<button class="page-btn is-disabled" aria-label="Nächste Seite">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M4.5 3l3 3-3 3" stroke="currentColor" stroke-width="1.6" stroke-linecap="round" stroke-linejoin="round"/></svg>
</button>
</div>
</div>
</div>
</div>
<!-- ============== EMPTY — noch keine Firma ============== -->
<div data-state="empty-none" style="display:none;">
<div class="panel">
<div class="empty-stage">
<div class="empty-ico">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none"><rect x="3.5" y="5.5" width="17" height="14" stroke="currentColor" stroke-width="1.5"/><path d="M3.5 9.5h17M8 13h1M11 13h1M8 16h1M11 16h1" stroke="currentColor" stroke-width="1.4" stroke-linecap="round"/></svg>
</div>
<h3 class="empty-title">Noch keine Firma angelegt</h3>
<p class="empty-sub">
Lege deine erste Firma an. Du kannst direkt im Anschluss eine Pressemitteilung darauf veröffentlichen — eine separate Freigabe der Firma ist nicht erforderlich.
</p>
<div class="flex items-center gap-2.5 mt-6">
<a href="User Firma Bearbeiten presseportale.html?mode=new" class="btn-primary">
<svg width="11" height="11" viewBox="0 0 12 12" fill="none"><path d="M6 2v8M2 6h8" stroke="currentColor" stroke-width="1.6" stroke-linecap="round"/></svg>
Erste Firma anlegen
</a>
<button class="btn-secondary">Beispielfirma laden</button>
</div>
<div class="mt-9 grid gap-3 w-full max-w-[560px]" style="grid-template-columns:repeat(3,1fr);">
<div class="text-left px-3 py-2.5 bg-bg-elev border border-bg-rule rounded-[3px]">
<div class="font-mono text-[9.5px] tracking-[0.16em] text-accent font-bold mb-1">01</div>
<div class="text-[11.5px] font-semibold text-ink leading-tight">Stammdaten erfassen</div>
</div>
<div class="text-left px-3 py-2.5 bg-bg-elev border border-bg-rule rounded-[3px]">
<div class="font-mono text-[9.5px] tracking-[0.16em] text-accent font-bold mb-1">02</div>
<div class="text-[11.5px] font-semibold text-ink leading-tight">Boilerplate schreiben</div>
</div>
<div class="text-left px-3 py-2.5 bg-bg-elev border border-bg-rule rounded-[3px]">
<div class="font-mono text-[9.5px] tracking-[0.16em] text-accent font-bold mb-1">03</div>
<div class="text-[11.5px] font-semibold text-ink leading-tight">Pressekontakte zuordnen</div>
</div>
</div>
</div>
</div>
</div>
<!-- ============== EMPTY — Filter ohne Treffer ============== -->
<div data-state="empty-filter" style="display:none;">
<div class="panel">
<div class="empty-stage">
<div class="empty-ico warm">
<svg width="28" height="28" viewBox="0 0 24 24" fill="none"><path d="M3 5h18l-7 9v5l-4 1v-6z" stroke="currentColor" stroke-width="1.5" stroke-linejoin="round"/></svg>
</div>
<h3 class="empty-title">Keine Firmen mit diesen Filtern</h3>
<p class="empty-sub">Aktive Filter passen auf keine Einträge. Filter zurücksetzen oder weiter fassen.</p>
<div class="flex items-center gap-2.5 mt-6">
<button class="btn-primary">Alle Filter zurücksetzen</button>
<button class="btn-secondary">Filter bearbeiten</button>
</div>
</div>
</div>
</div>
</article>
<!-- ============== ROLLEN-LEGENDE ============== -->
<article class="panel-warm p-5">
<div class="grid items-start gap-6" style="grid-template-columns:auto 1fr;">
<div class="min-w-[180px]">
<div class="section-eyebrow">Rollen pro Firma</div>
<p class="text-[12px] text-ink-3 leading-[1.55] mt-3 m-0 max-w-[220px]">
Mehrere Personen können einer Firma zugeordnet sein. Rolle steuert, was im Backend möglich ist.
</p>
</div>
<div class="grid gap-4" style="grid-template-columns:repeat(3,1fr);">
<div>
<span class="role-pill admin" style="margin-bottom:8px;">Admin</span>
<ul class="text-[11.5px] text-ink-2 leading-[1.7] mt-2 list-none p-0 m-0 space-y-0.5">
<li>Stammdaten &amp; Boilerplate</li>
<li>Pressekontakte verwalten</li>
<li>PMs erstellen, einreichen, archivieren</li>
<li>Weitere Mitglieder einladen</li>
</ul>
</div>
<div>
<span class="role-pill" style="margin-bottom:8px;">Redakteur</span>
<ul class="text-[11.5px] text-ink-2 leading-[1.7] mt-2 list-none p-0 m-0 space-y-0.5">
<li>PMs erstellen &amp; einreichen</li>
<li>Stammdaten lesen</li>
<li>Boilerplate lesen / Vorschlag</li>
<li class="text-ink-4">keine Mitglieder-Verwaltung</li>
</ul>
</div>
<div>
<span class="role-pill" style="margin-bottom:8px;">Beobachter <span class="text-ink-4 font-normal">· bald</span></span>
<ul class="text-[11.5px] text-ink-2 leading-[1.7] mt-2 list-none p-0 m-0 space-y-0.5">
<li>Read-only</li>
<li>Statistik &amp; PMs einsehen</li>
<li class="text-ink-4">keine Bearbeitung</li>
</ul>
</div>
</div>
</div>
</article>
<!-- ============== FUSSZEILE ============== -->
<footer class="flex items-center justify-between pt-4 pb-2 text-[11px] text-ink-3 border-t border-bg-rule">
<span>© 2026 presseportale.com · Publisher-Hub</span>
<span class="flex items-center gap-5">
<a href="#" class="hover:text-hub">Tastenkürzel</a>
<a href="#" class="hover:text-hub">Changelog</a>
<a href="#" class="hover:text-hub">Statusseite</a>
<a href="/cdn-cgi/l/email-protection#e1929491918e9395a1919384929284918e9395808d84cf828e8c" class="hover:text-hub">Support</a>
</span>
</footer>
</div>
</main>
</div>
</div>
<!-- ===================== TWEAKS ===================== -->
<script data-cfasync="false" src="/cdn-cgi/scripts/5c5dd728/cloudflare-static/email-decode.min.js"></script><script src="https://unpkg.com/react@18.3.1/umd/react.development.js" integrity="sha384-hD6/rw4ppMLGNu3tX5cjIb+uRZ7UkRJ6BPkLpg4hAu/6onKUg4lLsHAs9EBPT82L" crossorigin="anonymous"></script>
<script src="https://unpkg.com/react-dom@18.3.1/umd/react-dom.development.js" integrity="sha384-u6aeetuaXnQ38mYT8rp6sbXaQe3NL9t+IBXmnYxwkUI2Hw4bsp2Wvmx4yRQF1uAm" crossorigin="anonymous"></script>
<script src="https://unpkg.com/@babel/standalone@7.29.0/babel.min.js" integrity="sha384-m08KidiNqLdpJqLq95G/LEi8Qvjl/xUYll3QILypMoQ65QorJ9Lvtp2RXYGBFj1y" crossorigin="anonymous"></script>
<script type="text/babel" src="tweaks-panel.jsx"></script>
<div id="tweaks-root"></div>
<script type="text/babel">
const TWEAK_DEFAULTS = /*EDITMODE-BEGIN*/{
"view_state": "empty-none",
"show_legend": true
}/*EDITMODE-END*/;
function applyFirmenState(t) {
const host = document.querySelector("[data-state-host]");
if (host) {
host.querySelectorAll("[data-state]").forEach((el) => {
el.style.display = (el.getAttribute("data-state") === t.view_state) ? "" : "none";
});
}
// sync segment toggle
document.querySelectorAll(".seg-toggle button[data-viewmode]").forEach((b) => {
b.classList.toggle("is-active",
(t.view_state === "cards" && b.dataset.viewmode === "cards") ||
(t.view_state === "list" && b.dataset.viewmode === "list"));
});
const legend = document.querySelector(".panel-warm");
if (legend) legend.style.display = t.show_legend ? "" : "none";
}
function TweaksApp() {
const { TweaksPanel, TweakSection, TweakSelect, TweakToggle } = window;
const [t, setTweak] = window.useTweaks(TWEAK_DEFAULTS);
React.useEffect(() => {
applyFirmenState(t);
// wire toggle clicks to tweak state
const handlers = [];
document.querySelectorAll(".seg-toggle button[data-viewmode]").forEach((b) => {
const h = () => setTweak("view_state", b.dataset.viewmode);
b.addEventListener("click", h);
handlers.push([b, h]);
});
return () => handlers.forEach(([b, h]) => b.removeEventListener("click", h));
}, [t]);
return (
<TweaksPanel title="Tweaks">
<TweakSection label="Ansicht">
<TweakSelect
label="Zustand"
value={t.view_state}
onChange={(v) => setTweak("view_state", v)}
options={[
{ value: "cards", label: "Karten (Default)" },
{ value: "list", label: "Liste" },
{ value: "empty-none", label: "Empty · noch keine Firma" },
{ value: "empty-filter", label: "Empty · Filter ohne Treffer" },
]}
/>
</TweakSection>
<TweakSection label="Details">
<TweakToggle
label="Rollen-Legende"
value={t.show_legend}
onChange={(v) => setTweak("show_legend", v)}
/>
</TweakSection>
</TweaksPanel>
);
}
const root = ReactDOM.createRoot(document.getElementById("tweaks-root"));
root.render(<TweaksApp />);
</script>
</body>
</html>