
🚀Qwen3.8の2.4兆パラメータのパワー、ここまで来た。🤯✨
🚀Qwen3.8のパワー、ここまで来た。AIと作ったアプリが、もはや人間レベルを超え始めている。🤯✨
「AIでコードを書ける。」
そんな言葉は、もう古い。
Qwen3.8は、コードを書くだけでは終わらない。
設計し、
デザインし、
UIを考え、
アニメーションを作り、
ユーザー体験まで一気に完成させてしまう。
それを実感したのが、今回制作した Windows風WebOSアプリ だ。
💥まるで本物のOS!
電源を入れるとブート画面。
ロック画面。

デスクトップ。

スタートメニュー。

タスクバー。



ウィンドウ管理。
ドラッグ。
リサイズ。
最小化。
最大化。
右クリックメニュー。
クイック設定。
そして複数アプリが同時に動作する。
これが全部HTML・CSS・JavaScriptだけで動く。しかもブラウザ上で。🚀
見た目だけではない。
実際に操作できる。
まるで小さなWindowsがブラウザの中に誕生したような感覚だ。✨
⚡Qwen3.8の凄さは「理解力」
普通のAIなら、
「ボタンを追加してください。」
「ウィンドウをドラッグできるようにしてください。」
そんな細かい指示を何十回も繰り返す必要がある。
しかしQwen3.8は違う。
「Windowsっぽくしてください。」
その一言だけで、
UI構成
アニメーション
CSS設計
JavaScript構造
アプリ管理
ウィンドウシステム
ここまでまとめて考えてくれる。
まるで経験豊富なソフトウェアエンジニアとペアプログラミングしているような感覚だ。🔥
🎨デザインセンスまである
驚いたのはコードだけではない。
透明感のあるガラスUI。
ぼかしエフェクト。
美しいアイコン配置。
滑らかなアニメーション。
全体の統一感。
普通ならデザイナーとエンジニアが何日もかけて作るような雰囲気を、一気に組み上げてくる。✨
「コードを書くAI」ではなく、
「アプリをデザインするAI」
そんな時代が始まっている。
🚀アプリ開発の常識が変わる
昔は
仕様書を書く
UIを考える
コーディングする
デバッグする
これだけで何週間もかかった。
しかし今は、
💬「こんなアプリを作りたい。」
たったそれだけ。
Qwen3.8が土台を一気に構築してくれる。
もちろん細かな修正は必要だ。
しかし、ゼロから作る苦労は激減する。
これが生成AIの本当の革命だ。
🔥AIは"コード生成"から"共同開発"へ
私は今回改めて感じた。

Qwen3.8は単なるコードジェネレーターではない。
アイデアを理解し、
設計し、
改善案を考え、
完成形へ導いてくれる。
まさに開発パートナー。
一人で開発している感覚ではなく、
優秀なエンジニアチームと一緒に仕事をしている感覚になる。🤝
🌟未来はもう始まっている
AIは人間の仕事を奪う存在ではない。
人間の創造力を何倍にも増幅するエンジンだ。
思いついたアイデアを、その日のうちに動くアプリへ。
試作品から製品レベルへ。
想像から現実へ。
そのスピードは、これまでのソフトウェア開発とは比較にならない。🚀
Qwen3.8。
そのパワーは、単なるモデル性能の向上ではない。
「頭の中のアイデアを、現実世界へ最速で連れてきてくれるAI」
私はそう感じている。✨🔥

