rutbhics 1.0.0 → 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/README.md CHANGED
@@ -51,6 +51,22 @@ Nginx, atau hosting statis apa pun) — tidak butuh backend.
51
51
  tersimpan di `localStorage`.
52
52
  - **Pengaturan** — ganti wallpaper (4 pilihan) dan tema terang/gelap,
53
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.
54
70
 
55
71
  ## Peta jalan (roadmap)
56
72
 
@@ -58,8 +74,8 @@ Urutan pembangunan berikutnya (satu item per jadwal build):
58
74
 
59
75
  1. Aplikasi Files (penjelajah berkas virtual + localStorage) — selesai 26 Okt 2026
60
76
  2. Aplikasi Pengaturan (ganti wallpaper, tema terang/gelap) — selesai 26 Okt 2026
61
- 3. Notifikasi & pusat kontrol di taskbar
62
- 4. Musik/video player mini
77
+ 3. Notifikasi & pusat kontrol di taskbar — selesai 26 Okt 2026
78
+ 4. Musik/video player mini — selesai 26 Okt 2026
63
79
  5. Terminal web (perintah semu: help, about, echo, date)
64
80
  6. Mode jendela maximize + snap kiri/kanan
65
81
  7. Aplikasi Gambar (kanvas coret-coret + simpan)
@@ -72,11 +88,11 @@ Urutan pembangunan berikutnya (satu item per jadwal build):
72
88
  ## Struktur
73
89
 
74
90
  ```
75
- index.html — shell: wallpaper, ikon desktop, taskbar, menu Start
91
+ index.html — shell: wallpaper, ikon desktop, taskbar, menu Start, panel notifikasi & pusat kontrol
76
92
  login.html — halaman login (akun lokal perangkat) + kunci desktop
77
93
  css/style.css — seluruh tampilan desktop & aplikasi
78
- js/desktop.js — window manager (drag, minimize, focus, taskbar, jam WIB, terapkan pengaturan tersimpan)
79
- js/apps.js — aplikasi bawaan (obrolan, peramban, catatan, kalkulator, files, pengaturan)
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)
80
96
  package.json — metadata package npm + perintah `rutbhics`
81
97
  bin/rutbhics.js — server statis lokal (tanpa dependensi) untuk perintah `rutbhics`
82
98
  ```
package/css/style.css CHANGED
@@ -250,3 +250,69 @@ body.light-theme .calc-grid button { background: rgba(15, 23, 42, .06); }
250
250
  body.light-theme .calc-grid button:hover { background: rgba(15, 23, 42, .12); }
251
251
  body.light-theme .chat-contact.active { background: rgba(99, 102, 241, .15); }
252
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 CHANGED
@@ -35,13 +35,35 @@
35
35
  <footer id="taskbar">
36
36
  <button id="start-btn" title="Mulai">⊞ <span>Mulai</span></button>
37
37
  <div id="task-buttons"></div>
38
+ <button id="notif-btn" title="Notifikasi & pusat kontrol">🔔<span id="notif-badge" class="hidden">0</span></button>
38
39
  <div id="tray">
39
40
  <span id="clock-time">--:--</span>
40
41
  <span id="clock-date">--</span>
41
42
  </div>
42
43
  </footer>
43
44
 
44
- <script src="js/desktop.js"></script>
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
+
45
66
  <script src="js/apps.js"></script>
67
+ <script src="js/desktop.js"></script>
46
68
  </body>
47
69
  </html>
