Ui
All checks were successful
Deploy / deploy-staging (push) Successful in 8s
Deploy / deploy-production (push) Has been skipped

This commit is contained in:
2026-06-08 00:36:27 +02:00
parent a8b197592b
commit b23e390952
11 changed files with 336 additions and 17 deletions

View File

@@ -7,6 +7,17 @@ $activeSkin = $desktopPayload['meta']['active_skin'];
$baseSkinProfile = $desktopPayload['meta']['base_skin_profile'];
$skinProfile = $desktopPayload['meta']['skin_profile'];
$wallpaper = $desktopPayload['desktop']['wallpaper'];
$renderStartIcon = static function (array $profile): string {
$style = (string) ($profile['taskbar_icon_style'] ?? 'label');
return match ($style) {
'windows' => '<span class="start-glyph start-glyph-windows" aria-hidden="true"><span></span><span></span><span></span><span></span></span>',
'apple' => '<span class="start-glyph start-glyph-apple" aria-hidden="true"></span>',
'linux' => '<span class="start-glyph start-glyph-linux" aria-hidden="true"><span></span><span></span><span></span></span>',
default => '<span class="start-glyph start-glyph-label" aria-hidden="true">' . htmlspecialchars((string) ($profile['taskbar_icon'] ?? 'OS'), ENT_QUOTES) . '</span>',
};
};
?><!DOCTYPE html>
<html lang="de">
<head>
@@ -25,6 +36,7 @@ $wallpaper = $desktopPayload['desktop']['wallpaper'];
data-skin="<?= htmlspecialchars($activeSkin, ENT_QUOTES) ?>"
data-skin-family="<?= htmlspecialchars((string) ($skinProfile['family'] ?? $activeSkin), ENT_QUOTES) ?>"
data-taskbar-mode="<?= htmlspecialchars((string) ($skinProfile['taskbar_mode'] ?? 'taskbar'), ENT_QUOTES) ?>"
data-window-controls="<?= htmlspecialchars((string) ($skinProfile['window_controls'] ?? 'windows'), ENT_QUOTES) ?>"
style="--wallpaper: <?= htmlspecialchars($wallpaper, ENT_QUOTES) ?>;"
>
<div class="desktop-shell">
@@ -92,7 +104,7 @@ $wallpaper = $desktopPayload['desktop']['wallpaper'];
<footer class="taskbar">
<button class="start-button" id="start-button" type="button">
<span class="start-button-icon"><?= htmlspecialchars((string) ($skinProfile['taskbar_icon'] ?? ''), ENT_QUOTES) ?></span>
<span class="start-button-icon"><?= $renderStartIcon($skinProfile) ?></span>
<span class="start-button-label"><?= htmlspecialchars((string) ($skinProfile['taskbar_label'] ?? 'Menu'), ENT_QUOTES) ?></span>
</button>
<div class="taskbar-apps" id="taskbar-apps"></div>

View File

