diff --git a/app/resources/css/app.css b/app/resources/css/app.css
index bd05ae6..1cdcfdc 100644
--- a/app/resources/css/app.css
+++ b/app/resources/css/app.css
@@ -634,6 +634,48 @@
}
.clu-top-link a { color: var(--color-accent); font-weight: 600; }
+ /* Workspace switcher dropdown */
+ .clu-ws-dropdown {
+ position: absolute;
+ top: 100%; left: 0; right: 0;
+ margin-top: 6px;
+ background: rgba(255,255,255,0.98);
+ border: 1px solid var(--color-glass-border);
+ border-radius: var(--radius-sm);
+ box-shadow: 0 18px 50px -8px rgba(40,52,92,0.35), 0 4px 12px -4px rgba(40,52,92,0.15);
+ backdrop-filter: blur(28px) saturate(170%);
+ -webkit-backdrop-filter: blur(28px) saturate(170%);
+ z-index: 60;
+ padding: 6px;
+ max-height: 480px;
+ overflow-y: auto;
+ }
+ [x-cloak] { display: none !important; }
+ .clu-ws-section {
+ font-size: 10px;
+ text-transform: uppercase;
+ letter-spacing: 0.09em;
+ color: var(--color-muted-2);
+ font-weight: 600;
+ padding: 10px 10px 4px;
+ }
+ .clu-ws-item {
+ display: flex; align-items: center; gap: 9px;
+ padding: 7px 10px;
+ border-radius: var(--radius-xs);
+ color: var(--color-fg);
+ transition: background .12s;
+ text-decoration: none !important;
+ cursor: pointer;
+ }
+ .clu-ws-item:hover { background: rgba(255,255,255,0.7); }
+ .clu-ws-item.active { background: var(--color-accent-soft); }
+ .clu-ws-divider {
+ height: 1px;
+ background: var(--color-hairline);
+ margin: 6px 4px;
+ }
+
/* ───── App layout (sidebar fixed + main offset by padding) ───── */
.clu-app-grid {
position: relative;
diff --git a/app/resources/views/components/clu/sidebar.blade.php b/app/resources/views/components/clu/sidebar.blade.php
index dcdede9..51b52df 100644
--- a/app/resources/views/components/clu/sidebar.blade.php
+++ b/app/resources/views/components/clu/sidebar.blade.php
@@ -1,8 +1,33 @@
@props(['active' => null])
@php
- $ws = auth()->user()?->currentWorkspace;
- $wsInitials = $ws ? mb_strtoupper(mb_substr(preg_replace('/[^A-Za-z\s]/', '', $ws->name), 0, 2)) : '—';
+ $user = auth()->user();
+ $ws = $user?->currentWorkspace;
+ $wsInitials = $ws ? mb_strtoupper(mb_substr(preg_replace('/[^A-Za-z\s]/', '', $ws->name), 0, 2)) : '—';
+ $userInit = $user ? mb_strtoupper(mb_substr($user->name, 0, 1) . mb_substr(explode(' ', $user->name)[1] ?? '', 0, 1)) : '?';
+
+ // All workspaces the user can access: owned + via TeamMember.
+ $workspaces = collect();
+ if ($user) {
+ $workspaces = $workspaces->merge($user->ownedWorkspaces);
+ if (method_exists($user, 'teamMemberships')) {
+ $workspaces = $workspaces->merge($user->teamMemberships->pluck('workspace'));
+ } else {
+ // Fallback via TeamMember model
+ $memberWsIds = \App\Models\TeamMember::where('user_id', $user->id)->pluck('workspace_id');
+ $workspaces = $workspaces->merge(\App\Models\Workspace::whereIn('id', $memberWsIds)->get());
+ }
+ $workspaces = $workspaces->unique('id')->values();
+ // Fallback: at least the currently-active workspace.
+ if ($workspaces->isEmpty() && $ws) {
+ $workspaces = collect([$ws]);
+ }
+ }
+
+ $sitesUsed = $ws ? \App\Models\Site::count() : 0;
+ $sitesQuota = $ws?->quotas['sites'] ?? 0;
+ $planPct = $sitesQuota ? min(100, round($sitesUsed / $sitesQuota * 100)) : 0;
+
$items = [
['name' => 'dashboard', 'label' => 'Dashboard', 'route' => 'dashboard',
'svg' => '