diff --git a/partials/desktop/shell.php b/partials/desktop/shell.php
index f85bd459..9308c914 100644
--- a/partials/desktop/shell.php
+++ b/partials/desktop/shell.php
@@ -9,6 +9,7 @@ $skinProfile = $desktopPayload['meta']['skin_profile'];
$wallpaper = $desktopPayload['desktop']['wallpaper'];
$debugShellApp = is_array($desktopPayload['shell']['debug_app'] ?? null) ? $desktopPayload['shell']['debug_app'] : null;
$debugShellLabel = trim((string) ($debugShellApp['label'] ?? 'Debug')) ?: 'Debug';
+$sessionDisplayName = (string) ($desktopPayload['session']['display_name'] ?? 'Gast');
$assetVersion = static function (string $publicPath): string {
$filesystemPath = dirname(__DIR__, 2) . '/public' . $publicPath;
@@ -56,6 +57,42 @@ $renderStartIcon = static function (array $profile): string {
default => '' . htmlspecialchars((string) ($profile['taskbar_icon'] ?? 'OS'), ENT_QUOTES) . '',
};
};
+$buildInitials = static function (string $value): string {
+ $normalized = trim($value);
+
+ if ($normalized === '') {
+ return 'GU';
+ }
+
+ $parts = preg_split('/\s+/', $normalized) ?: [];
+ $parts = array_values(array_filter($parts, static fn ($part) => trim((string) $part) !== ''));
+
+ if (count($parts) <= 1) {
+ return strtoupper(substr((string) ($parts[0] ?? $normalized), 0, 2));
+ }
+
+ return strtoupper(substr((string) $parts[0], 0, 1) . substr((string) $parts[1], 0, 1));
+};
+$renderTrayVisual = static function (array $trayItem, string $displayName = '') use ($buildInitials): string {
+ $trayId = (string) ($trayItem['id'] ?? '');
+ $appId = (string) ($trayItem['app_id'] ?? '');
+
+ if ($trayId === 'account') {
+ $initials = htmlspecialchars($buildInitials($displayName), ENT_QUOTES);
+
+ return '' . $initials . 'Profil';
+ }
+
+ if ($appId === 'fx-rates') {
+ return 'FX';
+ }
+
+ if ($trayId === 'gitea-deploy-status') {
+ return 'Gitea Deploy Status';
+ }
+
+ return htmlspecialchars((string) ($trayItem['label'] ?? ''), ENT_QUOTES);
+};
?>
@@ -90,7 +127,7 @@ $renderStartIcon = static function (array $profile): string {
-
+
@@ -161,15 +198,15 @@ $renderStartIcon = static function (array $profile): string {
+ title="= htmlspecialchars($sessionDisplayName, ENT_QUOTES) ?>"title="= htmlspecialchars((string) $trayItem['title'], ENT_QUOTES) ?>"
+ >= $renderTrayVisual($trayItem, $sessionDisplayName) ?>
-
+
diff --git a/public/assets/desktop/desktop.css b/public/assets/desktop/desktop.css
index 6cfa01f7..4204c091 100644
--- a/public/assets/desktop/desktop.css
+++ b/public/assets/desktop/desktop.css
@@ -1383,7 +1383,22 @@ h1 {
font-size: 13px;
}
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
.tray-pill-button {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
border: 0;
color: inherit;
font: inherit;
@@ -1400,27 +1415,72 @@ h1 {
box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.18);
}
+.tray-pill-account {
+ min-width: 40px;
+ padding: 6px;
+}
+
+.tray-avatar {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 28px;
+ height: 28px;
+ border-radius: 999px;
+ background: rgba(255, 255, 255, 0.16);
+ font-size: 11px;
+ font-weight: 700;
+ letter-spacing: 0.04em;
+}
+
+.tray-icon {
+ display: inline-flex;
+ align-items: center;
+ justify-content: center;
+ width: 18px;
+ height: 18px;
+}
+
+.tray-icon svg {
+ width: 18px;
+ height: 18px;
+ fill: none;
+ stroke: currentColor;
+ stroke-width: 1.75;
+ stroke-linecap: round;
+ stroke-linejoin: round;
+}
+
.tray-pill-status {
min-width: 34px;
- padding: 8px 9px;
+ padding: 8px;
text-align: center;
- font-size: 18px;
+ font-size: 16px;
line-height: 1;
}
-.tray-pill-status--idle {
+body .tray-pill.tray-pill-status--idle {
color: #22c55e;
}
-.tray-pill-status--running {
+body .tray-pill.tray-pill-status--running {
color: #facc15;
}
-.tray-pill-status--error,
-.tray-pill-status--unconfigured {
+body .tray-pill.tray-pill-status--error,
+body .tray-pill.tray-pill-status--unconfigured {
color: #f97316;
}
+.tray-status-dot {
+ display: inline-block;
+ width: 12px;
+ height: 12px;
+ border-radius: 999px;
+ background: currentColor;
+ box-shadow: 0 0 0 1px rgba(255, 255, 255, 0.14), 0 0 14px currentColor;
+}
+
.tray-menu {
position: absolute;
right: 16px;
diff --git a/public/assets/desktop/desktop.js b/public/assets/desktop/desktop.js
index 370d37b1..0a6aaf6b 100644
--- a/public/assets/desktop/desktop.js
+++ b/public/assets/desktop/desktop.js
@@ -177,9 +177,12 @@ if (payloadNode) {
node.setAttribute('aria-expanded', String(debugState.open === true));
node.classList.toggle('is-active', debugState.open === true);
- node.textContent = debugState.open === true
- ? `${String(debugShellApp?.label || 'Debug')} an`
- : String(debugShellApp?.label || 'Debug');
+ node.setAttribute(
+ 'title',
+ debugState.open === true
+ ? `${String(debugShellApp?.label || 'Debug')} an`
+ : String(debugShellApp?.label || 'Debug')
+ );
});
};
@@ -384,7 +387,12 @@ if (payloadNode) {
const displayName = currentUserPreferences?.profile?.name || payload.session?.display_name;
if (accountNode && typeof displayName === 'string' && displayName.trim() !== '') {
- accountNode.textContent = displayName;
+ accountNode.setAttribute('title', displayName);
+
+ const avatarNode = accountNode.querySelector('.tray-avatar');
+ if (avatarNode instanceof HTMLElement) {
+ avatarNode.textContent = buildInitials(displayName);
+ }
}
if (startMenuUserName && typeof displayName === 'string' && displayName.trim() !== '') {