Startseite angepasst
This commit is contained in:
@@ -4,25 +4,36 @@ declare(strict_types=1);
|
||||
|
||||
namespace App\Modules\Central\Controllers;
|
||||
|
||||
use App\Modules\Identity\Services\AuthService;
|
||||
use App\Modules\Tenants\Services\TenantService;
|
||||
|
||||
class LandingController
|
||||
{
|
||||
public function __construct(
|
||||
private readonly TenantService $tenantService,
|
||||
private readonly AuthService $authService
|
||||
) {
|
||||
}
|
||||
|
||||
public function index(): array
|
||||
{
|
||||
return [
|
||||
'view' => 'welcome',
|
||||
'data' => [
|
||||
'title' => 'Kaffeeliste SaaS',
|
||||
'tenantOverview' => $this->tenantService->adminOverview(),
|
||||
'centralLoginPreview' => $this->authService->centralLoginPreview(),
|
||||
'title' => 'Die Kaffeeliste',
|
||||
'landingColumns' => [
|
||||
[
|
||||
'eyebrow' => 'Für Mitglieder',
|
||||
'title' => 'Schnell im Alltag',
|
||||
'copy' => 'Anmelden, Überblick sehen und direkt weitermachen.',
|
||||
],
|
||||
[
|
||||
'eyebrow' => 'Für Verantwortliche',
|
||||
'title' => 'Einfach verwalten',
|
||||
'copy' => 'Mitglieder, Bereiche und Abläufe an einem Ort.',
|
||||
],
|
||||
[
|
||||
'eyebrow' => 'Für Teams',
|
||||
'title' => 'Klar für alle',
|
||||
'copy' => 'Eine gemeinsame Kaffeeliste mit verständlicher Oberfläche.',
|
||||
],
|
||||
],
|
||||
'landingPreview' => [
|
||||
['label' => 'Schneller Überblick', 'value' => 'Direkt sichtbar'],
|
||||
['label' => 'Für Mitglieder', 'value' => 'Einfach nutzbar'],
|
||||
['label' => 'Für Verantwortliche', 'value' => 'Klar verwaltet'],
|
||||
],
|
||||
],
|
||||
];
|
||||
}
|
||||
|
||||
+282
-115
@@ -71,7 +71,6 @@ if ($page === 'logout' && $requestMethod === 'POST') {
|
||||
app_redirect('/');
|
||||
}
|
||||
|
||||
$marketing = app_marketing_messages();
|
||||
$flash = app_flash();
|
||||
$auth = app_auth_user();
|
||||
$pdo = null;
|
||||
@@ -223,7 +222,31 @@ if ($auth !== null && isset($restrictedPages[$page]) && !$restrictedPages[$page]
|
||||
|
||||
$canManageTenant = app_can_manage_tenant($auth);
|
||||
$tenantNavItems = app_tenant_navigation_items($auth, $tenantLicense);
|
||||
$guestNavItems = [
|
||||
['key' => 'home', 'href' => '/', 'label' => 'Start'],
|
||||
['key' => 'login', 'href' => '/login/', 'label' => 'Anmeldung'],
|
||||
['key' => 'tenants', 'href' => '/admin/login/', 'label' => 'Admin'],
|
||||
];
|
||||
$primaryNavItems = $auth === null ? $guestNavItems : $tenantNavItems;
|
||||
$currentNavLabel = 'Start';
|
||||
foreach ($primaryNavItems as $item) {
|
||||
if ($page === (string) ($item['key'] ?? '')) {
|
||||
$currentNavLabel = (string) ($item['label'] ?? $currentNavLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
$isMarketingHome = $page === 'home' && $auth === null;
|
||||
$landingColumns = [
|
||||
['eyebrow' => 'Für Mitglieder', 'title' => 'Schnell starten', 'copy' => 'Einloggen und direkt weiter.'],
|
||||
['eyebrow' => 'Für Verantwortliche', 'title' => 'Alles im Blick', 'copy' => 'Bereiche, Hinweise und Verwaltung an einem Ort.'],
|
||||
['eyebrow' => 'Für Standorte', 'title' => 'Gemeinsam organisiert', 'copy' => 'Ein klarer Ablauf für Teams und Bereiche.'],
|
||||
];
|
||||
$landingPreview = [
|
||||
['label' => 'Start', 'value' => 'Klar'],
|
||||
['label' => 'Team', 'value' => 'Gemeinsam'],
|
||||
['label' => 'Zugang', 'value' => 'Direkt'],
|
||||
];
|
||||
|
||||
?><!DOCTYPE html>
|
||||
<html lang="de">
|
||||
@@ -237,104 +260,248 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
body{margin:0;min-height:100vh;font-family:"Aptos","Segoe UI",sans-serif;color:var(--ink);background:linear-gradient(180deg,#f9f6ef 0%,var(--bg) 100%)}
|
||||
a{color:inherit;text-decoration:none}
|
||||
h1,h2,h3{font-family:Georgia,serif;letter-spacing:-.02em}
|
||||
.button,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;border:1px solid transparent;background:var(--brand);color:#fff;font:inherit;font-weight:700;cursor:pointer}
|
||||
.button,button{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:12px;border:1px solid transparent;background:var(--brand);color:#fff;font:inherit;font-weight:700;cursor:pointer}
|
||||
.button.secondary,.button--ghost{background:#fff;color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.page-shell{width:min(1460px,calc(100vw - 32px));margin:20px auto 40px;display:grid;grid-template-columns:minmax(280px,300px) minmax(0,1fr);gap:20px;align-items:start}
|
||||
.sidebar,.hero,.card,.alert{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);box-shadow:var(--shadow)}
|
||||
.sidebar{position:sticky;top:20px;display:grid;gap:16px;padding:18px;background:rgba(255,251,244,.96)}
|
||||
.sidebar__brand{display:grid;gap:6px;padding-bottom:14px;border-bottom:1px solid rgba(37,24,15,.1)}
|
||||
.page-shell{width:min(1320px,calc(100vw - 32px));margin:18px auto 34px;display:grid;gap:16px}
|
||||
.site-header{position:sticky;top:18px;z-index:20}
|
||||
.site-header__inner{display:flex;align-items:center;justify-content:space-between;gap:18px;padding:16px 18px;border:1px solid var(--line);border-radius:24px;background:rgba(255,255,255,.94);box-shadow:var(--shadow)}
|
||||
.site-brand{display:flex;align-items:center;gap:14px;min-width:0}
|
||||
.site-brand__mark{width:44px;height:44px;border-radius:16px;display:grid;place-items:center;background:linear-gradient(135deg,var(--brand) 0%,var(--brand-strong) 100%);color:#fff;font-weight:800;box-shadow:0 14px 28px rgba(var(--brand-rgb),.18)}
|
||||
.site-brand__title{margin:0;font-size:1.1rem;color:var(--ink)}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:var(--muted);font-size:.92rem}
|
||||
.site-nav,.site-actions,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.site-nav__link{display:inline-flex;align-items:center;justify-content:center;padding:10px 14px;border-radius:999px;border:1px solid transparent;background:transparent;color:var(--muted);font-weight:700}
|
||||
.site-nav__link:hover{text-decoration:none;color:var(--brand);background:rgba(255,255,255,.82);border-color:rgba(var(--brand-rgb),.14)}
|
||||
.site-nav__link.active{background:rgba(var(--brand-rgb),.10);color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.site-mobile{display:none;position:relative}
|
||||
.site-mobile[open]{z-index:20}
|
||||
.site-toggle{display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;list-style:none;padding:12px 14px;border-radius:16px;border:1px solid rgba(var(--brand-rgb),.12);background:#fff;color:var(--brand);font-weight:700}
|
||||
.site-toggle::-webkit-details-marker{display:none}
|
||||
.site-toggle::after{content:"";width:11px;height:11px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s ease}
|
||||
.site-mobile[open] .site-toggle::after{transform:rotate(225deg)}
|
||||
.site-panel{position:absolute;right:0;top:calc(100% + 12px);width:min(320px,calc(100vw - 32px));padding:14px;border-radius:18px;border:1px solid var(--line);background:#fffdf9;box-shadow:var(--shadow)}
|
||||
.site-stack{display:grid;gap:10px}
|
||||
.site-stack .site-nav__link{justify-content:flex-start;background:rgba(255,255,255,.78);border-color:rgba(37,24,15,.08);color:var(--ink)}
|
||||
.site-footer-actions{display:flex;justify-content:flex-end;margin-top:12px}
|
||||
.hero,.card,.alert{border:1px solid var(--line);border-radius:var(--radius);background:var(--card);box-shadow:var(--shadow)}
|
||||
.sidebar__eyebrow,.eyebrow{display:inline-block;color:var(--accent);text-transform:uppercase;letter-spacing:.14em;font-size:.8rem;font-weight:800}
|
||||
.sidebar__eyebrow{margin:0}
|
||||
.sidebar__title{margin:0;font-size:1.55rem;line-height:1.05}
|
||||
.sidebar__title{margin:0;font-size:1.32rem;line-height:1.04}
|
||||
.sidebar__subtitle,.muted,p{color:var(--muted)}
|
||||
.sidebar__meta,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.sidebar__section{display:inline-block;color:var(--accent);text-transform:uppercase;letter-spacing:.14em;font-size:.76rem;font-weight:800}
|
||||
.sidebar__nav,.sidebar__stack,.stack{display:grid;gap:10px}
|
||||
.sidebar__link{display:flex;align-items:center;justify-content:space-between;padding:12px 14px;border-radius:16px;border:1px solid rgba(var(--brand-rgb),.12);background:#fff;color:var(--brand);font-weight:700}
|
||||
.sidebar__link.active{background:var(--brand);color:#fff}
|
||||
.sidebar__footer{display:grid;gap:12px;padding-top:14px;border-top:1px solid rgba(37,24,15,.1)}
|
||||
.sidebar__mobile{display:none}
|
||||
.sidebar__mobile[open]{z-index:20}
|
||||
.sidebar__toggle{display:flex;align-items:center;justify-content:space-between;gap:12px;cursor:pointer;list-style:none;padding:12px 14px;border-radius:16px;border:1px solid rgba(var(--brand-rgb),.12);background:#fff;color:var(--brand);font-weight:700}
|
||||
.sidebar__toggle::-webkit-details-marker{display:none}
|
||||
.sidebar__toggle::after{content:"";width:11px;height:11px;border-right:2px solid currentColor;border-bottom:2px solid currentColor;transform:rotate(45deg);transition:transform .2s ease}
|
||||
.sidebar__mobile[open] .sidebar__toggle::after{transform:rotate(225deg)}
|
||||
.sidebar__panel{margin-top:12px;padding:14px;border-radius:18px;border:1px solid var(--line);background:#fffdf9;box-shadow:var(--shadow)}
|
||||
.content{min-width:0;display:grid;gap:18px}
|
||||
.hero,.card{padding:24px}
|
||||
.hero{margin-bottom:0;background:linear-gradient(180deg,#fffdf8 0%,#f9f4ea 100%)}
|
||||
.content{min-width:0;display:grid;gap:16px}
|
||||
.hero,.card{padding:20px}
|
||||
.hero{margin-bottom:0;background:rgba(255,255,255,.9)}
|
||||
.grid{display:grid;gap:18px}.grid-2{grid-template-columns:repeat(2,minmax(0,1fr))}.grid-3{grid-template-columns:repeat(3,minmax(0,1fr))}.grid-4{grid-template-columns:repeat(4,minmax(0,1fr))}
|
||||
h1{font-size:clamp(2rem,4vw,3.2rem);margin:0 0 12px}h2{font-size:1.35rem;margin:0 0 12px}h3{font-size:1.05rem;margin:0 0 10px}p{margin:0;line-height:1.6}
|
||||
.metric{padding:18px;border:1px solid var(--line);border-radius:16px;background:#fff}.metric strong{display:block;font-size:1.8rem;margin-bottom:8px}
|
||||
h1{font-size:clamp(1.9rem,4vw,2.8rem);margin:0 0 10px}h2{font-size:1.2rem;margin:0 0 10px}h3{font-size:1rem;margin:0 0 8px}p{margin:0;line-height:1.55}
|
||||
.metric{padding:18px;border:1px solid var(--line);border-radius:16px;background:#fff}.metric strong{display:block;font-size:1.55rem;margin-bottom:6px}
|
||||
.list{margin:14px 0 0;padding-left:18px;color:var(--muted);line-height:1.7}
|
||||
.alert{padding:16px 18px;margin-bottom:0}.alert-success{background:rgba(var(--brand-rgb),.08)}.alert-warning{background:rgba(var(--accent-rgb),.1)}.alert-error{background:rgba(154,31,31,.1)}.alert-info{background:rgba(var(--brand-rgb),.08)}
|
||||
.badge{display:inline-flex;align-items:center;padding:7px 12px;border-radius:999px;font-size:.86rem;font-weight:700}
|
||||
.badge-neutral{background:rgba(var(--brand-rgb),.08);color:var(--brand)}.badge-success{background:rgba(var(--brand-rgb),.12);color:var(--brand)}.badge-warning{background:rgba(var(--accent-rgb),.14);color:#8c6500}
|
||||
.table{overflow-x:auto}.table table{width:100%;border-collapse:collapse;min-width:720px}.table th,.table td{padding:13px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.table th{font-size:.85rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
||||
.table{overflow-x:auto}.table table{width:100%;border-collapse:collapse;min-width:720px}.table th,.table td{padding:12px 10px;border-bottom:1px solid var(--line);text-align:left;vertical-align:top}.table th{font-size:.78rem;letter-spacing:.08em;text-transform:uppercase;color:var(--muted)}
|
||||
form.grid{grid-template-columns:repeat(2,minmax(0,1fr))}label{display:flex;flex-direction:column;gap:8px;font-weight:700}input,select,textarea{width:100%;padding:12px 14px;border-radius:14px;border:1px solid rgba(37,24,15,.15);font:inherit;background:#fff;color:var(--ink)}textarea{min-height:120px}
|
||||
.footer{margin-top:0;text-align:center;color:var(--muted);font-size:.92rem}
|
||||
@media(max-width:960px){.page-shell{grid-template-columns:1fr;width:min(100vw - 20px,1460px)}.sidebar{position:static}.sidebar__desktop{display:none}.sidebar__mobile{display:block}.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.table table{min-width:0}}
|
||||
@media(min-width:961px){.sidebar__mobile{display:none}}
|
||||
.section-mini{display:grid;gap:10px}
|
||||
.section-mini__title{margin:0;font-size:1rem;color:var(--ink)}
|
||||
.section-mini__copy{margin:0;color:var(--muted)}
|
||||
body.landing-preview{background:radial-gradient(circle at top center,rgba(88,122,255,.22),transparent 28%),radial-gradient(circle at 20% 20%,rgba(42,72,170,.18),transparent 24%),linear-gradient(180deg,#05070d 0%,#0a0f1a 52%,#0d1320 100%);color:#f4f7ff}
|
||||
body.landing-preview h1,body.landing-preview h2,body.landing-preview h3{font-family:"Inter Tight","Aptos","Segoe UI",sans-serif;letter-spacing:-.04em}
|
||||
.marketing-shell{min-height:100vh;padding:0 0 92px;display:grid;gap:12px}
|
||||
.marketing-main{width:min(1240px,calc(100vw - 48px));margin:0 auto}
|
||||
.marketing-bar{position:sticky;top:0;z-index:20;display:flex;align-items:center;justify-content:space-between;gap:14px;height:88px;min-height:88px;width:100%;margin:0 0 20px;padding:0 18px;border:0;border-bottom:1px solid rgba(137,154,188,.18);border-radius:0;background:rgba(8,10,18,.82);box-shadow:none;backdrop-filter:blur(16px)}
|
||||
.marketing-brand{display:flex;align-items:center;gap:8px;flex:0 0 auto}
|
||||
.marketing-brand__mark{width:40px;height:40px;border-radius:10px;display:grid;place-items:center;background:linear-gradient(135deg,#587aff 0%,#243a91 100%);color:#fff;font-size:1rem;font-weight:800;box-shadow:none}
|
||||
.marketing-brand__title{margin:0;font-size:1.08rem;line-height:1;color:#f4f7ff}
|
||||
.marketing-brand__copy{display:none}
|
||||
.marketing-nav,.marketing-actions,.landing-actions{display:flex;flex-wrap:wrap;gap:12px;align-items:center}
|
||||
.marketing-nav{gap:6px}
|
||||
.marketing-nav a{display:inline-flex;align-items:center;justify-content:center;min-height:36px;padding:0 .3rem;border-radius:6px;border:1px solid transparent;font-size:1rem;font-weight:600;color:rgba(231,238,255,.8)}
|
||||
.marketing-nav a:hover{text-decoration:none;color:#f4f7ff;background:rgba(255,255,255,.06);border-color:rgba(201,214,255,.12)}
|
||||
.marketing-actions .button{min-height:38px;padding:.42rem .8rem;border-radius:8px;font-size:.94rem}
|
||||
.marketing-actions .button.secondary,.marketing-mobile__footer .button.secondary{background:transparent;color:#f4f7ff;border-color:rgba(201,214,255,.16);box-shadow:none}
|
||||
.marketing-mobile{display:none;position:relative}
|
||||
.marketing-mobile[open]{z-index:40}
|
||||
.marketing-mobile__toggle{display:inline-flex;align-items:center;justify-content:center;gap:10px;list-style:none;cursor:pointer;min-width:38px;min-height:38px;padding:.24rem .46rem;border-radius:8px;border:1px solid rgba(201,214,255,.14);background:rgba(255,255,255,.04);color:#f4f7ff}
|
||||
.marketing-mobile__toggle::-webkit-details-marker{display:none}
|
||||
.marketing-mobile__toggle::before{content:"";width:16px;height:10px;border-top:2px solid currentColor;border-bottom:2px solid currentColor;box-shadow:inset 0 -4px 0 0 currentColor}
|
||||
.marketing-mobile__panel{position:absolute;right:0;top:calc(100% + 10px);width:min(280px,calc(100vw - 24px));padding:14px;border-radius:16px;border:1px solid rgba(163,183,255,.14);background:rgba(8,10,18,.96);box-shadow:0 18px 36px rgba(1,5,13,.45)}
|
||||
.marketing-mobile__stack{display:grid;gap:8px}
|
||||
.marketing-mobile__stack .marketing-nav__link{justify-content:flex-start;padding:.55rem .7rem;background:rgba(255,255,255,.04);border-color:rgba(201,214,255,.1)}
|
||||
.marketing-mobile__footer{margin-top:12px;display:flex;justify-content:flex-end}
|
||||
.landing-hero{width:min(1240px,calc(100vw - 48px));margin:0 auto;padding-top:12px;display:grid;grid-template-columns:minmax(0,1.08fr) minmax(280px,.92fr);gap:40px;align-items:start}
|
||||
.landing-copy__eyebrow{margin:0 0 10px;text-transform:uppercase;letter-spacing:.16em;font-size:.78rem;font-weight:700;color:#8aa0d0}
|
||||
.landing-copy h1{margin:0;font-size:clamp(3.2rem,7vw,6.4rem);line-height:.9;letter-spacing:-.06em;color:#f4f7ff}
|
||||
.landing-copy p{margin:14px 0 0;color:rgba(219,228,248,.72);font-size:1.05rem;max-width:30rem;line-height:1.65}
|
||||
.landing-actions{margin-top:24px}
|
||||
.landing-visual,.landing-callout{border:1px solid rgba(163,183,255,.12);border-radius:20px;background:rgba(14,20,34,.86);box-shadow:0 16px 40px rgba(2,5,12,.34)}
|
||||
.landing-visual{padding:20px;display:grid;gap:12px}
|
||||
.landing-visual__top{display:flex;align-items:center;justify-content:space-between;gap:12px;padding-bottom:12px;border-bottom:1px solid rgba(163,183,255,.12)}
|
||||
.landing-visual__title{margin:0;font-size:1.1rem;color:#f4f7ff}
|
||||
.landing-visual__stack{display:grid;gap:12px}
|
||||
.landing-visual__row{display:flex;justify-content:space-between;gap:12px;align-items:center;padding:12px 14px;border-radius:16px;background:rgba(255,255,255,.04);color:rgba(216,225,246,.72);font-weight:600}
|
||||
.landing-visual__row strong{color:#f4f7ff}
|
||||
.landing-columns{width:min(1240px,calc(100vw - 48px));margin:0 auto;display:grid;grid-template-columns:repeat(3,minmax(0,1fr));gap:0;border-top:1px solid rgba(163,183,255,.12);border-bottom:1px solid rgba(163,183,255,.12)}
|
||||
.landing-column{padding:24px 18px}
|
||||
.landing-column + .landing-column{border-left:1px solid rgba(163,183,255,.12)}
|
||||
.landing-column__eyebrow{margin:0 0 10px;color:#8aa0d0;font-size:.78rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
|
||||
.landing-column h2{margin:0 0 10px;font-size:1.45rem;color:#f4f7ff}
|
||||
.landing-column p{margin:0;max-width:22rem;color:rgba(219,228,248,.72)}
|
||||
.landing-callout{width:min(1240px,calc(100vw - 48px));margin:0 auto;padding:24px 26px}
|
||||
.landing-callout__eyebrow{margin:0 0 8px;color:#8aa0d0;font-size:.78rem;font-weight:700;letter-spacing:.14em;text-transform:uppercase}
|
||||
.landing-callout h2{margin:0 0 10px;font-size:2rem;color:#f4f7ff}
|
||||
.landing-callout p{margin:0;max-width:36rem;color:rgba(217,226,246,.8)}
|
||||
.landing-cta{display:flex;align-items:center;justify-content:space-between;flex-wrap:wrap;gap:16px;padding-top:20px;margin-top:22px;border-top:1px solid rgba(163,183,255,.12)}
|
||||
.landing-cta__copy{display:grid;gap:6px}
|
||||
.landing-cta__copy strong{font-size:1.05rem;color:#f4f7ff}
|
||||
.landing-cta__copy span{color:rgba(217,226,246,.72)}
|
||||
.marketing-footer{position:fixed;left:0;right:0;bottom:0;z-index:25;margin-top:0;padding:10px 24px;border-top:1px solid rgba(137,154,188,.14);background:rgba(8,10,18,.88);backdrop-filter:blur(18px);color:rgba(209,217,235,.68);font-size:.92rem}
|
||||
.marketing-footer__inner{display:flex;justify-content:space-between;gap:16px;flex-wrap:wrap;width:min(1240px,calc(100% - 48px));margin:0 auto}
|
||||
@media(max-width:960px){.marketing-main,.landing-hero,.landing-columns,.landing-callout{width:calc(100vw - 24px)}.marketing-bar{height:72px;min-height:72px;margin:0 0 16px;padding:0 12px}.marketing-nav,.marketing-actions{display:none}.marketing-mobile{display:block}.landing-hero,.landing-columns{grid-template-columns:1fr}.landing-hero{gap:24px;padding-top:0}.landing-column{padding:20px 0}.landing-column + .landing-column{border-left:0;border-top:1px solid rgba(163,183,255,.12)}.landing-callout{padding:20px}.marketing-shell{padding:0 0 88px}.marketing-footer{padding:10px 12px}.marketing-footer__inner{width:100%}}
|
||||
@media(max-width:960px){.page-shell{width:min(100vw - 20px,1460px)}.site-header__inner{align-items:flex-start;padding:14px 16px}.site-nav{display:none}.site-mobile{display:block}.grid-2,.grid-3,.grid-4,form.grid{grid-template-columns:1fr}.table table{min-width:0}}
|
||||
@media(min-width:961px){.site-mobile{display:none}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page-shell">
|
||||
<aside class="sidebar" aria-label="Bereichsnavigation">
|
||||
<div class="sidebar__brand">
|
||||
<p class="sidebar__eyebrow">Die Kaffeeliste</p>
|
||||
<h1 class="sidebar__title"><?= $auth === null ? 'Start' : h((string) ($auth['tenant_name'] ?? 'Tenant')) ?></h1>
|
||||
<p class="sidebar__subtitle"><?= $auth === null ? 'Zentrale Anmeldung, Verwaltung und Tenant-Zugriff.' : 'Alle Bereiche des Tenants in einer gemeinsamen Navigation.' ?></p>
|
||||
</div>
|
||||
|
||||
<?php if ($auth === null): ?>
|
||||
<div class="sidebar__meta">
|
||||
<?= badge('Startseite') ?>
|
||||
<?= badge('Mandantenfähig', 'success') ?>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
|
||||
<div class="sidebar__desktop">
|
||||
<span class="sidebar__section">Bereiche</span>
|
||||
<nav class="sidebar__nav" aria-label="Navigation">
|
||||
<?php if ($auth === null): ?>
|
||||
<a href="/" class="sidebar__link <?= $page === 'home' ? 'active' : '' ?>" <?= $page === 'home' ? 'aria-current="page"' : '' ?>>Start</a>
|
||||
<a href="/login/" class="sidebar__link <?= $page === 'login' ? 'active' : '' ?>" <?= $page === 'login' ? 'aria-current="page"' : '' ?>>Anmeldung</a>
|
||||
<a href="/admin/login/" class="sidebar__link <?= $page === 'tenants' ? 'active' : '' ?>" <?= $page === 'tenants' ? 'aria-current="page"' : '' ?>>Verwaltung</a>
|
||||
<?php else: ?>
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="sidebar__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
<?php if ($auth !== null): ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary">Abmelden</button></form>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="sidebar__mobile">
|
||||
<summary class="sidebar__toggle">Menü</summary>
|
||||
<div class="sidebar__panel">
|
||||
<nav class="sidebar__stack" aria-label="Mobile Navigation">
|
||||
<?php if ($auth === null): ?>
|
||||
<a href="/" class="sidebar__link <?= $page === 'home' ? 'active' : '' ?>" <?= $page === 'home' ? 'aria-current="page"' : '' ?>>Start</a>
|
||||
<a href="/login/" class="sidebar__link <?= $page === 'login' ? 'active' : '' ?>" <?= $page === 'login' ? 'aria-current="page"' : '' ?>>Anmeldung</a>
|
||||
<a href="/admin/login/" class="sidebar__link <?= $page === 'tenants' ? 'active' : '' ?>" <?= $page === 'tenants' ? 'aria-current="page"' : '' ?>>Verwaltung</a>
|
||||
<?php else: ?>
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="sidebar__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
<?php endif; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
<?php if ($auth !== null): ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary">Abmelden</button></form>
|
||||
<?php endif; ?>
|
||||
<body class="<?= $isMarketingHome ? 'landing-preview' : '' ?>">
|
||||
<?php if ($isMarketingHome): ?>
|
||||
<main class="marketing-shell">
|
||||
<header class="marketing-bar">
|
||||
<div class="marketing-brand">
|
||||
<div class="marketing-brand__mark">K</div>
|
||||
<div>
|
||||
<h1 class="marketing-brand__title">Die Kaffeeliste</h1>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</aside>
|
||||
|
||||
<nav class="marketing-nav" aria-label="Startseiten-Navigation">
|
||||
<a href="/">Start</a>
|
||||
<a href="/login/">Anmeldung</a>
|
||||
<a href="/admin/login/">Admin</a>
|
||||
</nav>
|
||||
|
||||
<div class="marketing-actions">
|
||||
<a class="button secondary" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
|
||||
<details class="marketing-mobile">
|
||||
<summary class="marketing-mobile__toggle" aria-label="Menü"></summary>
|
||||
<div class="marketing-mobile__panel">
|
||||
<nav class="marketing-mobile__stack" aria-label="Mobile Startseiten-Navigation">
|
||||
<a href="/" class="marketing-nav__link">Start</a>
|
||||
<a href="/login/" class="marketing-nav__link">Anmeldung</a>
|
||||
<a href="/admin/login/" class="marketing-nav__link">Admin</a>
|
||||
</nav>
|
||||
<div class="marketing-mobile__footer">
|
||||
<a class="button secondary" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</header>
|
||||
|
||||
<?php if ($flash !== null): ?>
|
||||
<section class="alert alert-<?= h((string) ($flash['type'] ?? 'info')) ?>" style="width:min(1240px,calc(100vw - 48px));margin:0 auto;"><?= h((string) ($flash['message'] ?? '')) ?></section>
|
||||
<?php endif; ?>
|
||||
|
||||
<section class="marketing-hero landing-hero">
|
||||
<div class="landing-copy">
|
||||
<p class="landing-copy__eyebrow">Die Kaffeeliste</p>
|
||||
<h1>Kaffee im Team. Klar organisiert.</h1>
|
||||
<p>Für Mitglieder, Verantwortliche und Standorte.</p>
|
||||
<div class="landing-actions">
|
||||
<a class="button" href="/login/">Anmelden</a>
|
||||
<a class="button secondary" href="#bereiche">Mehr erfahren</a>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="landing-visual" aria-label="Produktvorschau">
|
||||
<div class="landing-visual__top">
|
||||
<h2 class="landing-visual__title">Übersicht</h2>
|
||||
<span>Heute</span>
|
||||
</div>
|
||||
<div class="landing-visual__stack">
|
||||
<?php foreach ($landingPreview as $item): ?>
|
||||
<div class="landing-visual__row">
|
||||
<span><?= h((string) ($item['label'] ?? '')) ?></span>
|
||||
<strong><?= h((string) ($item['value'] ?? '')) ?></strong>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="landing-columns" id="bereiche">
|
||||
<?php foreach ($landingColumns as $column): ?>
|
||||
<article class="landing-column">
|
||||
<p class="landing-column__eyebrow"><?= h((string) ($column['eyebrow'] ?? '')) ?></p>
|
||||
<h2><?= h((string) ($column['title'] ?? '')) ?></h2>
|
||||
<p><?= h((string) ($column['copy'] ?? '')) ?></p>
|
||||
</article>
|
||||
<?php endforeach; ?>
|
||||
</section>
|
||||
|
||||
<section class="landing-callout marketing-cta">
|
||||
<p class="landing-callout__eyebrow">Einfach im Alltag</p>
|
||||
<h2>Ein gemeinsamer Ort für die Kaffeeliste.</h2>
|
||||
<p>Weniger suchen. Weniger erklären. Klar durch den Tag.</p>
|
||||
<div class="landing-cta">
|
||||
<div class="landing-cta__copy">
|
||||
<strong>Bereit für den Start?</strong>
|
||||
<span>Direkt zur Anmeldung.</span>
|
||||
</div>
|
||||
<a class="button" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<footer class="marketing-footer">
|
||||
<div class="marketing-footer__inner">
|
||||
<span>Die Kaffeeliste</span>
|
||||
<span>Ein ruhiger Einstieg für Teams</span>
|
||||
</div>
|
||||
</footer>
|
||||
</main>
|
||||
<?php else: ?>
|
||||
<main class="page-shell">
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a href="<?= $auth === null ? '/' : '/dashboard/' ?>" class="site-brand">
|
||||
<div class="site-brand__mark">K</div>
|
||||
<div>
|
||||
<h1 class="site-brand__title">Die Kaffeeliste</h1>
|
||||
<p class="site-brand__subtitle"><?= $auth === null ? h($currentNavLabel) : h((string) ($auth['tenant_name'] ?? 'Bereich')) ?></p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<nav class="site-nav" aria-label="Hauptnavigation">
|
||||
<?php foreach ($primaryNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="site-nav__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
|
||||
<div class="site-actions">
|
||||
<?php if ($auth !== null): ?>
|
||||
<?= badge((string) ($auth['tenant_name'] ?? 'Bereich')) ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary">Abmelden</button></form>
|
||||
<?php else: ?>
|
||||
<a class="button secondary" href="/login/">Anmelden</a>
|
||||
<?php endif; ?>
|
||||
|
||||
<details class="site-mobile">
|
||||
<summary class="site-toggle">Menue</summary>
|
||||
<div class="site-panel">
|
||||
<nav class="site-stack" aria-label="Mobile Navigation">
|
||||
<?php foreach ($primaryNavItems as $item): ?>
|
||||
<a href="<?= h((string) ($item['href'] ?? '/')) ?>" class="site-nav__link <?= $page === (string) ($item['key'] ?? '') ? 'active' : '' ?>" <?= $page === (string) ($item['key'] ?? '') ? 'aria-current="page"' : '' ?>><?= h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<?php if ($auth !== null): ?>
|
||||
<div class="site-footer-actions">
|
||||
<form method="post" action="/logout/"><button type="submit" class="button secondary">Abmelden</button></form>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<?php if ($auth !== null && !empty($auth['acting_as_platform_admin'])): ?>
|
||||
@@ -387,13 +554,13 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<?php endif; ?>
|
||||
<?php elseif ($page === 'login'): ?>
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Zentrale Anmeldung</div>
|
||||
<div class="eyebrow">Anmeldung</div>
|
||||
<h1>Anmeldung</h1>
|
||||
<p>Zuerst wird der passende Bereich ermittelt. Falls mehrere Zuordnungen vorhanden sind, wählst du den richtigen Tenant aus und gibst danach dein Passwort ein.</p>
|
||||
<p>Mit deiner E-Mail weiter.</p>
|
||||
<div class="context" style="margin-top:16px">
|
||||
<?= badge('E-Mail zuerst') ?>
|
||||
<?= badge('Tenant-Erkennung') ?>
|
||||
<?= badge('Mehrfachzuordnung') ?>
|
||||
<?= badge('Einfach') ?>
|
||||
<?= badge('Direkt') ?>
|
||||
<?= badge('Klar') ?>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
@@ -408,7 +575,7 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<article class="card">
|
||||
<?php if ($loginStep === 'choose-tenant'): ?>
|
||||
<div class="eyebrow">Schritt 2</div>
|
||||
<h2>Passenden Tenant wählen</h2>
|
||||
<h2>Bereich waehlen</h2>
|
||||
<div class="stack">
|
||||
<?php foreach (($loginState['memberships'] ?? []) as $membership): ?>
|
||||
<form method="post" action="/login/" class="card">
|
||||
@@ -424,7 +591,7 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<?php elseif ($loginStep === 'password' && is_array($selectedMembership)): ?>
|
||||
<div class="eyebrow">Schritt 2</div>
|
||||
<h2>Passwort eingeben</h2>
|
||||
<p>Der Tenant wurde erkannt: <?= h((string) ($selectedMembership['tenant_name'] ?? '')) ?></p>
|
||||
<p><?= h((string) ($selectedMembership['tenant_name'] ?? '')) ?></p>
|
||||
<form method="post" action="/login/" class="stack" style="margin-top:16px">
|
||||
<input type="hidden" name="action" value="authenticate">
|
||||
<label>Passwort<input type="password" name="password" autocomplete="current-password" placeholder="Passwort eingeben"></label>
|
||||
@@ -444,11 +611,11 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<?php endif; ?>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Hilfe beim Einstieg</div>
|
||||
<h2>Wenn die Anmeldung nicht sofort klappt</h2>
|
||||
<div class="eyebrow">Hilfe</div>
|
||||
<h2>Wenn es nicht direkt klappt</h2>
|
||||
<ul class="list">
|
||||
<li>Prüfe zuerst, ob du die richtige berufliche E-Mail-Adresse verwendest.</li>
|
||||
<li>Wenn du in mehreren Bereichen arbeitest, wirst du automatisch zur passenden Auswahl geführt.</li>
|
||||
<li>Wenn du in mehreren Bereichen arbeitest, waehle den passenden aus.</li>
|
||||
<li>Falls kein Zugang gefunden wird, brauchst du meist nur eine Einladung oder den Kontakt zur verantwortlichen Person.</li>
|
||||
</ul>
|
||||
</article>
|
||||
@@ -495,13 +662,12 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<?php endif; ?>
|
||||
<?php elseif ($page === 'dashboard'): ?>
|
||||
<section class="hero">
|
||||
<div class="eyebrow">Tenant-Dashboard</div>
|
||||
<h1><?= h((string) $auth['tenant_name']) ?> Übersicht</h1>
|
||||
<p>Kontostand, Buchungen, Einzahlungen und die letzten Aktivitäten stehen direkt für diesen Tenant bereit.</p>
|
||||
<div class="eyebrow">Uebersicht</div>
|
||||
<h1><?= h((string) $auth['tenant_name']) ?></h1>
|
||||
<p>Alles Wichtige auf einen Blick.</p>
|
||||
<?php if (app_is_platform_admin($auth) && app_admin_user() !== null): ?>
|
||||
<div class="actions" style="margin-top:18px">
|
||||
<a class="button secondary" href="/admin/">Zur zentralen Verwaltung</a>
|
||||
<span class="badge">Mitglieder im Menü</span>
|
||||
<a class="button secondary" href="/admin/">Zur Verwaltung</a>
|
||||
</div>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -519,18 +685,18 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
|
||||
<section class="grid grid-2" style="margin-top:18px">
|
||||
<article class="card">
|
||||
<div class="eyebrow">Bereiche im Menü</div>
|
||||
<h2>Funktionen werden über das Menü geöffnet</h2>
|
||||
<ul class="list">
|
||||
<li>Hinweise, Support und Umfragen erscheinen als feste Hauptbereiche im Tenant-Menü.</li>
|
||||
<li>Mitglieder, Rollen, Buchungen und Zahlungen werden nur für passende Rollen eingeblendet.</li>
|
||||
<li>Einstellungen und Exporte erscheinen nur, wenn das Lizenzmodell diese Funktionen freischaltet.</li>
|
||||
</ul>
|
||||
<div class="eyebrow">Heute</div>
|
||||
<h2>Schnell weiter</h2>
|
||||
<div class="section-mini">
|
||||
<p class="section-mini__copy">Mitglieder, Buchungen und weitere Bereiche oeffnest du direkt ueber das Menue.</p>
|
||||
</div>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Lizenzlogik</div>
|
||||
<h2>Nur passende Funktionen sichtbar</h2>
|
||||
<p>Die Oberfläche blendet Bereiche nicht als tote Kacheln ein. Was im Menü erscheint, hängt von Rolle und Lizenz des Tenants ab.</p>
|
||||
<div class="eyebrow">Status</div>
|
||||
<h2>Alles im Blick</h2>
|
||||
<div class="section-mini">
|
||||
<p class="section-mini__copy">Die wichtigsten Zahlen und die letzten Buchungen stehen hier zuerst.</p>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@@ -563,18 +729,18 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
</article>
|
||||
<?php else: ?>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Mitgliedersicht</div>
|
||||
<div class="eyebrow">Dein Bereich</div>
|
||||
<h2>Dein Bereich</h2>
|
||||
<ul class="list">
|
||||
<li>Hier siehst du deinen Kontostand, letzte Buchungen und aktuelle Hinweise.</li>
|
||||
<li>Tenant-weite Buchungen und Mitgliederverwaltung bleiben bei den verantwortlichen Admins.</li>
|
||||
<li>Weitere Bereiche findest du direkt im Menue.</li>
|
||||
<li>Wenn dir etwas fehlt, wende dich an den Betreiber deines Bereichs.</li>
|
||||
</ul>
|
||||
</article>
|
||||
<article class="card">
|
||||
<div class="eyebrow">Nächster Schritt</div>
|
||||
<h2>Womit du direkt weiterkommst</h2>
|
||||
<p>Weitere Bereiche wechselst du direkt über das Menü. Sichtbar wird dort nur, was für deine Rolle freigegeben ist.</p>
|
||||
<div class="eyebrow">Naechster Schritt</div>
|
||||
<h2>Direkt weiter</h2>
|
||||
<p>Nutze das Menue fuer den naechsten Bereich.</p>
|
||||
</article>
|
||||
<?php endif; ?>
|
||||
</section>
|
||||
@@ -850,8 +1016,9 @@ $themeCss = app_tenant_theme_root_css($tenantSettings);
|
||||
<section class="alert alert-warning">Die angeforderte Seite konnte nicht gefunden werden.</section>
|
||||
<?php endif; ?>
|
||||
|
||||
<p class="footer">Die Kaffeeliste | zentrale Anmeldung, Tenant-Verwaltung und alle Kernprozesse der modernen Kaffeeliste</p>
|
||||
<p class="footer">Die Kaffeeliste</p>
|
||||
</div>
|
||||
</main>
|
||||
<?php endif; ?>
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -1,81 +1,22 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page_title', 'Die Kaffeeliste - Zentrale Anmeldung')
|
||||
@section('page_title', 'Die Kaffeeliste - Anmeldung')
|
||||
|
||||
@php
|
||||
$preview = $loginPreview ?? [
|
||||
'single' => [
|
||||
'email' => 'mia@berlin.example',
|
||||
'message' => 'Direkte Weiterleitung in den zugeordneten Tenant.',
|
||||
'message' => 'Direkt weiter.',
|
||||
'matches' => [['name' => 'Werk Berlin', 'domain' => 'berlin.kaffeeliste.de', 'login_mode' => 'oidc-first']],
|
||||
],
|
||||
'multiple' => [
|
||||
'email' => 'leitung@kaffeeliste.example',
|
||||
'message' => 'Tenant-Auswahl vor Passwort oder SSO.',
|
||||
'message' => 'Auswahl vor dem Einstieg.',
|
||||
'matches' => [
|
||||
['name' => 'Werk Berlin', 'domain' => 'berlin.kaffeeliste.de', 'login_mode' => 'oidc-first'],
|
||||
['name' => 'Werk Koeln', 'domain' => 'koeln.kaffeeliste.de', 'login_mode' => 'oidc-first'],
|
||||
['name' => 'Shared Services', 'domain' => 'shared.kaffeeliste.de', 'login_mode' => 'central-routing'],
|
||||
],
|
||||
],
|
||||
'unknown' => [
|
||||
'email' => 'extern@example.org',
|
||||
'message' => 'Rueckfuehrung auf Tenant-Admin oder Einladungspfad.',
|
||||
'matches' => [],
|
||||
],
|
||||
];
|
||||
$identityPolicy = $identityPolicy ?? [
|
||||
'local_login_required' => true,
|
||||
'external_login_mode' => 'ADFS/OIDC',
|
||||
'supported_modes' => ['password', 'adfs_oidc'],
|
||||
'email_mapping' => 'E-Mail-Mapping als primaerer Abgleich zwischen externer Identitaet und internem Benutzer',
|
||||
'fallback' => 'Bei Dubletten oder unklaren Zuordnungen wird ein Admin-Klaerfall erzeugt.',
|
||||
'recovery' => 'Lokale Anmeldung bleibt immer verfuegbar, auch wenn externe Provider ausfallen.',
|
||||
'clarification' => 'Der Erstlogin soll bestehende Konten wiederverwenden und nicht stillschweigend neue Duplikate anlegen.',
|
||||
'admin_clarification' => [
|
||||
'title' => 'Admin-Klaerfall',
|
||||
'description' => 'Wenn eine E-Mail-Adresse nicht eindeutig zugeordnet werden kann, wird der Fall an die zuständigen Admins eskaliert.',
|
||||
'steps' => ['Identitaet pruefen', 'Tenant zuordnen', 'Konto bestaetigen oder neu anlegen'],
|
||||
],
|
||||
];
|
||||
$roleMatrix = $roleMatrix ?? [
|
||||
'roles' => [
|
||||
['key' => 'platform_admin', 'summary' => 'Globaler Zugriff auf Plattform, Tenants und Betriebssteuerung.'],
|
||||
['key' => 'tenant_admin', 'summary' => 'Vollzugriff im eigenen Tenant inklusive Rollen- und Inhaltsverwaltung.'],
|
||||
['key' => 'finance_admin', 'summary' => 'Operative Buchungsrolle fuer Einzahlungen, Storno und Finanzreports.'],
|
||||
['key' => 'support_contact', 'summary' => 'Bearbeitung und Abschluss von Supportvorgaengen.'],
|
||||
['key' => 'survey_manager', 'summary' => 'Pflege, Freigabe und Auswertung von Surveys.'],
|
||||
['key' => 'member', 'summary' => 'Mitglied mit Zugriff auf eigenes Profil und freigegebene Inhalte.'],
|
||||
],
|
||||
'rules' => [
|
||||
'Tenant-Admin hat Vollzugriff im eigenen Tenant.',
|
||||
'Spezialrollen erhalten nur die fuer ihr Modul noetigen Rechte.',
|
||||
'Einzahlungen duerfen nur von Verantwortlichen storniert werden.',
|
||||
'Stricheintraege duerfen nur von Verantwortlichen geloescht werden.',
|
||||
],
|
||||
];
|
||||
$providers = $providers ?? [
|
||||
'password' => [
|
||||
'label' => 'Lokale Systemanmeldung',
|
||||
'type' => 'password',
|
||||
'required' => true,
|
||||
'description' => 'Pflichtweg fuer lokale Konten und Fallback bei externen Ausfaellen.',
|
||||
],
|
||||
'adfs_oidc' => [
|
||||
'label' => 'ADFS / OIDC',
|
||||
'type' => 'oidc',
|
||||
'required' => false,
|
||||
'description' => 'Zusatzoption fuer angebundene Identitaetsprovider.',
|
||||
],
|
||||
];
|
||||
$oidcProviders = $oidcProviders ?? [
|
||||
[
|
||||
'provider_key' => 'adfs-oidc-default',
|
||||
'driver' => 'oidc',
|
||||
'client_id' => 'tenant-client-id',
|
||||
'redirect_uri' => '/auth/oidc/adfs-oidc-default/callback',
|
||||
'scopes' => ['openid', 'profile', 'email'],
|
||||
],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@@ -83,53 +24,37 @@
|
||||
<section class="hero hero--split">
|
||||
<div class="hero__content">
|
||||
<div>
|
||||
<p class="hero__kicker">Mitglieder-Login</p>
|
||||
<h2 class="hero__title">Eine zentrale Anmeldung, die Mitglieder automatisch in den richtigen Tenant bringt.</h2>
|
||||
<p class="hero__kicker">Anmeldung</p>
|
||||
<h2 class="hero__title">Einfach einsteigen.</h2>
|
||||
<p class="hero__lead">
|
||||
Statt verschiedene Tenant-URLs zu kennen, starten Mitglieder zentral mit ihrer E-Mail-Adresse. Die Plattform
|
||||
erkennt Einzel- oder Mehrfachzuordnungen und fuehrt danach passend weiter: direkt in den Tenant, in eine
|
||||
Tenant-Auswahl oder in einen klaren Kontaktpfad.
|
||||
E-Mail eingeben, kurz pruefen, weitermachen.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero__actions">
|
||||
<a class="button" href="#mitglied-login">Mitglieder-Login starten</a>
|
||||
<a class="button button--ghost" href="/tenants">Admin Console ansehen</a>
|
||||
</div>
|
||||
<div class="hero__meta">
|
||||
<span class="badge">E-Mail first</span>
|
||||
<span class="badge">Tenant-Erkennung</span>
|
||||
<span class="badge">Lokaler Login Pflicht</span>
|
||||
<span class="badge">ADFS/OIDC zusaetzlich</span>
|
||||
<span class="badge badge--solid">Mehrfachzuordnung unterstuetzt</span>
|
||||
<span class="badge">Direkt</span>
|
||||
<span class="badge">Klar</span>
|
||||
<span class="badge badge--solid">Wenig Schritte</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="hero__aside">
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">Ein Tenant erkannt</p>
|
||||
<div class="metric__value">1 Klick</div>
|
||||
<p class="muted">Bei genau einer Mitgliedschaft wird direkt in den passenden Tenant weitergeleitet.</p>
|
||||
<p class="metric__label">Ein Treffer</p>
|
||||
<div class="metric__value">Direkt</div>
|
||||
<p class="muted">Ohne Umweg zum Ziel.</p>
|
||||
</article>
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">Mehrfachzuordnung</p>
|
||||
<div class="metric__value">{{ count($preview['multiple']['matches']) }} Tenants</div>
|
||||
<p class="muted">Die Auswahl erscheint nur dann, wenn fuer dieselbe Mail-Adresse mehrere Kontexte hinterlegt sind.</p>
|
||||
<p class="metric__label">Mehrere Treffer</p>
|
||||
<div class="metric__value">{{ count($preview['multiple']['matches']) }}</div>
|
||||
<p class="muted">Dann Auswahl anzeigen.</p>
|
||||
</article>
|
||||
<div class="callout">
|
||||
<strong>SSO und Passwort aus einem Einstieg</strong>
|
||||
Die zentrale Anmeldung entscheidet zuerst ueber den Tenant-Kontext. Danach folgen Passwort-Login, OIDC oder ein
|
||||
klarer Fallback fuer kleinere Tenants.
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section id="mitglied-login" class="split">
|
||||
<section class="split" style="margin-top: 18px;">
|
||||
<article class="form-panel">
|
||||
<p class="card__eyebrow">Zentraler Einstieg</p>
|
||||
<h3>Mit E-Mail und Passwort anmelden</h3>
|
||||
<p class="muted">
|
||||
Die E-Mail-Adresse steuert zuerst die Tenant-Erkennung. Der Passwort- oder SSO-Schritt kommt danach im richtigen Kontext.
|
||||
</p>
|
||||
<p class="card__eyebrow">Zugang</p>
|
||||
<h3>Anmelden</h3>
|
||||
<form class="form-grid" method="post" action="/login" style="margin-top: 18px;">
|
||||
<div class="field">
|
||||
<label for="email">E-Mail-Adresse</label>
|
||||
@@ -140,86 +65,60 @@
|
||||
<input id="password" name="password" type="password" autocomplete="current-password" placeholder="••••••••">
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<button type="submit">Zentral anmelden</button>
|
||||
<button type="submit">Weiter</button>
|
||||
<a class="button button--ghost" href="/forgot-password">Passwort vergessen?</a>
|
||||
</div>
|
||||
</form>
|
||||
<div class="note" style="margin-top: 18px;">
|
||||
Zielbild: Eine zentrale Anmeldung fuer Mitglieder, ohne dass zuerst Tenant-Keys oder Subdomains bekannt sein muessen.
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Was danach passiert</p>
|
||||
<h3>Automatische Weiterleitung oder Tenant-Auswahl.</h3>
|
||||
<p class="card__eyebrow">Ablauf</p>
|
||||
<h3>So geht es weiter</h3>
|
||||
<div class="timeline timeline--tight" style="margin-top: 18px;">
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Genau ein Treffer</p>
|
||||
<p class="timeline__meta">Die Plattform leitet direkt in den Tenant weiter und zeigt dort Passwort- oder SSO-Login an.</p>
|
||||
<p class="timeline__title">1. E-Mail</p>
|
||||
<p class="timeline__meta">Die Anmeldung startet mit der Adresse.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Mehrere Treffer</p>
|
||||
<p class="timeline__meta">Vor dem eigentlichen Login erscheint eine Tenant-Auswahl mit Namen, Domain und Login-Modell.</p>
|
||||
<p class="timeline__title">2. Pruefen</p>
|
||||
<p class="timeline__meta">Der passende Zugang wird zugeordnet.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Kein Treffer</p>
|
||||
<p class="timeline__meta">Statt Sackgasse gibt es einen klaren Rueckweg zu Tenant-Admin, Einladung oder Support.</p>
|
||||
<p class="timeline__title">3. Weiter</p>
|
||||
<p class="timeline__meta">Dann folgt der eigentliche Login.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="stack" style="margin-top:18px;">
|
||||
@foreach ($providers as $providerKey => $provider)
|
||||
<div class="feature-list__item" style="align-items:flex-start;">
|
||||
<div class="feature-list__badge">{{ strtoupper(substr((string) $providerKey, 0, 1)) }}</div>
|
||||
<div>
|
||||
<p class="feature-list__title">{{ $provider['label'] ?? $providerKey }}</p>
|
||||
<p class="feature-list__copy muted">{{ $provider['description'] ?? '' }}</p>
|
||||
</div>
|
||||
<span class="status {{ !empty($provider['required']) ? 'status--warning' : 'status--neutral' }}">
|
||||
{{ !empty($provider['required']) ? 'Pflicht' : 'Optional' }}
|
||||
</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="callout" style="margin-top:18px;">
|
||||
<strong>{{ $identityPolicy['admin_clarification']['title'] ?? 'Admin-Klaerfall' }}</strong>
|
||||
<p class="muted" style="margin-top:8px;">{{ $identityPolicy['admin_clarification']['description'] ?? $identityPolicy['fallback'] ?? '' }}</p>
|
||||
<ul class="list-reset" style="margin-top:12px;">
|
||||
@foreach (($identityPolicy['admin_clarification']['steps'] ?? []) as $step)
|
||||
<li style="margin-bottom:8px;">- {{ $step }}</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--3" style="margin-top: 18px;">
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Preview: Einzelzuordnung</p>
|
||||
<p class="card__eyebrow">Ein Treffer</p>
|
||||
<h3>{{ $preview['single']['email'] }}</h3>
|
||||
<p class="muted">{{ $preview['single']['message'] }}</p>
|
||||
<div class="tenant-grid" style="margin-top: 18px;">
|
||||
<div class="tenant-grid" style="margin-top: 16px;">
|
||||
@foreach ($preview['single']['matches'] as $tenant)
|
||||
<div class="tenant-row">
|
||||
<div class="tenant-row__meta">
|
||||
<p class="tenant-row__title">{{ $tenant['name'] }}</p>
|
||||
<p class="tenant-row__copy">{{ $tenant['domain'] }}</p>
|
||||
</div>
|
||||
<span class="status">Auto-Weiterleitung</span>
|
||||
<span class="status">Direkt</span>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Preview: Mehrfachzuordnung</p>
|
||||
<p class="card__eyebrow">Mehrere Treffer</p>
|
||||
<h3>{{ $preview['multiple']['email'] }}</h3>
|
||||
<p class="muted">{{ $preview['multiple']['message'] }}</p>
|
||||
<div class="tenant-grid" style="margin-top: 18px;">
|
||||
<div class="tenant-grid" style="margin-top: 16px;">
|
||||
@foreach ($preview['multiple']['matches'] as $tenant)
|
||||
<div class="tenant-row">
|
||||
<div class="tenant-row__meta">
|
||||
<p class="tenant-row__title">{{ $tenant['name'] }}</p>
|
||||
<p class="tenant-row__copy">{{ $tenant['domain'] }} - {{ $tenant['login_mode'] }}</p>
|
||||
<p class="tenant-row__copy">{{ $tenant['domain'] }}</p>
|
||||
</div>
|
||||
<span class="status status--warning">Auswahl</span>
|
||||
</div>
|
||||
@@ -228,94 +127,13 @@
|
||||
</article>
|
||||
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Preview: Unbekannte Mail</p>
|
||||
<h3>{{ $preview['unknown']['email'] }}</h3>
|
||||
<p class="muted">{{ $preview['unknown']['message'] }}</p>
|
||||
<div class="callout" style="margin-top: 18px;">
|
||||
<strong>Keine Sackgasse fuer Mitglieder</strong>
|
||||
Die Anwendung kann stattdessen Tenant-Kontakt, Einladung oder den zentralen Supportpfad anzeigen.
|
||||
<p class="card__eyebrow">Kein Treffer</p>
|
||||
<h3>Sauberer Rueckweg</h3>
|
||||
<p class="muted">Wenn keine Zuordnung passt, geht es klar weiter statt im Leeren zu enden.</p>
|
||||
<div class="callout" style="margin-top: 16px;">
|
||||
<strong>Kontakt oder Einladung</strong>
|
||||
Die Anmeldung zeigt dann den naechsten klaren Schritt.
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--2" style="margin-top: 18px;">
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Identity-Policy</p>
|
||||
<h3>Lokale Anmeldung plus ADFS/OIDC</h3>
|
||||
<div class="timeline timeline--tight" style="margin-top: 18px;">
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Pflicht</p>
|
||||
<p class="timeline__meta">{{ $identityPolicy['local_login_required'] ? 'Lokale Anmeldung bleibt immer verfuegbar.' : 'Lokale Anmeldung ist optional.' }}</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Zusaetzliche Option</p>
|
||||
<p class="timeline__meta">{{ $identityPolicy['external_login_mode'] ?? 'ADFS/OIDC' }}</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Abgleich</p>
|
||||
<p class="timeline__meta">{{ $identityPolicy['email_mapping'] ?? '' }}</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Klärfall</p>
|
||||
<p class="timeline__meta">{{ $identityPolicy['fallback'] ?? '' }}</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Admin-Klaerfall</p>
|
||||
<p class="timeline__meta">{{ $identityPolicy['admin_clarification']['description'] ?? '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Rollenmodell</p>
|
||||
<h3>Vorgeschlagene Rechtebasis</h3>
|
||||
<div class="stack" style="margin-top: 18px;">
|
||||
@foreach ($roleMatrix['roles'] ?? [] as $role)
|
||||
<div class="feature-list__item">
|
||||
<div class="feature-list__badge">{{ strtoupper(substr((string) ($role['key'] ?? ''), 0, 1)) }}</div>
|
||||
<div>
|
||||
<p class="feature-list__title">{{ $role['key'] ?? '' }}</p>
|
||||
<p class="feature-list__copy muted">{{ $role['summary'] ?? '' }}</p>
|
||||
</div>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--2" style="margin-top: 18px;">
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Provider-Details</p>
|
||||
<h3>Konfigurierte ADFS/OIDC-Verbindungen</h3>
|
||||
<div class="stack" style="margin-top: 18px;">
|
||||
@foreach ($oidcProviders as $provider)
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">{{ $provider['provider_key'] ?? 'provider' }}</p>
|
||||
<p class="timeline__meta">Driver: {{ $provider['driver'] ?? 'oidc' }} | Client-ID: {{ $provider['client_id'] ?? '-' }}</p>
|
||||
<p class="timeline__meta">Redirect: {{ $provider['redirect_uri'] ?? '-' }}</p>
|
||||
<p class="timeline__meta">Scopes: {{ implode(', ', $provider['scopes'] ?? []) }}</p>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Mindestregel</p>
|
||||
<h3>Lokale Anmeldung bleibt immer verfuegbar.</h3>
|
||||
<div class="callout" style="margin-top: 18px;">
|
||||
Selbst wenn ein externer Provider ausfaellt, bleibt der lokale Weg aktiv. So kann der Tenant weiterarbeiten und der Admin-Klaerfall bleibt handhabbar.
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="panel" style="margin-top: 18px;">
|
||||
<h3>Regeln fuer Rollen und Abgrenzung</h3>
|
||||
<ul class="list-reset">
|
||||
@foreach ($roleMatrix['rules'] ?? [] as $rule)
|
||||
<li style="margin-bottom: 12px;">
|
||||
<span class="status">Regel</span> {{ $rule }}
|
||||
</li>
|
||||
@endforeach
|
||||
</ul>
|
||||
</section>
|
||||
@endsection
|
||||
|
||||
@@ -3,42 +3,56 @@
|
||||
@section('page_title', 'Die Kaffeeliste - Übersicht')
|
||||
|
||||
@section('content')
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="hero__kicker">Übersicht</p>
|
||||
<h2 class="hero__title">Dein Bereich</h2>
|
||||
<p class="hero__lead">
|
||||
Das Dashboard zeigt den aktuellen Kontostand, die Nutzung im Monat und die letzten Buchungen.
|
||||
Die Werte sind hier bewusst als fachliche Anker platziert und können später direkt aus dem Ledger gespeist werden.
|
||||
</p>
|
||||
</div>
|
||||
<div class="toolbar">
|
||||
<span class="badge">Letzte Synchronisation: heute</span>
|
||||
<span class="badge">Tenant: Demo-Workspace</span>
|
||||
<span class="badge badge--solid">Saldo aktiv</span>
|
||||
<section class="hero hero--split">
|
||||
<div class="hero__content">
|
||||
<div>
|
||||
<p class="hero__kicker">Uebersicht</p>
|
||||
<h2 class="hero__title">Dein Bereich</h2>
|
||||
<p class="hero__lead">
|
||||
Die wichtigsten Werte auf einen Blick.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero__meta">
|
||||
<span class="badge">Heute</span>
|
||||
<span class="badge">Aktiv</span>
|
||||
<span class="badge badge--solid">Bereit</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<aside class="hero__aside">
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">Status</p>
|
||||
<div class="metric__value">OK</div>
|
||||
<p class="muted">Alles laeuft normal.</p>
|
||||
</article>
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">Letzte Aktivitaet</p>
|
||||
<div class="metric__value">Heute</div>
|
||||
<p class="muted">Neueste Aenderungen im Bereich.</p>
|
||||
</article>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--4">
|
||||
<article class="card metric">
|
||||
<p class="metric__label">Kontostand</p>
|
||||
<div class="metric__value">7,50 EUR</div>
|
||||
<p class="muted">Positiver Puffer für den laufenden Monat.</p>
|
||||
<p class="muted">Aktueller Stand.</p>
|
||||
</article>
|
||||
<article class="card metric">
|
||||
<p class="metric__label">Striche diesen Monat</p>
|
||||
<p class="metric__label">Striche</p>
|
||||
<div class="metric__value">5</div>
|
||||
<p class="muted">Verbrauch seit Monatsbeginn.</p>
|
||||
<p class="muted">Diesen Monat.</p>
|
||||
</article>
|
||||
<article class="card metric">
|
||||
<p class="metric__label">Einzahlungen diesen Monat</p>
|
||||
<p class="metric__label">Einzahlungen</p>
|
||||
<div class="metric__value">1</div>
|
||||
<p class="muted">Alle gebuchten Zahlungen im aktuellen Zeitraum.</p>
|
||||
<p class="muted">Diesen Monat.</p>
|
||||
</article>
|
||||
<article class="card metric">
|
||||
<p class="metric__label">Letzte Buchung</p>
|
||||
<div class="metric__value">Heute</div>
|
||||
<p class="muted">Aktuelle Aktivität im Mandantenbereich.</p>
|
||||
<p class="muted">Aktuelle Aktivitaet.</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@@ -46,10 +60,10 @@
|
||||
<article class="table-card">
|
||||
<div class="table-card__header">
|
||||
<div>
|
||||
<p class="card__eyebrow">Aktivität</p>
|
||||
<p class="card__eyebrow">Aktivitaet</p>
|
||||
<h3>Letzte Buchungen</h3>
|
||||
</div>
|
||||
<span class="pill">Live feed</span>
|
||||
<span class="pill">Live</span>
|
||||
</div>
|
||||
<div class="table-card__body">
|
||||
<table>
|
||||
@@ -86,24 +100,24 @@
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<h3>Arbeitsbereich</h3>
|
||||
<p class="muted">Die Navigation läuft vollständig über das Menü. Im Inhalt bleiben nur die fachlichen Schwerpunkte.</p>
|
||||
<h3>Bereiche</h3>
|
||||
<p class="muted">Im Menue liegen die zentralen Arbeitsbereiche.</p>
|
||||
<div class="timeline timeline--tight">
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Mitglieder und Rollen</p>
|
||||
<p class="timeline__meta">Im Menü sichtbar, wenn die angemeldete Rolle den Bereich verwalten darf.</p>
|
||||
<p class="timeline__title">Mitglieder</p>
|
||||
<p class="timeline__meta">Verwaltung im Hintergrund.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Buchungen und Zahlungen</p>
|
||||
<p class="timeline__meta">Nur für Finanzrollen freigeschaltet und dadurch nicht für jede Anmeldung sichtbar.</p>
|
||||
<p class="timeline__title">Buchungen</p>
|
||||
<p class="timeline__meta">Striche und Zahlungen zusammen.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Exporte und Zusatzfunktionen</p>
|
||||
<p class="timeline__meta">Werden im Menü nur angezeigt, wenn das Lizenzmodell sie für den Tenant enthält.</p>
|
||||
<p class="timeline__title">Exporte</p>
|
||||
<p class="timeline__meta">Nur wenn freigeschaltet.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="note" style="margin-top: 18px;">
|
||||
Der alte Funktionskern bleibt sichtbar: Saldo, Striche, Einzahlungen und letzte Aktionen bilden die produktive Achse.
|
||||
Die wichtigsten Zahlen sind hier, der Rest bleibt im Menue.
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
@@ -20,10 +20,28 @@
|
||||
$layoutNavItems = function_exists('app_tenant_navigation_items')
|
||||
? app_tenant_navigation_items($layoutAuth, $layoutLicense)
|
||||
: [];
|
||||
$layoutGuestItems = [
|
||||
['href' => '/', 'label' => 'Start'],
|
||||
['href' => '/login/', 'label' => 'Anmeldung'],
|
||||
['href' => '/admin/login/', 'label' => 'Admin'],
|
||||
];
|
||||
$layoutPrimaryNavItems = $layoutNavItems !== [] ? $layoutNavItems : $layoutGuestItems;
|
||||
$layoutCurrentLabel = 'Start';
|
||||
foreach ($layoutPrimaryNavItems as $item) {
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
if ($layoutPath === $itemHref) {
|
||||
$layoutCurrentLabel = (string) ($item['label'] ?? $layoutCurrentLabel);
|
||||
break;
|
||||
}
|
||||
}
|
||||
$layoutThemeCss = function_exists('app_tenant_theme_root_css')
|
||||
? app_tenant_theme_root_css($layoutThemeSettings)
|
||||
: '';
|
||||
@endphp
|
||||
@php
|
||||
$resolvedLayoutMode = trim((string) $__env->yieldContent('layout_mode', 'app'));
|
||||
@endphp
|
||||
<style>
|
||||
:root {
|
||||
{!! $layoutThemeCss !!}
|
||||
@@ -64,28 +82,7 @@
|
||||
margin: 14px auto;
|
||||
min-height: calc(100vh - 28px);
|
||||
display: grid;
|
||||
grid-template-columns: var(--sidebar-width) minmax(0, 1fr);
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.app-sidebar {
|
||||
position: sticky;
|
||||
top: 14px;
|
||||
align-self: start;
|
||||
min-height: calc(100vh - 28px);
|
||||
}
|
||||
|
||||
.app-sidebar__panel {
|
||||
height: calc(100vh - 28px);
|
||||
display: grid;
|
||||
grid-template-rows: auto auto 1fr auto;
|
||||
gap: 20px;
|
||||
padding: 24px 20px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 30px;
|
||||
background:
|
||||
linear-gradient(180deg, rgba(255, 252, 246, 0.98), rgba(249, 242, 231, 0.96));
|
||||
box-shadow: var(--shadow);
|
||||
gap: 16px;
|
||||
}
|
||||
|
||||
.brand {
|
||||
@@ -96,9 +93,9 @@
|
||||
}
|
||||
|
||||
.brand__mark {
|
||||
width: 46px;
|
||||
height: 46px;
|
||||
border-radius: 16px;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
border-radius: 14px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
font-weight: 700;
|
||||
@@ -108,8 +105,82 @@
|
||||
}
|
||||
|
||||
.brand__text { display: grid; gap: 3px; min-width: 0; }
|
||||
.brand__title { margin: 0; font-size: 1.16rem; font-weight: 700; letter-spacing: 0.01em; }
|
||||
.brand__title { margin: 0; font-size: 1.06rem; font-weight: 700; letter-spacing: 0.01em; }
|
||||
.brand__subtitle { margin: 0; color: var(--muted); font-size: 0.92rem; }
|
||||
.brand--link { color: inherit; text-decoration: none; }
|
||||
.brand--link:hover { text-decoration: none; }
|
||||
|
||||
.site-shell {
|
||||
min-height: 100vh;
|
||||
padding: 14px 0 24px;
|
||||
}
|
||||
|
||||
.site-header,
|
||||
.site-main,
|
||||
.app-footer {
|
||||
width: min(var(--content-width), calc(100% - 28px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.site-header {
|
||||
position: sticky;
|
||||
top: 14px;
|
||||
z-index: 30;
|
||||
margin-bottom: 18px;
|
||||
}
|
||||
|
||||
.site-header__inner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 18px;
|
||||
padding: 16px 18px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 22px;
|
||||
background: rgba(255, 251, 244, 0.92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.top-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.top-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
padding: 0.62rem 0.92rem;
|
||||
border-radius: 999px;
|
||||
border: 1px solid transparent;
|
||||
color: var(--muted);
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.top-nav__link:hover {
|
||||
text-decoration: none;
|
||||
color: var(--brand-strong);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border-color: rgba(var(--brand-rgb), 0.12);
|
||||
}
|
||||
|
||||
.top-nav__link.is-active {
|
||||
color: var(--brand-strong);
|
||||
background: rgba(var(--brand-rgb), 0.08);
|
||||
border-color: rgba(var(--brand-rgb), 0.14);
|
||||
}
|
||||
|
||||
.site-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: flex-end;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.sidebar-meta,
|
||||
.header-meta {
|
||||
@@ -135,25 +206,21 @@
|
||||
|
||||
.badge--solid { background: var(--brand); color: #fff; border-color: transparent; }
|
||||
|
||||
.sidebar-nav {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
align-content: start;
|
||||
}
|
||||
.sidebar-nav { display: grid; gap: 6px; align-content: start; }
|
||||
|
||||
.sidebar-nav__link {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding: 0.88rem 1rem;
|
||||
border-radius: 18px;
|
||||
padding: 0.76rem 0.92rem;
|
||||
border-radius: 14px;
|
||||
border: 1px solid rgba(44, 32, 23, 0.08);
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
color: var(--text);
|
||||
font-size: 0.95rem;
|
||||
font-size: 0.92rem;
|
||||
font-weight: 700;
|
||||
box-shadow: var(--shadow-soft);
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.sidebar-nav__link::after {
|
||||
@@ -165,7 +232,7 @@
|
||||
.sidebar-nav__link:hover {
|
||||
text-decoration: none;
|
||||
border-color: rgba(var(--brand-rgb), 0.22);
|
||||
background: rgba(255, 255, 255, 0.95);
|
||||
background: rgba(255, 255, 255, 0.92);
|
||||
}
|
||||
|
||||
.sidebar-nav__link.is-active {
|
||||
@@ -178,16 +245,17 @@
|
||||
|
||||
.sidebar-footer {
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
gap: 10px;
|
||||
align-content: end;
|
||||
}
|
||||
|
||||
.sidebar-note {
|
||||
padding: 14px 16px;
|
||||
border-radius: 18px;
|
||||
background: rgba(var(--brand-rgb), 0.08);
|
||||
border: 1px solid rgba(var(--brand-rgb), 0.12);
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(var(--brand-rgb), 0.06);
|
||||
border: 1px solid rgba(var(--brand-rgb), 0.1);
|
||||
color: var(--brand-strong);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.sidebar-note strong {
|
||||
@@ -198,38 +266,9 @@
|
||||
.app-body {
|
||||
min-width: 0;
|
||||
display: grid;
|
||||
grid-template-rows: auto 1fr auto;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.context-bar { padding-top: 6px; }
|
||||
|
||||
.context-bar__inner {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
padding: 20px 24px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: 28px;
|
||||
background: rgba(255, 251, 244, 0.92);
|
||||
box-shadow: var(--shadow);
|
||||
}
|
||||
|
||||
.context-copy { display: grid; gap: 6px; }
|
||||
|
||||
.context-copy__title {
|
||||
margin: 0;
|
||||
font-size: 1.5rem;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.context-copy__lead {
|
||||
margin: 0;
|
||||
color: var(--muted);
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
|
||||
.mobile-nav {
|
||||
display: none;
|
||||
position: relative;
|
||||
@@ -280,6 +319,14 @@
|
||||
gap: 10px;
|
||||
}
|
||||
|
||||
.mobile-nav__stack .top-nav__link {
|
||||
justify-content: flex-start;
|
||||
padding: 0.78rem 0.92rem;
|
||||
border: 1px solid rgba(44, 32, 23, 0.08);
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
color: var(--text);
|
||||
}
|
||||
|
||||
.mobile-nav__footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
@@ -291,8 +338,8 @@
|
||||
input[type="submit"] {
|
||||
appearance: none;
|
||||
border: 0;
|
||||
border-radius: 999px;
|
||||
padding: 0.8rem 1.15rem;
|
||||
border-radius: 12px;
|
||||
padding: 0.78rem 1.05rem;
|
||||
background: linear-gradient(135deg, var(--brand) 0%, var(--brand-strong) 100%);
|
||||
color: #fff;
|
||||
font: inherit;
|
||||
@@ -312,11 +359,11 @@
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
gap: 24px;
|
||||
gap: 16px;
|
||||
margin-bottom: 28px;
|
||||
padding: 30px;
|
||||
padding: 22px;
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-xl);
|
||||
border-radius: 22px;
|
||||
background:
|
||||
linear-gradient(135deg, rgba(255, 251, 244, 0.98), rgba(252, 247, 240, 0.95));
|
||||
box-shadow: var(--shadow);
|
||||
@@ -368,10 +415,10 @@
|
||||
.form-panel,
|
||||
.table-card {
|
||||
border: 1px solid var(--line);
|
||||
border-radius: var(--radius-lg);
|
||||
border-radius: 16px;
|
||||
background: var(--bg-elevated);
|
||||
box-shadow: var(--shadow);
|
||||
padding: 22px;
|
||||
padding: 18px;
|
||||
}
|
||||
|
||||
.card__eyebrow {
|
||||
@@ -403,8 +450,8 @@
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
align-items: flex-start;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.78);
|
||||
border: 1px solid rgba(31, 41, 51, 0.08);
|
||||
}
|
||||
@@ -434,8 +481,8 @@
|
||||
|
||||
.split {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
grid-template-columns: minmax(0, 1.4fr) minmax(0, 0.9fr);
|
||||
gap: 14px;
|
||||
grid-template-columns: minmax(0, 1.25fr) minmax(0, 0.95fr);
|
||||
}
|
||||
|
||||
.auth-summary__card {
|
||||
@@ -452,8 +499,8 @@
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
padding: 12px 14px;
|
||||
border-radius: 14px;
|
||||
background: rgba(255, 255, 255, 0.82);
|
||||
border: 1px solid rgba(31, 41, 51, 0.08);
|
||||
}
|
||||
@@ -463,10 +510,10 @@
|
||||
.tenant-row__copy { margin: 0; color: var(--muted); font-size: 0.92rem; }
|
||||
|
||||
.callout {
|
||||
padding: 16px 18px;
|
||||
border-radius: 18px;
|
||||
background: rgba(45, 106, 79, 0.08);
|
||||
border: 1px solid rgba(45, 106, 79, 0.14);
|
||||
padding: 14px 16px;
|
||||
border-radius: 16px;
|
||||
background: rgba(45, 106, 79, 0.06);
|
||||
border: 1px solid rgba(45, 106, 79, 0.12);
|
||||
color: var(--brand-strong);
|
||||
}
|
||||
|
||||
@@ -588,18 +635,229 @@
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.marketing-shell {
|
||||
min-height: 100vh;
|
||||
padding: 0 0 92px;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.marketing-main {
|
||||
width: min(1240px, calc(100% - 48px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
.marketing-bar {
|
||||
position: sticky;
|
||||
top: 0;
|
||||
z-index: 30;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 14px;
|
||||
height: 88px;
|
||||
min-height: 88px;
|
||||
width: 100%;
|
||||
margin: 0 0 20px;
|
||||
padding: 0 18px;
|
||||
border: 0;
|
||||
border-bottom: 1px solid rgba(137, 154, 188, 0.18);
|
||||
border-radius: 0;
|
||||
background: rgba(8, 10, 18, 0.82);
|
||||
box-shadow: none;
|
||||
backdrop-filter: blur(16px);
|
||||
}
|
||||
|
||||
.marketing-nav {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 6px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
|
||||
.marketing-bar .brand {
|
||||
gap: 8px;
|
||||
flex: 0 0 auto;
|
||||
}
|
||||
|
||||
.marketing-bar .brand__mark {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
border-radius: 10px;
|
||||
font-size: 1rem;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.marketing-bar .brand__text {
|
||||
gap: 1px;
|
||||
}
|
||||
|
||||
.marketing-bar .brand__title {
|
||||
font-size: 1.08rem;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.marketing-bar .brand__subtitle {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.marketing-nav__link {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36px;
|
||||
padding: 0 0.3rem;
|
||||
border-radius: 6px;
|
||||
border: 1px solid transparent;
|
||||
color: rgba(231, 238, 255, 0.8);
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.marketing-nav__link:hover {
|
||||
color: #f4f7ff;
|
||||
text-decoration: none;
|
||||
background: rgba(255, 255, 255, 0.06);
|
||||
border-color: rgba(201, 214, 255, 0.12);
|
||||
}
|
||||
|
||||
.marketing-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.marketing-actions .button {
|
||||
min-height: 38px;
|
||||
padding: 0.42rem 0.8rem;
|
||||
border-radius: 8px;
|
||||
font-size: 0.94rem;
|
||||
}
|
||||
|
||||
.marketing-actions .button--ghost,
|
||||
.marketing-mobile__footer .button--ghost {
|
||||
color: #f4f7ff;
|
||||
border-color: rgba(201, 214, 255, 0.16);
|
||||
}
|
||||
|
||||
.marketing-mobile {
|
||||
display: none;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.marketing-mobile[open] { z-index: 40; }
|
||||
|
||||
.marketing-mobile__toggle {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
list-style: none;
|
||||
cursor: pointer;
|
||||
min-width: 38px;
|
||||
min-height: 38px;
|
||||
padding: 0.24rem 0.46rem;
|
||||
border-radius: 8px;
|
||||
border: 1px solid rgba(201, 214, 255, 0.14);
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.marketing-mobile__toggle::-webkit-details-marker { display: none; }
|
||||
|
||||
.marketing-mobile__toggle::before {
|
||||
content: "";
|
||||
width: 16px;
|
||||
height: 10px;
|
||||
border-top: 2px solid currentColor;
|
||||
border-bottom: 2px solid currentColor;
|
||||
box-shadow: inset 0 -4px 0 0 currentColor;
|
||||
}
|
||||
|
||||
.marketing-mobile__panel {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: calc(100% + 10px);
|
||||
width: min(280px, calc(100vw - 24px));
|
||||
padding: 14px;
|
||||
border-radius: 16px;
|
||||
border: 1px solid rgba(163, 183, 255, 0.14);
|
||||
background: rgba(8, 10, 18, 0.96);
|
||||
box-shadow: 0 18px 36px rgba(1, 5, 13, 0.45);
|
||||
}
|
||||
|
||||
.marketing-mobile__stack {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
}
|
||||
|
||||
.marketing-mobile__stack .marketing-nav__link {
|
||||
justify-content: flex-start;
|
||||
padding: 0.55rem 0.7rem;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
border-color: rgba(201, 214, 255, 0.1);
|
||||
}
|
||||
|
||||
.marketing-mobile__footer {
|
||||
margin-top: 12px;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.marketing-main {
|
||||
display: grid;
|
||||
gap: 18px;
|
||||
}
|
||||
|
||||
.marketing-footer {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
z-index: 25;
|
||||
margin-top: 0;
|
||||
padding: 10px 24px;
|
||||
border-top: 1px solid rgba(137, 154, 188, 0.14);
|
||||
background: rgba(8, 10, 18, 0.88);
|
||||
backdrop-filter: blur(18px);
|
||||
color: rgba(209, 217, 235, 0.68);
|
||||
font-size: 0.92rem;
|
||||
}
|
||||
|
||||
.marketing-footer__inner {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 16px;
|
||||
flex-wrap: wrap;
|
||||
width: min(1240px, calc(100% - 48px));
|
||||
margin: 0 auto;
|
||||
}
|
||||
|
||||
@media (max-width: 980px) {
|
||||
.site-shell {
|
||||
padding: 10px 0 18px;
|
||||
}
|
||||
|
||||
.site-header,
|
||||
.site-main,
|
||||
.app-footer {
|
||||
width: calc(100% - 20px);
|
||||
}
|
||||
|
||||
.site-header__inner {
|
||||
padding: 14px 16px;
|
||||
align-items: flex-start;
|
||||
}
|
||||
|
||||
.top-nav { display: none; }
|
||||
.app-shell {
|
||||
width: calc(100% - 20px);
|
||||
margin: 10px auto;
|
||||
min-height: calc(100vh - 20px);
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.app-sidebar { display: none; }
|
||||
.mobile-nav { display: block; }
|
||||
.context-bar { padding-top: 0; }
|
||||
.context-bar__inner { padding: 18px; }
|
||||
.grid--2,
|
||||
.grid--3,
|
||||
.grid--4,
|
||||
@@ -608,99 +866,133 @@
|
||||
.hero,
|
||||
.card,
|
||||
.panel,
|
||||
.form-panel { padding: 18px; }
|
||||
.form-panel { padding: 16px; }
|
||||
.table-card { padding: 0; }
|
||||
table { min-width: 0; }
|
||||
.marketing-shell { padding: 0 0 88px; }
|
||||
.marketing-main {
|
||||
width: calc(100% - 24px);
|
||||
}
|
||||
.marketing-bar {
|
||||
height: 72px;
|
||||
min-height: 72px;
|
||||
margin: 0 0 16px;
|
||||
padding: 0 12px;
|
||||
}
|
||||
.marketing-nav,
|
||||
.marketing-actions { display: none; }
|
||||
.marketing-mobile { display: block; }
|
||||
.marketing-footer {
|
||||
padding: 10px 12px;
|
||||
}
|
||||
.marketing-footer__inner {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
@media (max-width: 980px) {
|
||||
.app-body { gap: 14px; }
|
||||
}
|
||||
</style>
|
||||
@yield('page_styles')
|
||||
</head>
|
||||
<body class="@yield('body_class')">
|
||||
<div class="app-shell">
|
||||
<aside class="app-sidebar" aria-label="Tenant-Navigation">
|
||||
<div class="app-sidebar__panel">
|
||||
@if ($resolvedLayoutMode === 'marketing')
|
||||
<div class="marketing-shell">
|
||||
<header class="marketing-bar">
|
||||
<div class="brand">
|
||||
<div class="brand__mark">K</div>
|
||||
<div class="brand__text">
|
||||
<h1 class="brand__title">Die Kaffeeliste</h1>
|
||||
<p class="brand__subtitle">Kaffeekasse und Verwaltung im Tenant</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav" aria-label="Hauptnavigation">
|
||||
@forelse ($layoutNavItems as $item)
|
||||
<nav class="marketing-nav" aria-label="Marketing-Navigation">
|
||||
<a href="/" class="marketing-nav__link">Start</a>
|
||||
<a href="/login/" class="marketing-nav__link">Anmeldung</a>
|
||||
<a href="/admin/login/" class="marketing-nav__link">Admin</a>
|
||||
</nav>
|
||||
|
||||
<div class="marketing-actions">
|
||||
<a class="button button--ghost" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
|
||||
<details class="marketing-mobile">
|
||||
<summary class="marketing-mobile__toggle" aria-label="Menü"></summary>
|
||||
<div class="marketing-mobile__panel">
|
||||
<nav class="marketing-mobile__stack" aria-label="Mobile Marketing-Navigation">
|
||||
<a href="/" class="marketing-nav__link">Start</a>
|
||||
<a href="/login/" class="marketing-nav__link">Anmeldung</a>
|
||||
<a href="/admin/login/" class="marketing-nav__link">Admin</a>
|
||||
</nav>
|
||||
<div class="marketing-mobile__footer">
|
||||
<a class="button button--ghost" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</header>
|
||||
|
||||
<main class="marketing-main">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<footer class="marketing-footer">
|
||||
<div class="marketing-footer__inner">
|
||||
<span>Die Kaffeeliste</span>
|
||||
<span>Ein ruhiger Einstieg für Teams</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
@else
|
||||
<div class="site-shell">
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a href="{{ is_array($layoutAuth) ? '/dashboard/' : '/' }}" class="brand brand--link">
|
||||
<div class="brand__mark">K</div>
|
||||
<div class="brand__text">
|
||||
<h1 class="brand__title">Die Kaffeeliste</h1>
|
||||
<p class="brand__subtitle">
|
||||
@if (is_array($layoutAuth))
|
||||
{{ $layoutCurrentLabel }}
|
||||
@else
|
||||
Für Teams gemacht.
|
||||
@endif
|
||||
</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<nav class="top-nav" aria-label="Hauptnavigation">
|
||||
@foreach ($layoutPrimaryNavItems as $item)
|
||||
@php
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
$isActive = $layoutPath === $itemHref;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="sidebar-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@empty
|
||||
@php
|
||||
$guestItems = [
|
||||
['href' => '/', 'label' => 'Start'],
|
||||
['href' => '/login/', 'label' => 'Anmeldung'],
|
||||
];
|
||||
@endphp
|
||||
@foreach ($guestItems as $item)
|
||||
@php
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
$isActive = $layoutPath === $itemHref;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] }}" class="sidebar-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] }}</a>
|
||||
@endforeach
|
||||
@endforelse
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="top-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
|
||||
<div class="sidebar-footer">
|
||||
<div class="sidebar-note">
|
||||
<strong>Menü statt Sprungboxen</strong>
|
||||
Bereiche werden über die Navigation geöffnet und nicht mehr im Inhalt verlinkt.
|
||||
</div>
|
||||
<div class="site-actions">
|
||||
@if (is_array($layoutAuth))
|
||||
<span class="pill">{{ $layoutAuth['tenant_name'] ?? 'Tenant' }}</span>
|
||||
<form method="post" action="/logout/">
|
||||
<button type="submit" class="button button--ghost" style="width: 100%;">Abmelden</button>
|
||||
<button type="submit" class="button button--ghost">Abmelden</button>
|
||||
</form>
|
||||
@else
|
||||
<a class="button button--ghost" href="/login/">Anmelden</a>
|
||||
@endif
|
||||
</div>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<div class="app-body">
|
||||
<section class="context-bar">
|
||||
<div class="context-bar__inner">
|
||||
<div class="context-copy">
|
||||
<h2 class="context-copy__title">Die Kaffeeliste</h2>
|
||||
<p class="context-copy__lead">
|
||||
@if (is_array($layoutAuth))
|
||||
Ruhige Tenant-Oberfläche mit klarer Navigation und ohne doppelte Statusinfos.
|
||||
@else
|
||||
Anmeldung, Tenant-Zugriff und Verwaltung bleiben in einer ruhigen gemeinsamen Struktur.
|
||||
@endif
|
||||
</p>
|
||||
@if (!is_array($layoutAuth))
|
||||
<div class="header-meta">
|
||||
<span class="badge">Mandantenfähig</span>
|
||||
<span class="badge">Mobil tauglich</span>
|
||||
</div>
|
||||
@endif
|
||||
</div>
|
||||
|
||||
<details class="mobile-nav">
|
||||
<summary class="mobile-nav__toggle">Menü</summary>
|
||||
<div class="mobile-nav__panel">
|
||||
<nav class="mobile-nav__stack" aria-label="Mobile Hauptnavigation">
|
||||
@forelse ($layoutNavItems as $item)
|
||||
@foreach ($layoutPrimaryNavItems as $item)
|
||||
@php
|
||||
$itemHref = rtrim((string) ($item['href'] ?? '/'), '/');
|
||||
$itemHref = $itemHref === '' ? '/' : $itemHref;
|
||||
$isActive = $layoutPath === $itemHref;
|
||||
@endphp
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="sidebar-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@empty
|
||||
<a href="/" class="sidebar-nav__link {{ $layoutPath === '/' ? 'is-active' : '' }}">Start</a>
|
||||
<a href="/login/" class="sidebar-nav__link {{ $layoutPath === '/login' ? 'is-active' : '' }}">Anmeldung</a>
|
||||
@endforelse
|
||||
<a href="{{ $item['href'] ?? '/' }}" class="top-nav__link {{ $isActive ? 'is-active' : '' }}" @if ($isActive) aria-current="page" @endif>{{ $item['label'] ?? 'Link' }}</a>
|
||||
@endforeach
|
||||
</nav>
|
||||
@if (is_array($layoutAuth))
|
||||
<div class="mobile-nav__footer">
|
||||
@@ -712,19 +1004,20 @@
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<main class="app-main">
|
||||
<main class="site-main app-main">
|
||||
@yield('content')
|
||||
</main>
|
||||
|
||||
<footer class="app-footer">
|
||||
<div class="app-footer__inner">
|
||||
<span>Die Kaffeeliste</span>
|
||||
<span>Mitglieder, Striche, Einzahlungen, Hinweise und Exporte in einem System</span>
|
||||
<span>Klare Bereiche mit Navigation oben</span>
|
||||
</div>
|
||||
</footer>
|
||||
</div>
|
||||
</div>
|
||||
@endif
|
||||
</body>
|
||||
</html>
|
||||
|
||||
@@ -46,82 +46,61 @@
|
||||
width:min(1460px,calc(100vw - 32px));
|
||||
margin:20px auto 40px;
|
||||
display:grid;
|
||||
grid-template-columns:minmax(280px,300px) minmax(0,1fr);
|
||||
gap:20px;
|
||||
align-items:start;
|
||||
}
|
||||
.sidebar,.hero,.card,.alert{
|
||||
.site-header{position:sticky;top:20px;z-index:20}
|
||||
.site-header__inner{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
gap:18px;
|
||||
padding:16px 18px;
|
||||
border:1px solid var(--line);
|
||||
border-radius:24px;
|
||||
background:rgba(255,251,244,.96);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.site-brand{display:flex;align-items:center;gap:14px;min-width:0}
|
||||
.site-brand__mark{
|
||||
width:44px;
|
||||
height:44px;
|
||||
border-radius:16px;
|
||||
display:grid;
|
||||
place-items:center;
|
||||
background:linear-gradient(135deg,var(--brand) 0%,var(--brand-strong) 100%);
|
||||
color:#fff;
|
||||
font-weight:800;
|
||||
box-shadow:0 14px 28px rgba(var(--brand-rgb),.18);
|
||||
}
|
||||
.site-brand__title{margin:0;font-family:Georgia,serif;font-size:1.1rem;letter-spacing:-.02em}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:var(--muted);font-size:.92rem}
|
||||
.site-nav,.site-actions,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.site-nav__link{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:10px 14px;
|
||||
border-radius:999px;
|
||||
border:1px solid transparent;
|
||||
color:var(--muted);
|
||||
font-weight:700;
|
||||
}
|
||||
.site-nav__link:hover{text-decoration:none;color:var(--brand);background:rgba(255,255,255,.82);border-color:rgba(var(--brand-rgb),.14)}
|
||||
.site-nav__link.active{background:rgba(var(--brand-rgb),.10);color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.hero,.card,.alert{
|
||||
border:1px solid var(--line);
|
||||
border-radius:var(--radius);
|
||||
background:var(--card);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.sidebar{
|
||||
position:sticky;
|
||||
top:20px;
|
||||
display:grid;
|
||||
gap:16px;
|
||||
padding:18px;
|
||||
background:rgba(255,251,244,.96);
|
||||
}
|
||||
.sidebar__brand{
|
||||
display:grid;
|
||||
gap:6px;
|
||||
padding-bottom:14px;
|
||||
border-bottom:1px solid rgba(37,24,15,.1);
|
||||
}
|
||||
.sidebar__eyebrow{
|
||||
margin:0;
|
||||
color:var(--accent);
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.16em;
|
||||
font-size:.78rem;
|
||||
font-weight:800;
|
||||
}
|
||||
.sidebar__title,.hero__title,.card h2,.card h3{
|
||||
.hero__title,.card h2,.card h3{
|
||||
font-family:Georgia,serif;
|
||||
letter-spacing:-.02em;
|
||||
}
|
||||
.sidebar__title{
|
||||
margin:0;
|
||||
font-size:1.55rem;
|
||||
line-height:1.05;
|
||||
}
|
||||
.sidebar__subtitle,.muted,p{color:var(--muted)}
|
||||
.sidebar__meta,.actions,.context{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.sidebar__section{
|
||||
display:inline-block;
|
||||
color:var(--accent);
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.14em;
|
||||
font-size:.76rem;
|
||||
font-weight:800;
|
||||
}
|
||||
.sidebar__nav{display:grid;gap:10px}
|
||||
.sidebar__link{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
padding:12px 14px;
|
||||
border-radius:16px;
|
||||
border:1px solid rgba(var(--brand-rgb),.12);
|
||||
background:#fff;
|
||||
color:var(--brand);
|
||||
font-weight:700;
|
||||
}
|
||||
.sidebar__link.active{
|
||||
background:var(--brand);
|
||||
color:#fff;
|
||||
}
|
||||
.sidebar__footer{
|
||||
display:grid;
|
||||
gap:12px;
|
||||
padding-top:14px;
|
||||
border-top:1px solid rgba(37,24,15,.1);
|
||||
}
|
||||
.sidebar__mobile{display:none}
|
||||
.sidebar__mobile[open]{z-index:20}
|
||||
.sidebar__toggle{
|
||||
.muted,p{color:var(--muted)}
|
||||
.site-mobile{display:none;position:relative}
|
||||
.site-mobile[open]{z-index:20}
|
||||
.site-toggle{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
@@ -135,8 +114,8 @@
|
||||
color:var(--brand);
|
||||
font-weight:700;
|
||||
}
|
||||
.sidebar__toggle::-webkit-details-marker{display:none}
|
||||
.sidebar__toggle::after{
|
||||
.site-toggle::-webkit-details-marker{display:none}
|
||||
.site-toggle::after{
|
||||
content:"";
|
||||
width:11px;
|
||||
height:11px;
|
||||
@@ -145,16 +124,21 @@
|
||||
transform:rotate(45deg);
|
||||
transition:transform .2s ease;
|
||||
}
|
||||
.sidebar__mobile[open] .sidebar__toggle::after{transform:rotate(225deg)}
|
||||
.sidebar__panel{
|
||||
margin-top:12px;
|
||||
.site-mobile[open] .site-toggle::after{transform:rotate(225deg)}
|
||||
.site-panel{
|
||||
position:absolute;
|
||||
right:0;
|
||||
top:calc(100% + 12px);
|
||||
width:min(320px,calc(100vw - 32px));
|
||||
padding:14px;
|
||||
border-radius:18px;
|
||||
border:1px solid var(--line);
|
||||
background:#fffdf9;
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.sidebar__stack{display:grid;gap:10px}
|
||||
.site-stack{display:grid;gap:10px}
|
||||
.site-stack .site-nav__link{justify-content:flex-start;background:rgba(255,255,255,.78);border-color:rgba(37,24,15,.08);color:var(--ink)}
|
||||
.site-footer-actions{display:flex;justify-content:flex-end;margin-top:12px}
|
||||
.content{min-width:0;display:grid;gap:18px}
|
||||
.hero{
|
||||
padding:24px;
|
||||
@@ -253,59 +237,56 @@
|
||||
.mono{font-family:Consolas,monospace}
|
||||
.footer{margin-top:18px;text-align:center;color:var(--muted);font-size:.92rem}
|
||||
@media(max-width:1040px){
|
||||
.page-shell{grid-template-columns:1fr;width:min(100vw - 20px,1460px)}
|
||||
.sidebar{position:static}
|
||||
.sidebar__desktop{display:none}
|
||||
.sidebar__mobile{display:block}
|
||||
.page-shell{width:min(100vw - 20px,1460px)}
|
||||
.site-header__inner{align-items:flex-start;padding:14px 16px}
|
||||
.site-nav{display:none}
|
||||
.site-mobile{display:block}
|
||||
.split,.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}
|
||||
table{min-width:0}
|
||||
}
|
||||
@media(min-width:1041px){
|
||||
.sidebar__mobile{display:none}
|
||||
.site-mobile{display:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page-shell">
|
||||
<aside class="sidebar" aria-label="Bereichsnavigation">
|
||||
<div class="sidebar__brand">
|
||||
<p class="sidebar__eyebrow">Die Kaffeeliste</p>
|
||||
<h1 class="sidebar__title">Support</h1>
|
||||
<p class="sidebar__subtitle">Support, Vorgänge und Rückmeldungen im Tenant.</p>
|
||||
</div>
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a href="/dashboard/" class="site-brand">
|
||||
<div class="site-brand__mark">K</div>
|
||||
<div>
|
||||
<h1 class="site-brand__title">Die Kaffeeliste</h1>
|
||||
<p class="site-brand__subtitle">Support</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="sidebar__meta">
|
||||
<?= support_badge($isManager ? 'Verantwortlichen-Sicht' : 'Mitgliedersicht', 'success') ?>
|
||||
<?= support_badge('Tenant-weit') ?>
|
||||
<?= support_badge('Status und Routing', 'warning') ?>
|
||||
</div>
|
||||
|
||||
<div class="sidebar__desktop">
|
||||
<span class="sidebar__section">Bereiche</span>
|
||||
<nav class="sidebar__nav" aria-label="Tenant-Menü">
|
||||
<nav class="site-nav" aria-label="Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="sidebar__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="site-nav__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
|
||||
<div class="site-actions">
|
||||
<?= support_badge($isManager ? 'Verantwortlichen-Sicht' : 'Mitgliedersicht', 'success') ?>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
|
||||
<details class="site-mobile">
|
||||
<summary class="site-toggle">Menue</summary>
|
||||
<div class="site-panel">
|
||||
<nav class="site-stack" aria-label="Mobiles Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="site-nav__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="site-footer-actions">
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="sidebar__mobile">
|
||||
<summary class="sidebar__toggle">Menü</summary>
|
||||
<div class="sidebar__panel">
|
||||
<nav class="sidebar__stack" aria-label="Mobiles Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a href="<?= support_h((string) ($item['href'] ?? '/')) ?>" class="sidebar__link <?= (($item['key'] ?? '') === 'support') ? 'active' : '' ?>" <?= (($item['key'] ?? '') === 'support') ? 'aria-current="page"' : '' ?>><?= support_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</aside>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section class="hero">
|
||||
@@ -351,7 +332,7 @@
|
||||
<label>Betreff<input name="subject" maxlength="255" placeholder="Worum geht es?" required></label>
|
||||
<label>Kategorie
|
||||
<select name="category">
|
||||
<?php foreach ($categories as $category): ?>
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<option value="<?= support_h($category) ?>"><?= support_h(ucfirst(str_replace('_', ' ', $category))) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
|
||||
@@ -47,76 +47,50 @@
|
||||
width:min(1460px,calc(100vw - 32px));
|
||||
margin:20px auto 40px;
|
||||
display:grid;
|
||||
grid-template-columns:minmax(280px,300px) minmax(0,1fr);
|
||||
gap:20px;
|
||||
align-items:start;
|
||||
}
|
||||
.sidebar,.hero,.card,.table-card,.note{
|
||||
border:1px solid var(--line);
|
||||
border-radius:var(--radius);
|
||||
background:var(--card);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.sidebar{
|
||||
position:sticky;
|
||||
top:20px;
|
||||
display:grid;
|
||||
gap:16px;
|
||||
padding:18px;
|
||||
background:rgba(255,251,244,.96);
|
||||
}
|
||||
.sidebar__brand{
|
||||
display:grid;
|
||||
gap:6px;
|
||||
padding-bottom:14px;
|
||||
border-bottom:1px solid rgba(37,24,15,.1);
|
||||
}
|
||||
.sidebar__eyebrow{
|
||||
margin:0;
|
||||
color:var(--accent);
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.16em;
|
||||
font-size:.78rem;
|
||||
font-weight:800;
|
||||
}
|
||||
.sidebar__title,.hero__title,.card h2,.card h3{font-family:Georgia,serif;letter-spacing:-.02em}
|
||||
.sidebar__title{
|
||||
margin:0;
|
||||
font-size:1.55rem;
|
||||
line-height:1.05;
|
||||
}
|
||||
.sidebar__subtitle,.muted{color:var(--muted)}
|
||||
.sidebar__meta,.actions,.context,.meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.sidebar__section{
|
||||
display:inline-block;
|
||||
color:var(--accent);
|
||||
text-transform:uppercase;
|
||||
letter-spacing:.14em;
|
||||
font-size:.76rem;
|
||||
font-weight:800;
|
||||
}
|
||||
.sidebar__nav{display:grid;gap:10px}
|
||||
.sidebar__link{
|
||||
.site-header{position:sticky;top:20px;z-index:20}
|
||||
.site-header__inner{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
padding:12px 14px;
|
||||
gap:18px;
|
||||
padding:16px 18px;
|
||||
border:1px solid var(--line);
|
||||
border-radius:24px;
|
||||
background:rgba(255,251,244,.96);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.site-brand{display:flex;align-items:center;gap:14px;min-width:0}
|
||||
.site-brand__mark{
|
||||
width:44px;
|
||||
height:44px;
|
||||
border-radius:16px;
|
||||
border:1px solid rgba(var(--brand-rgb),.12);
|
||||
background:#fff;
|
||||
color:var(--brand);
|
||||
display:grid;
|
||||
place-items:center;
|
||||
background:linear-gradient(135deg,var(--brand) 0%,var(--brand-strong) 100%);
|
||||
color:#fff;
|
||||
font-weight:800;
|
||||
box-shadow:0 14px 28px rgba(var(--brand-rgb),.18);
|
||||
}
|
||||
.site-brand__title{margin:0;font-size:1.1rem}
|
||||
.site-brand__subtitle{margin:2px 0 0;color:var(--muted);font-size:.92rem}
|
||||
.site-nav,.site-actions,.actions,.context,.meta{display:flex;flex-wrap:wrap;gap:10px;align-items:center}
|
||||
.site-nav__link{
|
||||
display:inline-flex;
|
||||
align-items:center;
|
||||
justify-content:center;
|
||||
padding:10px 14px;
|
||||
border-radius:999px;
|
||||
border:1px solid transparent;
|
||||
color:var(--muted);
|
||||
font-weight:700;
|
||||
}
|
||||
.sidebar__link.active{background:var(--brand);color:#fff}
|
||||
.sidebar__footer{
|
||||
display:grid;
|
||||
gap:12px;
|
||||
padding-top:14px;
|
||||
border-top:1px solid rgba(37,24,15,.1);
|
||||
}
|
||||
.sidebar__mobile{display:none}
|
||||
.sidebar__mobile[open]{z-index:20}
|
||||
.sidebar__toggle{
|
||||
.site-nav__link:hover{text-decoration:none;color:var(--brand);background:rgba(255,255,255,.82);border-color:rgba(var(--brand-rgb),.14)}
|
||||
.site-nav__link.active{background:rgba(var(--brand-rgb),.10);color:var(--brand);border-color:rgba(var(--brand-rgb),.18)}
|
||||
.site-mobile{display:none;position:relative}
|
||||
.site-mobile[open]{z-index:20}
|
||||
.site-toggle{
|
||||
display:flex;
|
||||
align-items:center;
|
||||
justify-content:space-between;
|
||||
@@ -130,8 +104,8 @@
|
||||
color:var(--brand);
|
||||
font-weight:700;
|
||||
}
|
||||
.sidebar__toggle::-webkit-details-marker{display:none}
|
||||
.sidebar__toggle::after{
|
||||
.site-toggle::-webkit-details-marker{display:none}
|
||||
.site-toggle::after{
|
||||
content:"";
|
||||
width:11px;
|
||||
height:11px;
|
||||
@@ -140,16 +114,34 @@
|
||||
transform:rotate(45deg);
|
||||
transition:transform .2s ease;
|
||||
}
|
||||
.sidebar__mobile[open] .sidebar__toggle::after{transform:rotate(225deg)}
|
||||
.sidebar__panel{
|
||||
margin-top:12px;
|
||||
.site-mobile[open] .site-toggle::after{transform:rotate(225deg)}
|
||||
.site-panel{
|
||||
position:absolute;
|
||||
right:0;
|
||||
top:calc(100% + 12px);
|
||||
width:min(320px,calc(100vw - 32px));
|
||||
padding:14px;
|
||||
border-radius:18px;
|
||||
border:1px solid var(--line);
|
||||
background:#fffdf9;
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.sidebar__stack{display:grid;gap:10px}
|
||||
.site-stack{display:grid;gap:10px}
|
||||
.site-stack .site-nav__link{
|
||||
justify-content:flex-start;
|
||||
background:rgba(255,255,255,.78);
|
||||
border-color:rgba(37,24,15,.08);
|
||||
color:var(--ink);
|
||||
}
|
||||
.site-footer-actions{display:flex;justify-content:flex-end;margin-top:12px}
|
||||
.hero,.card,.table-card,.note{
|
||||
border:1px solid var(--line);
|
||||
border-radius:var(--radius);
|
||||
background:var(--card);
|
||||
box-shadow:var(--shadow);
|
||||
}
|
||||
.hero__title,.card h2,.card h3{font-family:Georgia,serif;letter-spacing:-.02em}
|
||||
.sidebar__subtitle,.muted{color:var(--muted)}
|
||||
.content{min-width:0;display:grid;gap:18px}
|
||||
.hero{
|
||||
display:grid;
|
||||
@@ -216,58 +208,55 @@
|
||||
white-space:nowrap;
|
||||
}
|
||||
@media(max-width:960px){
|
||||
.page-shell{grid-template-columns:1fr;width:min(100vw - 20px,1460px)}
|
||||
.sidebar{position:static}
|
||||
.sidebar__desktop{display:none}
|
||||
.sidebar__mobile{display:block}
|
||||
.page-shell{width:min(100vw - 20px,1460px)}
|
||||
.site-header__inner{align-items:flex-start;padding:14px 16px}
|
||||
.site-nav{display:none}
|
||||
.site-mobile{display:block}
|
||||
.hero,.grid--2,.grid--3,.grid--4{grid-template-columns:1fr}
|
||||
}
|
||||
@media(min-width:961px){
|
||||
.sidebar__mobile{display:none}
|
||||
.site-mobile{display:none}
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<main class="page-shell">
|
||||
<aside class="sidebar" aria-label="Bereichsnavigation">
|
||||
<div class="sidebar__brand">
|
||||
<p class="sidebar__eyebrow">Die Kaffeeliste</p>
|
||||
<h1 class="sidebar__title">Rollen</h1>
|
||||
<p class="sidebar__subtitle">Rollen und Rechte im Tenant.</p>
|
||||
</div>
|
||||
<header class="site-header">
|
||||
<div class="site-header__inner">
|
||||
<a href="/dashboard/" class="site-brand">
|
||||
<div class="site-brand__mark">K</div>
|
||||
<div>
|
||||
<h1 class="site-brand__title">Die Kaffeeliste</h1>
|
||||
<p class="site-brand__subtitle">Rollen</p>
|
||||
</div>
|
||||
</a>
|
||||
|
||||
<div class="sidebar__meta">
|
||||
<span class="badge">lokal + ADFS/OIDC</span>
|
||||
<span class="badge">Rollenmatrix</span>
|
||||
<span class="badge">Delegation</span>
|
||||
</div>
|
||||
|
||||
<div class="sidebar__desktop">
|
||||
<span class="sidebar__section">Bereiche</span>
|
||||
<nav class="sidebar__nav" aria-label="Tenant-Menü">
|
||||
<nav class="site-nav" aria-label="Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a class="sidebar__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<a class="site-nav__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
|
||||
<div class="site-actions">
|
||||
<span class="pill">Rollenmatrix</span>
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
|
||||
<details class="site-mobile">
|
||||
<summary class="site-toggle">Menue</summary>
|
||||
<div class="site-panel">
|
||||
<nav class="site-stack" aria-label="Mobiles Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a class="site-nav__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="site-footer-actions">
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details class="sidebar__mobile">
|
||||
<summary class="sidebar__toggle">Menü</summary>
|
||||
<div class="sidebar__panel">
|
||||
<nav class="sidebar__stack" aria-label="Mobiles Tenant-Menü">
|
||||
<?php foreach ($tenantNavItems as $item): ?>
|
||||
<a class="sidebar__link <?= (($item['key'] ?? '') === 'roles') ? 'active' : '' ?>" href="<?= tenant_roles_h((string) ($item['href'] ?? '/')) ?>" <?= (($item['key'] ?? '') === 'roles') ? 'aria-current="page"' : '' ?>><?= tenant_roles_h((string) ($item['label'] ?? 'Link')) ?></a>
|
||||
<?php endforeach; ?>
|
||||
</nav>
|
||||
<div class="sidebar__footer">
|
||||
<form method="post" action="/logout/"><button type="submit" class="button button--ghost">Abmelden</button></form>
|
||||
</div>
|
||||
</div>
|
||||
</details>
|
||||
</aside>
|
||||
</header>
|
||||
|
||||
<div class="content">
|
||||
<section class="hero">
|
||||
|
||||
@@ -1,176 +1,308 @@
|
||||
@extends('layouts.app')
|
||||
|
||||
@section('page_title', 'Die Kaffeeliste - Zentrale Plattform')
|
||||
@section('layout_mode', 'marketing')
|
||||
@section('page_title', 'Die Kaffeeliste')
|
||||
@section('body_class', 'landing-preview')
|
||||
|
||||
@php
|
||||
$overview = $tenantOverview ?? [
|
||||
'metrics' => [
|
||||
['label' => 'Aktive Tenants', 'value' => '4', 'detail' => 'Mandanten ueber die Plattform verteilt.'],
|
||||
['label' => 'Mitglieder gesamt', 'value' => '100', 'detail' => 'Aktive Nutzerkonten im Verbund.'],
|
||||
['label' => 'SSO-Abdeckung', 'value' => '4', 'detail' => 'Tenant-Logins mit zentraler Identitaetsstrategie.'],
|
||||
['label' => 'Betriebsstatus', 'value' => 'Stabil', 'detail' => 'Queues und Exporte fuer den Webspace-Betrieb vorbereitet.'],
|
||||
$columns = $landingColumns ?? [
|
||||
[
|
||||
'eyebrow' => 'Für Mitglieder',
|
||||
'title' => 'Schnell starten',
|
||||
'copy' => 'Einloggen und direkt weiter.',
|
||||
],
|
||||
[
|
||||
'eyebrow' => 'Für Verantwortliche',
|
||||
'title' => 'Alles im Blick',
|
||||
'copy' => 'Bereiche, Hinweise und Verwaltung an einem Ort.',
|
||||
],
|
||||
[
|
||||
'eyebrow' => 'Für Standorte',
|
||||
'title' => 'Gemeinsam organisiert',
|
||||
'copy' => 'Ein klarer Ablauf für Teams und Bereiche.',
|
||||
],
|
||||
'tenants' => [],
|
||||
];
|
||||
$preview = $centralLoginPreview ?? [
|
||||
'single' => ['email' => 'mia@berlin.example', 'matches' => [['name' => 'Werk Berlin', 'domain' => 'berlin.kaffeeliste.de']]],
|
||||
'multiple' => ['email' => 'leitung@kaffeeliste.example', 'matches' => [['name' => 'Werk Berlin', 'domain' => 'berlin.kaffeeliste.de'], ['name' => 'Werk Koeln', 'domain' => 'koeln.kaffeeliste.de']]],
|
||||
'unknown' => ['email' => 'extern@example.org', 'matches' => []],
|
||||
$preview = $landingPreview ?? [
|
||||
['label' => 'Start', 'value' => 'Klar'],
|
||||
['label' => 'Team', 'value' => 'Gemeinsam'],
|
||||
['label' => 'Zugang', 'value' => 'Direkt'],
|
||||
];
|
||||
@endphp
|
||||
|
||||
@section('page_styles')
|
||||
<style>
|
||||
body.landing-preview {
|
||||
background:
|
||||
radial-gradient(circle at top center, rgba(88, 122, 255, 0.22), transparent 28%),
|
||||
radial-gradient(circle at 20% 20%, rgba(42, 72, 170, 0.18), transparent 24%),
|
||||
linear-gradient(180deg, #05070d 0%, #0a0f1a 52%, #0d1320 100%);
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
body.landing-preview h1,
|
||||
body.landing-preview h2,
|
||||
body.landing-preview h3 {
|
||||
font-family: "Inter Tight", "Aptos", "Segoe UI", sans-serif;
|
||||
letter-spacing: -0.04em;
|
||||
}
|
||||
|
||||
.landing-wrap {
|
||||
display: grid;
|
||||
gap: 56px;
|
||||
padding: 8px 0 12px;
|
||||
}
|
||||
|
||||
.hero {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1.08fr) minmax(280px, 0.92fr);
|
||||
gap: 40px;
|
||||
align-items: start;
|
||||
padding-top: 12px;
|
||||
}
|
||||
|
||||
.kicker {
|
||||
margin: 0 0 10px;
|
||||
font-size: 0.78rem;
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 0.16em;
|
||||
color: #8aa0d0;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero h1 {
|
||||
margin: 0;
|
||||
font-size: clamp(3.2rem, 7vw, 6.4rem);
|
||||
line-height: 0.9;
|
||||
}
|
||||
|
||||
.hero p {
|
||||
margin: 14px 0 0;
|
||||
max-width: 30rem;
|
||||
font-size: 1.05rem;
|
||||
line-height: 1.65;
|
||||
color: rgba(219, 228, 248, 0.72);
|
||||
}
|
||||
|
||||
.actions {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
gap: 12px;
|
||||
margin-top: 24px;
|
||||
}
|
||||
|
||||
.landing-preview,
|
||||
.landing-callout {
|
||||
border: 1px solid rgba(163, 183, 255, 0.12);
|
||||
border-radius: 20px;
|
||||
background: rgba(14, 20, 34, 0.86);
|
||||
box-shadow: 0 16px 40px rgba(2, 5, 12, 0.34);
|
||||
}
|
||||
|
||||
.landing-preview {
|
||||
padding: 20px;
|
||||
display: grid;
|
||||
gap: 12px;
|
||||
}
|
||||
|
||||
.landing-preview__header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid rgba(163, 183, 255, 0.12);
|
||||
}
|
||||
|
||||
.landing-preview__title {
|
||||
margin: 0;
|
||||
font-size: 1.1rem;
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.landing-preview__line {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 12px 14px;
|
||||
border-radius: 16px;
|
||||
background: rgba(255, 255, 255, 0.04);
|
||||
color: rgba(216, 225, 246, 0.72);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.landing-preview__line strong {
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.landing-columns {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, minmax(0, 1fr));
|
||||
gap: 0;
|
||||
border-top: 1px solid rgba(163, 183, 255, 0.12);
|
||||
border-bottom: 1px solid rgba(163, 183, 255, 0.12);
|
||||
}
|
||||
|
||||
.landing-column {
|
||||
padding: 24px 18px;
|
||||
}
|
||||
|
||||
.landing-column + .landing-column {
|
||||
border-left: 1px solid rgba(163, 183, 255, 0.12);
|
||||
}
|
||||
|
||||
.landing-column__eyebrow {
|
||||
margin: 0 0 10px;
|
||||
color: #8aa0d0;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.landing-column h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 1.45rem;
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.landing-column p {
|
||||
margin: 0;
|
||||
max-width: 22rem;
|
||||
color: rgba(219, 228, 248, 0.72);
|
||||
}
|
||||
|
||||
.landing-callout {
|
||||
padding: 24px 26px;
|
||||
}
|
||||
|
||||
.landing-callout__eyebrow {
|
||||
margin: 0 0 8px;
|
||||
color: #8aa0d0;
|
||||
font-size: 0.78rem;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0.14em;
|
||||
text-transform: uppercase;
|
||||
}
|
||||
|
||||
.landing-callout h2 {
|
||||
margin: 0 0 10px;
|
||||
font-size: 2rem;
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.landing-callout p {
|
||||
margin: 0;
|
||||
max-width: 36rem;
|
||||
color: rgba(217, 226, 246, 0.8);
|
||||
}
|
||||
|
||||
.landing-cta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
padding-top: 20px;
|
||||
margin-top: 22px;
|
||||
border-top: 1px solid rgba(163, 183, 255, 0.12);
|
||||
}
|
||||
|
||||
.landing-cta__copy {
|
||||
display: grid;
|
||||
gap: 6px;
|
||||
}
|
||||
|
||||
.landing-cta__copy strong {
|
||||
font-size: 1.05rem;
|
||||
color: #f4f7ff;
|
||||
}
|
||||
|
||||
.landing-cta__copy span {
|
||||
color: rgba(217, 226, 246, 0.72);
|
||||
}
|
||||
|
||||
@media (max-width: 960px) {
|
||||
.landing-wrap {
|
||||
gap: 36px;
|
||||
}
|
||||
|
||||
.hero,
|
||||
.landing-columns {
|
||||
grid-template-columns: 1fr;
|
||||
}
|
||||
|
||||
.hero {
|
||||
gap: 24px;
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
.landing-column {
|
||||
padding: 20px 0;
|
||||
}
|
||||
|
||||
.landing-column + .landing-column {
|
||||
border-left: 0;
|
||||
border-top: 1px solid rgba(163, 183, 255, 0.12);
|
||||
}
|
||||
|
||||
.landing-callout {
|
||||
padding: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@endsection
|
||||
|
||||
@section('content')
|
||||
<section class="hero hero--split">
|
||||
<div class="hero__content">
|
||||
<div class="landing-wrap">
|
||||
<section class="hero">
|
||||
<div>
|
||||
<p class="hero__kicker">Zentrale Plattform fuer Mitglieder und Verantwortliche</p>
|
||||
<h2 class="hero__title">Kaffeeliste verbindet zentrale Anmeldung, Tenant-Steuerung und den operativen Alltag in einer klaren SaaS-Oberflaeche.</h2>
|
||||
<p class="hero__lead">
|
||||
Endanwender finden ihren Kontostand, Striche, Einzahlungen und Hinweise ohne Tenant-Chaos. Gleichzeitig
|
||||
behalten Verantwortliche alle Mandanten, Domains, SSO-Pfade und Mehrfachzuordnungen in einer gemeinsamen
|
||||
Admin-Konsole im Blick.
|
||||
</p>
|
||||
</div>
|
||||
<div class="hero__actions">
|
||||
<a class="button" href="/login">Zentrale Anmeldung starten</a>
|
||||
<a class="button button--ghost" href="/tenants">Tenant Console ansehen</a>
|
||||
</div>
|
||||
<div class="hero__meta">
|
||||
<span class="badge badge--solid">Ein Login fuer alle Mitgliedschaften</span>
|
||||
<span class="badge">Automatische Tenant-Weiterleitung</span>
|
||||
<span class="badge">Mehrfachzuordnung mit Auswahlfluss</span>
|
||||
</div>
|
||||
</div>
|
||||
<p class="kicker">Die Kaffeeliste</p>
|
||||
<h1>Kaffee im Team. Klar organisiert.</h1>
|
||||
<p>Für Mitglieder, Verantwortliche und Standorte.</p>
|
||||
|
||||
<aside class="hero__aside">
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">{{ $overview['metrics'][0]['label'] }}</p>
|
||||
<div class="metric__value">{{ $overview['metrics'][0]['value'] }}</div>
|
||||
<p class="muted">{{ $overview['metrics'][0]['detail'] }}</p>
|
||||
</article>
|
||||
<article class="card metric metric--compact">
|
||||
<p class="metric__label">{{ $overview['metrics'][1]['label'] }}</p>
|
||||
<div class="metric__value">{{ $overview['metrics'][1]['value'] }}</div>
|
||||
<p class="muted">{{ $overview['metrics'][1]['detail'] }}</p>
|
||||
</article>
|
||||
<div class="callout">
|
||||
<strong>Zentraler Login statt Inseln</strong>
|
||||
Mitglieder geben zuerst nur ihre E-Mail-Adresse an. Danach entscheidet die Plattform, ob direkt in einen Tenant
|
||||
weitergeleitet wird oder ob zuerst eine Tenant-Auswahl erscheinen muss.
|
||||
</div>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--3">
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Fuer Mitglieder</p>
|
||||
<h3>Schneller Einstieg ohne Tenant-Raten</h3>
|
||||
<p class="muted">
|
||||
Eine zentrale Anmeldung nimmt die Mail-Adresse entgegen und fuehrt danach automatisch in den passenden Bereich,
|
||||
statt Nutzer auf Subdomains oder technische Tenant-Keys zu verweisen.
|
||||
</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Fuer Verantwortliche</p>
|
||||
<h3>Alle Tenants in einer Admin-Console</h3>
|
||||
<p class="muted">
|
||||
Domains, SSO-Abdeckung, Rollout-Status und Mehrfachzuordnungen lassen sich tenantuebergreifend steuern und
|
||||
priorisieren.
|
||||
</p>
|
||||
</article>
|
||||
<article class="card">
|
||||
<p class="card__eyebrow">Fuer den Betrieb</p>
|
||||
<h3>Webspace-tauglich und tenantbewusst</h3>
|
||||
<p class="muted">
|
||||
Die Produktflaechen bleiben leichtgewichtig, dokumentiert und anschlussfaehig fuer Cron, Importe, Exporte und
|
||||
spaetere Identity-Ausbaustufen.
|
||||
</p>
|
||||
</article>
|
||||
</section>
|
||||
|
||||
<section class="split" style="margin-top: 18px;">
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Login-Fluss</p>
|
||||
<h3>So funktioniert die zentrale Anmeldung fuer Mitglieder.</h3>
|
||||
<div class="feature-list" style="margin-top: 18px;">
|
||||
<div class="feature-list__item">
|
||||
<div class="feature-list__badge">1</div>
|
||||
<div>
|
||||
<p class="feature-list__title">E-Mail zuerst</p>
|
||||
<p class="feature-list__copy">Die Plattform prueft zentral, in welchen Tenants die Mail-Adresse hinterlegt ist.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-list__item">
|
||||
<div class="feature-list__badge">2</div>
|
||||
<div>
|
||||
<p class="feature-list__title">Automatische Entscheidung</p>
|
||||
<p class="feature-list__copy">Bei genau einer Mitgliedschaft erfolgt die Weiterleitung direkt in den korrekten Tenant.</p>
|
||||
</div>
|
||||
</div>
|
||||
<div class="feature-list__item">
|
||||
<div class="feature-list__badge">3</div>
|
||||
<div>
|
||||
<p class="feature-list__title">Auswahl bei Mehrfachzuordnung</p>
|
||||
<p class="feature-list__copy">Bei mehreren Tenants erscheint zuerst eine Auswahl, damit Mitglieder bewusst den richtigen Kontext waehlen.</p>
|
||||
</div>
|
||||
<div class="actions">
|
||||
<a class="button" href="/login/">Anmelden</a>
|
||||
<a class="button button--ghost" href="#bereiche">Mehr erfahren</a>
|
||||
</div>
|
||||
</div>
|
||||
</article>
|
||||
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Mehrfachzuordnung</p>
|
||||
<h3>Beispiel fuer eine zentrale Tenant-Auswahl.</h3>
|
||||
<p class="muted">
|
||||
Fuer <strong>{{ $preview['multiple']['email'] }}</strong> werden mehrere Mitgliedschaften erkannt. Statt Fehlleitungen
|
||||
oder separater Logins zeigt die Plattform alle erreichbaren Tenants in einer klaren Auswahl.
|
||||
</p>
|
||||
<div class="tenant-grid" style="margin-top: 18px;">
|
||||
@foreach ($preview['multiple']['matches'] as $tenant)
|
||||
<div class="tenant-row">
|
||||
<div class="tenant-row__meta">
|
||||
<p class="tenant-row__title">{{ $tenant['name'] }}</p>
|
||||
<p class="tenant-row__copy">{{ $tenant['domain'] }}</p>
|
||||
</div>
|
||||
<span class="status">Auswaehlbar</span>
|
||||
<aside class="landing-preview" aria-label="Vorschau">
|
||||
<div class="landing-preview__header">
|
||||
<h2 class="landing-preview__title">Übersicht</h2>
|
||||
<span>Heute</span>
|
||||
</div>
|
||||
@foreach ($preview as $item)
|
||||
<div class="landing-preview__line">
|
||||
<span>{{ $item['label'] ?? '' }}</span>
|
||||
<strong>{{ $item['value'] ?? '' }}</strong>
|
||||
</div>
|
||||
@endforeach
|
||||
</div>
|
||||
<div class="note" style="margin-top: 18px;">
|
||||
Genau diese Logik wird spaeter im produktiven Login verwendet: kein technischer Tenant-Schritt fuer Mitglieder,
|
||||
aber trotzdem ein sauberer Kontext fuer jede Organisation.
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</aside>
|
||||
</section>
|
||||
|
||||
<section class="grid grid--2" style="margin-top: 18px;">
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Endanwender-Nutzen</p>
|
||||
<h3>Was Mitglieder auf der Plattform erwarten duerfen.</h3>
|
||||
<ul class="list-reset" style="margin-top: 18px;">
|
||||
<li><span class="status">Kontostand</span> Ein klares Dashboard mit Verbrauch, Zahlungen und letzten Buchungen.</li>
|
||||
<li><span class="status">Hinweise</span> Tenantbezogene Inhalte, FAQ und operative Informationen an einem Ort.</li>
|
||||
<li><span class="status">Mitgliedschaften</span> Ein zentraler Einstieg auch fuer Personen mit mehreren Teams oder Standorten.</li>
|
||||
<li><span class="status">Self-Service</span> Direkte Wege zu Ledger, Zahlungen und spaeterem Passwort-/SSO-Fallback.</li>
|
||||
</ul>
|
||||
</article>
|
||||
<section class="landing-columns" id="bereiche" aria-label="Bereiche">
|
||||
@foreach ($columns as $column)
|
||||
<article class="landing-column">
|
||||
<p class="landing-column__eyebrow">{{ $column['eyebrow'] ?? '' }}</p>
|
||||
<h2>{{ $column['title'] ?? '' }}</h2>
|
||||
<p>{{ $column['copy'] ?? '' }}</p>
|
||||
</article>
|
||||
@endforeach
|
||||
</section>
|
||||
|
||||
<article class="panel">
|
||||
<p class="card__eyebrow">Admin-Perspektive</p>
|
||||
<h3>Was die zentrale Tenant Console sichtbar macht.</h3>
|
||||
<div class="timeline timeline--tight" style="margin-top: 18px;">
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Tenant-Portfolio</p>
|
||||
<p class="timeline__meta">Mitgliederzahlen, Betriebsstatus und Login-Modell aller Mandanten in einer Sicht.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Identity-Steuerung</p>
|
||||
<p class="timeline__meta">SSO-Abdeckung, Fallback-Logins und tenantbezogene Einstiegspfade konsistent halten.</p>
|
||||
</div>
|
||||
<div class="timeline__item">
|
||||
<p class="timeline__title">Rollout & Betrieb</p>
|
||||
<p class="timeline__meta">Onboarding, Migrationen und operative To-dos nicht pro Tenant verstreut, sondern zentral priorisiert.</p>
|
||||
<section class="landing-callout">
|
||||
<p class="landing-callout__eyebrow">Einfach im Alltag</p>
|
||||
<h2>Ein gemeinsamer Ort für die Kaffeeliste.</h2>
|
||||
<p>Weniger suchen. Weniger erklären. Klar durch den Tag.</p>
|
||||
|
||||
<div class="landing-cta">
|
||||
<div class="landing-cta__copy">
|
||||
<strong>Bereit für den Start?</strong>
|
||||
<span>Direkt zur Anmeldung.</span>
|
||||
</div>
|
||||
<a class="button" href="/login/">Anmelden</a>
|
||||
</div>
|
||||
</article>
|
||||
</section>
|
||||
</section>
|
||||
</div>
|
||||
@endsection
|
||||
|
||||
Reference in New Issue
Block a user