rutbhics 0.0.0-stage → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Zennith
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,98 @@
1
- # Temporary Holding Version
1
+ # Rutbhics
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Desktop komputer di dalam browser: window manager, taskbar, menu Start, dan
4
+ aplikasi yang berjalan langsung di halaman web — tanpa build step, tanpa
5
+ framework, murni HTML + CSS + JavaScript vanilla (seefisien mungkin).
6
+
7
+ ## Cara menjalankan
8
+
9
+ Buka `index.html` langsung di browser, atau dari terminal:
10
+
11
+ ```bash
12
+ python3 -m http.server 8000
13
+ # lalu buka http://localhost:8000
14
+ ```
15
+
16
+ ## Install lewat npm
17
+
18
+ ```bash
19
+ npm install -g rutbhics
20
+ rutbhics
21
+ # lalu buka http://localhost:8080
22
+ ```
23
+
24
+ Port bisa diubah: `rutbhics --port 3000` atau `PORT=3000 rutbhics`.
25
+ Tanpa install permanen: `npx rutbhics`.
26
+
27
+ ## Login & penggunaan sebagai server
28
+
29
+ Rutbhics bisa dijalankan sebagai server web statis (mis. `python3 -m http.server`,
30
+ Nginx, atau hosting statis apa pun) — tidak butuh backend.
31
+
32
+ - Buka halaman utama: pengunjung diarahkan ke `login.html`.
33
+ - Kunjungan pertama: buat akun perangkat (nama pengguna + kata sandi).
34
+ Akun tersimpan lokal di perangkat itu (kata sandi di-hash SHA-256 bila
35
+ konteks aman, fallback hash sederhana bila tidak).
36
+ - Setelah masuk, desktop terkunci di balik sesi tab itu; keluar lewat
37
+ tombol **Keluar** di menu Start.
38
+ - Catatan jujur: kunci ini hanya pengaman demo sisi klien, bukan
39
+ autentikasi server sungguhan. Untuk akun multi-pengguna yang aman,
40
+ diperlukan backend (mis. Node + database + sesi) di tahap berikutnya.
41
+
42
+ ## Aplikasi v1
43
+
44
+ - **Obrolan** — aplikasi chatting gaya WhatsApp (daftar kontak, kirim pesan,
45
+ balasan otomatis). Riwayat tersimpan di `localStorage` perangkat.
46
+ - **Peramban** — mini browser berbasis iframe + kolom alamat + tautan cepat.
47
+ Catatan: banyak situs besar memblokir tampil di iframe (X-Frame-Options).
48
+ - **Catatan** — catatan teks tersimpan di `localStorage`.
49
+ - **Kalkulator** — operasi dasar tanpa `eval`.
50
+ - **Files** — penjelajah berkas virtual (folder, berkas teks, edit, hapus),
51
+ tersimpan di `localStorage`.
52
+ - **Pengaturan** — ganti wallpaper (4 pilihan) dan tema terang/gelap,
53
+ tersimpan di `localStorage` dan diterapkan saat desktop dibuka lagi.
54
+ - **Musik** — player audio & video mini: putar/jeda, trek sebelum/
55
+ sesudah, geser posisi, volume, daftar putar. Trek demo dari URL publik
56
+ (butuh internet); trek tambahan (URL audio/video apa pun) bisa
57
+ ditambahkan dan tersimpan di `localStorage`.
58
+
59
+ ## Notifikasi & pusat kontrol (taskbar)
60
+
61
+ - Tombol lonceng 🔔 di taskbar dengan badge jumlah notifikasi belum dibaca.
62
+ - Panel notifikasi: daftar notifikasi (waktu WIB), hapus semua, dan
63
+ otomatis tandai sudah dibaca saat panel dibuka. Notifikasi tersimpan di
64
+ `localStorage`, maksimal 30 terbaru.
65
+ - Pusat kontrol di panel yang sama: ganti tema terang/gelap (tersambung ke
66
+ Pengaturan lewat `localStorage`), mode Jangan Ganggu (mematikan toast
67
+ pop-up), tombol buka Pengaturan, dan tombol kirim notifikasi tes.
68
+ - Toast pop-up muncul tiap notifikasi baru (kecuali mode Jangan Ganggu
69
+ aktif), hilang sendiri setelah 4 detik atau saat diklik.
70
+
71
+ ## Peta jalan (roadmap)
72
+
73
+ Urutan pembangunan berikutnya (satu item per jadwal build):
74
+
75
+ 1. Aplikasi Files (penjelajah berkas virtual + localStorage) — selesai 26 Okt 2026
76
+ 2. Aplikasi Pengaturan (ganti wallpaper, tema terang/gelap) — selesai 26 Okt 2026
77
+ 3. Notifikasi & pusat kontrol di taskbar — selesai 26 Okt 2026
78
+ 4. Musik/video player mini — selesai 26 Okt 2026
79
+ 5. Terminal web (perintah semu: help, about, echo, date)
80
+ 6. Mode jendela maximize + snap kiri/kanan
81
+ 7. Aplikasi Gambar (kanvas coret-coret + simpan)
82
+ 8. Widget jam/kalender di desktop
83
+ 9. Mode multi-bahasa (Indonesia/Inggris)
84
+ 10. Halaman login/pengguna + profil — selesai 26 Okt 2026 (dimajukan atas permintaan langsung)
85
+
86
+ > Nama proyek: **Rutbhics** (dipilih Kang Mas, 26 Okt 2026).
87
+
88
+ ## Struktur
89
+
90
+ ```
91
+ index.html — shell: wallpaper, ikon desktop, taskbar, menu Start, panel notifikasi & pusat kontrol
92
+ login.html — halaman login (akun lokal perangkat) + kunci desktop
93
+ css/style.css — seluruh tampilan desktop & aplikasi
94
+ js/desktop.js — window manager (drag, minimize, focus, taskbar, jam WIB, terapkan pengaturan tersimpan, notifikasi & pusat kontrol)
95
+ js/apps.js — aplikasi bawaan (obrolan, peramban, catatan, kalkulator, files, pengaturan, musik)
96
+ package.json — metadata package npm + perintah `rutbhics`
97
+ bin/rutbhics.js — server statis lokal (tanpa dependensi) untuk perintah `rutbhics`
98
+ ```
@@ -0,0 +1,62 @@
1
+ #!/usr/bin/env node
2
+ // Rutbhics — server statis lokal, tanpa dependensi.
3
+ // Pakai: rutbhics [--port 8080] atau PORT=8080 rutbhics
4
+ // Lalu buka http://localhost:8080 di browser.
5
+ "use strict";
6
+ const http = require("node:http");
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+
10
+ const ROOT = path.join(__dirname, "..");
11
+ const MIME = {
12
+ ".html": "text/html; charset=utf-8",
13
+ ".css": "text/css; charset=utf-8",
14
+ ".js": "text/javascript; charset=utf-8",
15
+ ".json": "application/json; charset=utf-8",
16
+ ".png": "image/png",
17
+ ".jpg": "image/jpeg",
18
+ ".svg": "image/svg+xml",
19
+ ".ico": "image/x-icon",
20
+ ".txt": "text/plain; charset=utf-8"
21
+ };
22
+
23
+ function parsePort(argv) {
24
+ let port = parseInt(process.env.PORT, 10);
25
+ const i = argv.indexOf("--port");
26
+ if (i !== -1 && argv[i + 1]) port = parseInt(argv[i + 1], 10);
27
+ if (!port || port < 1 || port > 65535) port = 8080;
28
+ return port;
29
+ }
30
+
31
+ const port = parsePort(process.argv.slice(2));
32
+ const server = http.createServer((req, res) => {
33
+ try {
34
+ const url = new URL(req.url, "http://localhost");
35
+ let filePath = path.normalize(path.join(ROOT, url.pathname));
36
+ if (!filePath.startsWith(ROOT)) {
37
+ res.writeHead(403, { "Content-Type": "text/plain; charset=utf-8" });
38
+ res.end("Forbidden");
39
+ return;
40
+ }
41
+ if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
42
+ filePath = path.join(filePath, "index.html");
43
+ }
44
+ fs.readFile(filePath, (err, data) => {
45
+ if (err) {
46
+ res.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
47
+ res.end("Tidak ditemukan");
48
+ return;
49
+ }
50
+ const ext = path.extname(filePath).toLowerCase();
51
+ res.writeHead(200, { "Content-Type": MIME[ext] || "application/octet-stream" });
52
+ res.end(data);
53
+ });
54
+ } catch (e) {
55
+ res.writeHead(500, { "Content-Type": "text/plain; charset=utf-8" });
56
+ res.end("Terjadi kesalahan");
57
+ }
58
+ });
59
+ server.listen(port, () => {
60
+ console.log("Rutbhics berjalan di http://localhost:" + port);
61
+ console.log("Buka alamat itu di browser. Tekan Ctrl+C untuk berhenti.");
62
+ });
package/css/style.css ADDED
@@ -0,0 +1,318 @@
1
+ * { box-sizing: border-box; margin: 0; padding: 0; }
2
+ :root {
3
+ --bg: #0b1120;
4
+ --surface: rgba(19, 28, 49, .92);
5
+ --surface-solid: #131c31;
6
+ --ink: #e5eaf4;
7
+ --muted: #94a3b8;
8
+ --accent: #6366f1;
9
+ --accent-2: #22d3ee;
10
+ --border: rgba(255, 255, 255, .12);
11
+ --radius: 14px;
12
+ }
13
+ html, body { height: 100%; overflow: hidden; }
14
+ body {
15
+ font-family: system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
16
+ color: var(--ink);
17
+ background: var(--bg);
18
+ user-select: none;
19
+ }
20
+ button { font: inherit; cursor: pointer; }
21
+ input, textarea { font: inherit; color: var(--ink); }
22
+
23
+ /* Wallpaper */
24
+ #wallpaper {
25
+ position: fixed; inset: 0; z-index: 0;
26
+ background:
27
+ radial-gradient(600px 400px at 20% 15%, rgba(99, 102, 241, .45), transparent 60%),
28
+ radial-gradient(700px 500px at 85% 80%, rgba(34, 211, 238, .30), transparent 60%),
29
+ linear-gradient(160deg, #0f172a, #1e1b4b 55%, #0b1120);
30
+ }
31
+ #wallpaper::after {
32
+ content: ""; position: absolute; inset: 0; opacity: .12;
33
+ background-image: radial-gradient(rgba(255,255,255,.7) 1px, transparent 1px);
34
+ background-size: 26px 26px;
35
+ }
36
+
37
+ /* Ikon desktop */
38
+ #desktop-icons {
39
+ position: fixed; z-index: 1; top: 16px; left: 16px;
40
+ display: flex; flex-direction: column; gap: 18px;
41
+ }
42
+ .desk-icon {
43
+ width: 76px; border: 0; background: transparent; color: var(--ink);
44
+ display: flex; flex-direction: column; align-items: center; gap: 6px;
45
+ padding: 8px 4px; border-radius: 10px;
46
+ }
47
+ .desk-icon:hover { background: rgba(255, 255, 255, .12); }
48
+ .desk-icon .emoji { font-size: 32px; line-height: 1; }
49
+ .desk-icon span { font-size: 12px; text-shadow: 0 1px 4px #000; }
50
+
51
+ /* Jendela */
52
+ .window {
53
+ position: fixed; z-index: 10; display: flex; flex-direction: column;
54
+ width: 640px; height: 440px; min-width: 320px; min-height: 240px;
55
+ background: var(--surface); border: 1px solid var(--border);
56
+ border-radius: var(--radius); box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
57
+ overflow: hidden; backdrop-filter: blur(8px);
58
+ }
59
+ .window.hidden-win { display: none; }
60
+ .win-header {
61
+ display: flex; align-items: center; gap: 10px;
62
+ padding: 10px 12px; background: rgba(255, 255, 255, .06);
63
+ cursor: grab; touch-action: none;
64
+ }
65
+ .win-header:active { cursor: grabbing; }
66
+ .win-title { flex: 1; font-weight: 600; font-size: 14px; }
67
+ .win-btn {
68
+ width: 30px; height: 28px; border: 0; border-radius: 8px;
69
+ background: rgba(255, 255, 255, .08); color: var(--ink); font-size: 14px;
70
+ }
71
+ .win-btn:hover { background: rgba(255, 255, 255, .2); }
72
+ .win-btn.close:hover { background: #ef4444; }
73
+ .win-body { flex: 1; overflow: auto; padding: 14px; user-select: text; }
74
+
75
+ /* Taskbar */
76
+ #taskbar {
77
+ position: fixed; z-index: 100; left: 0; right: 0; bottom: 0;
78
+ height: 52px; display: flex; align-items: center; gap: 8px;
79
+ padding: 6px 10px; background: rgba(11, 17, 32, .9);
80
+ border-top: 1px solid var(--border); backdrop-filter: blur(10px);
81
+ }
82
+ #start-btn {
83
+ border: 0; border-radius: 10px; padding: 8px 14px;
84
+ background: var(--accent); color: #fff; font-weight: 700;
85
+ }
86
+ #start-btn:hover { filter: brightness(1.15); }
87
+ #task-buttons { flex: 1; display: flex; gap: 6px; overflow-x: auto; }
88
+ .task-btn {
89
+ border: 1px solid transparent; border-radius: 10px; padding: 8px 12px;
90
+ background: transparent; color: var(--ink); white-space: nowrap; font-size: 13px;
91
+ }
92
+ .task-btn:hover { background: rgba(255, 255, 255, .1); }
93
+ .task-btn.active { background: rgba(255, 255, 255, .16); border-color: var(--border); }
94
+ #tray { display: flex; flex-direction: column; align-items: flex-end; font-size: 12px; line-height: 1.3; }
95
+ #clock-time { font-weight: 700; }
96
+ #clock-date { color: var(--muted); }
97
+
98
+ /* Menu Start */
99
+ #start-menu {
100
+ position: fixed; z-index: 99; left: 10px; bottom: 60px; width: 340px;
101
+ background: var(--surface); border: 1px solid var(--border);
102
+ border-radius: var(--radius); padding: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
103
+ }
104
+ #start-menu.hidden { display: none; }
105
+ #start-search {
106
+ width: 100%; padding: 10px 12px; border-radius: 10px;
107
+ border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none;
108
+ }
109
+ #start-search:focus { border-color: var(--accent); }
110
+ #start-grid { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; margin-top: 12px; }
111
+ .start-app {
112
+ border: 0; border-radius: 12px; padding: 14px 6px;
113
+ background: transparent; color: var(--ink);
114
+ display: flex; flex-direction: column; gap: 6px; align-items: center; font-size: 12px;
115
+ }
116
+ .start-app:hover { background: rgba(255, 255, 255, .1); }
117
+ .start-app .emoji { font-size: 26px; }
118
+
119
+ /* Aplikasi: Chat */
120
+ .chat-wrap { display: flex; gap: 12px; height: 100%; min-height: 300px; }
121
+ .chat-contacts { width: 150px; display: flex; flex-direction: column; gap: 6px; overflow-y: auto; }
122
+ .chat-contact {
123
+ border: 1px solid transparent; border-radius: 10px; padding: 10px;
124
+ background: transparent; color: var(--ink); text-align: left; font-size: 13px;
125
+ }
126
+ .chat-contact.active { background: rgba(99, 102, 241, .25); border-color: var(--border); }
127
+ .chat-main { flex: 1; display: flex; flex-direction: column; min-width: 0; }
128
+ .chat-msgs { flex: 1; overflow-y: auto; display: flex; flex-direction: column; gap: 8px; padding: 4px; }
129
+ .msg { max-width: 80%; padding: 8px 12px; border-radius: 14px; font-size: 14px; line-height: 1.4; }
130
+ .msg.me { align-self: flex-end; background: var(--accent); color: #fff; border-bottom-right-radius: 4px; }
131
+ .msg.them { align-self: flex-start; background: rgba(255, 255, 255, .1); border-bottom-left-radius: 4px; }
132
+ .chat-input { display: flex; gap: 8px; margin-top: 10px; }
133
+ .chat-input input {
134
+ flex: 1; padding: 10px 12px; border-radius: 10px;
135
+ border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none;
136
+ }
137
+ .btn-primary {
138
+ border: 0; border-radius: 10px; padding: 10px 16px;
139
+ background: var(--accent); color: #fff; font-weight: 600;
140
+ }
141
+ .btn-primary:hover { filter: brightness(1.15); }
142
+
143
+ /* Aplikasi: Browser */
144
+ .browser-bar { display: flex; gap: 8px; margin-bottom: 10px; }
145
+ .browser-bar input {
146
+ flex: 1; padding: 10px 12px; border-radius: 10px;
147
+ border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none;
148
+ }
149
+ .browser-frame { width: 100%; height: 340px; border: 1px solid var(--border); border-radius: 10px; background: #fff; }
150
+ .quick-links { display: flex; flex-wrap: wrap; gap: 6px; margin-bottom: 10px; }
151
+ .chip {
152
+ border: 1px solid var(--border); border-radius: 999px; padding: 6px 12px;
153
+ background: transparent; color: var(--ink); font-size: 12px;
154
+ }
155
+ .chip:hover { background: rgba(255, 255, 255, .1); }
156
+ .hint { color: var(--muted); font-size: 12px; margin-top: 8px; line-height: 1.5; }
157
+
158
+ /* Aplikasi: Catatan & Kalkulator */
159
+ .notes-area {
160
+ width: 100%; height: 300px; padding: 12px; border-radius: 10px;
161
+ border: 1px solid var(--border); background: rgba(255, 255, 255, .06);
162
+ outline: none; resize: vertical; line-height: 1.5;
163
+ }
164
+ .calc-display {
165
+ width: 100%; padding: 14px; margin-bottom: 10px; text-align: right;
166
+ font-size: 26px; font-weight: 700; border-radius: 10px;
167
+ border: 1px solid var(--border); background: rgba(0, 0, 0, .3); min-height: 56px;
168
+ }
169
+ .calc-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
170
+ .calc-grid button {
171
+ padding: 14px 0; border-radius: 10px; border: 1px solid var(--border);
172
+ background: rgba(255, 255, 255, .07); color: var(--ink); font-size: 16px;
173
+ }
174
+ .calc-grid button:hover { background: rgba(255, 255, 255, .15); }
175
+ .calc-grid button.op { background: rgba(99, 102, 241, .3); }
176
+ .calc-grid button.eq { background: var(--accent); color: #fff; }
177
+
178
+ /* Aplikasi: Files */
179
+ .files-bar { display: flex; align-items: center; gap: 8px; margin-bottom: 12px; flex-wrap: wrap; }
180
+ .files-path { flex: 1; min-width: 120px; font-size: 13px; color: var(--muted); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
181
+ .files-list { display: flex; flex-direction: column; gap: 4px; }
182
+ .file-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 10px; }
183
+ .file-row:hover { background: rgba(255, 255, 255, .07); }
184
+ .file-icon { font-size: 20px; line-height: 1; }
185
+ .file-name { flex: 1; border: 0; background: transparent; color: var(--ink); text-align: left; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
186
+ .files-view { margin-top: 14px; border-top: 1px solid var(--border); padding-top: 12px; }
187
+ .files-view-title { font-weight: 600; font-size: 14px; margin-bottom: 8px; }
188
+ .files-editor { width: 100%; height: 180px; padding: 12px; border-radius: 10px; border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none; resize: vertical; line-height: 1.5; margin-bottom: 10px; }
189
+
190
+ /* Halaman login & pengguna di menu Start */
191
+ .login-page { min-height: 100vh; display: flex; align-items: center; justify-content: center; padding: 20px; position: relative; z-index: 1; }
192
+ .login-card { width: 100%; max-width: 360px; background: var(--surface); border: 1px solid var(--border); border-radius: 18px; padding: 28px; box-shadow: 0 24px 60px rgba(0, 0, 0, .55); }
193
+ .login-card h1 { font-size: 28px; margin-bottom: 4px; }
194
+ .login-card p.sub { color: var(--muted); font-size: 13px; margin-bottom: 20px; }
195
+ .login-card label { display: block; font-size: 13px; color: var(--muted); margin: 12px 0 6px; }
196
+ .login-card input { width: 100%; padding: 11px 12px; border-radius: 10px; border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none; }
197
+ .login-card input:focus { border-color: var(--accent); }
198
+ .login-card .btn-primary { width: 100%; margin-top: 18px; }
199
+ .login-error { color: #f87171; font-size: 13px; min-height: 18px; margin-top: 10px; }
200
+ .login-link { background: none; border: 0; color: var(--accent-2); font-size: 13px; margin-top: 14px; }
201
+ .login-link.hidden { display: none; }
202
+ #start-user { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-top: 12px; padding-top: 12px; border-top: 1px solid var(--border); font-size: 12px; color: var(--muted); }
203
+ #logout-btn { border: 1px solid var(--border); border-radius: 8px; padding: 6px 12px; background: transparent; color: var(--ink); font-size: 12px; }
204
+ #logout-btn:hover { background: rgba(255, 255, 255, .1); }
205
+
206
+ /* Aplikasi: Pengaturan */
207
+ .settings-title { font-size: 16px; margin-bottom: 4px; }
208
+ .settings-head { font-size: 13px; color: var(--muted); margin: 14px 0 8px; }
209
+ .wp-grid { display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; }
210
+ .wp-swatch {
211
+ position: relative; height: 72px; border: 2px solid transparent;
212
+ border-radius: 12px; overflow: hidden; padding: 0;
213
+ }
214
+ .wp-swatch:hover { filter: brightness(1.1); }
215
+ .wp-swatch.active { border-color: var(--accent-2); box-shadow: 0 0 0 2px rgba(34, 211, 238, .35); }
216
+ .wp-label {
217
+ position: absolute; left: 8px; bottom: 6px; font-size: 12px; color: #fff;
218
+ text-shadow: 0 1px 4px #000;
219
+ }
220
+ .theme-row { display: flex; gap: 8px; }
221
+ .theme-btn.active { background: rgba(99, 102, 241, .3); border-color: var(--accent); }
222
+
223
+ /* Tema terang */
224
+ body.light-theme {
225
+ --bg: #e2e8f0;
226
+ --surface: rgba(255, 255, 255, .94);
227
+ --surface-solid: #ffffff;
228
+ --ink: #0f172a;
229
+ --muted: #475569;
230
+ --border: rgba(15, 23, 42, .15);
231
+ }
232
+ body.light-theme #taskbar { background: rgba(255, 255, 255, .92); }
233
+ body.light-theme .win-header { background: rgba(15, 23, 42, .05); }
234
+ body.light-theme .desk-icon:hover,
235
+ body.light-theme .task-btn:hover,
236
+ body.light-theme .start-app:hover,
237
+ body.light-theme .file-row:hover,
238
+ body.light-theme .chip:hover,
239
+ body.light-theme .win-btn:hover { background: rgba(15, 23, 42, .08); }
240
+ body.light-theme .desk-icon span { text-shadow: 0 1px 4px rgba(255, 255, 255, .8); }
241
+ body.light-theme .chat-input input,
242
+ body.light-theme .browser-bar input,
243
+ body.light-theme #start-search,
244
+ body.light-theme .notes-area,
245
+ body.light-theme .files-editor,
246
+ body.light-theme .login-card input { background: rgba(15, 23, 42, .05); }
247
+ body.light-theme .msg.them { background: rgba(15, 23, 42, .08); }
248
+ body.light-theme .calc-display { background: rgba(15, 23, 42, .06); }
249
+ body.light-theme .calc-grid button { background: rgba(15, 23, 42, .06); }
250
+ body.light-theme .calc-grid button:hover { background: rgba(15, 23, 42, .12); }
251
+ body.light-theme .chat-contact.active { background: rgba(99, 102, 241, .15); }
252
+ body.light-theme #wallpaper::after { opacity: .06; }
253
+
254
+ /* Taskbar: tombol notifikasi */
255
+ #notif-btn {
256
+ position: relative; border: 0; border-radius: 10px; padding: 8px 12px;
257
+ background: transparent; color: var(--ink); font-size: 16px;
258
+ }
259
+ #notif-btn:hover { background: rgba(255, 255, 255, .1); }
260
+ #notif-badge {
261
+ position: absolute; top: 2px; right: 2px; min-width: 18px; height: 18px;
262
+ padding: 0 4px; border-radius: 999px; background: #ef4444; color: #fff;
263
+ font-size: 11px; font-weight: 700; display: flex; align-items: center; justify-content: center;
264
+ }
265
+ #notif-badge.hidden { display: none; }
266
+
267
+ /* Panel notifikasi & pusat kontrol */
268
+ #notif-panel {
269
+ position: fixed; z-index: 99; right: 10px; bottom: 60px; width: 340px;
270
+ max-height: 60vh; overflow-y: auto;
271
+ background: var(--surface); border: 1px solid var(--border);
272
+ border-radius: var(--radius); padding: 14px; box-shadow: 0 24px 60px rgba(0, 0, 0, .55);
273
+ }
274
+ #notif-panel.hidden { display: none; }
275
+ .notif-panel-head { display: flex; align-items: center; justify-content: space-between; gap: 8px; margin-bottom: 10px; }
276
+ .notif-panel-head strong { font-size: 14px; }
277
+ .notif-item { padding: 8px 10px; border-radius: 10px; background: rgba(255, 255, 255, .05); margin-bottom: 6px; }
278
+ .notif-item.unread { background: rgba(99, 102, 241, .2); }
279
+ .notif-text { display: block; font-size: 13px; line-height: 1.4; }
280
+ .notif-time { font-size: 11px; color: var(--muted); }
281
+ .control-row { display: flex; gap: 8px; flex-wrap: wrap; margin-bottom: 8px; }
282
+
283
+ /* Toast notifikasi */
284
+ #toast-wrap {
285
+ position: fixed; z-index: 101; right: 10px; bottom: 60px;
286
+ display: flex; flex-direction: column; gap: 8px; align-items: flex-end;
287
+ }
288
+ .toast {
289
+ max-width: 320px; padding: 10px 14px; border-radius: 12px; font-size: 13px;
290
+ background: var(--surface-solid); border: 1px solid var(--border);
291
+ box-shadow: 0 12px 30px rgba(0, 0, 0, .5); cursor: pointer;
292
+ }
293
+ body.light-theme #notif-btn:hover { background: rgba(15, 23, 42, .08); }
294
+ body.light-theme .notif-item { background: rgba(15, 23, 42, .05); }
295
+ body.light-theme .notif-item.unread { background: rgba(99, 102, 241, .15); }
296
+
297
+ /* Aplikasi: Musik */
298
+ .music-stage { margin-bottom: 12px; }
299
+ .music-title { font-weight: 600; font-size: 14px; margin-bottom: 8px; }
300
+ .music-media { width: 100%; border-radius: 10px; background: #000; }
301
+ .music-media.hidden { display: none; }
302
+ .music-error { color: #f87171; font-size: 13px; margin-top: 8px; }
303
+ .music-error.hidden { display: none; }
304
+ .music-controls { display: flex; align-items: center; gap: 8px; margin-bottom: 10px; flex-wrap: wrap; }
305
+ .music-time { font-size: 13px; color: var(--muted); }
306
+ .music-seek { width: 100%; margin-bottom: 10px; accent-color: var(--accent); }
307
+ .music-volrow { display: flex; align-items: center; gap: 10px; margin-bottom: 14px; }
308
+ .music-vol { flex: 1; accent-color: var(--accent); }
309
+ .music-list { display: flex; flex-direction: column; gap: 4px; margin-bottom: 14px; }
310
+ .music-row { display: flex; align-items: center; gap: 10px; padding: 8px 10px; border-radius: 10px; }
311
+ .music-row:hover { background: rgba(255, 255, 255, .07); }
312
+ .music-row.active { background: rgba(99, 102, 241, .25); }
313
+ .music-name { flex: 1; border: 0; background: transparent; color: var(--ink); text-align: left; font-size: 14px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
314
+ .music-add { display: flex; gap: 8px; margin-bottom: 10px; }
315
+ .music-add input { flex: 1; padding: 10px 12px; border-radius: 10px; border: 1px solid var(--border); background: rgba(255, 255, 255, .06); outline: none; }
316
+ body.light-theme .music-row:hover { background: rgba(15, 23, 42, .08); }
317
+ body.light-theme .music-row.active { background: rgba(99, 102, 241, .15); }
318
+ body.light-theme .music-add input { background: rgba(15, 23, 42, .05); }
package/index.html ADDED
@@ -0,0 +1,69 @@
1
+ <!DOCTYPE html>
2
+ <html lang="id">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Rutbhics</title>
7
+ <link rel="stylesheet" href="css/style.css">
8
+ <script>
9
+ // Kunci desktop: tanpa sesi login, kembali ke halaman login.
10
+ try {
11
+ if (!sessionStorage.getItem("wd-session")) { location.replace("login.html"); }
12
+ } catch (e) { /* abaikan: kunci demo, bukan pengaman sungguhan */ }
13
+ </script>
14
+ </head>
15
+ <body>
16
+ <div id="wallpaper"></div>
17
+
18
+ <!-- Ikon desktop -->
19
+ <div id="desktop-icons"></div>
20
+
21
+ <!-- Jendela -->
22
+ <div id="windows"></div>
23
+
24
+ <!-- Menu Start -->
25
+ <div id="start-menu" class="hidden">
26
+ <input id="start-search" type="text" placeholder="Cari aplikasi...">
27
+ <div id="start-grid"></div>
28
+ <div id="start-user">
29
+ <span id="start-username"></span>
30
+ <button id="logout-btn">Keluar</button>
31
+ </div>
32
+ </div>
33
+
34
+ <!-- Taskbar -->
35
+ <footer id="taskbar">
36
+ <button id="start-btn" title="Mulai">⊞ <span>Mulai</span></button>
37
+ <div id="task-buttons"></div>
38
+ <button id="notif-btn" title="Notifikasi & pusat kontrol">🔔<span id="notif-badge" class="hidden">0</span></button>
39
+ <div id="tray">
40
+ <span id="clock-time">--:--</span>
41
+ <span id="clock-date">--</span>
42
+ </div>
43
+ </footer>
44
+
45
+ <!-- Panel notifikasi & pusat kontrol -->
46
+ <div id="notif-panel" class="hidden">
47
+ <div class="notif-panel-head">
48
+ <strong>Notifikasi</strong>
49
+ <button id="notif-clear" class="chip">Hapus semua</button>
50
+ </div>
51
+ <div id="notif-list"></div>
52
+ <div class="notif-panel-head" style="margin-top:14px">
53
+ <strong>Pusat kontrol</strong>
54
+ </div>
55
+ <div class="control-row">
56
+ <button id="cc-theme" class="chip">☀️ Ganti ke terang</button>
57
+ <button id="cc-dnd" class="chip">🔔 Jangan ganggu: nonaktif</button>
58
+ </div>
59
+ <div class="control-row">
60
+ <button id="cc-settings" class="btn-primary">Buka Pengaturan</button>
61
+ <button id="cc-test" class="chip">Kirim notifikasi tes</button>
62
+ </div>
63
+ </div>
64
+ <div id="toast-wrap"></div>
65
+
66
+ <script src="js/apps.js"></script>
67
+ <script src="js/desktop.js"></script>
68
+ </body>
69
+ </html>