Files
42_104/caddy/www/web-macos/index.html
2026-06-23 19:33:50 +08:00

1022 lines
47 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!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>