Files
oxpoe eaf05f2811 OPEN-ISP v0.5 (pickle-lime) — rilis open-source perdana
Aplikasi billing & manajemen ISP (fiber optic) — PWA pelanggan, portal teknisi & kolektor,
admin panel lengkap, multi-vendor OLT, RADIUS, WhatsApp/Telegram, pembayaran QRIS.

Kredit & lisensi (ISC):
- Base kode & repo: alijayanet/billing-rtrw oleh Alijaya Net — terima kasih banyak
  atas base kode dan repo yang bermanfaat.
- Add-on: GoWA, LibreSpeed/OpenSpeedTest, Uptime Kuma, notification-forwarder.
- Dikembangkan menjadi OPEN-ISP oleh oxpoe — https://github.com/oxpoe/open-isp
2026-10-09 05:51:21 +07:00

62 lines
3.2 KiB
JavaScript

/* theme-toggle.js — switch tema global (portal & pelanggan).
- Sinkronkan data-theme + data-bs-theme + class .light-theme + color-scheme
- Pakai #topbarThemeBtn / .theme-fab yang sudah ada, jika tidak buat floating.
- MutationObserver: bila skrip lain mengubah data-theme, atribut lain ikut sinkron (live).
- pageshow/visibilitychange: menyegarkan saat PWA resume / bfcache. */
(function () {
var KEY = 'landing-theme';
function current() { return document.documentElement.getAttribute('data-theme') || 'dark'; }
function syncIcon() {
var isLight = current() === 'light';
var btn = document.getElementById('topbarThemeBtn');
if (btn) { var i = btn.querySelector('i'); if (i) i.className = isLight ? 'bi bi-moon-stars' : 'bi bi-sun'; }
}
function sync() {
var isLight = current() === 'light';
var bs = isLight ? 'light' : 'dark';
if (document.documentElement.getAttribute('data-bs-theme') !== bs) document.documentElement.setAttribute('data-bs-theme', bs);
if (document.documentElement.classList.contains('light-theme') !== isLight) document.documentElement.classList.toggle('light-theme', isLight);
if (document.body && document.body.classList.contains('light-theme') !== isLight) document.body.classList.toggle('light-theme', isLight);
try { if (document.documentElement.style.colorScheme !== bs) document.documentElement.style.colorScheme = bs; } catch (e) {}
syncIcon();
}
function apply(t) {
t = t || (function () { try { return localStorage.getItem(KEY); } catch (e) { return null; } })() || 'dark';
document.documentElement.setAttribute('data-theme', t);
sync();
return t;
}
window.applyTheme = apply;
window.toggleTheme = function () {
var cur = current() === 'dark' ? 'light' : 'dark';
try { localStorage.setItem(KEY, cur); } catch (e) {}
apply(cur);
try { document.body && document.body.getBoundingClientRect(); } catch (e) {} // paksa repaint
};
apply();
// Bila skrip lain (mis. toggleDashTheme) mengubah data-theme, sinkronkan atribut lain
try { new MutationObserver(sync).observe(document.documentElement, { attributes: true, attributeFilter: ['data-theme', 'class'] }); } catch (e) {}
function bind(el) {
if (!el || el.dataset.themeBound || el.hasAttribute('onclick')) return;
el.dataset.themeBound = '1';
el.addEventListener('click', function (e) { e.preventDefault(); window.toggleTheme(); });
}
function mount() {
var top = document.getElementById('topbarThemeBtn');
if (top) { bind(top); syncIcon(); return; }
var fab = document.querySelector('.theme-fab');
if (fab) { bind(fab); return; }
var b = document.createElement('button');
b.type = 'button'; b.className = 'theme-fab';
b.setAttribute('aria-label', 'Ganti tema'); b.setAttribute('title', 'Mode Gelap / Terang');
b.innerHTML = '<i class="bi bi-circle-half"></i>';
b.addEventListener('click', window.toggleTheme);
document.body.appendChild(b);
}
if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', mount); else mount();
window.addEventListener('pageshow', function () { apply(); });
document.addEventListener('visibilitychange', function () { if (!document.hidden) sync(); });
})();