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 +21 -0
- package/README.md +97 -2
- package/bin/rutbhics.js +62 -0
- package/css/style.css +318 -0
- package/index.html +69 -0
- package/js/apps.js +533 -0
- package/js/desktop.js +314 -0
- package/login.html +101 -0
- package/package.json +24 -4
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
|
-
#
|
|
1
|
+
# Rutbhics
|
|
2
2
|
|
|
3
|
-
|
|
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
|
+
```
|
package/bin/rutbhics.js
ADDED
|
@@ -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>
|