<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebOS 11</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
html, body {
width: 100%; height: 100%; overflow: hidden;
font-family: 'Segoe UI Variable', 'Segoe UI', -apple-system, 'Hiragino Sans', 'Meiryo', sans-serif;
user-select: none;
-webkit-font-smoothing: antialiased;
}
button { font-family: inherit; }
input, textarea { font-family: inherit; user-select: text; }
/* ============ BOOT SCREEN ============ */
#bootScreen {
position: fixed; inset: 0; background: #000 ;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
z-index: 100000; color: white;
transition: opacity 0.6s;
}
#bootScreen .hide { opacity: 0; pointer-events: none; }
.winLogo {
width: 80px; height: 80px;
display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
margin-bottom: 40px;
}
.winLogo div { background: #0078d4 ; border-radius: 2px; }
.bootSpinner {
width: 32px; height: 32px;
border: 3px solid transparent;
border-top-color: #0078d4 ;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }
/* ============ LOCK SCREEN ============ */
#lockScreen {
position: fixed; inset: 0;
background-image: url('https://image.qwenlm.ai/public_source/87ab8d20-e013-45cd-92ef-8628a57cd074/1989aafe5-8faf-43e5-8479-e5d226a78794.png');
background-size: cover; background-position: center;
display: flex; flex-direction: column;
align-items: center; justify-content: center;
z-index: 90000; color: white; cursor: pointer;
transition: transform 0.5s cubic-bezier(0.4, 0, 0.2, 1), opacity 0.5s;
}
#lockScreen .hide { transform: translateY(-100%); opacity: 0; pointer-events: none; }
.lockTime {
font-size: 120px; font-weight: 300; line-height: 1;
text-shadow: 0 4px 20px rgba(0,0,0,0.4);
}
.lockDate {
font-size: 28px; font-weight: 400; margin-top: 8px;
text-shadow: 0 2px 10px rgba(0,0,0,0.4);
}
.lockHint {
position: absolute; bottom: 60px;
font-size: 14px; opacity: 0.9;
animation: pulse 2s ease-in-out infinite;
}
@keyframes pulse { 0%,100%{opacity:0.6} 50%{opacity:1} }
/* ============ DESKTOP ============ */
#desktop {
position: fixed; inset: 0;
background-image: url('https://image.qwenlm.ai/public_source/87ab8d20-e013-45cd-92ef-8628a57cd074/1989aafe5-8faf-43e5-8479-e5d226a78794.png');
background-size: cover; background-position: center;
overflow: hidden;
}
/* Desktop Icons */
#desktopIcons {
position: absolute; top: 12px; left: 12px;
display: flex; flex-direction: column; flex-wrap: wrap;
gap: 4px; max-height: calc(100vh - 80px);
align-content: flex-start;
}
.dIcon {
width: 84px; padding: 8px 4px;
display: flex; flex-direction: column; align-items: center; gap: 4px;
border-radius: 4px; cursor: pointer;
border: 1px solid transparent;
transition: background 0.1s;
}
.dIcon:hover { background: rgba(255,255,255,0.15); }
.dIcon.sel { background: rgba(255,255,255,0.22); border-color: rgba(255,255,255,0.35); }
.dIcon .ic {
width: 40px; height: 40px;
display: flex; align-items: center; justify-content: center;
font-size: 32px;
filter: drop-shadow(0 2px 4px rgba(0,0,0,0.4));
}
.dIcon .lb {
color: white; font-size: 12px; text-align: center;
text-shadow: 0 1px 3px rgba(0,0,0,0.9);
line-height: 1.2; word-break: break-word;
}
/* ============ WINDOWS ============ */
#winLayer { position: absolute; inset: 0; pointer-events: none; }
.win {
position: absolute;
background: rgba(243, 243, 243, 0.85);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 1px solid rgba(0,0,0,0.08);
border-radius: 8px;
box-shadow: 0 18px 50px rgba(0,0,0,0.35), 0 4px 12px rgba(0,0,0,0.2);
display: flex; flex-direction: column;
overflow: hidden; pointer-events: auto;
min-width: 360px; min-height: 240px;
transition: opacity 0.18s, transform 0.18s;
}
.win.min { opacity: 0; transform: scale(0.85) translateY(40px); pointer-events: none; }
.win.max { border-radius: 0; }
.win.opening { animation: winOpen 0.22s cubic-bezier(0.1, 0.9, 0.2, 1); }
@keyframes winOpen {
from { opacity: 0; transform: scale(0.92); }
to { opacity: 1; transform: scale(1); }
}
.titleBar {
height: 36px; display: flex; align-items: stretch;
background: transparent; flex-shrink: 0;
cursor: default;
}
.titleBar .tbTitle {
flex: 1; display: flex; align-items: center; gap: 8px;
padding: 0 12px; font-size: 12px; color: #1a1a1a ;
}
.titleBar .tbTitle .ic { font-size: 14px; }
.tbCtrls { display: flex; }
.tbBtn {
width: 46px; height: 36px;
display: flex; align-items: center; justify-content: center;
cursor: pointer; transition: background 0.1s;
color: #1a1a1a ;
}
.tbBtn:hover { background: rgba(0,0,0,0.06); }
.tbBtn.close:hover { background: #c42b1c ; color: white; }
.tbBtn svg { width: 10px; height: 10px; stroke: currentColor; stroke-width: 1; fill: none; }
.winBody {
flex: 1; overflow: hidden; position: relative;
background: white;
display: flex; flex-direction: column;
}
.rH { position: absolute; z-index: 20; }
.rH.n { top: 0; left: 8px; right: 8px; height: 4px; cursor: n-resize; }
.rH.s { bottom: 0; left: 8px; right: 8px; height: 4px; cursor: s-resize; }
.rH.e { right: 0; top: 8px; bottom: 8px; width: 4px; cursor: e-resize; }
.rH.w { left: 0; top: 8px; bottom: 8px; width: 4px; cursor: w-resize; }
.rH.ne { top: 0; right: 0; width: 8px; height: 8px; cursor: ne-resize; }
.rH.nw { top: 0; left: 0; width: 8px; height: 8px; cursor: nw-resize; }
.rH.se { bottom: 0; right: 0; width: 8px; height: 8px; cursor: se-resize; }
.rH.sw { bottom: 0; left: 0; width: 8px; height: 8px; cursor: sw-resize; }
/* ============ TASKBAR ============ */
#taskbar {
position: absolute; bottom: 0; left: 0; right: 0; height: 48px;
background: rgba(32, 32, 32, 0.7);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border-top: 1px solid rgba(255,255,255,0.08);
display: flex; align-items: center; justify-content: space-between;
padding: 0 8px; z-index: 50000;
}
.tbCenter { display: flex; align-items: center; gap: 4px; }
.tbIcon {
width: 40px; height: 40px;
display: flex; align-items: center; justify-content: center;
border-radius: 6px; cursor: pointer;
transition: background 0.12s;
font-size: 22px; position: relative;
}
.tbIcon:hover { background: rgba(255,255,255,0.08); }
.tbIcon:active { background: rgba(255,255,255,0.04); transform: scale(0.95); }
.tbIcon.run::after {
content: ''; position: absolute; bottom: 2px; left: 50%;
transform: translateX(-50%);
width: 6px; height: 3px; background: #60cdff ;
border-radius: 2px;
}
.tbIcon.act::after {
content: ''; position: absolute; bottom: 2px; left: 50%;
transform: translateX(-50%);
width: 16px; height: 3px; background: #60cdff ;
border-radius: 2px;
}
.tbRight {
display: flex; align-items: center; gap: 2px;
color: white; font-size: 12px;
}
.trayBtn {
padding: 6px 10px; border-radius: 6px; cursor: pointer;
transition: background 0.12s;
display: flex; align-items: center; gap: 8px;
}
.trayBtn:hover { background: rgba(255,255,255,0.08); }
.clock {
text-align: right; line-height: 1.25;
padding: 4px 10px; border-radius: 6px; cursor: pointer;
}
.clock:hover { background: rgba(255,255,255,0.08); }
/* ============ START MENU ============ */
#startMenu {
position: absolute; bottom: 60px; left: 50%;
transform: translateX(-50%) translateY(20px) scale(0.96);
width: 640px; height: 700px;
background: rgba(243, 243, 243, 0.88);
backdrop-filter: blur(60px) saturate(180%);
-webkit-backdrop-filter: blur(60px) saturate(180%);
border: 1px solid rgba(255,255,255,0.4);
border-radius: 10px;
box-shadow: 0 24px 60px rgba(0,0,0,0.4);
padding: 36px 40px 24px;
opacity: 0; pointer-events: none;
transition: opacity 0.2s, transform 0.2s cubic-bezier(0.1, 0.9, 0.2, 1);
z-index: 60000;
display: flex; flex-direction: column;
}
#startMenu .open {
opacity: 1; pointer-events: auto;
transform: translateX(-50%) translateY(0) scale(1);
}
.smSearch {
width: 100%; height: 36px;
background: rgba(255,255,255,0.7);
border: 1px solid rgba(0,0,0,0.08);
border-bottom: 2px solid #0078d4 ;
border-radius: 6px; padding: 0 12px 0 38px;
font-size: 13px; outline: none;
background-image: url("data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='14' height='14' viewBox='0 0 24 24' fill='none' stroke='%23666' stroke-width='2'><circle cx='11' cy='11' r='7'/><path d='m20 20-3.5-3.5'/></svg>");
background-repeat: no-repeat; background-position: 12px center;
}
.smSection { margin-top: 24px; }
.smHead {
display: flex; justify-content: space-between; align-items: center;
margin-bottom: 12px;
}
.smHead h3 { font-size: 14px; font-weight: 600; color: #1a1a1a ; }
.smHead button {
font-size: 12px; padding: 4px 10px;
background: rgba(255,255,255,0.6); border: none;
border-radius: 4px; cursor: pointer; color: #1a1a1a ;
}
.smHead button:hover { background: rgba(255,255,255,0.9); }
.smGrid {
display: grid; grid-template-columns: repeat(6, 1fr);
gap: 4px;
}
.smTile {
display: flex; flex-direction: column; align-items: center;
padding: 12px 4px; border-radius: 6px; cursor: pointer;
transition: background 0.12s;
}
.smTile:hover { background: rgba(0,0,0,0.05); }
.smTile .ic { font-size: 30px; margin-bottom: 6px; }
.smTile .nm { font-size: 11px; color: #1a1a1a ; text-align: center; }
.smRecList { display: grid; grid-template-columns: 1fr 1fr; gap: 4px; }
.smRec {
display: flex; align-items: center; gap: 12px;
padding: 8px 10px; border-radius: 6px; cursor: pointer;
}
.smRec:hover { background: rgba(0,0,0,0.05); }
.smRec .ic { font-size: 24px; }
.smRec .info .nm { font-size: 12px; color: #1a1a1a ; font-weight: 500; }
.smRec .info .sub { font-size: 11px; color: #666 ; }
.smFoot {
margin-top: auto; padding-top: 16px;
border-top: 1px solid rgba(0,0,0,0.08);
display: flex; justify-content: space-between; align-items: center;
}
.smUser { display: flex; align-items: center; gap: 10px; padding: 6px 8px; border-radius: 6px; cursor: pointer; }
.smUser:hover { background: rgba(0,0,0,0.05); }
.smAvatar {
width: 32px; height: 32px; border-radius: 50%;
background: linear-gradient(135deg, #0078d4 , #00bcf2 );
display: flex; align-items: center; justify-content: center;
color: white; font-weight: 600; font-size: 14px;
}
.smPower {
width: 36px; height: 36px; border-radius: 6px;
display: flex; align-items: center; justify-content: center;
cursor: pointer; font-size: 16px;
}
.smPower:hover { background: rgba(0,0,0,0.05); }
/* ============ QUICK SETTINGS ============ */
#quickSettings {
position: absolute; bottom: 60px; right: 8px;
width: 360px;
background: rgba(243, 243, 243, 0.88);
backdrop-filter: blur(60px) saturate(180%);
-webkit-backdrop-filter: blur(60px) saturate(180%);
border: 1px solid rgba(255,255,255,0.4);
border-radius: 10px;
box-shadow: 0 24px 60px rgba(0,0,0,0.4);
padding: 20px;
opacity: 0; pointer-events: none;
transform: translateY(20px) scale(0.96);
transition: opacity 0.2s, transform 0.2s cubic-bezier(0.1, 0.9, 0.2, 1);
z-index: 60000;
}
#quickSettings .open {
opacity: 1; pointer-events: auto;
transform: translateY(0) scale(1);
}
.qsGrid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-bottom: 16px; }
.qsBtn {
background: rgba(255,255,255,0.6);
border-radius: 6px; padding: 12px 8px;
display: flex; flex-direction: column; align-items: center; gap: 6px;
cursor: pointer; font-size: 11px; color: #1a1a1a ;
transition: all 0.12s; min-height: 64px;
justify-content: center;
}
.qsBtn:hover { background: rgba(255,255,255,0.9); }
.qsBtn.on { background: #0078d4 ; color: white; }
.qsBtn .ic { font-size: 18px; }
.qsSlider { display: flex; align-items: center; gap: 10px; padding: 8px 4px; }
.qsSlider input[type=range] {
flex: 1; -webkit-appearance: none; appearance: none;
height: 4px; background: rgba(0,0,0,0.15); border-radius: 2px; outline: none;
}
.qsSlider input[type=range]::-webkit-slider-thumb {
-webkit-appearance: none; appearance: none;
width: 18px; height: 18px; border-radius: 50%;
background: #0078d4 ; cursor: pointer;
border: 4px solid white;
box-shadow: 0 1px 3px rgba(0,0,0,0.2);
}
/* ============ CONTEXT MENU ============ */
#ctxMenu {
position: absolute;
background: rgba(243, 243, 243, 0.92);
backdrop-filter: blur(40px) saturate(180%);
-webkit-backdrop-filter: blur(40px) saturate(180%);
border: 1px solid rgba(0,0,0,0.08);
border-radius: 8px;
box-shadow: 0 12px 32px rgba(0,0,0,0.25);
padding: 4px; min-width: 220px;
z-index: 70000; display: none;
}
.ctxItem {
padding: 8px 12px; border-radius: 4px;
font-size: 13px; cursor: pointer;
display: flex; align-items: center; gap: 12px;
color: #1a1a1a ;
}
.ctxItem:hover { background: rgba(0,0,0,0.05); }
.ctxItem .ic { width: 16px; text-align: center; }
.ctxSep { height: 1px; background: rgba(0,0,0,0.08); margin: 4px 0; }
/* ============ APP STYLES ============ */
/* File Explorer */
.feSide {
width: 220px; background: rgba(249,249,249,0.6);
border-right: 1px solid rgba(0,0,0,0.06);
padding: 8px; overflow-y: auto; font-size: 13px;
}
.feItem {
padding: 7px 10px; border-radius: 4px; cursor: pointer;
display: flex; align-items: center; gap: 10px;
color: #1a1a1a ;
}
.feItem:hover { background: rgba(0,0,0,0.04); }
.feItem.act { background: rgba(0,120,212,0.1); }
.feGroup { padding: 8px 10px 4px; font-size: 11px; color: #666 ; font-weight: 600; }
.feBar {
padding: 8px 12px; background: rgba(249,249,249,0.6);
border-bottom: 1px solid rgba(0,0,0,0.06);
display: flex; align-items: center; gap: 8px;
}
.feNavBtn {
width: 28px; height: 28px; border: none; background: transparent;
border-radius: 4px; cursor: pointer; font-size: 14px; color: #444 ;
}
.feNavBtn:hover { background: rgba(0,0,0,0.05); }
.fePath {
flex: 1; padding: 5px 12px; background: white;
border: 1px solid rgba(0,0,0,0.1); border-radius: 4px;
font-size: 12px;
}
.feGrid {
flex: 1; padding: 16px; overflow-y: auto;
display: grid; grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
gap: 4px; align-content: start;
}
.feFile {
display: flex; flex-direction: column; align-items: center;
padding: 10px 6px; border-radius: 4px; cursor: pointer;
text-align: center;
}
.feFile:hover { background: rgba(0,120,212,0.08); }
.feFile.sel { background: rgba(0,120,212,0.18); }
.feFile .ic { font-size: 40px; margin-bottom: 4px; }
.feFile .nm { font-size: 12px; word-break: break-word; color: #1a1a1a ; }
/* Notepad */
.npMenu {
padding: 4px 8px; background: rgba(249,249,249,0.6);
border-bottom: 1px solid rgba(0,0,0,0.06);
display: flex; gap: 4px; font-size: 12px;
}
.npMenu span { padding: 4px 8px; border-radius: 4px; cursor: pointer; }
.npMenu span:hover { background: rgba(0,0,0,0.05); }
.npArea {
flex: 1; width: 100%; border: none; outline: none;
padding: 14px; font-family: 'Cascadia Code', 'Consolas', monospace;
font-size: 14px; resize: none; line-height: 1.5;
background: white; color: #1a1a1a ;
}
.npStatus {
padding: 4px 12px; background: rgba(249,249,249,0.6);
border-top: 1px solid rgba(0,0,0,0.06);
font-size: 11px; color: #666 ;
display: flex; gap: 16px;
}
/* Calculator */
.calcWrap { display: flex; flex-direction: column; height: 100%; background: #f3f3f3 ; }
.calcHead { padding: 12px 16px; font-size: 13px; font-weight: 600; color: #1a1a1a ; }
.calcDisp {
padding: 8px 20px 16px; text-align: right;
min-height: 110px; display: flex; flex-direction: column; justify-content: flex-end;
}
.calcHist { font-size: 14px; color: #888 ; min-height: 18px; }
.calcCur { font-size: 42px; font-weight: 300; color: #1a1a1a ; overflow: hidden; }
.calcBtns {
flex: 1; display: grid; grid-template-columns: repeat(4, 1fr);
gap: 2px; padding: 4px;
}
.cBtn {
border: none; border-radius: 4px; cursor: pointer;
font-size: 16px; background: #fafafa ; color: #1a1a1a ;
transition: background 0.1s;
}
.cBtn:hover { background: #f0f0f0 ; }
.cBtn.op { background: #f0f0f0 ; }
.cBtn.op:hover { background: #e8e8e8 ; }
.cBtn.eq { background: #0078d4 ; color: white; }
.cBtn.eq:hover { background: #106ebe ; }
/* Settings */
.setSide {
width: 280px; background: rgba(249,249,249,0.6);
border-right: 1px solid rgba(0,0,0,0.06);
padding: 20px 12px; overflow-y: auto;
}
.setUser {
display: flex; align-items: center; gap: 12px;
padding: 12px; border-radius: 6px; margin-bottom: 16px;
background: rgba(255,255,255,0.5);
}
.setUser .av {
width: 48px; height: 48px; border-radius: 50%;
background: linear-gradient(135deg, #0078d4 , #00bcf2 );
display: flex; align-items: center; justify-content: center;
color: white; font-weight: 600; font-size: 18px;
}
.setUser .nm { font-size: 14px; font-weight: 600; color: #1a1a1a ; }
.setUser .em { font-size: 11px; color: #666 ; }
.setNav {
padding: 10px 12px; border-radius: 4px; cursor: pointer;
font-size: 13px; display: flex; align-items: center; gap: 12px;
color: #1a1a1a ; margin-bottom: 2px;
}
.setNav:hover { background: rgba(0,0,0,0.04); }
.setNav.act { background: rgba(0,120,212,0.1); color: #0078d4 ; font-weight: 500; }
.setContent { flex: 1; padding: 32px 40px; overflow-y: auto; background: #fafafa ; }
.setContent h1 { font-size: 28px; font-weight: 600; margin-bottom: 24px; color: #1a1a1a ; }
.setCard {
background: white; border-radius: 6px; padding: 16px 20px;
margin-bottom: 12px; border: 1px solid rgba(0,0,0,0.05);
}
.setRow {
display: flex; justify-content: space-between; align-items: center;
padding: 10px 0;
}
.setRow + .setRow { border-top: 1px solid rgba(0,0,0,0.05); }
.setRow .lb { font-size: 13px; color: #1a1a1a ; }
.setRow .ds { font-size: 12px; color: #666 ; margin-top: 2px; }
.toggle {
width: 40px; height: 20px; background: #888 ; border-radius: 10px;
position: relative; cursor: pointer; transition: background 0.2s;
}
.toggle.on { background: #0078d4 ; }
.toggle::after {
content: ''; position: absolute; top: 2px; left: 2px;
width: 16px; height: 16px; background: white; border-radius: 50%;
transition: left 0.2s;
}
.toggle.on::after { left: 22px; }
/* Browser */
.brBar {
padding: 8px 12px; background: rgba(249,249,249,0.8);
border-bottom: 1px solid rgba(0,0,0,0.06);
display: flex; align-items: center; gap: 6px;
}
.brBtn {
width: 30px; height: 30px; border: none; background: transparent;
border-radius: 4px; cursor: pointer; font-size: 14px; color: #444 ;
}
.brBtn:hover { background: rgba(0,0,0,0.05); }
.brUrl {
flex: 1; height: 30px; padding: 0 14px;
border: 1px solid transparent; border-radius: 15px;
background: white; font-size: 12px; outline: none;
}
.brUrl:focus { border-color: #0078d4 ; }
.brTabs {
display: flex; background: rgba(249,249,249,0.8);
padding: 6px 6px 0; gap: 2px;
}
.brTab {
padding: 8px 14px; background: white; border-radius: 8px 8px 0 0;
font-size: 12px; display: flex; align-items: center; gap: 8px;
cursor: pointer; max-width: 200px;
}
.brTab .x { opacity: 0.5; }
.brTab .x:hover { opacity: 1; }
.brPage { flex: 1; overflow-y: auto; background: white; }
/* Terminal */
.termWrap {
background: #0c0c0c ; color: #cccccc ; height: 100%;
font-family: 'Cascadia Code', 'Consolas', monospace;
font-size: 13px; padding: 12px; overflow-y: auto;
display: flex; flex-direction: column;
}
.termOut { white-space: pre-wrap; flex: 1; }
.termLine { display: flex; }
.termPrompt { color: #6ec8ff ; white-space: pre; }
.termInput {
flex: 1; background: transparent; border: none; outline: none;
color: #cccccc ; font-family: inherit; font-size: inherit;
}
/* Paint */
.ptBar {
padding: 8px 12px; background: rgba(249,249,249,0.8);
border-bottom: 1px solid rgba(0,0,0,0.06);
display: flex; gap: 6px; align-items: center; flex-wrap: wrap;
}
.ptBtn {
padding: 6px 10px; border: 1px solid transparent;
background: white; border-radius: 4px; cursor: pointer;
font-size: 12px; color: #1a1a1a ;
}
.ptBtn:hover { background: #f0f0f0 ; }
.ptBtn.act { border-color: #0078d4 ; background: #e6f2fb ; }
.ptColor {
width: 32px; height: 28px; border: 1px solid #ddd ;
border-radius: 4px; cursor: pointer; padding: 0;
}
.ptCanvasWrap {
flex: 1; overflow: auto; background: #d8d8d8 ;
padding: 16px; display: flex; align-items: flex-start; justify-content: flex-start;
}
.ptCanvas {
background: white; box-shadow: 0 2px 8px rgba(0,0,0,0.15);
cursor: crosshair;
}
/* About */
.aboutWrap { padding: 32px; overflow-y: auto; height: 100%; background: #fafafa ; }
.aboutCard {
background: white; border-radius: 8px; padding: 24px;
border: 1px solid rgba(0,0,0,0.05); max-width: 600px; margin: 0 auto;
}
.aboutLogo {
width: 80px; height: 80px; margin: 0 auto 16px;
display: grid; grid-template-columns: 1fr 1fr; gap: 4px;
}
.aboutLogo div { background: #0078d4 ; border-radius: 4px; }
/* Photos */
.phGrid {
flex: 1; padding: 16px; overflow-y: auto;
display: grid; grid-template-columns: repeat(auto-fill, minmax(160px, 1fr));
gap: 8px; align-content: start;
}
.phItem {
aspect-ratio: 1; border-radius: 6px; overflow: hidden;
cursor: pointer; position: relative;
background: linear-gradient(135deg, #667eea , #764ba2 );
}
.phItem:nth-child(2n) { background: linear-gradient(135deg, #f093fb , #f5576c ); }
.phItem:nth-child(3n) { background: linear-gradient(135deg, #4facfe , #00f2fe ); }
.phItem:nth-child(4n) { background: linear-gradient(135deg, #43e97b , #38f9d7 ); }
.phItem:nth-child(5n) { background: linear-gradient(135deg, #fa709a , #fee140 ); }
.phItem .lb {
position: absolute; bottom: 0; left: 0; right: 0;
padding: 8px; color: white; font-size: 11px;
background: linear-gradient(transparent, rgba(0,0,0,0.6));
}
/* Scrollbars */
::-webkit-scrollbar { width: 12px; height: 12px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
background: rgba(0,0,0,0.2); border-radius: 6px;
border: 3px solid transparent; background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover { background: rgba(0,0,0,0.4); background-clip: padding-box; border: 3px solid transparent; }
</style>
</head>
<body>
<!-- BOOT SCREEN -->
<div id="bootScreen">
<div class="winLogo"><div></div><div></div><div></div><div></div></div>
<div class="bootSpinner"></div>
</div>
<!-- LOCK SCREEN -->
<div id="lockScreen">
<div class="lockTime" id="lockTime">12:00</div>
<div class="lockDate" id="lockDate">月曜日, 7月20日</div>
<div class="lockHint">クリックしてロック解除</div>
</div>
<!-- DESKTOP -->
<div id="desktop">
<div id="desktopIcons"></div>
<div id="winLayer"></div>
<!-- START MENU -->
<div id="startMenu">
<input type="text" class="smSearch" placeholder="アプリ、設定、ドキュメントを検索" id="smSearchInput">
<div class="smSection">
<div class="smHead">
<h3>ピン留め済み</h3>
<button>すべてのアプリ ></button>
</div>
<div class="smGrid" id="smPinned"></div>
</div>
<div class="smSection" style="flex:1; overflow:hidden; display:flex; flex-direction:column;">
<div class="smHead">
<h3>おすすめ</h3>
<button>その他 ></button>
</div>
<div class="smRecList" id="smRec"></div>
</div>
<div class="smFoot">
<div class="smUser">
<div class="smAvatar">U</div>
<span style="font-size:13px; color:#1a1a1a;">ユーザー</span>
</div>
<div class="smPower" onclick="lockOS()" title="ロック">⏻</div>
</div>
</div>
<!-- QUICK SETTINGS -->
<div id="quickSettings">
<div class="qsGrid">
<div class="qsBtn on" data-qs="wifi"><div class="ic">📶</div><div>Wi-Fi</div><div style="font-size:10px; opacity:0.8;">HomeNet</div></div>
<div class="qsBtn on" data-qs="bt"><div class="ic">🔵</div><div>Bluetooth</div></div>
<div class="qsBtn" data-qs="air"><div class="ic">✈️</div><div>機内モード</div></div>
<div class="qsBtn" data-qs="save"><div class="ic">🔋</div><div>バッテリーセーバー</div></div>
<div class="qsBtn on" data-qs="night"><div class="ic">🌙</div><div>夜間モード</div></div>
<div class="qsBtn" data-qs="focus"><div class="ic">🎯</div><div>フォーカス</div></div>
</div>
<div class="qsSlider">
<span style="font-size:16px;">☀️</span>
<input type="range" min="20" max="100" value="80">
</div>
<div class="qsSlider">
<span style="font-size:16px;">🔊</span>
<input type="range" min="0" max="100" value="65">
</div>
<div style="display:flex; justify-content:space-between; align-items:center; margin-top:12px; padding-top:12px; border-top:1px solid rgba(0,0,0,0.08);">
<div style="display:flex; align-items:center; gap:8px; font-size:12px; color:#1a1a1a;">
<span>🔋</span><span>87%</span><span style="color:#666;">· 残り 5:42</span>
</div>
<div style="font-size:16px; cursor:pointer;" onclick="openApp('settings')">⚙️</div>
</div>
</div>
<!-- CONTEXT MENU -->
<div id="ctxMenu"></div>
<!-- TASKBAR -->
<div id="taskbar">
<div></div>
<div class="tbCenter">
<div class="tbIcon" id="tbStart" title="スタート">
<svg width="22" height="22" viewBox="0 0 24 24">
<rect x="3" y="3" width="8" height="8" fill="#0078d4" rx="1"/>
<rect x="13" y="3" width="8" height="8" fill="#0078d4" rx="1"/>
<rect x="3" y="13" width="8" height="8" fill="#0078d4" rx="1"/>
<rect x="13" y="13" width="8" height="8" fill="#0078d4" rx="1"/>
</svg>
</div>
<div class="tbIcon" title="検索">🔍</div>
<div class="tbIcon" title="タスクビュー">🗔</div>
<div id="tbApps" style="display:flex; gap:4px;"></div>
</div>
<div class="tbRight">
<div class="trayBtn" id="trayBtn">
<span>📶</span><span>🔊</span><span>🔋</span>
</div>
<div class="clock" id="clockDisp"></div>
</div>
</div>
</div>
<script>
// ============ APP REGISTRY ============
const APPS = {
explorer: { name: 'エクスプローラー', icon: '📁', w: 880, h: 560, render: renderExplorer },
notepad: { name: 'メモ帳', icon: '📝', w: 640, h: 480, render: renderNotepad },
calc: { name: '電卓', icon: '🧮', w: 340, h: 520, render: renderCalc },
settings: { name: '設定', icon: '⚙️', w: 900, h: 600, render: renderSettings },
browser: { name: 'Edge', icon: '🌐', w: 960, h: 640, render: renderBrowser },
terminal: { name: 'ターミナル', icon: '⌨️', w: 760, h: 480, render: renderTerminal },
paint: { name: 'ペイント', icon: '🎨', w: 820, h: 580, render: renderPaint },
photos: { name: 'フォト', icon: '🖼️', w: 760, h: 540, render: renderPhotos },
store: { name: 'Microsoft Store', icon: '🛍️', w: 880, h: 600, render: renderStore },
about: { name: 'システム情報', icon: '💻', w: 640, h: 520, render: renderAbout },
recycle: { name: 'ごみ箱', icon: '🗑️', w: 720, h: 480, render: renderRecycle }
};
const DESKTOP_ICONS = ['explorer', 'recycle', 'browser', 'notepad', 'about'];
const PINNED_APPS = ['explorer', 'browser', 'notepad', 'calc', 'settings', 'paint', 'terminal', 'photos', 'store', 'about', 'recycle'];
const REC_APPS = [
{ id: 'notepad', sub: '最近使用' },
{ id: 'photos', sub: '昨日' },
{ id: 'browser', sub: '2時間前' },
{ id: 'explorer', sub: '先週' }
];
// ============ STATE ============
let winList = [];
let zCounter = 100;
let activeWin = null;
let dragInfo = null;
let resizeInfo = null;
// ============ BOOT SEQUENCE ============
setTimeout(() => {
document.getElementById('bootScreen').classList.add('hide');
}, 1800);
document.getElementById('lockScreen').addEventListener('click', () => {
document.getElementById('lockScreen').classList.add('hide');
});
function lockOS() {
closeStart();
document.getElementById('lockScreen').classList.remove('hide');
}
// ============ CLOCK ============
function tickClock() {
const n = new Date();
const hh = String(n.getHours()).padStart(2,'0');
const mm = String(n.getMinutes()).padStart(2,'0');
const y = n.getFullYear();
const mo = String(n.getMonth()+1).padStart(2,'0');
const d = String(n.getDate()).padStart(2,'0');
document.getElementById('clockDisp').innerHTML =
`<div>${hh}:${mm}</div><div>${y}/${mo}/${d}</div>`;
document.getElementById('lockTime').textContent = `${hh}:${mm}`;
const days = ['日曜日','月曜日','火曜日','水曜日','木曜日','金曜日','土曜日'];
document.getElementById('lockDate').textContent =
`${days[n.getDay()]}, ${n.getMonth()+1}月${n.getDate()}日`;
}
tickClock();
setInterval(tickClock, 1000);
// ============ DESKTOP ICONS ============
function buildDesktop() {
const c = document.getElementById('desktopIcons');
c.innerHTML = '';
DESKTOP_ICONS.forEach(id => {
const a = APPS[id];
const el = document.createElement('div');
el.className = 'dIcon';
el.innerHTML = `<div class="ic">${a.icon}</div><div class="lb">${a.name}</div>`;
el.addEventListener('click', (e) => {
e.stopPropagation();
document.querySelectorAll('.dIcon').forEach(x => x.classList.remove('sel'));
el.classList.add('sel');
});
el.addEventListener('dblclick', () => openApp(id));
c.appendChild(el);
});
}
buildDesktop();
// ============ START MENU ============
function buildStart() {
const pin = document.getElementById('smPinned');
pin.innerHTML = '';
PINNED_APPS.forEach(id => {
const a = APPS[id];
const t = document.createElement('div');
t.className = 'smTile';
t.innerHTML = `<div class="ic">${a.icon}</div><div class="nm">${a.name}</div>`;
t.onclick = () => { openApp(id); closeStart(); };
pin.appendChild(t);
});
const rec = document.getElementById('smRec');
rec.innerHTML = '';
REC_APPS.forEach(r => {
const a = APPS[r.id];
const t = document.createElement('div');
t.className = 'smRec';
t.innerHTML = `<div class="ic">${a.icon}</div><div class="info"><div class="nm">${a.name}</div><div class="sub">${r.sub}</div></div>`;
t.onclick = () => { openApp(r.id); closeStart(); };
rec.appendChild(t);
});
}
buildStart();
function toggleStart(e) {
e && e.stopPropagation();
const m = document.getElementById('startMenu');
closeQuick();
m.classList.toggle('open');
}
function closeStart() { document.getElementById('startMenu').classList.remove('open'); }
function toggleQuick(e) {
e && e.stopPropagation();
const q = document.getElementById('quickSettings');
closeStart();
q.classList.toggle('open');
}
function closeQuick() { document.getElementById('quickSettings').classList.remove('open'); }
document.getElementById('tbStart').addEventListener('click', toggleStart);
document.getElementById('trayBtn').addEventListener('click', toggleQuick);
document.getElementById('clockDisp').addEventListener('click', toggleQuick);
document.querySelectorAll('[data-qs]').forEach(b => {
b.addEventListener('click', () => b.classList.toggle('on'));
});
// ============ CONTEXT MENU ============
document.getElementById('desktop').addEventListener('contextmenu', (e) => {
if (e.target.closest('.win') || e.target.closest('#taskbar') ||
e.target.closest('#startMenu') || e.target.closest('#quickSettings')) return;
e.preventDefault();
showCtx(e.clientX, e.clientY, [
{ ic: '👁️', lb: '表示', fn: () => {} },
{ ic: '🔃', lb: '最新の情報に更新', fn: () => {} },
{ sep: true },
{ ic: '📋', lb: '貼り付け', fn: () => {} },
{ ic: '⌨️', lb: 'ターミナルで開く', fn: () => openApp('terminal') },
{ sep: true },
{ ic: '🖼️', lb: '個人用設定', fn: () => openApp('settings') },
{ ic: '💻', lb: 'ディスプレイ設定', fn: () => openApp('settings') },
{ sep: true },
{ ic: 'ℹ️', lb: 'システム情報', fn: () => openApp('about') }
]);
});
function showCtx(x, y, items) {
const m = document.getElementById('ctxMenu');
m.innerHTML = '';
items.forEach(it => {
if (it.sep) {
const s = document.createElement('div');
s.className = 'ctxSep';
m.appendChild(s);
} else {
const i = document.createElement('div');
i.className = 'ctxItem';
i.innerHTML = `<span class="ic">${it.ic}</span><span>${it.lb}</span>`;
i.onclick = () => { it.fn(); m.style.display = 'none'; };
m.appendChild(i);
}
});
m.style.display = 'block';
const mw = m.offsetWidth, mh = m.offsetHeight;
m.style.left = Math.min(x, window.innerWidth - mw - 4) + 'px';
m.style.top = Math.min(y, window.innerHeight - mh - 4) + 'px';
}
document.addEventListener('click', (e) => {
if (!e.target.closest('#ctxMenu')) document.getElementById('ctxMenu').style.display = 'none';
if (!e.target.closest('#startMenu') && !e.target.closest('#tbStart')) closeStart();
if (!e.target.closest('#quickSettings') && !e.target.closest('#trayBtn') && !e.target.closest('#clockDisp')) closeQuick();
if (!e.target.closest('.dIcon')) document.querySelectorAll('.dIcon').forEach(x => x.classList.remove('sel'));
});
// ============ WINDOW MANAGEMENT ============
function openApp(id) {
const app = APPS[id];
if (!app) return;
const wid = 'w' + Date.now() + Math.floor(Math.random()*1000);
const w = app.w, h = app.h;
const off = (winList.length * 28) % 120;
const x = Math.max(20, (window.innerWidth - w) / 2 + off - 60);
const y = Math.max(20, (window.innerHeight - h - 60) / 2 + off - 60);
const win = { id: wid, appId: id, x, y, w, h, min: false, max: false, prev: null };
winList.push(win);
const el = document.createElement('div');
el.className = 'win opening';
el.id = wid;
el.style.left = x + 'px';
el.style.top = y + 'px';
el.style.width = w + 'px';
el.style.height = h + 'px';
el.style.zIndex = ++zCounter;
el.innerHTML = `
<div class="titleBar" data-wid="${wid}">
<div class="tbTitle"><span class="ic">${app.icon}</span><span>${app.name}</span></div>
<div class="tbCtrls">
<div class="tbBtn" data-act="min" title="最小化">
<svg viewBox="0 0 10 10"><line x1="0" y1="5" x2="10" y2="5"/></svg>
</div>
<div class="tbBtn" data-act="max" title="最大化">
<svg viewBox="0 0 10 10"><rect x="0.5" y="0.5" width="9" height="9"/></svg>
</div>
<div class="tbBtn close" data-act="close" title="閉じる">
<svg viewBox="0 0 10 10"><line x1="0" y1="0" x2="10" y2="10"/><line x1="10" y1="0" x2="0" y2="10"/></svg>
</div>
</div>
</div>
<div class="winBody" id="${wid}-body"></div>
<div class="rH n" data-dir="n"></div>
<div class="rH s" data-dir="s"></div>
<div class="rH e" data-dir="e"></div>
<div class="rH w" data-dir="w"></div>
<div class="rH ne" data-dir="ne"></div>
<div class="rH nw" data-dir="nw"></div>
<div class="rH se" data-dir="se"></div>
<div class="rH sw" data-dir="sw"></div>
`;
document.getElementById('winLayer').appendChild(el);
setTimeout(() => el.classList.remove('opening'), 240);
app.render(document.getElementById(`${wid}-body`), wid);
// Title bar drag
const tb = el.querySelector('.titleBar');
tb.addEventListener('mousedown', (e) => {
if (e.target.closest('.tbBtn')) return;
focusWin(wid);
const w = winList.find(x => x.id === wid);
if (w.max) return;
dragInfo = { wid, sx: e.clientX, sy: e.clientY, ox: w.x, oy: w.y };
e.preventDefault();
});
tb.addEventListener('dblclick', (e) => {
if (e.target.closest('.tbBtn')) return;
toggleMax(wid);
});
// Control buttons
el.querySelectorAll('.tbBtn').forEach(b => {
b.addEventListener('click', () => {
const a = b.dataset.act;
if (a === 'min') minWin(wid);
else if (a === 'max') toggleMax(wid);
else if (a === 'close') closeWin(wid);
});
});
// Resize handles
el.querySelectorAll('.rH').forEach(h => {
h.addEventListener('mousedown', (e) => {
e.stopPropagation();
focusWin(wid);
const w = winList.find(x => x.id === wid);
if (w.max) return;
resizeInfo = {
wid, dir: h.dataset.dir,
sx: e.clientX, sy: e.clientY,
ox: w.x, oy: w.y, ow: w.w, oh: w.h
};
e.preventDefault();
});
});
el.addEventListener('mousedown', () => focusWin(wid));
focusWin(wid);
updateTaskbar();
}
function focusWin(wid) {
const el = document.getElementById(wid);
if (!el) return;
el.style.zIndex = ++zCounter;
activeWin = wid;
const w = winList.find(x => x.id === wid);
if (w && w.min) {
w.min = false;
el.classList.remove('min');
}
updateTaskbar();
}
function minWin(wid) {
const w = winList.find(x => x.id === wid);
const el = document.getElementById(wid);
if (!w || !el) return;
w.min = true;
el.classList.add('min');
updateTaskbar();
}
function toggleMax(wid) {
const w = winList.find(x => x.id === wid);
const el = document.getElementById(wid);
if (!w || !el) return;
if (w.max) {
w.max = false;
el.classList.remove('max');
if (w.prev) { w.x = w.prev.x; w.y = w.prev.y; w.w = w.prev.w; w.h = w.prev.h; }
el.style.left = w.x + 'px';
el.style.top = w.y + 'px';
el.style.width = w.w + 'px';
el.style.height = w.h + 'px';
} else {
w.prev = { x: w.x, y: w.y, w: w.w, h: w.h };
w.max = true;
el.classList.add('max');
el.style.left = '0px';
el.style.top = '0px';
el.style.width = '100vw';
el.style.height = 'calc(100vh - 48px)';
}
}
function closeWin(wid) {
const el = document.getElementById(wid);
if (el) el.remove();
winList = winList.filter(w => w.id !== wid);
updateTaskbar();
}
function updateTaskbar() {
const c = document.getElementById('tbApps');
c.innerHTML = '';
const seen = new Set();
winList.forEach(w => {
if (seen.has(w.appId)) return;
seen.add(w.appId);
const a = APPS[w.appId];
const b = document.createElement('div');
b.className = 'tbIcon run';
if (activeWin === w.id && !w.min) b.classList.add('act');
b.innerHTML = `<span style="font-size:22px;">${a.icon}</span>`;
b.title = a.name;
b.onclick = () => {
// find topmost window of this app
const wins = winList.filter(x => x.appId === w.appId);
const top = wins[wins.length - 1];
if (activeWin === top.id && !top.min) minWin(top.id);
else focusWin(top.id);
};
c.appendChild(b);
});
}
document.addEventListener('mousemove', (e) => {
if (dragInfo) {
const w = winList.find(x => x.id === dragInfo.wid);
const el = document.getElementById(dragInfo.wid);
if (!w || !el) return;
w.x = dragInfo.ox + (e.clientX - dragInfo.sx);
w.y = Math.max(0, dragInfo.oy + (e.clientY - dragInfo.sy));
el.style.left = w.x + 'px';
el.style.top = w.y + 'px';
}
if (resizeInfo) {
const w = winList.find(x => x.id === resizeInfo.wid);
const el = document.getElementById(resizeInfo.wid);
if (!w || !el) return;
const dx = e.clientX - resizeInfo.sx;
const dy = e.clientY - resizeInfo.sy;
let { ox, oy, ow, oh, dir } = resizeInfo;
let nx = ox, ny = oy, nw = ow, nh = oh;
if (dir.includes('e')) nw = Math.max(360, ow + dx);
if (dir.includes('s')) nh = Math.max(240, oh + dy);
if (dir.includes('w')) { nw = Math.max(360, ow - dx); nx = ox + (ow - nw); }
if (dir.includes('n')) { nh = Math.max(240, oh - dy); ny = oy + (oh - nh); }
w.x = nx; w.y = ny; w.w = nw; w.h = nh;
el.style.left = nx + 'px';
el.style.top = ny + 'px';
el.style.width = nw + 'px';
el.style.height = nh + 'px';
}
});
document.addEventListener('mouseup', () => { dragInfo = null; resizeInfo = null; });
// ============ APP: FILE EXPLORER ============
function renderExplorer(body, wid) {
const folders = {
'ホーム': { ic: '🏠', files: [
{ ic: '📁', nm: 'デスクトップ' }, { ic: '📁', nm: 'ドキュメント' },
{ ic: '📁', nm: 'ダウンロード' }, { ic: '📁', nm: 'ピクチャ' },
{ ic: '📁', nm: 'ミュージック' }, { ic: '📁', nm: 'ビデオ' }
]},
'デスクトップ': { ic: '🖥️', files: [
{ ic: '📄', nm: 'readme.txt' }, { ic: '🖼️', nm: 'wallpaper.png' },
{ ic: '📁', nm: 'Projects' }, { ic: '📊', nm: 'report.xlsx' }
]},
'ドキュメント': { ic: '📄', files: [
{ ic: '📄', nm: 'report.docx' }, { ic: '📊', nm: 'budget.xlsx' },
{ ic: '📄', nm: 'notes.txt' }, { ic: '📁', nm: 'Work' },
{ ic: '📕', nm: 'manual.pdf' }
]},
'ダウンロード': { ic: '⬇️', files: [
{ ic: '📦', nm: 'installer.exe' }, { ic: '🖼️', nm: 'photo.jpg' },
{ ic: '🎵', nm: 'song.mp3' }, { ic: '🗜️', nm: 'archive.zip' }
]},
'ピクチャ': { ic: '🖼️', files: [
{ ic: '🖼️', nm: 'vacation.jpg' }, { ic: '🖼️', nm: 'family.png' },
{ ic: '🖼️', nm: 'screenshot.png' }, { ic: '🖼️', nm: 'art.jpg' }
]},
'ミュージック': { ic: '🎵', files: [
{ ic: '🎵', nm: 'track01.mp3' }, { ic: '🎵', nm: 'track02.mp3' },
{ ic: '🎵', nm: 'playlist.m3u' }
]},
'ビデオ': { ic: '🎬', files: [
{ ic: '🎬', nm: 'movie.mp4' }, { ic: '🎬', nm: 'clip.mov' }
]},
'PC': { ic: '💻', files: [
{ ic: '💾', nm: 'ローカルディスク (C:)' },
{ ic: '💿', nm: 'データ (D:)' },
{ ic: '💽', nm: 'USBドライブ (E:)' }
]}
};
let cur = 'ホーム';
body.innerHTML = `
<div class="feBar">
<button class="feNavBtn">←</button>
<button class="feNavBtn">→</button>
<button class="feNavBtn">↑</button>
<div class="fePath" id="${wid}-path">🏠 ホーム</div>
<input type="text" placeholder="検索" style="width:180px; padding:5px 10px; border:1px solid rgba(0,0,0,0.1); border-radius:4px; font-size:12px; outline:none;">
</div>
<div style="display:flex; flex:1; overflow:hidden;">
<div class="feSide" id="${wid}-side"></div>
<div class="feGrid" id="${wid}-grid"></div>
</div>
`;
const side = body.querySelector(`#${wid}-side`);
const grid = body.querySelector(`#${wid}-grid`);
const path = body.querySelector(`#${wid}-path`);
side.innerHTML = `
<div class="feGroup">クイックアクセス</div>
${Object.keys(folders).map(k =>
`<div class="feItem ${k===cur?'act':''}" data-k="${k}"><span>${folders[k].ic}</span><span>${k}</span></div>`
).join('')}
<div class="feGroup">ネットワーク</div>
<div class="feItem"><span>🌐</span><span>ネットワーク</span></div>
`;
function renderGrid() {
const f = folders[cur];
path.innerHTML = `${f.ic} ${cur}`;
grid.innerHTML = f.files.map((x,i) =>
`<div class="feFile" data-i="${i}"><div class="ic">${x.ic}</div><div class="nm">${x.nm}</div></div>`
).join('');
grid.querySelectorAll('.feFile').forEach(el => {
el.onclick = () => {
grid.querySelectorAll('.feFile').forEach(x => x.classList.remove('sel'));
el.classList.add('sel');
};
el.ondblclick = () => {
const f = folders[cur].files[el.dataset.i];
if (f.ic === '📁') {
const target = Object.keys(folders).find(k => k === f.nm);
if (target) { cur = target; renderSide(); renderGrid(); }
}
};
});
}
function renderSide() {
side.querySelectorAll('.feItem').forEach(it => {
it.classList.toggle('act', it.dataset.k === cur);
});
}
side.querySelectorAll('.feItem[data-k]').forEach(it => {
it.onclick = () => { cur = it.dataset.k; renderSide(); renderGrid(); };
});
renderGrid();
}
// ============ APP: NOTEPAD ============
function renderNotepad(body, wid) {
body.innerHTML = `
<div class="npMenu">
<span>ファイル</span><span>編集</span><span>表示</span>
</div>
<textarea class="npArea" id="${wid}-ta" spellcheck="false">WebOS 11 へようこそ!
これはブラウザ上で動作する Windows 11 風 OS です。
機能一覧:
• ドラッグでウィンドウ移動
• 8方向リサイズ
• 最小化 / 最大化 / 閉じる
• 中央配置タスクバー
• スタートメニュー(ピン留め + おすすめ)
• クイック設定パネル
• 右クリックコンテキストメニュー
• 複数の動作アプリ
自由に編集してください。</textarea>
<div class="npStatus">
<span id="${wid}-pos">行 1, 列 1</span>
<span>UTF-8</span>
<span>Windows (CRLF)</span>
</div>
`;
const ta = body.querySelector(`#${wid}-ta`);
const pos = body.querySelector(`#${wid}-pos`);
ta.addEventListener('keyup', upd);
ta.addEventListener('click', upd);
function upd() {
const v = ta.value.substr(0, ta.selectionStart);
const lines = v.split('\n');
pos.textContent = `行 ${lines.length}, 列 ${lines[lines.length-1].length + 1}`;
}
}
// ============ APP: CALCULATOR ============
function renderCalc(body, wid) {
let cur = '0', hist = '', prev = null, op = null, justEq = false;
body.innerHTML = `
<div class="calcWrap">
<div class="calcHead">標準</div>
<div class="calcDisp">
<div class="calcHist" id="${wid}-h"></div>
<div class="calcCur" id="${wid}-c">0</div>
</div>
<div class="calcBtns">
<button class="cBtn op" data-a="pct">%</button>
<button class="cBtn op" data-a="ce">CE</button>
<button class="cBtn op" data-a="c">C</button>
<button class="cBtn op" data-a="bk">⌫</button>
<button class="cBtn op" data-a="inv">¹/ₓ</button>
<button class="cBtn op" data-a="sq">x²</button>
<button class="cBtn op" data-a="rt">√</button>
<button class="cBtn op" data-op="/">÷</button>
<button class="cBtn" data-n="7">7</button>
<button class="cBtn" data-n="8">8</button>
<button class="cBtn" data-n="9">9</button>
<button class="cBtn op" data-op="*">×</button>
<button class="cBtn" data-n="4">4</button>
<button class="cBtn" data-n="5">5</button>
<button class="cBtn" data-n="6">6</button>
<button class="cBtn op" data-op="-">−</button>
<button class="cBtn" data-n="1">1</button>
<button class="cBtn" data-n="2">2</button>
<button class="cBtn" data-n="3">3</button>
<button class="cBtn op" data-op="+">+</button>
<button class="cBtn" data-a="neg">±</button>
<button class="cBtn" data-n="0">0</button>
<button class="cBtn" data-n=".">.</button>
<button class="cBtn eq" data-a="eq">=</button>
</div>
</div>
`;
const cEl = body.querySelector(`#${wid}-c`);
const hEl = body.querySelector(`#${wid}-h`);
const sym = {'+':'+','-':'−','*':'×','/':'÷'};
function upd() {
let s = cur;
if (s.length > 12) s = parseFloat(s).toPrecision(10);
cEl.textContent = s;
hEl.textContent = hist;
}
function compute(a, b, o) {
a = parseFloat(a); b = parseFloat(b);
if (o === '+') return a + b;
if (o === '-') return a - b;
if (o === '*') return a * b;
if (o === '/') return b === 0 ? 'エラー' : a / b;
}
body.querySelectorAll('.cBtn').forEach(b => {
b.onclick = () => {
if (b.dataset.n !== undefined) {
if (justEq) { cur = '0'; justEq = false; }
if (b.dataset.n === '.') { if (!cur.includes('.')) cur += '.'; }
else cur = cur === '0' ? b.dataset.n : cur + b.dataset.n;
} else if (b.dataset.op) {
if (prev !== null && op && !justEq) cur = String(compute(prev, cur, op));
prev = cur; op = b.dataset.op;
hist = `${prev} ${sym[op]}`;
cur = '0'; justEq = false;
} else {
const a = b.dataset.a;
if (a === 'c') { cur = '0'; hist = ''; prev = null; op = null; }
else if (a === 'ce') cur = '0';
else if (a === 'bk') cur = cur.length > 1 ? cur.slice(0,-1) : '0';
else if (a === 'neg') cur = cur.startsWith('-') ? cur.slice(1) : (cur !== '0' ? '-'+cur : '0');
else if (a === 'pct') cur = String(parseFloat(cur)/100);
else if (a === 'inv') cur = String(1/parseFloat(cur));
else if (a === 'sq') cur = String(Math.pow(parseFloat(cur),2));
else if (a === 'rt') cur = String(Math.sqrt(parseFloat(cur)));
else if (a === 'eq') {
if (prev !== null && op) {
hist = `${prev} ${sym[op]} ${cur} =`;
cur = String(compute(prev, cur, op));
prev = null; op = null; justEq = true;
}
}
}
upd();
};
});
}
// ============ APP: SETTINGS ============
function renderSettings(body, wid) {
const sections = {
system: { ic: '💻', nm: 'システム' },
personal: { ic: '🎨', nm: '個人用設定' },
network: { ic: '📶', nm: 'ネットワークとインターネット' },
apps: { ic: '📦', nm: 'アプリ' },
account: { ic: '👤', nm: 'アカウント' },
time: { ic: '🕐', nm: '時刻と言語' },
game: { ic: '🎮', nm: 'ゲーム' },
access: { ic: '♿', nm: 'アクセシビリティ' },
privacy: { ic: '🔒', nm: 'プライバシーとセキュリティ' },
update: { ic: '🔄', nm: 'Windows Update' }
};
let cur = 'system';
body.innerHTML = `
<div style="display:flex; flex:1; overflow:hidden;">
<div class="setSide">
<div class="setUser">
<div class="av">U</div>
<div><div class="nm">ユーザー</div><div class="em">user@webos.local</div></div>
</div>
<input type="text" placeholder="設定を検索" style="width:100%; padding:7px 12px; border:1px solid rgba(0,0,0,0.1); border-radius:4px; font-size:12px; outline:none; margin-bottom:12px;">
<div id="${wid}-nav"></div>
</div>
<div class="setContent" id="${wid}-content"></div>
</div>
`;
const nav = body.querySelector(`#${wid}-nav`);
const content = body.querySelector(`#${wid}-content`);
const contents = {
system: `
<h1>システム</h1>
<div class="setCard">
<div class="setRow"><div><div class="lb">ディスプレイ</div><div class="ds">解像度、明るさ、夜間モード</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">サウンド</div><div class="ds">音量、出力デバイス</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">通知</div><div class="ds">アプリからの通知</div></div><div class="toggle on" onclick="this.classList.toggle('on')"></div></div>
<div class="setRow"><div><div class="lb">フォーカス</div><div class="ds">集中モードの設定</div></div><div class="toggle" onclick="this.classList.toggle('on')"></div></div>
<div class="setRow"><div><div class="lb">電源とバッテリー</div><div class="ds">スリープ、バッテリー使用量</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">ストレージ</div><div class="ds">256 GB 中 128 GB 使用</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">マルチタスク</div><div class="ds">スナップウィンドウ、仮想デスクトップ</div></div><span>›</span></div>
<div class="setRow" style="cursor:pointer;" onclick="openApp('about')"><div><div class="lb">このPCについて</div><div class="ds">デバイス仕様、Windows 仕様</div></div><span>›</span></div>
</div>`,
personal: `
<h1>個人用設定</h1>
<div class="setCard">
<div class="setRow"><div><div class="lb">背景</div><div class="ds">壁紙、スライドショー</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">色</div><div class="ds">アクセントカラー、モード</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">テーマ</div><div class="ds">Windows テーマの保存と作成</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">ロック画面</div><div class="ds">画像、スライドショー</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">スタート</div><div class="ds">レイアウト、フォルダー</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">タスクバー</div><div class="ds">動作、バッジ</div></div><span>›</span></div>
</div>
<div class="setCard">
<div style="font-size:13px; font-weight:600; margin-bottom:12px;">アクセントカラー</div>
<div style="display:grid; grid-template-columns:repeat(8,1fr); gap:8px;">
${['#0078d4','#0099bc','#7a7574','#767676','#ff8c00','#e81123','#c30052','#881798'].map((c,i) =>
`<div style="aspect-ratio:1; background:${c}; border-radius:4px; cursor:pointer; ${i===0?'outline:2px solid #000 ; outline-offset:2px;':''}"></div>`
).join('')}
</div>
</div>`,
network: `
<h1>ネットワークとインターネット</h1>
<div class="setCard">
<div class="setRow"><div><div class="lb">Wi-Fi</div><div class="ds">接続済み - HomeNetwork</div></div><div class="toggle on" onclick="this.classList.toggle('on')"></div></div>
<div class="setRow"><div><div class="lb">利用可能なネットワーク</div><div class="ds">Neighbor_WiFi, CoffeeShop, Office_5G</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">データ使用量</div><div class="ds">今月 12.4 GB 使用</div></div><span>›</span></div>
</div>`,
apps: `<h1>アプリ</h1><div class="setCard"><div class="setRow"><div><div class="lb">インストール済みアプリ</div><div class="ds">11 個のアプリ</div></div><span>›</span></div><div class="setRow"><div><div class="lb">既定のアプリ</div></div><span>›</span></div><div class="setRow"><div><div class="lb">スタートアップ</div></div><span>›</span></div></div>`,
account: `<h1>アカウント</h1><div class="setCard"><div class="setRow"><div><div class="lb">ユーザー情報</div><div class="ds">ユーザー / user@webos.local</div></div><span>›</span></div><div class="setRow"><div><div class="lb">サインインオプション</div></div><span>›</span></div></div>`,
time: `<h1>時刻と言語</h1><div class="setCard"><div class="setRow"><div><div class="lb">日付と時刻</div><div class="ds">${new Date().toLocaleString('ja-JP')}</div></div><span>›</span></div><div class="setRow"><div><div class="lb">言語</div><div class="ds">日本語</div></div><span>›</span></div></div>`,
game: `<h1>ゲーム</h1><div class="setCard"><div class="setRow"><div><div class="lb">Xbox Game Bar</div></div><div class="toggle on" onclick="this.classList.toggle('on')"></div></div></div>`,
access: `<h1>アクセシビリティ</h1><div class="setCard"><div class="setRow"><div><div class="lb">テキストサイズ</div></div><span>›</span></div><div class="setRow"><div><div class="lb">視覚効果</div></div><span>›</span></div><div class="setRow"><div><div class="lb">マウスポインターとタッチ</div></div><span>›</span></div></div>`,
privacy: `<h1>プライバシーとセキュリティ</h1><div class="setCard"><div class="setRow"><div><div class="lb">Windows セキュリティ</div><div class="ds">保護は最新の状態です</div></div><span style="color:#107c10;">✓</span></div><div class="setRow"><div><div class="lb">位置情報</div></div><div class="toggle on" onclick="this.classList.toggle('on')"></div></div></div>`,
update: `
<h1>Windows Update</h1>
<div class="setCard" style="background:linear-gradient(135deg,#e6f2fb,#f0f9ff);">
<div style="display:flex; align-items:center; gap:16px;">
<div style="font-size:36px;">✓</div>
<div style="flex:1;">
<div style="font-size:16px; font-weight:600;">最新の状態です</div>
<div style="font-size:12px; color:#666; margin-top:4px;">最終チェック: 今日 ${new Date().toLocaleTimeString('ja-JP',{hour:'2-digit',minute:'2-digit'})}</div>
</div>
<button style="padding:8px 16px; background:#0078d4; color:white; border:none; border-radius:4px; cursor:pointer; font-size:13px;">更新プログラムのチェック</button>
</div>
</div>
<div class="setCard">
<div class="setRow"><div><div class="lb">更新を一時停止</div><div class="ds">1週間</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">更新履歴</div></div><span>›</span></div>
<div class="setRow"><div><div class="lb">詳細オプション</div></div><span>›</span></div>
</div>`
};
function render() {
nav.innerHTML = Object.keys(sections).map(k =>
`<div class="setNav ${k===cur?'act':''}" data-k="${k}"><span>${sections[k].ic}</span><span>${sections[k].nm}</span></div>`
).join('');
nav.querySelectorAll('.setNav').forEach(n => {
n.onclick = () => { cur = n.dataset.k; render(); };
});
content.innerHTML = contents[cur];
}
render();
}
// ============ APP: BROWSER ============
function renderBrowser(body, wid) {
body.innerHTML = `
<div class="brTabs">
<div class="brTab"><span>🌐</span><span>新しいタブ</span><span class="x">×</span></div>
<div class="brTab" style="background:transparent;">+</div>
</div>
<div class="brBar">
<button class="brBtn">←</button>
<button class="brBtn">→</button>
<button class="brBtn">⟳</button>
<input type="text" class="brUrl" id="${wid}-url" value="edge://home">
<button class="brBtn">⭐</button>
<button class="brBtn">⋯</button>
</div>
<div class="brPage" id="${wid}-page"></div>
`;
const page = body.querySelector(`#${wid}-page`);
const url = body.querySelector(`#${wid}-url`);
function renderHome() {
page.innerHTML = `
<div style="padding:60px 32px; text-align:center; max-width:700px; margin:0 auto;">
<div style="font-size:64px; margin-bottom:16px;">🌐</div>
<h1 style="font-size:32px; font-weight:300; margin-bottom:24px; color:#1a1a1a;">WebOS ブラウザ</h1>
<div style="display:flex; max-width:500px; margin:0 auto 32px; background:white; border:1px solid #ddd ; border-radius:24px; padding:4px; box-shadow:0 4px 12px rgba(0,0,0,0.06);">
<input type="text" placeholder="Web を検索" style="flex:1; border:none; outline:none; padding:8px 16px; font-size:14px;">
<button style="background:#0078d4; color:white; border:none; border-radius:20px; padding:8px 20px; cursor:pointer;">検索</button>
</div>
<div style="display:grid; grid-template-columns:repeat(4,1fr); gap:12px;">
${[
{ic:'📰',nm:'ニュース'},{ic:'🎬',nm:'動画'},
{ic:'🛒',nm:'ショッピング'},{ic:'📧',nm:'メール'},
{ic:'🗺️',nm:'マップ'},{ic:'🎮',nm:'ゲーム'},
{ic:'📚',nm:'学習'},{ic:'🎵',nm:'音楽'}
].map(x => `
<div style="padding:16px; background:#f5f5f5; border-radius:8px; cursor:pointer; transition:background 0.15s;" onmouseover="this.style.background='#ebebeb'" onmouseout="this.style.background='#f5f5f5'">
<div style="font-size:28px; margin-bottom:6px;">${x.ic}</div>
<div style="font-size:12px; color:#333;">${x.nm}</div>
</div>
`).join('')}
</div>
</div>
`;
}
renderHome();
url.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const v = url.value.trim();
page.innerHTML = `<div style="padding:40px; text-align:center; color:#666;">
<div style="font-size:48px; margin-bottom:16px;">🔍</div>
<div style="font-size:16px; color:#1a1a1a; margin-bottom:8px;">「${v}」を検索中...</div>
<div style="font-size:13px;">これはシミュレーションされたブラウザです</div>
</div>`;
}
});
}
// ============ APP: TERMINAL ============
function renderTerminal(body, wid) {
body.innerHTML = `
<div class="termWrap" id="${wid}-tw">
<div style="color:#6ec8ff; margin-bottom:8px;">WebOS PowerShell</div>
<div style="margin-bottom:8px;">Copyright (C) WebOS Corporation. All rights reserved.</div>
<div class="termOut" id="${wid}-out"></div>
<div class="termLine">
<span class="termPrompt">PS C:\\Users\\User> </span>
<input type="text" class="termInput" id="${wid}-in" autofocus>
</div>
</div>
`;
const out = body.querySelector(`#${wid}-out`);
const inp = body.querySelector(`#${wid}-in`);
const tw = body.querySelector(`#${wid}-tw`);
const cmds = {
help: '使用可能なコマンド: help, dir, cls, echo, date, ver, whoami, hostname, calc, time, exit',
ver: 'WebOS [Version 11.0.22621.2026]',
whoami: 'webos\\user',
hostname: 'WEBOS-PC',
date: () => new Date().toLocaleDateString('ja-JP'),
time: () => new Date().toLocaleTimeString('ja-JP'),
dir: () => ` ディレクトリ: C:\\Users\\User\n\nMode LastWriteTime Length Name\n---- ------------- ------ ----\nd---- 2026/07/20 10:00 Desktop\nd---- 2026/07/20 10:00 Documents\nd---- 2026/07/20 10:00 Downloads\nd---- 2026/07/20 10:00 Pictures\n-a--- 2026/07/20 09:30 1024 readme.txt`,
cls: () => { out.innerHTML = ''; return null; }
};
inp.addEventListener('keydown', (e) => {
if (e.key === 'Enter') {
const cmd = inp.value;
const line = document.createElement('div');
line.innerHTML = `<span style="color:#6ec8ff;">PS C:\\Users\\User> </span>${escapeHtml(cmd)}`;
out.appendChild(line);
if (cmd.trim()) {
const parts = cmd.trim().split(/\s+/);
const base = parts[0].toLowerCase();
let res;
if (base === 'echo') res = parts.slice(1).join(' ');
else if (base === 'calc') { try { res = String(Function('"use strict";return (' + parts.slice(1).join(' ') + ')')()); } catch { res = 'エラー: 式を評価できません'; } }
else if (cmds[base]) res = typeof cmds[base] === 'function' ? cmds[base]() : cmds[base];
else res = `'${base}' は、内部コマンドまたは外部コマンドとして認識されていません。`;
if (res !== null && res !== undefined) {
const r = document.createElement('div');
r.style.whiteSpace = 'pre-wrap';
r.textContent = res;
out.appendChild(r);
}
}
inp.value = '';
tw.scrollTop = tw.scrollHeight;
}
});
tw.addEventListener('click', () => inp.focus());
}
function escapeHtml(s) { return s.replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
// ============ APP: PAINT ============
function renderPaint(body, wid) {
body.innerHTML = `
<div class="ptBar">
<button class="ptBtn act" data-t="brush">🖌️ ブラシ</button>
<button class="ptBtn" data-t="eraser">🧽 消しゴム</button>
<button class="ptBtn" data-t="line">📏 線</button>
<button class="ptBtn" data-t="rect">▭ 矩形</button>
<button class="ptBtn" data-t="circle">◯ 円</button>
<span style="color:#ccc; margin:0 4px;">|</span>
<input type="color" class="ptColor" id="${wid}-col" value="#000000">
<label style="font-size:12px; display:flex; align-items:center; gap:6px;">
太さ <input type="range" min="1" max="30" value="3" id="${wid}-sz" style="width:80px;">
</label>
<span style="color:#ccc; margin:0 4px;">|</span>
<button class="ptBtn" id="${wid}-clr">🗑️ クリア</button>
<button class="ptBtn" id="${wid}-save">💾 保存</button>
</div>
<div class="ptCanvasWrap">
<canvas class="ptCanvas" id="${wid}-cv" width="760" height="480"></canvas>
</div>
`;
const cv = body.querySelector(`#${wid}-cv`);
const ctx = cv.getContext('2d');
ctx.fillStyle = 'white'; ctx.fillRect(0,0,cv.width,cv.height);
const col = body.querySelector(`#${wid}-col`);
const sz = body.querySelector(`#${wid}-sz`);
let tool = 'brush', drawing = false, sx = 0, sy = 0, snap = null;
body.querySelectorAll('[data-t]').forEach(b => {
b.onclick = () => {
body.querySelectorAll('[data-t]').forEach(x => x.classList.remove('act'));
b.classList.add('act');
tool = b.dataset.t;
};
});
body.querySelector(`#${wid}-clr`).onclick = () => {
ctx.fillStyle = 'white'; ctx.fillRect(0,0,cv.width,cv.height);
};
body.querySelector(`#${wid}-save`).onclick = () => {
const a = document.createElement('a');
a.download = 'paint.png'; a.href = cv.toDataURL(); a.click();
};
function pos(e) {
const r = cv.getBoundingClientRect();
return { x: e.clientX - r.left, y: e.clientY - r.top };
}
cv.addEventListener('mousedown', (e) => {
const p = pos(e); sx = p.x; sy = p.y; drawing = true;
if (['line','rect','circle'].includes(tool)) snap = ctx.getImageData(0,0,cv.width,cv.height);
else { ctx.beginPath(); ctx.moveTo(sx, sy); }
});
cv.addEventListener('mousemove', (e) => {
if (!drawing) return;
const p = pos(e);
ctx.strokeStyle = col.value;
ctx.fillStyle = col.value;
ctx.lineWidth = sz.value;
ctx.lineCap = 'round'; ctx.lineJoin = 'round';
if (tool === 'brush') { ctx.lineTo(p.x, p.y); ctx.stroke(); }
else if (tool === 'eraser') {
ctx.save(); ctx.strokeStyle = 'white'; ctx.lineTo(p.x, p.y); ctx.stroke(); ctx.restore();
} else if (snap) {
ctx.putImageData(snap, 0, 0);
ctx.beginPath();
if (tool === 'line') { ctx.moveTo(sx, sy); ctx.lineTo(p.x, p.y); ctx.stroke(); }
else if (tool === 'rect') ctx.strokeRect(sx, sy, p.x-sx, p.y-sy);
else if (tool === 'circle') {
const r = Math.hypot(p.x-sx, p.y-sy);
ctx.arc(sx, sy, r, 0, Math.PI*2); ctx.stroke();
}
}
});
cv.addEventListener('mouseup', () => { drawing = false; snap = null; });
cv.addEventListener('mouseleave', () => { drawing = false; snap = null; });
}
// ============ APP: PHOTOS ============
function renderPhotos(body, wid) {
const items = [
{ nm: '夕焼け', g: 'linear-gradient(135deg,#ff6b6b,#feca57)' },
{ nm: '山岳', g: 'linear-gradient(135deg,#667eea,#764ba2)' },
{ nm: '海辺', g: 'linear-gradient(135deg,#4facfe,#00f2fe)' },
{ nm: '森', g: 'linear-gradient(135deg,#43e97b,#38f9d7)' },
{ nm: '花畑', g: 'linear-gradient(135deg,#fa709a,#fee140)' },
{ nm: '夜景', g: 'linear-gradient(135deg,#30cfd0,#330867)' },
{ nm: '砂漠', g: 'linear-gradient(135deg,#f6d365,#fda085)' },
{ nm: '雪景色', g: 'linear-gradient(135deg,#e0c3fc,#8ec5fc)' },
{ nm: '湖', g: 'linear-gradient(135deg,#a1c4fd,#c2e9fb)' },
{ nm: '草原', g: 'linear-gradient(135deg,#84fab0,#8fd3f4)' },
{ nm: '星空', g: 'linear-gradient(135deg,#0f2027,#2c5364)' },
{ nm: '紅葉', g: 'linear-gradient(135deg,#ff9a9e,#fecfef)' }
];
body.innerHTML = `
<div style="display:flex; flex:1; overflow:hidden;">
<div class="feSide">
<div class="feGroup">コレクション</div>
<div class="feItem act"><span>🖼️</span><span>すべての写真</span></div>
<div class="feItem"><span>📅</span><span>日付別</span></div>
<div class="feItem"><span>📁</span><span>アルバム</span></div>
<div class="feItem"><span>⭐</span><span>お気に入り</span></div>
<div class="feItem"><span>🗑️</span><span>削除済み</span></div>
</div>
<div style="flex:1; display:flex; flex-direction:column; overflow:hidden;">
<div style="padding:16px 20px; background:rgba(249,249,249,0.6); border-bottom:1px solid rgba(0,0,0,0.06);">
<div style="font-size:18px; font-weight:600; color:#1a1a1a;">すべての写真</div>
<div style="font-size:12px; color:#666; margin-top:2px;">${items.length} 項目</div>
</div>
<div class="phGrid">
${items.map(x => `<div class="phItem" style="background:${x.g};"><div class="lb">${x.nm}</div></div>`).join('')}
</div>
</div>
</div>
`;
}
// ============ APP: STORE ============
function renderStore(body, wid) {
const apps = [
{ nm: 'Spotify', ic: '🎵', cat: '音楽', r: '4.5' },
{ nm: 'Netflix', ic: '🎬', cat: 'エンタメ', r: '4.3' },
{ nm: 'Discord', ic: '💬', cat: 'ソーシャル', r: '4.6' },
{ nm: 'VS Code', ic: '💻', cat: '開発', r: '4.8' },
{ nm: 'Slack', ic: '💼', cat: 'ビジネス', r: '4.4' },
{ nm: 'Zoom', ic: '📹', cat: '会議', r: '4.2' },
{ nm: 'Notion', ic: '📓', cat: '生産性', r: '4.7' },
{ nm: 'Figma', ic: '🎨', cat: 'デザイン', r: '4.6' }
];
body.innerHTML = `
<div style="display:flex; flex:1; overflow:hidden;">
<div class="feSide">
<div class="feItem act"><span>🏠</span><span>ホーム</span></div>
<div class="feItem"><span>📱</span><span>アプリ</span></div>
<div class="feItem"><span>🎮</span><span>ゲーム</span></div>
<div class="feItem"><span>🎬</span><span>映画とTV</span></div>
<div class="feItem"><span>📥</span><span>ライブラリ</span></div>
</div>
<div style="flex:1; padding:24px 32px; overflow-y:auto; background:#fafafa;">
<h1 style="font-size:24px; font-weight:600; margin-bottom:16px; color:#1a1a1a;">Microsoft Store</h1>
<div style="padding:32px; background:linear-gradient(135deg,#0078d4,#00bcf2); color:white; border-radius:8px; margin-bottom:24px;">
<div style="font-size:20px; font-weight:600;">注目のアプリ</div>
<div style="margin-top:8px; opacity:0.9;">最新のアプリとゲームを探索しよう</div>
<button style="margin-top:16px; padding:8px 20px; background:white; color:#0078d4; border:none; border-radius:4px; cursor:pointer; font-weight:600;">今すぐチェック</button>
</div>
<div style="font-size:16px; font-weight:600; margin-bottom:12px; color:#1a1a1a;">人気のアプリ</div>
<div style="display:grid; grid-template-columns:repeat(auto-fill, minmax(180px, 1fr)); gap:12px;">
${apps.map(a => `
<div style="background:white; border:1px solid rgba(0,0,0,0.06); border-radius:8px; padding:16px; cursor:pointer;">
<div style="font-size:36px; margin-bottom:8px;">${a.ic}</div>
<div style="font-weight:600; font-size:14px; color:#1a1a1a;">${a.nm}</div>
<div style="font-size:11px; color:#888; margin-top:2px;">${a.cat}</div>
<div style="font-size:12px; color:#f5a623; margin-top:6px;">★ ${a.r}</div>
<button style="margin-top:10px; width:100%; padding:6px; background:#0078d4; color:white; border:none; border-radius:4px; cursor:pointer; font-size:12px;">入手</button>
</div>
`).join('')}
</div>
</div>
</div>
`;
}
// ============ APP: ABOUT ============
function renderAbout(body, wid) {
body.innerHTML = `
<div class="aboutWrap">
<div class="aboutCard">
<div class="aboutLogo"><div></div><div></div><div></div><div></div></div>
<h1 style="text-align:center; font-size:24px; font-weight:600; margin-bottom:4px; color:#1a1a1a;">WebOS 11</h1>
<div style="text-align:center; font-size:13px; color:#666; margin-bottom:24px;">Pro バージョン 22H2</div>
<div style="border-top:1px solid rgba(0,0,0,0.08); padding-top:16px;">
<div class="setRow"><div class="lb">デバイス名</div><div style="font-size:13px; color:#1a1a1a;">WEBOS-PC</div></div>
<div class="setRow"><div class="lb">プロセッサ</div><div style="font-size:13px; color:#1a1a1a;">Web Browser Engine @ 3.2GHz</div></div>
<div class="setRow"><div class="lb">実装 RAM</div><div style="font-size:13px; color:#1a1a1a;">16.0 GB</div></div>
<div class="setRow"><div class="lb">システム種類</div><div style="font-size:13px; color:#1a1a1a;">64 ビットオペレーティングシステム</div></div>
<div class="setRow"><div class="lb">エディション</div><div style="font-size:13px; color:#1a1a1a;">WebOS 11 Pro</div></div>
<div class="setRow"><div class="lb">バージョン</div><div style="font-size:13px; color:#1a1a1a;">22H2</div></div>
<div class="setRow"><div class="lb">OS ビルド</div><div style="font-size:13px; color:#1a1a1a;">22621.2026</div></div>
</div>
<div style="margin-top:20px; padding:12px; background:#f0f9ff; border-radius:6px; font-size:12px; color:#0078d4; text-align:center;">
ブラウザ上で動作する Windows 11 風 OS シミュレーション
</div>
</div>
</div>
`;
}
// ============ APP: RECYCLE ============
function renderRecycle(body, wid) {
body.innerHTML = `
<div class="feBar">
<button class="feNavBtn">←</button>
<button class="feNavBtn">→</button>
<div class="fePath">🗑️ ごみ箱</div>
<button style="padding:5px 12px; background:white; border:1px solid rgba(0,0,0,0.1); border-radius:4px; cursor:pointer; font-size:12px;">ごみ箱を空にする</button>
</div>
<div style="flex:1; padding:40px; text-align:center; color:#888; display:flex; flex-direction:column; align-items:center; justify-content:center;">
<div style="font-size:80px; margin-bottom:16px; opacity:0.4;">🗑️</div>
<div style="font-size:16px; color:#1a1a1a;">ごみ箱は空です</div>
<div style="font-size:12px; margin-top:8px;">削除した項目がここに表示されます</div>
</div>
`;
}
// ============ AUTO-OPEN WELCOME ============
setTimeout(() => {
if (!document.getElementById('lockScreen').classList.contains('hide')) return;
}, 2500);
// Keyboard shortcut
document.addEventListener('keydown', (e) => {
if (e.key === 'Escape') { closeStart(); closeQuick(); document.getElementById('ctxMenu').style.display = 'none'; }
});
</script>
</body>
</html>
- #エッセイ
- #AI
- #感覚
- #アプリ
- #夏の1コマ
- #コード
- #000
- #666
- #Qwen3
- #ddd
- #888
- #444
- #f0f0f0
- #desktop
- #FAFAFA
- #CCCCCC
- #764ba2
- #667eea
- #1a1a1a
- #f093fb
- #f3f3f3
- #f5576c
- #e8e8e8
- #fa709a
- #fee140
- #4facfe
- #00f2fe
- #0078d4
- #43e97b
- #38f9d7
- #taskbar
- #D8D8D8
- #Lockscreen
- #0c0c0c
- #StartMenu
- #c42b1c
- #quicksettings
- #bootScreen
- #desktopIcons
- #winLayer
- #60cdff
- #00bcf2
- #ctxMenu
- #106ebe
- #6ec8ff
- #e6f2fb