package/js/apps.js CHANGED
@@ -1,5 +1,5 @@
1
- // Web Desktop — aplikasi bawaan: Obrolan, Peramban, Catatan, Kalkulator, Files, Pengaturan.
2
- // Data Obrolan, Catatan & Files tersimpan di localStorage perangkat yang membuka web ini.
1
+ // Web Desktop — aplikasi bawaan: Obrolan, Peramban, Catatan, Kalkulator, Files, Pengaturan, Musik.
2
+ // Data Obrolan, Catatan, Files & Musik tersimpan di localStorage perangkat yang membuka web ini.
3
3
  (function () {
4
4
  "use strict";
5
5
 
@@ -353,12 +353,181 @@
353
353
  body.appendChild(el('<p class="hint">Pengaturan tersimpan di perangkat ini dan langsung diterapkan saat desktop dibuka lagi.</p>'));
354
354
  }
355
355
 
356
+ // ---------- Musik (player audio & video mini) ----------
357
+ // Trek demo diputar dari URL publik (butuh internet). Trek tambahan
358
+ // (URL audio/video apa pun) tersimpan di localStorage perangkat.
359
+ var DEMO_TRACKS = [
360
+ { title: "SoundHelix Song 1 (demo)", url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3", type: "audio" },
361
+ { title: "SoundHelix Song 2 (demo)", url: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-2.mp3", type: "audio" },
362
+ { title: "Flower — demo video (MDN)", url: "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.mp4", type: "video" }
363
+ ];
364
+ function guessType(url) {
365
+ var u = url.split("?")[0].toLowerCase();
366
+ if (/\.(mp4|webm|mov|mkv)$/.test(u)) return "video";
367
+ return "audio";
368
+ }
369
+ function fmtTime(s) {
370
+ if (!isFinite(s)) return "0:00";
371
+ var m = Math.floor(s / 60), sec = Math.floor(s % 60);
372
+ return m + ":" + (sec < 10 ? "0" : "") + sec;
373
+ }
374
+ function renderMusic(body) {
375
+ var custom = store("wd-music", []);
376
+ if (!Array.isArray(custom)) custom = [];
377
+ var tracks = DEMO_TRACKS.concat(custom);
378
+ var current = 0;
379
+ var media = null; // elemen <audio> atau <video> yang sedang aktif
380
+
381
+ var stage = el('<div class="music-stage"></div>');
382
+ var titleEl = el('<p class="music-title"></p>');
383
+ var audioEl = el('<audio class="music-media" preload="metadata"></audio>');
384
+ var videoEl = el('<video class="music-media hidden" preload="metadata"></video>');
385
+ stage.appendChild(titleEl);
386
+ stage.appendChild(audioEl);
387
+ stage.appendChild(videoEl);
388
+ var errEl = el('<p class="music-error hidden"></p>');
389
+ stage.appendChild(errEl);
390
+
391
+ var controls = el('<div class="music-controls"></div>');
392
+ var prevBtn = el('<button class="chip">⏮</button>');
393
+ var playBtn = el('<button class="btn-primary">▶ Putar</button>');
394
+ var nextBtn = el('<button class="chip">⏭</button>');
395
+ var timeEl = el('<span class="music-time">0:00 / 0:00</span>');
396
+ controls.appendChild(prevBtn); controls.appendChild(playBtn);
397
+ controls.appendChild(nextBtn); controls.appendChild(timeEl);
398
+
399
+ var seek = el('<input class="music-seek" type="range" min="0" max="100" value="0">');
400
+ var volRow = el('<div class="music-volrow"></div>');
401
+ var volLabel = el('<span class="hint" style="margin:0">Volume</span>');
402
+ var vol = el('<input class="music-vol" type="range" min="0" max="100" value="80">');
403
+ volRow.appendChild(volLabel); volRow.appendChild(vol);
404
+
405
+ var listEl = el('<div class="music-list"></div>');
406
+
407
+ var addRow = el('<div class="music-add"></div>');
408
+ var urlInput = el('<input type="text" placeholder="Tempel URL audio/video, mis. https://.../lagu.mp3">');
409
+ var addBtn = el('<button class="btn-primary">Tambah</button>');
410
+ addRow.appendChild(urlInput); addRow.appendChild(addBtn);
411
+
412
+ body.appendChild(stage); body.appendChild(controls);
413
+ body.appendChild(seek); body.appendChild(volRow);
414
+ body.appendChild(listEl); body.appendChild(addRow);
415
+ body.appendChild(el('<p class="hint">Trek demo butuh internet. Trek tambahan tersimpan di perangkat ini.</p>'));
416
+
417
+ function currentTrack() { return tracks[current]; }
418
+ function tryPlay() {
419
+ if (!media) return;
420
+ try {
421
+ var p = media.play();
422
+ if (p && p.then) {
423
+ p.then(function () { playBtn.textContent = "⏸ Jeda"; }).catch(function () {});
424
+ } else {
425
+ playBtn.textContent = "⏸ Jeda";
426
+ }
427
+ } catch (e) { /* abaikan: pengguna bisa menekan Putar */ }
428
+ }
429
+ function setMedia() {
430
+ audioEl.pause();
431
+ videoEl.pause();
432
+ var t = currentTrack();
433
+ var isVideo = t.type === "video";
434
+ audioEl.classList.toggle("hidden", isVideo);
435
+ videoEl.classList.toggle("hidden", !isVideo);
436
+ media = isVideo ? videoEl : audioEl;
437
+ media.src = t.url;
438
+ media.volume = vol.value / 100;
439
+ titleEl.textContent = (isVideo ? "🎬 " : "🎵 ") + t.title;
440
+ errEl.classList.add("hidden");
441
+ playBtn.textContent = "▶ Putar";
442
+ seek.value = 0;
443
+ timeEl.textContent = "0:00 / 0:00";
444
+ drawList();
445
+ }
446
+ function playTrack(i) {
447
+ current = (i + tracks.length) % tracks.length;
448
+ setMedia();
449
+ tryPlay();
450
+ }
451
+ function drawList() {
452
+ listEl.innerHTML = "";
453
+ tracks.forEach(function (t, i) {
454
+ var row = el('<div class="music-row' + (i === current ? " active" : "") + '"></div>');
455
+ var name = el('<button class="music-name"></button>');
456
+ name.textContent = (t.type === "video" ? "🎬 " : "🎵 ") + t.title;
457
+ name.addEventListener("click", function () { playTrack(i); });
458
+ row.appendChild(name);
459
+ if (i >= DEMO_TRACKS.length) {
460
+ var del = el('<button class="chip">Hapus</button>');
461
+ del.addEventListener("click", function () {
462
+ custom = custom.filter(function (c) { return c.url !== t.url; });
463
+ save("wd-music", custom);
464
+ tracks = DEMO_TRACKS.concat(custom);
465
+ if (current >= tracks.length) current = tracks.length - 1;
466
+ setMedia();
467
+ });
468
+ row.appendChild(del);
469
+ }
470
+ listEl.appendChild(row);
471
+ });
472
+ }
473
+ function bindMediaEvents(m) {
474
+ m.addEventListener("timeupdate", function () {
475
+ if (m.duration) seek.value = Math.floor(m.currentTime / m.duration * 100);
476
+ timeEl.textContent = fmtTime(m.currentTime) + " / " + fmtTime(m.duration);
477
+ });
478
+ m.addEventListener("loadedmetadata", function () {
479
+ timeEl.textContent = fmtTime(m.currentTime) + " / " + fmtTime(m.duration);
480
+ });
481
+ m.addEventListener("ended", function () { playTrack(current + 1); });
482
+ m.addEventListener("error", function () {
483
+ errEl.textContent = "Gagal memuat media ini. Periksa URL atau koneksi internet, lalu coba trek lain.";
484
+ errEl.classList.remove("hidden");
485
+ playBtn.textContent = "▶ Putar";
486
+ });
487
+ }
488
+ bindMediaEvents(audioEl);
489
+ bindMediaEvents(videoEl);
490
+
491
+ playBtn.addEventListener("click", function () {
492
+ if (!media) return;
493
+ if (media.paused) {
494
+ tryPlay();
495
+ } else {
496
+ media.pause();
497
+ playBtn.textContent = "▶ Putar";
498
+ }
499
+ });
500
+ prevBtn.addEventListener("click", function () { playTrack(current - 1); });
501
+ nextBtn.addEventListener("click", function () { playTrack(current + 1); });
502
+ seek.addEventListener("input", function () {
503
+ if (media && media.duration) media.currentTime = seek.value / 100 * media.duration;
504
+ });
505
+ vol.addEventListener("input", function () {
506
+ if (media) media.volume = vol.value / 100;
507
+ });
508
+ addBtn.addEventListener("click", function () {
509
+ var url = urlInput.value.trim();
510
+ if (!url) return;
511
+ var title = url.split("/").pop().split("?")[0] || url;
512
+ try { title = decodeURIComponent(title); } catch (e) {}
513
+ custom.push({ title: title, url: url, type: guessType(url) });
514
+ save("wd-music", custom);
515
+ tracks = DEMO_TRACKS.concat(custom);
516
+ urlInput.value = "";
517
+ drawList();
518
+ });
519
+ urlInput.addEventListener("keydown", function (e) { if (e.key === "Enter") addBtn.click(); });
520
+
521
+ setMedia();
522
+ }
523
+
356
524
  window.APPS = {
357
525
  chat: { emoji: "💬", title: "Obrolan", render: renderChat },
358
526
  browser: { emoji: "🌐", title: "Peramban", render: renderBrowser },
359
527
  notes: { emoji: "📝", title: "Catatan", render: renderNotes },
360
528
  calc: { emoji: "🧮", title: "Kalkulator", render: renderCalc },
361
529
  files: { emoji: "📁", title: "Files", render: renderFiles },
362
- settings: { emoji: "⚙️", title: "Pengaturan", render: renderSettings }
530
+ settings: { emoji: "⚙️", title: "Pengaturan", render: renderSettings },
531
+ music: { emoji: "🎵", title: "Musik", render: renderMusic }
363
532
  };
364
533
  })();
package/js/desktop.js CHANGED
@@ -181,5 +181,134 @@
181
181
  }