@@ -293,12 +293,29 @@ h1 {
cursor: move;
}
.window-title {
display: inline-flex;
align-items: center;
min-width: 0;
font-size: 14px;
font-weight: 700;
letter-spacing: 0.01em;
}
.window-header-spacer {
width: 64px;
flex: 0 0 64px;
}
.window-actions {
display: flex;
gap: 8px;
align-items: center;
}
.window-action {
display: inline-grid;
place-items: center;
width: 12px;
height: 12px;
padding: 0;
@@ -308,6 +325,11 @@ h1 {
cursor: pointer;
}
.window-action span {
font-size: 10px;
line-height: 1;
}
.window-action[data-action="close"] {
background: #ef4444;
}
@@ -362,9 +384,12 @@ h1 {
}
.start-button-icon {
font-size: 11px;
font-weight: 700;
opacity: 0.8;
display: inline-flex;
align-items: center;
justify-content: center;
width: 18px;
height: 18px;
flex: 0 0 18px;
}
.taskbar-app-button.is-focused {
@@ -376,6 +401,71 @@ h1 {
opacity: 0.62;
}
.start-glyph {
display: inline-grid;
place-items: center;
}
.start-glyph-windows {
grid-template-columns: repeat(2, 1fr);
gap: 2px;
width: 16px;
height: 16px;
}
.start-glyph-windows span {
width: 7px;
height: 7px;
background: currentColor;
border-radius: 1px;
}
.start-glyph-apple {
position: relative;
width: 14px;
height: 16px;
}
.start-glyph-apple::before,
.start-glyph-apple::after {
content: "";
position: absolute;
background: currentColor;
}
.start-glyph-apple::before {
inset: 3px 1px 0;
border-radius: 48% 48% 42% 42% / 42% 42% 58% 58%;
}
.start-glyph-apple::after {
top: 0;
right: 3px;
width: 5px;
height: 5px;
border-radius: 0 100% 0 100%;
transform: rotate(-24deg);
}
.start-glyph-linux {
gap: 2px;
width: 14px;
}
.start-glyph-linux span {
display: block;
width: 14px;
height: 3px;
border-radius: 999px;
background: currentColor;
}
.start-glyph-label {
font-size: 10px;
font-weight: 700;
letter-spacing: 0.04em;
}
.start-menu {
position: fixed;
left: 24px;
@@ -470,6 +560,29 @@ h1 {
overflow: auto;
}
.taskbar-app-button {
display: inline-flex;
align-items: center;
gap: 10px;
}
.taskbar-app-button-badge {
display: inline-grid;
place-items: center;
width: 26px;
height: 26px;
border-radius: 8px;
background: rgba(15, 23, 42, 0.22);
font-size: 11px;
font-weight: 700;
line-height: 1;
}
.taskbar-app-button-label {
display: inline-block;
white-space: nowrap;
}
.tray {
display: flex;
align-items: center;

View File

@@ -26,6 +26,62 @@ if (payloadNode) {
let topDesktopInset = 0;
let sideDesktopInset = 0;
let bottomDesktopInset = 120;
const windowControlsMode = document.body.dataset.windowControls || 'windows';
const escapeHtml = (value) => String(value)
.replaceAll('&', '&amp;')
.replaceAll('<', '&lt;')
.replaceAll('>', '&gt;')
.replaceAll('"', '&quot;')
.replaceAll("'", '&#39;');
const buildTaskbarButtonContent = (record) => `
<span class="taskbar-app-button-badge">${escapeHtml(record.definition.icon || 'AP')}</span>
<span class="taskbar-app-button-label">${escapeHtml(record.title)}</span>
`;
const buildWindowActions = () => {
if (windowControlsMode === 'mac') {
return `
<div class="window-actions window-actions--mac">
<button class="window-action" data-action="close" type="button" aria-label="Schliessen"></button>
<button class="window-action" data-action="minimize" type="button" aria-label="Minimieren"></button>
<button class="window-action" data-action="maximize" type="button" aria-label="Maximieren"></button>
</div>
`;
}
const maximizeGlyph = windowControlsMode === 'linux' ? '▢' : '□';
return `
<div class="window-actions window-actions--${windowControlsMode}">
<button class="window-action" data-action="minimize" type="button" aria-label="Minimieren"><span>-</span></button>
<button class="window-action" data-action="maximize" type="button" aria-label="Maximieren"><span>${maximizeGlyph}</span></button>
<button class="window-action" data-action="close" type="button" aria-label="Schliessen"><span>&times;</span></button>
</div>
`;
};
const buildWindowHeader = (definition) => {
const title = escapeHtml(definition.title);
if (windowControlsMode === 'mac') {
return `
<header class="window-header window-header--mac">
${buildWindowActions()}
<strong class="window-title">${title}</strong>
<span class="window-header-spacer" aria-hidden="true"></span>
</header>
`;
}
return `
<header class="window-header window-header--${windowControlsMode}">
<strong class="window-title">${title}</strong>
${buildWindowActions()}
</header>
`;
};
const loadWindowState = () => {
try {
@@ -247,7 +303,7 @@ if (payloadNode) {
} else if (record.node.classList.contains('is-focused')) {
button.classList.add('is-focused');
}
button.textContent = record.title;
button.innerHTML = buildTaskbarButtonContent(record);
button.addEventListener('click', () => {
if (record.state.minimized) {
toggleMinimize(record.id);
@@ -380,18 +436,12 @@ if (payloadNode) {
const createWindow = (definition) => {
const node = document.createElement('article');
node.className = 'window';
node.dataset.windowControls = windowControlsMode;
node.innerHTML = `
<header class="window-header">
<strong>${definition.title}</strong>
<div class="window-actions">
<button class="window-action" data-action="minimize" type="button" aria-label="Minimieren"></button>
<button class="window-action" data-action="maximize" type="button" aria-label="Maximieren"></button>
<button class="window-action" data-action="close" type="button" aria-label="Schliessen"></button>
</div>
</header>
${buildWindowHeader(definition)}
<div class="window-content">
<p>${definition.content}</p>
<p><strong>Route:</strong> ${definition.app_id}</p>
<p>${escapeHtml(definition.content)}</p>
<p><strong>Route:</strong> ${escapeHtml(definition.app_id)}</p>
</div>
<div class="window-resize-handle" data-edge="top" aria-hidden="true"></div>
<div class="window-resize-handle" data-edge="right" aria-hidden="true"></div>

View File

@@ -11,6 +11,7 @@ return [
'taskbar_mode' => 'dock',
'taskbar_label' => 'Launchpad',
'taskbar_icon' => 'Mac',
'taskbar_icon_style' => 'apple',
'window_controls' => 'mac',
'maximize_over_system_bar' => true,
];

View File

@@ -48,3 +48,51 @@ body[data-skin="apple"] .start-menu {
bottom: 106px;
transform: translateX(-50%);
}
body[data-skin="apple"] .window {
border-radius: 14px;
border: 1px solid rgba(255, 255, 255, 0.58);
box-shadow: 0 28px 70px rgba(17, 24, 39, 0.24);
}
body[data-skin="apple"] .window-header {
position: relative;
min-height: 42px;
justify-content: flex-start;
gap: 14px;
padding: 10px 14px;
background: linear-gradient(180deg, #fff7eb, #fde9c9);
}
body[data-skin="apple"] .window-title {
position: absolute;
left: 50%;
transform: translateX(-50%);
font-size: 13px;
color: #4b5563;
}
body[data-skin="apple"] .window-action {
width: 12px;
height: 12px;
}
body[data-skin="apple"] .window-action span {
display: none;
}
body[data-skin="apple"] .taskbar-app-button {
min-width: 56px;
padding: 10px 12px;
justify-content: center;
}
body[data-skin="apple"] .taskbar-app-button-label {
display: none;
}
body[data-skin="apple"] .taskbar-app-button-badge {
width: 30px;
height: 30px;
border-radius: 10px;
}

View File

@@ -11,6 +11,7 @@ return [
'taskbar_mode' => 'taskbar',
'taskbar_label' => 'Menu',
'taskbar_icon' => 'OS',
'taskbar_icon_style' => 'label',
'window_controls' => 'windows',
'maximize_over_system_bar' => false,
];

View File

@@ -11,6 +11,7 @@ return [
'taskbar_mode' => 'panel',
'taskbar_label' => 'Menu',
'taskbar_icon' => 'Mint',
'taskbar_icon_style' => 'linux',
'window_controls' => 'linux',
'maximize_over_system_bar' => false,
];

View File

@@ -31,3 +31,46 @@ body[data-skin="linux"] .start-menu {
bottom: 58px;
border-radius: 18px 18px 0 0;
}
body[data-skin="linux"] .window {
border-radius: 8px 8px 0 0;
border: 1px solid rgba(71, 85, 105, 0.42);
}
body[data-skin="linux"] .window-header {
min-height: 34px;
padding: 7px 8px 7px 12px;
background: linear-gradient(180deg, #eef1f4, #dce2e8);
}
body[data-skin="linux"] .window-title {
font-size: 13px;
color: #334155;
}
body[data-skin="linux"] .window-actions {
gap: 4px;
}
body[data-skin="linux"] .window-action {
width: 26px;
height: 22px;
border-radius: 4px;
background: linear-gradient(180deg, #f9fafb, #dbe4ea);
border: 1px solid rgba(100, 116, 139, 0.45);
color: #334155;
}
body[data-skin="linux"] .window-action[data-action="close"] {
background: linear-gradient(180deg, #fca5a5, #ef4444);
color: #fff;
}
body[data-skin="linux"] .window-action span {
font-size: 11px;
}
body[data-skin="linux"] .taskbar-app-button {
border-radius: 6px;
min-width: 136px;
}

View File

@@ -11,6 +11,7 @@ return [
'taskbar_mode' => 'taskbar',
'taskbar_label' => 'Start',
'taskbar_icon' => 'Win',
'taskbar_icon_style' => 'windows',
'window_controls' => 'windows',
'maximize_over_system_bar' => false,
];

View File

@@ -26,8 +26,8 @@ body[data-skin="windows"] .taskbar {
body[data-skin="windows"] .start-button {
min-width: 94px;
justify-content: center;
border-radius: 8px;
background: rgba(255, 255, 255, 0.14);
border-radius: 6px;
background: linear-gradient(180deg, rgba(76, 144, 255, 0.4), rgba(35, 86, 171, 0.4));
}
body[data-skin="windows"] .start-menu {
@@ -35,3 +35,50 @@ body[data-skin="windows"] .start-menu {
bottom: 62px;
border-radius: 14px 14px 0 0;
}
body[data-skin="windows"] .window {
border-radius: 10px 10px 0 0;
border: 1px solid rgba(69, 120, 194, 0.45);
}
body[data-skin="windows"] .window-header {
min-height: 38px;
padding: 8px 8px 8px 14px;
background: linear-gradient(180deg, #eff7ff, #dcecff 54%, #cae0fb);
}
body[data-skin="windows"] .window-title {
color: #12345b;
}
body[data-skin="windows"] .window-actions {
gap: 2px;
}
body[data-skin="windows"] .window-action {
width: 42px;
height: 28px;
border-radius: 0;
background: transparent;
color: #12345b;
}
body[data-skin="windows"] .window-action:hover {
background: rgba(29, 78, 216, 0.12);
}
body[data-skin="windows"] .window-action[data-action="close"]:hover {
background: #dc2626;
color: #fff;
}
body[data-skin="windows"] .window-action span {
font-size: 13px;
font-weight: 700;
}
body[data-skin="windows"] .taskbar-app-button {
border-radius: 6px;
min-width: 148px;
background: rgba(255, 255, 255, 0.14);
}

View File

@@ -66,6 +66,7 @@ final class SkinResolver
'taskbar_mode' => 'taskbar',
'taskbar_label' => 'Menu',
'taskbar_icon' => strtoupper(substr($skinId, 0, 3)),
'taskbar_icon_style' => 'label',
'window_controls' => 'windows',
'maximize_over_system_bar' => false,
],
@@ -85,6 +86,7 @@ final class SkinResolver
'taskbar_mode' => 'taskbar',
'taskbar_label' => 'Menu',
'taskbar_icon' => 'OS',
'taskbar_icon_style' => 'label',
'window_controls' => 'windows',
'maximize_over_system_bar' => false,
],