// Netiquette Indonesia Homepage — interactive shell
// Renders the live regions (hero medallions, trust counters, solution tabs)
// over static HTML. Modeled on websg/assets/js/app.jsx, trimmed to AMS+IMS only.
const MODULES = [
{ id: "ams", short: "AMS", name: "Akuntansi", color: "#2E7D32", tint: "#e8f5e9", icon: "ti-book", tag: "Accounting Management System",
desc: "Kelola pembukuan bisnis Anda, pantau arus kas, dan hasilkan laporan keuangan secara real-time, tanpa perlu lagi mencocokkan spreadsheet secara manual.",
features: ["Pembukuan double-entry lengkap", "Laporan keuangan detail (Laba Rugi, Neraca, dan lainnya)", "Invoicing & penagihan otomatis", "Terintegrasi langsung dengan modul Inventaris", "Visibilitas real-time atas kinerja keuangan bisnis"],
img: "assets/module-ams.png" },
{ id: "ims", short: "IMS", name: "Inventaris", color: "#C62828", tint: "#fce4ec", icon: "ti-package", tag: "Inventory Management System",
desc: "Pantau stok secara real-time, kelola pembelian dan penjualan di semua lokasi, dan hindari selisih stok yang selama ini bikin repot dicatat manual.",
features: ["Manajemen pembelian & penjualan", "Stok real-time di berbagai lokasi/gudang", "Dukungan lokasi, batch, kedaluwarsa, & matrix item", "Otomatis terhubung ke modul Akuntansi", "Laporan inventaris lengkap"],
img: "assets/module-ims.png" }];
// ───────────────────────────────────────────────
// Hero carousel (brand orbit variant, AMS + IMS)
// ───────────────────────────────────────────────
function HeroCarousel() {
// Two modules only, placed left/right around the center hub.
const positions = [Math.PI, 0]; // AMS = left, IMS = right
return (
{MODULES.map((mod, idx) => {
const ang = positions[idx];
const r = 42;
const x = 50 + Math.cos(ang) * r;
const y = 50 + Math.sin(ang) * r;
return (
{mod.name}
{mod.short}
);
})}
1 Sistem. 2 Solusi Terintegrasi. Untuk UKM Indonesia.
);
}
// ───────────────────────────────────────────────
// Animated counter
// ───────────────────────────────────────────────
function Counter({ to, suffix = "", duration = 1400 }) {
const [val, setVal] = React.useState(0);
const ref = React.useRef(null);
const started = React.useRef(false);
React.useEffect(() => {
const el = ref.current;
if (!el) return;
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting && !started.current) {
started.current = true;
const start = performance.now();
const tick = (now) => {
const t = Math.min(1, (now - start) / duration);
const eased = 1 - Math.pow(1 - t, 3);
setVal(Math.round(to * eased));
if (t < 1) requestAnimationFrame(tick);
};
requestAnimationFrame(tick);
}
});
}, { threshold: 0.3 });
io.observe(el);
return () => io.disconnect();
}, [to]);
const formatted = to >= 1000 ? val.toLocaleString() : val.toString();
return {formatted}{suffix};
}
// ───────────────────────────────────────────────
// Solutions tabs (AMS / IMS)
// ───────────────────────────────────────────────
function SolutionsTabs() {
const [active, setActive] = React.useState(MODULES[0].id);
const m = MODULES.find((x) => x.id === active);
return (
<>
{MODULES.map((mod) =>
)}
>);
}
// ───────────────────────────────────────────────
// Scroll reveal
// ───────────────────────────────────────────────
function ScrollReveal() {
React.useEffect(() => {
const els = document.querySelectorAll(".reveal");
const io = new IntersectionObserver((entries) => {
entries.forEach((e) => {
if (e.isIntersecting) {
e.target.classList.add("in");
io.unobserve(e.target);
}
});
}, { threshold: 0.12 });
els.forEach((el) => io.observe(el));
return () => io.disconnect();
}, []);
return null;
}
// ───────────────────────────────────────────────
// Main app — mounts pieces into placeholders
// ───────────────────────────────────────────────
function App() {
return (
<>
{/* Mount interactive regions via portals into placeholders in index.html */}
{document.getElementById("hero-visual-mount") &&
ReactDOM.createPortal(, document.getElementById("hero-visual-mount"))}
{document.getElementById("trust-mount") &&
ReactDOM.createPortal(
Singapura, Malaysia, Hong Kong, Thailand & Indonesia
Negara Termasuk Indonesia
Modul Terintegrasi (AMS & IMS)
,
document.getElementById("trust-mount")
)}
{document.getElementById("solutions-mount") &&
ReactDOM.createPortal(, document.getElementById("solutions-mount"))}
>);
}
ReactDOM.createRoot(document.getElementById("app-root")).render();