182
182
  } catch (e) {}
183
183
 
184
+ // ---------- Notifikasi & pusat kontrol (taskbar) ----------
185
+ // Notifikasi tersimpan di localStorage (wd-notifs); mode jangan-ganggu di wd-dnd.
186
+ function notifStore(fallback) {
187
+ try {
188
+ var raw = localStorage.getItem("wd-notifs");
189
+ return raw ? JSON.parse(raw) : fallback;
190
+ } catch (e) { return fallback; }
191
+ }
192
+ function notifSave(list) {
193
+ try { localStorage.setItem("wd-notifs", JSON.stringify(list)); } catch (e) { /* abaikan */ }
194
+ }
195
+ var notifs = notifStore([]);
196
+ function isDnd() {
197
+ try { return localStorage.getItem("wd-dnd") === "1"; } catch (e) { return false; }
198
+ }
199
+ function timeWIB() {
200
+ return new Intl.DateTimeFormat("id-ID", {
201
+ timeZone: "Asia/Jakarta", hour: "2-digit", minute: "2-digit", hour12: false
202
+ }).format(new Date());
203
+ }
204
+ var notifBtn = document.getElementById("notif-btn");
205
+ var notifBadge = document.getElementById("notif-badge");
206
+ var notifPanel = document.getElementById("notif-panel");
207
+ var notifListEl = document.getElementById("notif-list");
208
+ var toastWrap = document.getElementById("toast-wrap");
209
+
210
+ function unreadCount() {
211
+ return notifs.filter(function (n) { return !n.read; }).length;
212
+ }
213
+ function drawNotifs() {
214
+ var unread = unreadCount();
215
+ if (unread > 0) {
216
+ notifBadge.textContent = unread > 9 ? "9+" : String(unread);
217
+ notifBadge.classList.remove("hidden");
218
+ } else {
219
+ notifBadge.classList.add("hidden");
220
+ }
221
+ notifListEl.innerHTML = "";
222
+ if (!notifs.length) {
223
+ var empty = document.createElement("p");
224
+ empty.className = "hint";
225
+ empty.textContent = "Belum ada notifikasi.";
226
+ notifListEl.appendChild(empty);
227
+ return;
228
+ }
229
+ notifs.slice().reverse().forEach(function (n) {
230
+ var row = document.createElement("div");
231
+ row.className = "notif-item" + (n.read ? "" : " unread");
232
+ var t = document.createElement("span");
233
+ t.className = "notif-text";
234
+ t.textContent = n.text;
235
+ var time = document.createElement("span");
236
+ time.className = "notif-time";
237
+ time.textContent = n.time;
238
+ row.appendChild(t);
239
+ row.appendChild(time);
240
+ notifListEl.appendChild(row);
241
+ });
242
+ }
243
+ function showToast(text) {
244
+ var toast = document.createElement("div");
245
+ toast.className = "toast";
246
+ toast.textContent = text;
247
+ toast.addEventListener("click", function () { toast.remove(); });
248
+ toastWrap.appendChild(toast);
249
+ setTimeout(function () { toast.remove(); }, 4000);
250
+ }
251
+ function pushNotif(text, opts) {
252
+ opts = opts || {};
253
+ notifs.push({ text: text, time: timeWIB(), read: false });
254
+ if (notifs.length > 30) notifs = notifs.slice(-30);
255
+ notifSave(notifs);
256
+ drawNotifs();
257
+ if (!opts.silent && !isDnd()) showToast(text);
258
+ }
259
+ function markAllRead() {
260
+ notifs.forEach(function (n) { n.read = true; });
261
+ notifSave(notifs);
262
+ drawNotifs();
263
+ }
264
+ notifBtn.addEventListener("click", function () {
265
+ var willOpen = notifPanel.classList.contains("hidden");
266
+ notifPanel.classList.toggle("hidden");
267
+ startMenu.classList.add("hidden");
268
+ if (willOpen) markAllRead();
269
+ });
270
+ document.addEventListener("click", function (e) {
271
+ if (!notifPanel.classList.contains("hidden") &&
272
+ !e.target.closest("#notif-panel") && !e.target.closest("#notif-btn")) {
273
+ notifPanel.classList.add("hidden");
274
+ }
275
+ });
276
+ document.getElementById("notif-clear").addEventListener("click", function () {
277
+ notifs = [];
278
+ notifSave(notifs);
279
+ drawNotifs();
280
+ });
281
+ var ccTheme = document.getElementById("cc-theme");
282
+ function refreshThemeBtn() {
283
+ ccTheme.textContent = document.body.classList.contains("light-theme")
284
+ ? "🌙 Ganti ke gelap" : "☀️ Ganti ke terang";
285
+ }
286
+ ccTheme.addEventListener("click", function () {
287
+ var light = document.body.classList.toggle("light-theme");
288
+ try { localStorage.setItem("wd-theme", light ? "light" : "dark"); } catch (e) {}
289
+ refreshThemeBtn();
290
+ });
291
+ var ccDnd = document.getElementById("cc-dnd");
292
+ function refreshDndBtn() {
293
+ ccDnd.textContent = isDnd() ? "🔕 Jangan ganggu: aktif" : "🔔 Jangan ganggu: nonaktif";
294
+ }
295
+ ccDnd.addEventListener("click", function () {
296
+ try { localStorage.setItem("wd-dnd", isDnd() ? "0" : "1"); } catch (e) {}
297
+ refreshDndBtn();
298
+ });
299
+ document.getElementById("cc-settings").addEventListener("click", function () {
300
+ notifPanel.classList.add("hidden");
301
+ openApp("settings");
302
+ });
303
+ document.getElementById("cc-test").addEventListener("click", function () {
304
+ pushNotif("Ini notifikasi tes dari pusat kontrol.");
305
+ });
306
+ refreshThemeBtn();
307
+ refreshDndBtn();
308
+ drawNotifs();
309
+ if (!notifs.length) {
310
+ pushNotif("Selamat datang di Rutbhics! Ketuk lonceng di taskbar untuk notifikasi & pusat kontrol.");
311
+ }
312
+
184
313
  renderLaunchers("");
185
314
  })();
package/package.json CHANGED
@@ -22,5 +22,5 @@
22
22
  "scripts": {
23
23
  "start": "node bin/rutbhics.js"
24
24
  },
25
- "version": "1.0.0"
26
- }
25
+ "version": "1.0.1"
26
+ }