1022 lines
47 KiB
HTML
1022 lines
47 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="zh-TW">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
|
||
<title>macOS Web Simulator</title>
|
||
|
||
<!-- Tailwind CSS (透過 CDN 載入) -->
|
||
<script src="https://cdn.tailwindcss.com"></script>
|
||
|
||
<!-- FontAwesome 圖示庫 (透過 CDN 載入) -->
|
||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
|
||
|
||
<style>
|
||
/* --- 核心作業系統樣式 --- */
|
||
:root {
|
||
--glass-bg: rgba(255, 255, 255, 0.75);
|
||
--glass-border: rgba(255, 255, 255, 0.4);
|
||
--dock-bg: rgba(255, 255, 255, 0.25);
|
||
--text-color: #000;
|
||
--window-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);
|
||
}
|
||
|
||
.dark-mode {
|
||
--glass-bg: rgba(30, 30, 30, 0.75);
|
||
--glass-border: rgba(255, 255, 255, 0.15);
|
||
--dock-bg: rgba(0, 0, 0, 0.4);
|
||
--text-color: #fff;
|
||
}
|
||
|
||
body {
|
||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
|
||
overflow: hidden;
|
||
user-select: none;
|
||
-webkit-user-select: none;
|
||
cursor: default;
|
||
}
|
||
|
||
/* 桌面背景 */
|
||
#desktop {
|
||
background-image: url('https://images.unsplash.com/photo-1490079027102-cd08f2308c73?q=80&w=2070&auto=format&fit=crop');
|
||
background-size: cover;
|
||
background-position: center;
|
||
transition: background-image 0.5s ease;
|
||
}
|
||
|
||
/* 毛玻璃特效工具類 */
|
||
.glass {
|
||
background: var(--glass-bg);
|
||
backdrop-filter: blur(20px);
|
||
-webkit-backdrop-filter: blur(20px);
|
||
border: 1px solid var(--glass-border);
|
||
color: var(--text-color);
|
||
}
|
||
|
||
/* --- 開機畫面 --- */
|
||
#boot-screen {
|
||
position: fixed;
|
||
top: 0; left: 0; width: 100%; height: 100%;
|
||
background: #000;
|
||
z-index: 9999;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
align-items: center;
|
||
color: white;
|
||
transition: opacity 1s ease-out;
|
||
}
|
||
.loading-bar {
|
||
width: 200px;
|
||
height: 6px;
|
||
background: #333;
|
||
border-radius: 3px;
|
||
margin-top: 40px;
|
||
overflow: hidden;
|
||
}
|
||
.loading-progress {
|
||
height: 100%;
|
||
background: #fff;
|
||
width: 0%;
|
||
transition: width 2s ease-in-out;
|
||
}
|
||
|
||
/* --- 選單列 (Menu Bar) --- */
|
||
#menubar {
|
||
height: 30px;
|
||
z-index: 5000;
|
||
}
|
||
|
||
/* --- Dock 列 --- */
|
||
#dock-container {
|
||
position: absolute;
|
||
bottom: 10px;
|
||
left: 0;
|
||
width: 100%;
|
||
display: flex;
|
||
justify-content: center;
|
||
z-index: 6000;
|
||
pointer-events: none;
|
||
}
|
||
#dock {
|
||
pointer-events: auto;
|
||
display: flex;
|
||
align-items: flex-end;
|
||
padding: 8px 12px;
|
||
border-radius: 24px;
|
||
gap: 8px;
|
||
transition: all 0.2s ease;
|
||
box-shadow: 0 10px 20px rgba(0,0,0,0.15);
|
||
border: 1px solid rgba(255,255,255,0.2);
|
||
}
|
||
.dock-item {
|
||
width: 50px;
|
||
height: 50px;
|
||
transition: all 0.2s cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
||
cursor: pointer;
|
||
position: relative;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: center;
|
||
justify-content: center;
|
||
}
|
||
.dock-item:hover {
|
||
transform: translateY(-10px) scale(1.2);
|
||
margin: 0 10px;
|
||
}
|
||
.dock-item:active {
|
||
transform: translateY(-10px) scale(1.1);
|
||
filter: brightness(0.8);
|
||
}
|
||
.dock-item img, .dock-item .app-icon-div {
|
||
width: 100%;
|
||
height: 100%;
|
||
border-radius: 12px;
|
||
object-fit: cover;
|
||
box-shadow: 0 4px 6px rgba(0,0,0,0.2);
|
||
}
|
||
.dock-dot {
|
||
width: 4px;
|
||
height: 4px;
|
||
background: rgba(255,255,255,0.9);
|
||
box-shadow: 0 0 2px rgba(0,0,0,0.5);
|
||
border-radius: 50%;
|
||
position: absolute;
|
||
bottom: -6px;
|
||
opacity: 0;
|
||
transition: opacity 0.3s;
|
||
}
|
||
|
||
/* Dock 提示框 */
|
||
.dock-tooltip {
|
||
position: absolute;
|
||
top: -45px;
|
||
background: rgba(20, 20, 20, 0.8);
|
||
color: white;
|
||
padding: 5px 10px;
|
||
border-radius: 6px;
|
||
font-size: 12px;
|
||
opacity: 0;
|
||
transition: opacity 0.2s;
|
||
pointer-events: none;
|
||
white-space: nowrap;
|
||
backdrop-filter: blur(8px);
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
box-shadow: 0 4px 12px rgba(0,0,0,0.2);
|
||
}
|
||
.dock-item:hover .dock-tooltip {
|
||
opacity: 1;
|
||
}
|
||
|
||
/* --- 視窗樣式 --- */
|
||
.window {
|
||
position: absolute;
|
||
border-radius: 12px;
|
||
box-shadow: var(--window-shadow);
|
||
display: flex;
|
||
flex-direction: column;
|
||
overflow: hidden;
|
||
min-width: 300px;
|
||
min-height: 200px;
|
||
pointer-events: auto;
|
||
transition: width 0.3s, height 0.3s, transform 0.2s, opacity 0.2s;
|
||
will-change: transform, width, height;
|
||
}
|
||
|
||
/* 視窗動畫狀態 */
|
||
.window.opening {
|
||
animation: popIn 0.25s cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
.window.closing {
|
||
transform: scale(0.9);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
}
|
||
.window.minimized {
|
||
transform: scale(0) translateY(500px);
|
||
opacity: 0;
|
||
pointer-events: none;
|
||
transition: all 0.4s cubic-bezier(0.16, 1, 0.3, 1);
|
||
}
|
||
|
||
@keyframes popIn {
|
||
from { transform: scale(0.95); opacity: 0; }
|
||
to { transform: scale(1); opacity: 1; }
|
||
}
|
||
|
||
.title-bar {
|
||
height: 36px;
|
||
display: flex;
|
||
align-items: center;
|
||
padding: 0 16px;
|
||
cursor: default;
|
||
flex-shrink: 0;
|
||
background: rgba(255,255,255,0.01);
|
||
}
|
||
.traffic-lights {
|
||
display: flex;
|
||
gap: 8px;
|
||
z-index: 10; /* 確保按鈕在最上層 */
|
||
}
|
||
.traffic-light {
|
||
width: 12px;
|
||
height: 12px;
|
||
border-radius: 50%;
|
||
cursor: pointer;
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 8px;
|
||
color: rgba(0,0,0,0.6);
|
||
opacity: 1;
|
||
transition: transform 0.1s;
|
||
}
|
||
.traffic-light:active { filter: brightness(0.8); transform: scale(0.95); }
|
||
.traffic-light i { opacity: 0; transition: opacity 0.1s; }
|
||
.traffic-lights:hover .traffic-light i { opacity: 1; }
|
||
|
||
.close-btn { background: #ff5f57; border: 1px solid #e0443e; }
|
||
.min-btn { background: #febc2e; border: 1px solid #d89e24; }
|
||
.max-btn { background: #28c840; border: 1px solid #1aab29; }
|
||
|
||
.window-title {
|
||
flex-grow: 1;
|
||
text-align: center;
|
||
font-weight: 600;
|
||
font-size: 13px;
|
||
color: var(--text-color);
|
||
opacity: 0.9;
|
||
pointer-events: none;
|
||
}
|
||
|
||
.window-content {
|
||
flex-grow: 1;
|
||
overflow: auto;
|
||
background: rgba(255, 255, 255, 0.95);
|
||
position: relative;
|
||
}
|
||
.dark-mode .window-content {
|
||
background: rgba(40, 40, 40, 0.95);
|
||
color: white;
|
||
}
|
||
|
||
/* --- 應用程式專屬樣式 --- */
|
||
/* 計算機 */
|
||
.calc-btn {
|
||
background: rgba(255,255,255,0.5);
|
||
border: 1px solid rgba(0,0,0,0.05);
|
||
transition: background 0.1s;
|
||
font-size: 1.1rem;
|
||
}
|
||
.calc-btn:active { background: rgba(200,200,200,0.5); }
|
||
.calc-btn.orange { background: #f39c12; color: white; border: none; }
|
||
.calc-btn.orange:active { background: #d35400; }
|
||
.dark-mode .calc-btn { background: rgba(80,80,80,0.8); color: white; border: none; }
|
||
.dark-mode .calc-btn:active { background: rgba(100,100,100,0.8); }
|
||
|
||
/* 終端機 */
|
||
.terminal-body {
|
||
background: #1a1b26;
|
||
color: #a9b1d6;
|
||
font-family: 'Menlo', 'Monaco', 'Courier New', monospace;
|
||
padding: 12px;
|
||
height: 100%;
|
||
font-size: 14px;
|
||
line-height: 1.5;
|
||
}
|
||
.terminal-input {
|
||
background: transparent;
|
||
border: none;
|
||
color: #a9b1d6;
|
||
outline: none;
|
||
width: 80%;
|
||
font-family: inherit;
|
||
}
|
||
|
||
/* App 圖示樣式 (CSS 繪製) */
|
||
.app-icon-div {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: center;
|
||
font-size: 26px;
|
||
color: white;
|
||
background: linear-gradient(135deg, #ccc, #999);
|
||
text-shadow: 0 2px 4px rgba(0,0,0,0.2);
|
||
}
|
||
.icon-finder { background: linear-gradient(180deg, #1c87fb 0%, #0c56f9 100%); font-size: 22px; }
|
||
.icon-safari { background: white; color: #1c87fb; position: relative; overflow: hidden;}
|
||
.icon-safari::before { content:''; position: absolute; top:0; left:0; right:0; bottom:0; background: radial-gradient(circle, #1c87fb 50%, #2980b9 100%); z-index: 1; }
|
||
.icon-safari i { position: relative; z-index: 2; color: white; font-size: 32px; }
|
||
|
||
.icon-messages { background: linear-gradient(135deg, #4ADE80, #22C55E); }
|
||
.icon-mail { background: linear-gradient(135deg, #60A5FA, #3B82F6); }
|
||
.icon-photos { background: linear-gradient(135deg, #ff9a9e 0%, #fecfef 99%, #fecfef 100%); }
|
||
.icon-terminal { background: #333; border: 1px solid #555; color: #0f0; font-family: monospace; font-weight: bold; }
|
||
.icon-notes { background: linear-gradient(to bottom, #fef9c3, #fde047); color: #555; }
|
||
.icon-settings { background: linear-gradient(135deg, #94a3b8, #64748b); }
|
||
.icon-calc { background: #4b5563; }
|
||
.icon-trash { background: transparent; border: none; box-shadow: none !important; color: #ddd; font-size: 30px; }
|
||
|
||
/* 右鍵選單 */
|
||
#context-menu {
|
||
display: none;
|
||
position: absolute;
|
||
width: 220px;
|
||
background: rgba(245, 245, 245, 0.9);
|
||
border-radius: 8px;
|
||
box-shadow: 0 8px 20px rgba(0,0,0,0.2);
|
||
z-index: 9999;
|
||
padding: 5px;
|
||
backdrop-filter: blur(12px);
|
||
border: 1px solid rgba(255,255,255,0.3);
|
||
}
|
||
.dark-mode #context-menu {
|
||
background: rgba(40,40,40,0.9);
|
||
color: white;
|
||
border: 1px solid rgba(255,255,255,0.1);
|
||
}
|
||
.ctx-item {
|
||
padding: 4px 10px;
|
||
font-size: 14px;
|
||
border-radius: 4px;
|
||
cursor: pointer;
|
||
display: flex;
|
||
justify-content: space-between;
|
||
}
|
||
.ctx-item:hover {
|
||
background: #3b82f6;
|
||
color: white;
|
||
}
|
||
.ctx-separator {
|
||
height: 1px;
|
||
background: rgba(0,0,0,0.1);
|
||
margin: 4px 6px;
|
||
}
|
||
.dark-mode .ctx-separator { background: rgba(255,255,255,0.15); }
|
||
|
||
</style>
|
||
</head>
|
||
<body class="h-screen w-screen overflow-hidden bg-black select-none">
|
||
|
||
<!-- 開機畫面 -->
|
||
<div id="boot-screen">
|
||
<i class="fab fa-apple text-6xl mb-8"></i>
|
||
<div class="loading-bar">
|
||
<div class="loading-progress" id="boot-progress"></div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右鍵選單 -->
|
||
<div id="context-menu">
|
||
<div class="ctx-item"><span>新增資料夾</span></div>
|
||
<div class="ctx-item"><span>取得資訊</span></div>
|
||
<div class="ctx-separator"></div>
|
||
<div class="ctx-item" onclick="app.launch('terminal')"><span>開啟終端機</span></div>
|
||
<div class="ctx-separator"></div>
|
||
<div class="ctx-item" onclick="app.launch('settings')"><span>更換桌布...</span></div>
|
||
</div>
|
||
|
||
<!-- 桌面環境 -->
|
||
<div id="desktop" class="w-full h-full relative bg-gray-800" onclick="ui.hideContextMenu()">
|
||
|
||
<!-- 選單列 -->
|
||
<div id="menubar" class="glass w-full flex items-center justify-between px-4 text-sm font-medium shadow-sm">
|
||
<div class="flex items-center space-x-4">
|
||
<div class="cursor-pointer hover:opacity-70 px-2" onclick="ui.toggleStartMenu()"><i class="fab fa-apple text-lg"></i></div>
|
||
<div id="active-app-name" class="font-bold">Finder</div>
|
||
<div class="hidden md:flex space-x-4 text-xs opacity-90">
|
||
<span class="cursor-pointer hover:text-blue-500">檔案</span>
|
||
<span class="cursor-pointer hover:text-blue-500">編輯</span>
|
||
<span class="cursor-pointer hover:text-blue-500">檢視</span>
|
||
<span class="cursor-pointer hover:text-blue-500">前往</span>
|
||
<span class="cursor-pointer hover:text-blue-500">視窗</span>
|
||
<span class="cursor-pointer hover:text-blue-500">輔助說明</span>
|
||
</div>
|
||
</div>
|
||
<div class="flex items-center space-x-5 pr-2">
|
||
<div class="flex space-x-4 text-xs items-center opacity-90">
|
||
<i class="fas fa-battery-full text-sm"></i>
|
||
<i class="fas fa-wifi text-sm"></i>
|
||
<i class="fas fa-search text-sm cursor-pointer"></i>
|
||
<i class="fas fa-sliders-h text-sm cursor-pointer"></i>
|
||
</div>
|
||
<div id="clock" class="text-xs font-medium ml-2">Mon Jun 22 9:41 AM</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 視窗容器 -->
|
||
<div id="windows-container" class="absolute top-8 bottom-20 left-0 right-0 overflow-hidden pointer-events-none">
|
||
<!-- 視窗會動態插入此處 -->
|
||
</div>
|
||
|
||
<!-- Dock 列 -->
|
||
<div id="dock-container">
|
||
<div id="dock" class="glass">
|
||
<!-- 圖示會動態生成 -->
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 應用程式模板 (App Templates) -->
|
||
<template id="tmpl-finder">
|
||
<div class="flex h-full flex-col">
|
||
<div class="flex h-full">
|
||
<div class="w-1/4 bg-gray-100 dark:bg-gray-800 p-2 text-sm space-y-1 border-r border-gray-200 dark:border-gray-700 pt-3">
|
||
<div class="text-gray-500 text-[11px] font-bold mb-1 px-2">喜好項目</div>
|
||
<div class="p-1 px-2 rounded hover:bg-blue-500 hover:text-white cursor-pointer flex items-center gap-2"><i class="fas fa-hdd text-blue-400 w-4 text-center"></i> Macintosh HD</div>
|
||
<div class="p-1 px-2 rounded hover:bg-blue-500 hover:text-white cursor-pointer flex items-center gap-2"><i class="fas fa-desktop text-blue-300 w-4 text-center"></i> 桌面</div>
|
||
<div class="p-1 px-2 rounded bg-gray-300 dark:bg-gray-700 flex items-center gap-2"><i class="fas fa-file text-gray-400 w-4 text-center"></i> 文件</div>
|
||
<div class="p-1 px-2 rounded hover:bg-blue-500 hover:text-white cursor-pointer flex items-center gap-2"><i class="fas fa-download text-blue-300 w-4 text-center"></i> 下載項目</div>
|
||
<div class="p-1 px-2 rounded hover:bg-blue-500 hover:text-white cursor-pointer flex items-center gap-2"><i class="fas fa-code-branch text-green-500 w-4 text-center"></i> Developer</div>
|
||
</div>
|
||
<div class="w-3/4 p-4 grid grid-cols-4 gap-4 content-start bg-white dark:bg-gray-900">
|
||
<div class="flex flex-col items-center gap-2 cursor-pointer hover:bg-blue-100 dark:hover:bg-gray-700 p-3 rounded transition-colors">
|
||
<i class="fas fa-folder text-5xl text-blue-300 drop-shadow-md"></i>
|
||
<span class="text-xs text-center font-medium">專案資料</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2 cursor-pointer hover:bg-blue-100 dark:hover:bg-gray-700 p-3 rounded transition-colors">
|
||
<i class="fas fa-image text-5xl text-purple-400 drop-shadow-md"></i>
|
||
<span class="text-xs text-center font-medium">假期照片.jpg</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2 cursor-pointer hover:bg-blue-100 dark:hover:bg-gray-700 p-3 rounded transition-colors">
|
||
<i class="fas fa-file-pdf text-5xl text-red-400 drop-shadow-md"></i>
|
||
<span class="text-xs text-center font-medium">年度報告.pdf</span>
|
||
</div>
|
||
<div class="flex flex-col items-center gap-2 cursor-pointer hover:bg-blue-100 dark:hover:bg-gray-700 p-3 rounded transition-colors">
|
||
<i class="fas fa-file-code text-5xl text-yellow-400 drop-shadow-md"></i>
|
||
<span class="text-xs text-center font-medium">script.py</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="tmpl-safari">
|
||
<div class="flex flex-col h-full">
|
||
<div class="bg-gray-100 dark:bg-gray-800 p-2 flex items-center gap-3 border-b border-gray-300 dark:border-gray-600">
|
||
<div class="flex gap-3 text-gray-500 ml-1">
|
||
<i class="fas fa-chevron-left hover:text-black cursor-pointer"></i>
|
||
<i class="fas fa-chevron-right hover:text-black cursor-pointer"></i>
|
||
</div>
|
||
<div class="bg-white dark:bg-gray-700 rounded-lg flex-grow text-center text-xs py-1.5 flex items-center justify-center gap-2 shadow-sm border border-gray-200 dark:border-gray-600">
|
||
<i class="fas fa-lock text-gray-400 text-[10px]"></i> apple.com/tw/macbook-air
|
||
</div>
|
||
<div class="flex gap-3 text-gray-500 mr-1">
|
||
<i class="fas fa-share-alt hover:text-black cursor-pointer text-xs"></i>
|
||
<i class="fas fa-plus hover:text-black cursor-pointer text-xs"></i>
|
||
</div>
|
||
</div>
|
||
<div class="flex-grow bg-white flex flex-col items-center justify-center text-gray-800 overflow-y-auto relative">
|
||
<div class="absolute top-0 left-0 w-full h-64 bg-gray-50 border-b border-gray-100"></div>
|
||
<div class="z-10 flex flex-col items-center text-center p-8">
|
||
<i class="fab fa-apple text-6xl mb-6 animate-pulse"></i>
|
||
<h1 class="text-4xl font-semibold mb-2 tracking-tight">MacBook Air 15"</h1>
|
||
<p class="text-xl text-gray-500 mb-8">大,有可為。</p>
|
||
<div class="flex gap-4">
|
||
<button class="bg-blue-600 hover:bg-blue-700 text-white px-6 py-1.5 rounded-full text-sm font-medium transition">購買</button>
|
||
<button class="text-blue-600 hover:underline px-4 py-1.5 rounded-full text-sm font-medium">進一步了解 ></button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="tmpl-calc">
|
||
<div class="h-full w-full bg-gray-900 flex flex-col text-white p-3 select-none">
|
||
<div id="calc-display" class="flex-grow flex items-end justify-end text-5xl px-2 font-light mb-3 break-all tracking-tight">0</div>
|
||
<div class="grid grid-cols-4 gap-3 h-4/5">
|
||
<button class="calc-btn rounded-full text-black" onclick="apps.calc.clear()">AC</button>
|
||
<button class="calc-btn rounded-full text-black">+/-</button>
|
||
<button class="calc-btn rounded-full text-black">%</button>
|
||
<button class="calc-btn orange rounded-full text-2xl pb-1" onclick="apps.calc.op('/')">÷</button>
|
||
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(7)">7</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(8)">8</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(9)">9</button>
|
||
<button class="calc-btn orange rounded-full text-2xl pb-1" onclick="apps.calc.op('*')">×</button>
|
||
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(4)">4</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(5)">5</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(6)">6</button>
|
||
<button class="calc-btn orange rounded-full text-2xl pb-1" onclick="apps.calc.op('-')">-</button>
|
||
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(1)">1</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(2)">2</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.num(3)">3</button>
|
||
<button class="calc-btn orange rounded-full text-2xl pb-1" onclick="apps.calc.op('+')">+</button>
|
||
|
||
<button class="calc-btn rounded-full bg-gray-700 col-span-2 text-left pl-7" onclick="apps.calc.num(0)">0</button>
|
||
<button class="calc-btn rounded-full bg-gray-700" onclick="apps.calc.dot()">.</button>
|
||
<button class="calc-btn orange rounded-full text-2xl pb-1" onclick="apps.calc.solve()">=</button>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="tmpl-terminal">
|
||
<div class="terminal-body cursor-text" onclick="document.getElementById('term-input').focus()">
|
||
<div id="term-output">
|
||
<div>Last login: <span id="term-login-time"></span> on ttys000</div>
|
||
<div class="mb-2 text-gray-400">Type 'help' for available commands.</div>
|
||
</div>
|
||
<div class="flex">
|
||
<span class="text-green-400 mr-2 font-bold">user@macbook ~ %</span>
|
||
<input id="term-input" type="text" class="terminal-input" autocomplete="off">
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="tmpl-notes">
|
||
<div class="flex h-full">
|
||
<div class="w-1/3 bg-gray-50 dark:bg-gray-800 border-r border-gray-200 dark:border-gray-700 p-2 pt-3">
|
||
<div class="text-xs font-bold text-gray-500 mb-2 text-center">iCloud</div>
|
||
<div class="bg-yellow-100 dark:bg-gray-600 p-3 rounded-lg cursor-pointer mb-2 shadow-sm">
|
||
<div class="font-bold text-sm dark:text-white mb-1">購物清單</div>
|
||
<div class="text-xs text-gray-500 dark:text-gray-300">雞蛋, 牛奶...</div>
|
||
<div class="text-[10px] text-gray-400 mt-1">9:41 AM</div>
|
||
</div>
|
||
<div class="hover:bg-gray-200 dark:hover:bg-gray-700 p-3 rounded-lg cursor-pointer">
|
||
<div class="font-bold text-sm dark:text-white mb-1">會議記錄</div>
|
||
<div class="text-xs text-gray-500 dark:text-gray-300">專案啟動...</div>
|
||
</div>
|
||
</div>
|
||
<div class="w-2/3 bg-white dark:bg-gray-900 p-6 text-gray-800 dark:text-gray-100 outline-none leading-relaxed" contenteditable="true">
|
||
<h1 class="text-2xl font-bold mb-4">購物清單</h1>
|
||
<ul class="list-none space-y-2">
|
||
<li class="flex items-center gap-2"><i class="far fa-check-circle text-yellow-500"></i> 雞蛋</li>
|
||
<li class="flex items-center gap-2"><i class="far fa-circle text-gray-300"></i> 牛奶</li>
|
||
<li class="flex items-center gap-2"><i class="far fa-circle text-gray-300"></i> 蘋果</li>
|
||
</ul>
|
||
<br>
|
||
<p class="text-sm text-gray-400 italic">點擊此處開始編輯...</p>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<template id="tmpl-settings">
|
||
<div class="p-6 flex flex-col gap-6 h-full bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-white">
|
||
<div class="flex items-center gap-4 bg-white dark:bg-gray-700 p-3 rounded-xl shadow-sm border border-gray-200 dark:border-gray-600">
|
||
<div class="w-14 h-14 bg-gradient-to-br from-gray-300 to-gray-400 rounded-full flex items-center justify-center text-2xl text-white shadow-inner">
|
||
<i class="fas fa-user"></i>
|
||
</div>
|
||
<div>
|
||
<h2 class="font-bold text-lg">User</h2>
|
||
<p class="text-xs text-gray-500 dark:text-gray-300">Apple ID, iCloud, 媒體與購買項目</p>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="space-y-4">
|
||
<div>
|
||
<h3 class="font-bold mb-3 text-xs text-gray-500 uppercase tracking-wider ml-1">外觀</h3>
|
||
<div class="flex gap-6 bg-white dark:bg-gray-700 p-4 rounded-xl shadow-sm border border-gray-200 dark:border-gray-600">
|
||
<div class="cursor-pointer flex flex-col items-center gap-2 group" onclick="ui.setTheme('light')">
|
||
<div class="w-20 h-14 bg-[#eef] border-2 border-gray-300 rounded-lg shadow-sm group-hover:border-blue-500 transition"></div>
|
||
<span class="text-xs font-medium">淺色</span>
|
||
</div>
|
||
<div class="cursor-pointer flex flex-col items-center gap-2 group" onclick="ui.setTheme('dark')">
|
||
<div class="w-20 h-14 bg-[#334] border-2 border-gray-600 rounded-lg shadow-sm group-hover:border-blue-500 transition"></div>
|
||
<span class="text-xs font-medium">深色</span>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div>
|
||
<h3 class="font-bold mb-3 text-xs text-gray-500 uppercase tracking-wider ml-1">桌布</h3>
|
||
<div class="flex gap-3 overflow-x-auto pb-2">
|
||
<div onclick="ui.setWallpaper('https://images.unsplash.com/photo-1490079027102-cd08f2308c73?q=80&w=2070')" class="flex-shrink-0 w-24 h-16 rounded-lg bg-cover cursor-pointer border-2 border-transparent hover:border-blue-500 transition shadow-sm" style="background-image: url('https://images.unsplash.com/photo-1490079027102-cd08f2308c73?q=80&w=200')"></div>
|
||
<div onclick="ui.setWallpaper('https://images.unsplash.com/photo-1506744038136-46273834b3fb?q=80&w=2070')" class="flex-shrink-0 w-24 h-16 rounded-lg bg-cover cursor-pointer border-2 border-transparent hover:border-blue-500 transition shadow-sm" style="background-image: url('https://images.unsplash.com/photo-1506744038136-46273834b3fb?q=80&w=200')"></div>
|
||
<div onclick="ui.setWallpaper('https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=2074')" class="flex-shrink-0 w-24 h-16 rounded-lg bg-cover cursor-pointer border-2 border-transparent hover:border-blue-500 transition shadow-sm" style="background-image: url('https://images.unsplash.com/photo-1470071459604-3b5ec3a7fe05?q=80&w=200')"></div>
|
||
<div onclick="ui.setWallpaper('https://images.unsplash.com/photo-1621193793262-4127d9ea5460?q=80&w=1974')" class="flex-shrink-0 w-24 h-16 rounded-lg bg-cover cursor-pointer border-2 border-transparent hover:border-blue-500 transition shadow-sm" style="background-image: url('https://images.unsplash.com/photo-1621193793262-4127d9ea5460?q=80&w=200')"></div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<!-- Scripts -->
|
||
<script>
|
||
// 定義全域變數,確保 HTML 中的 onclick 事件能存取到
|
||
const state = {
|
||
windows: {}, // 儲存視窗 DOM 元素
|
||
windowStates: {}, // 儲存最大化/最小化狀態
|
||
zIndex: 100,
|
||
activeWindow: null,
|
||
darkMode: false
|
||
};
|
||
|
||
const apps = {
|
||
finder: { title: 'Finder', iconClass: 'icon-finder', iconHtml: '<i class="fas fa-smile"></i>', tmpl: 'tmpl-finder', w: 680, h: 450 },
|
||
safari: { title: 'Safari', iconClass: 'icon-safari', iconHtml: '<i class="fas fa-compass"></i>', tmpl: 'tmpl-safari', w: 850, h: 550 },
|
||
messages: { title: '訊息', iconClass: 'icon-messages', iconHtml: '<i class="fas fa-comment-dots"></i>', tmpl: null, w: 380, h: 550, empty: true },
|
||
mail: { title: '郵件', iconClass: 'icon-mail', iconHtml: '<i class="fas fa-envelope"></i>', tmpl: null, w: 750, h: 500, empty: true },
|
||
notes: { title: '備忘錄', iconClass: 'icon-notes', iconHtml: '<i class="fas fa-sticky-note"></i>', tmpl: 'tmpl-notes', w: 550, h: 420 },
|
||
calc: { title: '計算機', iconClass: 'icon-calc', iconHtml: '<i class="fas fa-calculator"></i>', tmpl: 'tmpl-calc', w: 260, h: 380, fixed: true },
|
||
terminal: { title: '終端機', iconClass: 'icon-terminal', iconHtml: '<span style="font-family:monospace; font-weight:bold; color:#0f0;">>_</span>', tmpl: 'tmpl-terminal', w: 580, h: 380 },
|
||
settings: { title: '系統設定', iconClass: 'icon-settings', iconHtml: '<i class="fas fa-cog"></i>', tmpl: 'tmpl-settings', w: 640, h: 480 },
|
||
trash: { title: '垃圾桶', iconClass: 'icon-trash', iconHtml: '<i class="fas fa-trash-alt"></i>', action: 'clearTrash' }
|
||
};
|
||
|
||
// 應用程式邏輯 (計算機)
|
||
apps.calc.current = '';
|
||
apps.calc.prev = '';
|
||
apps.calc.operation = null;
|
||
apps.calc.num = (n) => { apps.calc.current += n; apps.calc.updateDisplay(); };
|
||
apps.calc.dot = () => { if (!apps.calc.current.includes('.')) apps.calc.current += '.'; apps.calc.updateDisplay(); }
|
||
apps.calc.op = (op) => { if (apps.calc.current === '') return; apps.calc.prev = apps.calc.current; apps.calc.current = ''; apps.calc.operation = op; };
|
||
apps.calc.solve = () => {
|
||
if (!apps.calc.operation || !apps.calc.prev) return;
|
||
let result;
|
||
const p = parseFloat(apps.calc.prev);
|
||
const c = parseFloat(apps.calc.current);
|
||
switch(apps.calc.operation) {
|
||
case '+': result = p + c; break;
|
||
case '-': result = p - c; break;
|
||
case '*': result = p * c; break;
|
||
case '/': result = p / c; break;
|
||
}
|
||
apps.calc.current = result.toString();
|
||
apps.calc.operation = null;
|
||
apps.calc.prev = '';
|
||
apps.calc.updateDisplay();
|
||
};
|
||
apps.calc.clear = () => { apps.calc.current = ''; apps.calc.prev = ''; apps.calc.operation = null; apps.calc.updateDisplay('0'); };
|
||
apps.calc.updateDisplay = (val) => {
|
||
const display = document.getElementById('calc-display');
|
||
if (display) display.innerText = val || apps.calc.current || '0';
|
||
};
|
||
|
||
const ui = {
|
||
init: () => {
|
||
// 開機動畫流程
|
||
const progressBar = document.getElementById('boot-progress');
|
||
setTimeout(() => progressBar.style.width = '100%', 100);
|
||
setTimeout(() => {
|
||
const boot = document.getElementById('boot-screen');
|
||
boot.style.opacity = '0';
|
||
setTimeout(() => boot.remove(), 1000);
|
||
}, 2000);
|
||
|
||
ui.renderDock();
|
||
ui.startClock();
|
||
ui.setupGlobalEvents();
|
||
|
||
// 終端機鍵盤監聽
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.target.id === 'term-input' && e.key === 'Enter') {
|
||
ui.handleTerminalCommand(e.target.value);
|
||
e.target.value = '';
|
||
e.target.scrollIntoView();
|
||
}
|
||
});
|
||
},
|
||
|
||
handleTerminalCommand: (input) => {
|
||
const output = document.getElementById('term-output');
|
||
output.innerHTML += `<div class="mt-1"><span class="text-green-400 font-bold">user@macbook ~ %</span> ${input}</div>`;
|
||
|
||
let response = '';
|
||
const cmd = input.trim().toLowerCase();
|
||
if (cmd === 'help') response = 'Available commands: help, date, clear, whoami, ls, reboot, hello';
|
||
else if (cmd === 'date') response = new Date().toString();
|
||
else if (cmd === 'whoami') response = 'user';
|
||
else if (cmd === 'ls') response = 'Documents Downloads Desktop Pictures Music';
|
||
else if (cmd === 'hello') response = 'Hello World! 台灣 NO.1';
|
||
else if (cmd === 'clear') { output.innerHTML = ''; response = null; }
|
||
else if (cmd === 'reboot') location.reload();
|
||
else if (cmd !== '') response = `zsh: command not found: ${cmd}`;
|
||
|
||
if (response) output.innerHTML += `<div class="mb-2 text-gray-300 whitespace-pre-wrap">${response}</div>`;
|
||
},
|
||
|
||
renderDock: () => {
|
||
const dock = document.getElementById('dock');
|
||
dock.innerHTML = ''; // 清空
|
||
Object.keys(apps).forEach(key => {
|
||
const app = apps[key];
|
||
const el = document.createElement('div');
|
||
el.className = 'dock-item';
|
||
el.onclick = () => app.action ? ui[app.action]() : ui.toggleWindow(key);
|
||
|
||
const icon = document.createElement('div');
|
||
icon.className = `app-icon-div ${app.iconClass} shadow-lg`;
|
||
icon.innerHTML = app.iconHtml;
|
||
|
||
const dot = document.createElement('div');
|
||
dot.className = 'dock-dot';
|
||
dot.id = `dot-${key}`;
|
||
|
||
const tooltip = document.createElement('div');
|
||
tooltip.className = 'dock-tooltip';
|
||
tooltip.innerText = app.title;
|
||
|
||
el.appendChild(tooltip);
|
||
el.appendChild(icon);
|
||
el.appendChild(dot);
|
||
dock.appendChild(el);
|
||
});
|
||
},
|
||
|
||
startClock: () => {
|
||
const update = () => {
|
||
const now = new Date();
|
||
const days = ['週日', '週一', '週二', '週三', '週四', '週五', '週六'];
|
||
const dateStr = `${days[now.getDay()]} ${now.getMonth() + 1}月${now.getDate()}日 ${now.getHours().toString().padStart(2,'0')}:${now.getMinutes().toString().padStart(2,'0')}`;
|
||
document.getElementById('clock').innerText = dateStr;
|
||
|
||
const termTime = document.getElementById('term-login-time');
|
||
if (termTime && !termTime.innerText) termTime.innerText = now.toString();
|
||
};
|
||
update();
|
||
setInterval(update, 1000);
|
||
},
|
||
|
||
toggleWindow: (key) => {
|
||
const win = state.windows[key];
|
||
if (win) {
|
||
if (win.classList.contains('minimized')) {
|
||
ui.restoreWindow(key);
|
||
} else {
|
||
// 如果已開啟且是當前視窗,則最小化
|
||
if (state.activeWindow === key) {
|
||
ui.minimizeWindow(key);
|
||
} else {
|
||
ui.bringToFront(key);
|
||
}
|
||
}
|
||
} else {
|
||
ui.openWindow(key);
|
||
}
|
||
},
|
||
|
||
openWindow: (key) => {
|
||
const app = apps[key];
|
||
document.getElementById(`dot-${key}`).style.opacity = '1';
|
||
document.getElementById('active-app-name').innerText = app.title;
|
||
|
||
const win = document.createElement('div');
|
||
win.className = `window glass opening`;
|
||
win.id = `win-${key}`;
|
||
win.style.width = app.w + 'px';
|
||
win.style.height = app.h + 'px';
|
||
|
||
// 初始位置計算
|
||
const offset = (Object.keys(state.windows).length % 10) * 20;
|
||
const left = Math.max(20, (window.innerWidth - app.w) / 2 + offset);
|
||
const top = Math.max(40, (window.innerHeight - app.h) / 2 + offset - 20);
|
||
win.style.left = left + 'px';
|
||
win.style.top = top + 'px';
|
||
win.style.zIndex = ++state.zIndex;
|
||
|
||
win.onmousedown = () => ui.bringToFront(key);
|
||
|
||
// 標題列與控制項
|
||
const header = document.createElement('div');
|
||
header.className = 'title-bar';
|
||
|
||
// 紅綠燈按鈕 (明確使用 onclick="ui.xxx" 確保觸發)
|
||
header.innerHTML = `
|
||
<div class="traffic-lights">
|
||
<div class="traffic-light close-btn" onclick="ui.closeWindow('${key}')"><i class="fas fa-times"></i></div>
|
||
<div class="traffic-light min-btn" onclick="ui.minimizeWindow('${key}')"><i class="fas fa-minus"></i></div>
|
||
<div class="traffic-light max-btn" onclick="ui.toggleMaximize('${key}')"><i class="fas fa-expand-alt"></i></div>
|
||
</div>
|
||
<div class="window-title">${app.title}</div>
|
||
<div style="width: 52px;"></div> <!-- 佔位符 -->
|
||
`;
|
||
|
||
const content = document.createElement('div');
|
||
content.className = 'window-content';
|
||
|
||
if (app.tmpl) {
|
||
const tmpl = document.getElementById(app.tmpl);
|
||
content.appendChild(tmpl.content.cloneNode(true));
|
||
} else if (app.empty) {
|
||
content.innerHTML = `<div class="flex flex-col items-center justify-center h-full text-gray-400 text-center p-8"><i class="fas fa-hammer text-4xl mb-4 opacity-50"></i><p>此應用程式正在開發中</p></div>`;
|
||
}
|
||
|
||
win.appendChild(header);
|
||
win.appendChild(content);
|
||
document.getElementById('windows-container').appendChild(win);
|
||
|
||
// 動畫結束後移除 class 以確保拖曳順暢
|
||
setTimeout(() => win.classList.remove('opening'), 300);
|
||
|
||
// 啟用拖曳功能
|
||
ui.makeDraggable(win, header);
|
||
|
||
state.windows[key] = win;
|
||
state.windowStates[key] = { maximized: false };
|
||
ui.bringToFront(key);
|
||
},
|
||
|
||
closeWindow: (key) => {
|
||
if(window.event) window.event.stopPropagation();
|
||
|
||
const win = document.getElementById(`win-${key}`);
|
||
if (win) {
|
||
win.classList.add('closing');
|
||
setTimeout(() => {
|
||
win.remove();
|
||
delete state.windows[key];
|
||
delete state.windowStates[key];
|
||
document.getElementById(`dot-${key}`).style.opacity = '0';
|
||
|
||
// 還原選單列名稱
|
||
const keys = Object.keys(state.windows);
|
||
const next = keys.filter(k => !state.windows[k].classList.contains('minimized')).pop();
|
||
if (next) {
|
||
ui.bringToFront(next);
|
||
} else {
|
||
document.getElementById('active-app-name').innerText = 'Finder';
|
||
state.activeWindow = null;
|
||
}
|
||
}, 200);
|
||
}
|
||
},
|
||
|
||
minimizeWindow: (key) => {
|
||
if(window.event) window.event.stopPropagation();
|
||
const win = state.windows[key];
|
||
if (win) {
|
||
win.classList.add('minimized');
|
||
if (state.activeWindow === key) {
|
||
document.getElementById('active-app-name').innerText = 'Finder';
|
||
}
|
||
}
|
||
},
|
||
|
||
restoreWindow: (key) => {
|
||
const win = state.windows[key];
|
||
if (win) {
|
||
win.classList.remove('minimized');
|
||
ui.bringToFront(key);
|
||
}
|
||
},
|
||
|
||
toggleMaximize: (key) => {
|
||
if(window.event) window.event.stopPropagation();
|
||
const win = state.windows[key];
|
||
const winState = state.windowStates[key];
|
||
|
||
if (!winState.maximized) {
|
||
// 記錄目前位置
|
||
winState.prevLeft = win.style.left;
|
||
winState.prevTop = win.style.top;
|
||
winState.prevWidth = win.style.width;
|
||
winState.prevHeight = win.style.height;
|
||
|
||
// 最大化 (避開選單列和 Dock)
|
||
win.style.top = '30px';
|
||
win.style.left = '0px';
|
||
win.style.width = '100%';
|
||
win.style.height = (window.innerHeight - 30 - 80) + 'px';
|
||
win.style.borderRadius = '0';
|
||
|
||
winState.maximized = true;
|
||
} else {
|
||
// 還原
|
||
win.style.left = winState.prevLeft;
|
||
win.style.top = winState.prevTop;
|
||
win.style.width = winState.prevWidth;
|
||
win.style.height = winState.prevHeight;
|
||
win.style.borderRadius = '12px';
|
||
|
||
winState.maximized = false;
|
||
}
|
||
},
|
||
|
||
bringToFront: (key) => {
|
||
if (state.windows[key]) {
|
||
state.windows[key].style.zIndex = ++state.zIndex;
|
||
state.activeWindow = key;
|
||
document.getElementById('active-app-name').innerText = apps[key].title;
|
||
}
|
||
},
|
||
|
||
makeDraggable: (element, handle) => {
|
||
let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;
|
||
|
||
handle.onmousedown = dragMouseDown;
|
||
handle.ontouchstart = dragMouseDown;
|
||
|
||
function dragMouseDown(e) {
|
||
// 最大化時不可拖曳
|
||
const key = element.id.replace('win-', '');
|
||
if (state.windowStates[key] && state.windowStates[key].maximized) return;
|
||
|
||
e = e || window.event;
|
||
// 避免選取文字,但若點擊的是紅綠燈按鈕則放行
|
||
if (!e.target.classList.contains('traffic-light')) {
|
||
e.preventDefault();
|
||
}
|
||
|
||
pos3 = e.clientX || e.touches[0].clientX;
|
||
pos4 = e.clientY || e.touches[0].clientY;
|
||
|
||
document.onmouseup = closeDragElement;
|
||
document.onmousemove = elementDrag;
|
||
document.ontouchend = closeDragElement;
|
||
document.ontouchmove = elementDrag;
|
||
|
||
// 拖曳時置頂
|
||
element.style.zIndex = ++state.zIndex;
|
||
state.activeWindow = key;
|
||
document.getElementById('active-app-name').innerText = apps[key].title;
|
||
}
|
||
|
||
function elementDrag(e) {
|
||
e = e || window.event;
|
||
e.preventDefault();
|
||
|
||
const clientX = e.clientX || (e.touches ? e.touches[0].clientX : 0);
|
||
const clientY = e.clientY || (e.touches ? e.touches[0].clientY : 0);
|
||
|
||
pos1 = pos3 - clientX;
|
||
pos2 = pos4 - clientY;
|
||
pos3 = clientX;
|
||
pos4 = clientY;
|
||
|
||
let newTop = (element.offsetTop - pos2);
|
||
let newLeft = (element.offsetLeft - pos1);
|
||
|
||
// 邊界限制
|
||
if (newTop < 30) newTop = 30;
|
||
|
||
element.style.top = newTop + "px";
|
||
element.style.left = newLeft + "px";
|
||
}
|
||
|
||
function closeDragElement() {
|
||
document.onmouseup = null;
|
||
document.onmousemove = null;
|
||
document.ontouchend = null;
|
||
document.ontouchmove = null;
|
||
}
|
||
},
|
||
|
||
clearTrash: () => {
|
||
const trashIcon = document.querySelector('.icon-trash i');
|
||
trashIcon.className = 'fas fa-trash text-3xl text-gray-400';
|
||
setTimeout(() => {
|
||
trashIcon.className = 'fas fa-trash-alt text-3xl text-gray-400';
|
||
alert('垃圾桶已清空!乾乾淨淨。');
|
||
}, 600);
|
||
},
|
||
|
||
setupGlobalEvents: () => {
|
||
const desktop = document.getElementById('desktop');
|
||
const cm = document.getElementById('context-menu');
|
||
|
||
desktop.addEventListener('contextmenu', (e) => {
|
||
if (e.target === desktop || e.target.id === 'windows-container') {
|
||
e.preventDefault();
|
||
cm.style.display = 'block';
|
||
cm.style.left = Math.min(e.clientX, window.innerWidth - 220) + 'px';
|
||
cm.style.top = Math.min(e.clientY, window.innerHeight - 150) + 'px';
|
||
}
|
||
});
|
||
},
|
||
|
||
hideContextMenu: () => {
|
||
document.getElementById('context-menu').style.display = 'none';
|
||
},
|
||
|
||
setTheme: (mode) => {
|
||
state.darkMode = (mode === 'dark');
|
||
const root = document.documentElement;
|
||
if (state.darkMode) {
|
||
root.classList.add('dark-mode');
|
||
root.classList.add('dark');
|
||
} else {
|
||
root.classList.remove('dark-mode');
|
||
root.classList.remove('dark');
|
||
}
|
||
},
|
||
|
||
setWallpaper: (url) => {
|
||
document.getElementById('desktop').style.backgroundImage = `url('${url}')`;
|
||
},
|
||
|
||
toggleStartMenu: () => {
|
||
alert(" Apple Menu\n\n• 關於這台 Mac\n• 系統設定...\n• App Store...\n------------------\n• 強制結束 Finder\n------------------\n• 睡眠\n• 重新開機\n• 關機");
|
||
},
|
||
|
||
launch: (key) => {
|
||
ui.hideContextMenu();
|
||
ui.openWindow(key);
|
||
}
|
||
};
|
||
|
||
const app = ui; // 用於右鍵選單的別名
|
||
|
||
// 關鍵:將變數暴露給 Window 物件,讓 HTML inline events 可以存取
|
||
window.ui = ui;
|
||
window.apps = apps;
|
||
window.app = app;
|
||
|
||
// 啟動
|
||
window.onload = ui.init;
|
||
|
||
</script>
|
||
</body>
|
||
</html>
|