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

This commit is contained in:
2026-06-07 00:51:49 +02:00
parent 08c0edc68b
commit e1d16d818d
8 changed files with 724 additions and 39 deletions

View File

@@ -181,6 +181,25 @@ h1 {
box-shadow: var(--shadow);
}
.widget-card-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 10px;
}
.widget-badge {
display: inline-grid;
place-items: center;
width: 34px;
height: 34px;
border-radius: 12px;
background: rgba(15, 23, 42, 0.58);
font-size: 12px;
font-weight: 700;
}
.window-layer {
position: absolute;
inset: 24px 24px 92px 24px;
@@ -272,6 +291,70 @@ h1 {
cursor: pointer;
}
.start-menu {
position: absolute;
left: 24px;
bottom: 102px;
z-index: 90;
display: grid;
grid-template-columns: minmax(240px, 280px) minmax(280px, 360px);
gap: 16px;
width: min(720px, calc(100% - 48px));
padding: 18px;
border-radius: 24px;
background: rgba(15, 23, 42, 0.82);
border: 1px solid var(--shell-border);
box-shadow: var(--shadow);
backdrop-filter: blur(24px);
}
.start-menu-column,
.start-menu-block {
display: grid;
gap: 12px;
}
.start-menu-entry {
display: grid;
grid-template-columns: 44px minmax(0, 1fr) auto;
align-items: center;
gap: 12px;
width: 100%;
padding: 12px 14px;
border: 1px solid rgba(255, 255, 255, 0.08);
border-radius: 18px;
background: rgba(255, 255, 255, 0.06);
color: inherit;
text-align: left;
cursor: pointer;
}
.start-menu-entry strong,
.start-menu-entry span {
display: block;
}
.start-menu-entry small {
opacity: 0.7;
}
.start-menu-entry-badge,
.start-menu-entry-toggle {
display: inline-grid;
place-items: center;
min-width: 32px;
height: 32px;
border-radius: 12px;
background: rgba(255, 255, 255, 0.1);
font-size: 12px;
font-weight: 700;
}
.start-menu-entry-toggle.is-active {
background: var(--accent);
color: #0f172a;
}
.taskbar-apps {
display: flex;
gap: 10px;
@@ -314,6 +397,15 @@ h1 {
margin-top: 24px;
}
.start-menu {
position: fixed;
left: 16px;
right: 16px;
bottom: 88px;
width: auto;
grid-template-columns: 1fr;
}
.window-layer {
position: static;
inset: auto;

View File

@@ -3,10 +3,17 @@ const payloadNode = document.getElementById('desktop-payload');
if (payloadNode) {
const payload = JSON.parse(payloadNode.textContent);
const iconsRoot = document.getElementById('desktop-icons');
const widgetZoneRoot = document.getElementById('widget-zone');
const windowsRoot = document.getElementById('window-layer');
const taskbarRoot = document.getElementById('taskbar-apps');
const startButton = document.getElementById('start-button');
const startMenu = document.getElementById('start-menu');
const startMenuApps = document.getElementById('start-menu-apps');
const startMenuWidgets = document.getElementById('start-menu-widgets');
const startMenuActions = document.getElementById('start-menu-actions');
const clockNode = document.getElementById('clock');
const windows = new Map();
const activeWidgets = new Set(payload.widgets.active_ids);
let zIndex = 20;
const renderClock = () => {
@@ -36,6 +43,15 @@ if (payloadNode) {
record.node.classList.add('is-focused');
};
const closeStartMenu = () => {
if (!startMenu) {
return;
}
startMenu.hidden = true;
startButton?.setAttribute('aria-expanded', 'false');
};
const syncTaskbar = () => {
taskbarRoot.innerHTML = '';
@@ -152,6 +168,153 @@ if (payloadNode) {
syncTaskbar();
};
const openApp = (app) => {
const existing = Array.from(windows.values()).find((record) => record.definition.app_id === app.app_id);
if (existing) {
existing.node.classList.remove('is-minimized');
focusWindow(existing.id);
return;
}
createWindow({
window_id: `window-${app.app_id}-${Date.now()}`,
app_id: app.app_id,
title: app.title,
x: 64 + Math.round(Math.random() * 80),
y: 70 + Math.round(Math.random() * 60),
width: app.default_width || 640,
height: app.default_height || 420,
content: app.summary || '',
});
};
const renderWidgets = () => {
widgetZoneRoot.innerHTML = '';
payload.widgets.registry
.filter((widget) => activeWidgets.has(widget.widget_id))
.forEach((widget) => {
const card = document.createElement('article');
card.className = 'widget-card';
card.innerHTML = `
<div class="widget-card-header">
<div>
<h3>${widget.title}</h3>
<p>${widget.summary || ''}</p>
</div>
<span class="widget-badge">${widget.icon || 'WG'}</span>
</div>
<p>${widget.content || ''}</p>
`;
widgetZoneRoot.appendChild(card);
});
};
const renderMenuWidgets = () => {
startMenuWidgets.innerHTML = '';
payload.widgets.registry.forEach((widget) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'start-menu-entry';
button.innerHTML = `
<span class="start-menu-entry-badge">${widget.icon || 'WG'}</span>
<span>
<strong>${widget.title}</strong>
<small>${widget.summary || ''}</small>
</span>
<span class="start-menu-entry-toggle ${activeWidgets.has(widget.widget_id) ? 'is-active' : ''}">
${activeWidgets.has(widget.widget_id) ? 'On' : 'Off'}
</span>
`;
button.addEventListener('click', () => {
if (activeWidgets.has(widget.widget_id)) {
activeWidgets.delete(widget.widget_id);
} else {
activeWidgets.add(widget.widget_id);
}
renderWidgets();
renderMenuWidgets();
});
startMenuWidgets.appendChild(button);
});
};
const runQuickAction = (actionId) => {
if (actionId === 'open-public-dashboard') {
const publicDashboard = payload.apps.find((app) => app.app_id === 'public-dashboard');
if (publicDashboard) {
openApp(publicDashboard);
}
closeStartMenu();
return;
}
if (actionId === 'toggle-all-widgets') {
const shouldShowAll = activeWidgets.size !== payload.widgets.registry.length;
activeWidgets.clear();
if (shouldShowAll) {
payload.widgets.registry.forEach((widget) => activeWidgets.add(widget.widget_id));
}
renderWidgets();
renderMenuWidgets();
return;
}
if (actionId === 'cycle-skin') {
const skins = payload.meta.available_skins;
const currentIndex = skins.indexOf(payload.meta.active_skin);
const nextSkin = skins[(currentIndex + 1) % skins.length];
window.location.search = `?skin=${encodeURIComponent(nextSkin)}`;
}
};
const renderStartMenu = () => {
startMenuApps.innerHTML = '';
startMenuActions.innerHTML = '';
payload.apps.forEach((app) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'start-menu-entry';
button.innerHTML = `
<span class="start-menu-entry-badge">${app.icon}</span>
<span>
<strong>${app.title}</strong>
<small>${app.summary || ''}</small>
</span>
<span class="start-menu-entry-toggle">Open</span>
`;
button.addEventListener('click', () => {
openApp(app);
closeStartMenu();
});
startMenuApps.appendChild(button);
});
payload.menu.quick_actions.forEach((action) => {
const button = document.createElement('button');
button.type = 'button';
button.className = 'start-menu-entry';
button.innerHTML = `
<span class="start-menu-entry-badge">QA</span>
<span>
<strong>${action.label}</strong>
<small>${action.description}</small>
</span>
<span class="start-menu-entry-toggle">Run</span>
`;
button.addEventListener('click', () => runQuickAction(action.action_id));
startMenuActions.appendChild(button);
});
renderMenuWidgets();
};
payload.apps.forEach((app) => {
const icon = document.createElement('button');
icon.type = 'button';
@@ -160,29 +323,46 @@ if (payloadNode) {
<span class="desktop-icon-badge">${app.icon}</span>
<span class="desktop-icon-label">${app.title}</span>
`;
icon.addEventListener('click', () => {
const existing = Array.from(windows.values()).find((record) => record.definition.app_id === app.app_id);
if (existing) {
existing.node.classList.remove('is-minimized');
focusWindow(existing.id);
return;
}
createWindow({
window_id: `window-${app.app_id}-${Date.now()}`,
app_id: app.app_id,
title: app.title,
x: 64 + Math.round(Math.random() * 80),
y: 70 + Math.round(Math.random() * 60),
width: app.default_width || 640,
height: app.default_height || 420,
content: app.summary || '',
});
});
icon.addEventListener('click', () => openApp(app));
iconsRoot.appendChild(icon);
});
startButton?.setAttribute('aria-expanded', 'false');
startButton?.addEventListener('click', () => {
if (!startMenu) {
return;
}
const isOpen = !startMenu.hidden;
startMenu.hidden = isOpen;
startButton.setAttribute('aria-expanded', String(!isOpen));
});
document.addEventListener('click', (event) => {
if (!startMenu || startMenu.hidden) {
return;
}
const target = event.target;
if (!(target instanceof Node)) {
return;
}
if (startMenu.contains(target) || startButton?.contains(target)) {
return;
}
closeStartMenu();
});
document.addEventListener('keydown', (event) => {
if (event.key === 'Escape') {
closeStartMenu();
}
});
renderWidgets();
renderStartMenu();
payload.windows.forEach(createWindow);
renderClock();
window.setInterval(renderClock, 1000);