<!doctype html>
<html lang="id">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />

<title>Voice Promax — Kloning Suara &amp; Teks jadi Suara AI, 600+ Bahasa</title>
<meta name="description" content="Kloning suara dari rekaman 20 detik dan ubah teks apa pun menjadi suara yang terdengar manusiawi, dalam 600+ bahasa. Engine berjalan di GPU Google Colab milik Anda sendiri." />
<link rel="canonical" href="https://voicepromax.com/" />
<meta name="theme-color" content="#070a12" />

<!-- Favicon ditanam sebagai data URI, sama seperti di dashboard, supaya
     ikon tab selalu muncul tanpa bergantung pada request /favicon.ico
     atau /logo.png yang terpisah. -->
<link rel="icon" type="image/webp" href="data:image/webp;base64,UklGRpYFAABXRUJQVlA4WAoAAAAQAAAAPwAAPwAAQUxQSOMAAAABkBvZtmpH+74rllKRSRYzmAxJcALMaDEzuCpZrIEAmDEEnnfu2UPWO/5UREwAAHigbPHJZyY4fr5eB3j82yPvXJn823JE7i+PfqGKJktFyVFEDkjBFFVoUISb8M6jiz+VJvUHB+BdrojSqMYsBM4oNCu8QH5QGg5atECxJFx5zGAp8MVXGg/87+fYmryiWlJ+2qBYEj2tZLAU2Bg9VLET9FkGShirmZ9shsckf1j5wQN4590eRSyEmDdZkYOLME2qaLI0BPIwBw6Ac6i+pcFHnYDDPz3QsPlCkvTz/Ul7JiIHAABWUDggjAQAANAYAJ0BKkAAQAA+PRyLRCIhoRQKBaggA8S0AE6ZZdu34XzE6o/j/5rv/JcYgWcA6EvlM9ZF6AHggfBf+13oof/+kgfk3kz/pf5ZaKL+xY7//S70T+u+hT/iNYR/oX+o/pPuifxH/L8pXzb/zPcG/kH8+/zv298jf+sZlOyIkEHjIDrEhtSgEg0igeRA1z0SJx+U1d6QGYBmm4HZisXlh+zXx8cUwmz8GT0Ox4Sbt1nDMG3q1NyLM4spOYqnjx7wlRfagKLT72t//0A3hmZGAAD+/5OffhLXylFL2MIjGUEc0LjNgVOYI+/ln/2oMM10Azh9/ezhvz/H3zeuHHuUkBiER6gQ518PKGXR8OibAQKOCVfZQcJZ+W+9wBI3D9Krm/M1AXKq3R5ie47iv0UeJf0o1/uFus6q7NpUgzKcomT/53ePjoh7BcuRTM8zgrvNZ2b1RRP+MzyvrVN/M94OAZhv/tNes/p4t7xAtCM6wIKS/S4cr50BY85EvcI+EfgVnUcGNzjK/74IDIFv/+nhH85o53SQwwel4KEMm3ikP1vIotJ3igKeQ+Z7r+DWuRIf/L96hwvwMzHKZPGxHln3jv2XXDTh2LsM7y//FahEGKAu6lVYznl1aIiHHdxEkqm6EmnkH8hJS3SIhOUz46I20/0FIBP6dFWKed5H1eURZO2oYNLzEmt+htG/BH7/mpxXmu/xnjMjkEVpMweNg5JYr73bKNbAqTfOmTHRiNgdqx94ftxfWF/BkhU3gsvo18Vu0Xn98catsNLQI+/6mi/RQ4cBgFM+2O6kyLTYr/thPzX675S8CWXpNxDe7na3wIjUVgBW+2GSwR0mP2Dnj/BtPySX0t+h19r8bLnYqV5//LM4A/jgLIt6z/74OE2JkR1ty7/v4v5tKjoaMzadPM9YIVID1Uy+lnanNcqaTQ7TslD6NrlBQU6d1zv9sk76aXZKcahLqVgDdx9bvxcEeZVKCZit8TMo8atRvLbNXdjwsekkVx+6X5ihMeEnqMN6IlFBB9hUoUu8PTzzutS5RF3XFYJ2Fer8eqJ00CYKHjuWscgAdTL/4ZrWOUHmrTprl/2H4tV/mElpr4v0Ecy8qC0Duu2P4dJza7wAgD3zKqs8TDMzuVd2it19UmHu55J3HYCUOX5yAmk8va148PEczisL62d+HW17zb4p1pjJ77XmVBN+v/7uTbrXs6jHYhl1VCExp0YwfdBi1ibt7Ya7kjCBx+HlDt4F85hgC8vIDavvmkoLLbWfAZOuZz/d3pwuZkNYVaupcsNNHgMAkgoOj4VXXeWWjg92XJOF0sQ5kEnsVX6AygiGoUO//1b97FC3VH5B9GvF/xj6XSeJQDqQ6uQFIjmucJbp8AOsHnqbR/PeO9+1sS235vwluNNsPo3t13QJ44gswU4S1Q1vnXMspqi5QnkNvuK4C1tjH0PL4hEe7EFp2Fqcv7v5uf5bTKuOk94jOL+pEGVWhmoxdosV9S5wlbLNuJlAKyjkun0CvIR0z3v04Bbh/yzvqoXx8VI6AM32KrxeZiS1lvwWGgaAAA==" />
<link rel="apple-touch-icon" href="/logo.png" />

<meta property="og:type" content="website" />
<meta property="og:site_name" content="Voice Promax" />
<meta property="og:locale" content="id_ID" />
<meta property="og:url" content="https://voicepromax.com/" />
<meta property="og:title" content="Voice Promax — Kloning Suara &amp; Teks jadi Suara AI" />
<meta property="og:description" content="Kloning suara dari rekaman 20 detik, podcast multi-pembicara, buku audio, dan ekspresi emosi. Langsung dari browser." />
<meta property="og:image" content="/logo.png" />
<meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Voice Promax — Kloning Suara &amp; Teks jadi Suara AI" />
<meta name="twitter:description" content="Kloning suara dari rekaman 20 detik, podcast multi-pembicara, buku audio, dan ekspresi emosi." />
<meta name="twitter:image" content="/logo.png" />

<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:wght@500;600;700;800&family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet" />

<script>
// Dijalankan di <head>, SEBELUM halaman digambar.
//
// Kenapa kelas ini dipasang lewat JavaScript dan bukan langsung ditulis di HTML:
// aturan animasi menyembunyikan elemen (opacity 0) sampai ia terlihat di layar.
// Kalau kelasnya melekat permanen di HTML lalu JavaScript gagal dimuat — diblokir
// pemblokir skrip, jaringan putus di tengah, apa pun — seluruh isi halaman akan
// tidak pernah muncul. Dengan cara ini, JavaScript mati berarti animasinya tidak
// jalan dan semuanya langsung terlihat. Itu kegagalan yang benar.
//
// Ditaruh di <head> supaya tidak ada kedipan: kalau dipasang setelah halaman
// tergambar, isinya akan sempat terlihat lalu tiba-tiba disembunyikan.
(function () {
  try {
    var gerakBoleh = !(window.matchMedia && window.matchMedia("(prefers-reduced-motion: reduce)").matches);
    if ("IntersectionObserver" in window && gerakBoleh) {
      document.documentElement.classList.add("animasi");
    }
  } catch (e) {}
})();
</script>

<style>
  :root {
    /* Tema mengikuti logo: biru-kehijauan di kiri, ungu-magenta di kanan. */
    --bg: #070a12;
    --bg-2: #0b0f1a;
    --panel: #101623;
    --panel-2: #0c111c;
    --line: rgba(255,255,255,.08);
    --line-2: rgba(255,255,255,.14);
    --text: #eef2f9;
    --muted: #8b97ad;
    --teal: #22d3ee;
    --teal-2: #5eead4;
    --ungu: #a855f7;
    --ungu-2: #e879f9;
    --ok: #34d399;
    --warn: #fbbf24;
    --danger: #fb7185;
    --grad: linear-gradient(100deg, var(--teal) 0%, #6366f1 48%, var(--ungu) 100%);
    --grad-lembut: linear-gradient(100deg, rgba(34,211,238,.16), rgba(168,85,247,.16));
    --r: 14px;
    --tulis: "Plus Jakarta Sans", Inter, system-ui, sans-serif;
    --baca: Inter, system-ui, sans-serif;
  }
  * { box-sizing: border-box; }
  html { scroll-behavior: smooth; }
  body { margin: 0; background: var(--bg); color: var(--text); font-family: var(--baca);
    line-height: 1.6; -webkit-font-smoothing: antialiased; overflow-x: hidden; }
  h1, h2, h3, .tulis { font-family: var(--tulis); letter-spacing: -.02em; }
  a { color: var(--teal-2); }
  img { max-width: 100%; display: block; }
  .wrap { max-width: 1120px; margin: 0 auto; padding: 0 20px; }
  .sembunyi { display: none !important; }

  /* ============ bilah atas ============ */
  .nav { position: sticky; top: 0; z-index: 60; background: rgba(7,10,18,.72);
    backdrop-filter: blur(14px); border-bottom: 1px solid transparent; transition: border-color .25s, background .25s; }
  .nav.turun { border-bottom-color: var(--line); background: rgba(7,10,18,.9); }
  .nav .wrap { display: flex; align-items: center; gap: 26px; height: 66px; }
  .merek { margin-right: auto; display: flex; align-items: center; gap: 10px; text-decoration: none; }
  .merek img { height: 34px; width: auto; }
  /* Logo ditanam langsung sebagai data URI (bukan /logo.png) supaya tidak
     bergantung pada permintaan jaringan terpisah — persis seperti cara
     dashboard menampilkan logonya. Nama merek teks tetap disiapkan sebagai
     jaring pengaman kalau data URI-nya suatu saat gagal dirender. */
  .merek-teks { font-family: var(--tulis); font-weight: 800; font-size: 17px;
    letter-spacing: -.02em; background: var(--grad); -webkit-background-clip: text;
    background-clip: text; color: transparent; }
  @media (max-width: 480px) {
    .merek img { height: 26px; }
    .nav .wrap { gap: 8px; }
    /* Tanpa ini, logo + "Masuk" + "Daftar" melebihi lebar layar 360px. */
    .nav .btn.kecil { padding: 8px 11px; font-size: 12.5px; }
    .nav .btn .panah { display: none; }
  }
  .navlink { color: var(--muted); text-decoration: none; font-size: 14px; font-weight: 600; font-family: var(--tulis); }
  .navlink:hover { color: var(--text); }
  @media (max-width: 900px) { .navlink { display: none; } }

  /* ============ tombol ============ */
  .btn { display: inline-flex; align-items: center; justify-content: center; gap: 9px;
    border-radius: 11px; font-family: var(--tulis); font-weight: 700; font-size: 15px;
    padding: 12px 22px; cursor: pointer; border: 1px solid transparent; text-decoration: none;
    transition: filter .15s, border-color .15s, transform .07s, box-shadow .2s; white-space: nowrap; }
  .btn:active { transform: translateY(1px); }
  .btn.utama { background: var(--grad); color: #05070d; box-shadow: 0 6px 26px rgba(99,102,241,.32); }
  .btn.utama:hover { filter: brightness(1.08); box-shadow: 0 8px 32px rgba(99,102,241,.45); }
  .btn.hantu { background: rgba(255,255,255,.03); color: var(--text); border-color: var(--line-2); }
  .btn.hantu:hover { border-color: var(--teal); background: rgba(34,211,238,.06); }
  .btn.kecil { padding: 9px 16px; font-size: 13.5px; border-radius: 9px; }
  .btn.penuh { width: 100%; }
  .btn:disabled { opacity: .5; cursor: default; transform: none; }
  .btn .panah { transition: transform .18s; }
  .btn:hover .panah { transform: translateX(3px); }

  .btn.google { background: #fff; color: #1f1f1f; font-weight: 600; border-color: #dadce0; box-shadow: none; }
  .btn.google:hover { background: #f2f4f7; }
  .btn.google svg { flex: none; }

  /* ============ hero ============ */
  .hero { position: relative; padding: 84px 0 60px; text-align: center; overflow: hidden; }
  .cahaya { position: absolute; border-radius: 50%; pointer-events: none; }
  .cahaya.a { top: -280px; left: 50%; transform: translateX(-62%); width: 720px; height: 560px;
    background: radial-gradient(circle, rgba(34,211,238,.20), transparent 62%); }
  .cahaya.b { top: -220px; left: 50%; transform: translateX(-12%); width: 700px; height: 560px;
    background: radial-gradient(circle, rgba(168,85,247,.22), transparent 62%); }
  .jala { position: absolute; inset: 0; pointer-events: none; opacity: .5;
    background-image: linear-gradient(rgba(255,255,255,.045) 1px, transparent 1px),
      linear-gradient(90deg, rgba(255,255,255,.045) 1px, transparent 1px);
    background-size: 62px 62px;
    mask-image: radial-gradient(ellipse 72% 60% at 50% 30%, #000, transparent 76%);
    -webkit-mask-image: radial-gradient(ellipse 72% 60% at 50% 30%, #000, transparent 76%); }

  /* ================= MUNCUL SAAT DIGULIR =================
     Semua aturan di sini bersyarat pada .animasi di <html>. Tanpa kelas itu
     (JavaScript mati, atau pengguna meminta gerak dikurangi) tidak ada yang
     disembunyikan sama sekali — lihat catatan di skrip <head>.

     Yang dianimasikan hanya opacity dan transform. Keduanya ditangani kartu
     grafis, jadi tidak ada tata letak yang dihitung ulang saat halaman digulir. */

  .animasi [data-muncul] {
    opacity: 0;
    /* Lengkung ini melambat panjang di ujung, jadi berhentinya terasa mengendap
       alih-alih memotong tiba-tiba. */
    transition: opacity .8s cubic-bezier(.16, 1, .3, 1),
                transform .8s cubic-bezier(.16, 1, .3, 1);
    transition-delay: calc(var(--i, 0) * 90ms);
  }

  /* Ragam arah masuk. Blok yang berdampingan diberi arah berbeda supaya
     halaman tidak terasa satu gerakan yang diulang terus. */
  .animasi [data-muncul="naik"]  { transform: translateY(26px); }
  .animasi [data-muncul="kiri"]  { transform: translateX(-34px); }
  .animasi [data-muncul="kanan"] { transform: translateX(34px); }
  .animasi [data-muncul="skala"] { transform: scale(.93); }
  /* Sedikit melewati batas lalu kembali — untuk elemen kecil yang butuh tekanan,
     seperti lencana VS. */
  .animasi [data-muncul="pegas"] {
    transform: scale(.5);
    transition-timing-function: cubic-bezier(.34, 1.56, .64, 1);
  }
  /* Garis yang menggambar dirinya sendiri dari kiri. */
  .animasi [data-muncul="garis"] { transform: scaleX(0); transform-origin: left center; opacity: 1; }
  .kepala.tengah .garis { transform-origin: center; }

  /* Ditaruh SETELAH semua ragam di atas: kekhususannya sama, jadi yang menang
     adalah yang tertulis paling akhir. */
  .animasi [data-muncul].tampil { opacity: 1; transform: none; }

  /* Catatan soal will-change: sengaja TIDAK dipakai di sini.
     Karena animasinya kini berulang, elemennya tidak pernah "selesai", jadi
     will-change harus menyala terus — dan itu berarti puluhan lapisan komposit
     hidup permanen di memori. Browser modern sudah menaikkan transform dan
     opacity ke kartu grafis sendiri begitu transisinya mulai; keuntungan
     will-change di sini hanya satu bingkai di awal, tidak sebanding harganya. */

  /* ---- teks masuk kata demi kata ----
     Setiap kata duduk di dalam kotak ber-overflow:hidden, dan isinya digeser
     dari bawah kotak itu. Hasilnya kata-kata seperti terangkat dari balik garis,
     bukan sekadar memudar.

     padding-bottom + margin-bottom negatif itu WAJIB: tanpanya, overflow:hidden
     memotong ekor huruf yang menjulur ke bawah seperti g, y, dan j. */
  .kata { display: inline-block; overflow: hidden; vertical-align: bottom;
    padding-bottom: .16em; margin-bottom: -.16em; }
  .animasi [data-teks] .kata > i {
    display: inline-block; font-style: inherit;
    transform: translateY(115%);
    transition: transform .75s cubic-bezier(.16, 1, .3, 1);
    /* Dua penjumlah: --i menggeser seluruh blok relatif blok lain, --w menggeser
       kata ini relatif kata sebelumnya. */
    transition-delay: calc(var(--i, 0) * 90ms + var(--w, 0) * 26ms);
  }
  .animasi [data-teks].tampil .kata > i { transform: none; }

  /* ---- judul hero, baris demi baris ----
     Sengaja per BARIS, bukan per kata. Baris kedua memakai teks bergradien
     (background-clip: text), dan memotongnya jadi banyak kotak inline-block
     membuat gradiennya pecah per kata. */
  .judul .baris { display: block; overflow: hidden;
    padding-bottom: .1em; margin-bottom: -.1em; }
  .animasi .judul[data-muncul] .baris > span {
    display: block;
    transform: translateY(108%);
    transition: transform .95s cubic-bezier(.16, 1, .3, 1);
    transition-delay: calc(var(--l, 0) * 130ms);
  }
  .animasi .judul[data-muncul].tampil .baris > span { transform: none; }

  /* ---- ikon kartu menyusul induknya ----
     Dianimasikan lewat kelas .tampil di kartunya, bukan diawasi sendiri, supaya
     urutannya pasti: kartu dulu, ikonnya menyusul. */
  .animasi .kartu[data-muncul] .ikon {
    transform: scale(.55) rotate(-12deg);
    opacity: 0;
    transition: transform .6s cubic-bezier(.34, 1.56, .64, 1), opacity .5s ease;
    transition-delay: calc(var(--i, 0) * 90ms + 220ms);
  }
  .animasi .kartu[data-muncul].tampil .ikon { transform: none; opacity: 1; }

  /* ---- nomor langkah ---- */
  .animasi .langkah > div[data-muncul]::before {
    opacity: 0; transform: translateY(10px);
    transition: opacity .6s ease, transform .6s cubic-bezier(.16, 1, .3, 1);
    transition-delay: calc(var(--i, 0) * 90ms + 200ms);
  }
  .animasi .langkah > div[data-muncul].tampil::before { opacity: 1; transform: none; }

  /* ---- garis tumbuh di bawah kepala bagian ---- */
  .garis { display: block; height: 2px; width: 76px; border-radius: 99px;
    background: var(--grad); margin: 20px 0 0; }
  .kepala.tengah .garis { margin-left: auto; margin-right: auto; }

  /* ---- bentuk hias yang muncul saat digulir ---- */
  .bentuk { position: absolute; inset: 0; pointer-events: none; overflow: hidden; z-index: -1; }
  .bentuk b { position: absolute; display: block; border-radius: 50%; }
  .bentuk .o1 { width: 320px; height: 320px; left: -90px; top: 8%;
    background: radial-gradient(circle, rgba(34,211,238,.16), transparent 68%); }
  .bentuk .o2 { width: 380px; height: 380px; right: -120px; bottom: 4%;
    background: radial-gradient(circle, rgba(168,85,247,.16), transparent 68%); }
  .bentuk .cincin { width: 220px; height: 220px; right: 6%; top: 14%;
    border: 1px solid rgba(255,255,255,.07); background: none; }
  .animasi .bentuk b[data-muncul] { transition-duration: 1.3s; }

  /* ================= LAMPU YANG MENGIKUTI KURSOR =================
     Dulu lapisan ini berada DI DALAM hero dengan position:absolute, jadi
     kotaknya hanya sebesar hero dan efeknya berhenti begitu digulir ke bawah.
     Sekarang position:fixed seukuran layar, dan mengikuti kursor di seluruh
     halaman tanpa perlu ikut menghitung posisi gulir sama sekali.

     z-index 0 menaruhnya DI BAWAH semua isi halaman. Supaya cahayanya tetap
     terlihat, latar belakang bagian yang berselang-seling diubah dari warna
     pekat menjadi lapisan tipis tembus pandang (lihat #dengar dan #cara). */
  .lampu { position: fixed; inset: 0; z-index: 0; pointer-events: none;
    opacity: 0; transition: opacity .5s ease; contain: strict; }
  body.terang .lampu { opacity: 1; }

  /* Kedua lapisan dipusatkan di titik (0,0) lalu DIGESER dengan transform.
     transform ditangani kartu grafis dan tidak memicu penggambaran ulang —
     berbeda dengan menganimasikan posisi gradien seukuran layar, yang memaksa
     seluruh layar digambar ulang tiap bingkai. */
  .kilau, .jala-lampu { position: absolute; left: 0; top: 0;
    width: 780px; height: 780px; margin: -390px 0 0 -390px;
    transform: translate3d(var(--lx, 50vw), var(--ly, 34vh), 0);
    will-change: transform; }

  .kilau { background: radial-gradient(circle closest-side,
    rgba(168,85,247,.28), rgba(99,102,241,.15) 40%, rgba(34,211,238,.07) 62%, transparent 72%); }

  /* Jala yang lebih terang, ditampakkan hanya di sekitar kursor. Inilah yang
     membuat latarnya terasa benar-benar DITERANGI, bukan sekadar ditimpa warna.
     background-position dibalik arah geserannya supaya garis jalanya tetap diam
     di layar dan tidak ikut terbawa kursor. */
  .jala-lampu {
    background-image: linear-gradient(rgba(125,211,252,.30) 1px, transparent 1px),
      linear-gradient(90deg, rgba(125,211,252,.30) 1px, transparent 1px);
    background-size: 62px 62px;
    background-position: calc(-1 * var(--lx, 0px)) calc(-1 * var(--ly, 0px));
    mask-image: radial-gradient(circle closest-side, #000 0%, rgba(0,0,0,.5) 24%, transparent 46%);
    -webkit-mask-image: radial-gradient(circle closest-side, #000 0%, rgba(0,0,0,.5) 24%, transparent 46%); }

  /* Di layar sentuh tidak ada kursor untuk diikuti, dan lapisan tambahan hanya
     memakan daya baterai. Dimatikan lewat media query, bukan lewat JavaScript,
     supaya tidak pernah sempat digambar. */
  @media (hover: none), (prefers-reduced-motion: reduce) {
    .lampu { display: none; }
  }

  /* Isi halaman harus berada DI ATAS lampu. */
  .hero, section, footer { position: relative; z-index: 1; }
  .hero .wrap { position: relative; z-index: 1; }

  /* .pil sudah tidak dipakai di hero (dihapus atas permintaan), disimpan
     kalau-kalau ingin dipakai lagi di bagian lain. */
  .pil { display: inline-flex; align-items: center; gap: 8px; font-size: 12.5px; font-weight: 600;
    color: var(--teal-2); background: rgba(34,211,238,.07); border: 1px solid rgba(34,211,238,.26);
    border-radius: 99px; padding: 6px 15px; margin-bottom: 26px; font-family: var(--tulis); }
  .pil .titik { width: 6px; height: 6px; border-radius: 50%; background: var(--ok); flex: none;
    box-shadow: 0 0 0 0 rgba(52,211,153,.6); animation: denyut 2.4s infinite; }
  @keyframes denyut { 70% { box-shadow: 0 0 0 7px rgba(52,211,153,0); } 100% { box-shadow: 0 0 0 0 rgba(52,211,153,0); } }

  h1.judul { font-size: clamp(2.4rem, 7.4vw, 4.6rem); line-height: 1.04; font-weight: 800;
    letter-spacing: -.035em; margin: 0 0 22px; }
  h1.judul .baris2 { display: block; background: var(--grad); -webkit-background-clip: text;
    background-clip: text; color: transparent; -webkit-text-fill-color: transparent; }

  /* ---- judul hero: pergantian teks berkala dengan efek glitch ----
     Dipicu lewat kelas .judul-glitch yang ditambahkan JS sesaat sebelum
     teksnya diganti, lalu dilepas lagi setelah animasi selesai.

     Efeknya meniru glitch RGB-split: dua salinan teks (lewat ::before/::after,
     isinya dari atribut data-text yang disamakan JS dengan textContent asli)
     diwarnai cyan & magenta, lalu dipotong jadi potongan horizontal yang
     bergeser cepat di atas teks aslinya.

     PENTING: position/isolation HANYA dipasang lewat .judul-glitch (kelas
     sementara saat animasi saja), bukan permanen di span-nya. Kalau dipasang
     permanen, itu bikin baris gradien (baris2, pakai background-clip:text)
     jadi tidak kelihatan sama sekali di Safari/iOS — sudah pernah kejadian. */
  .judul .baris > span { display: inline-block; }
  .judul .baris > span::before,
  .judul .baris > span::after {
    content: attr(data-text);
    position: absolute; left: 0; top: 0; width: 100%; height: 100%;
    pointer-events: none; opacity: 0; background: none; -webkit-text-fill-color: initial;
  }
  .judul-glitch { position: relative; isolation: isolate; }
  .judul-glitch::before {
    opacity: 1; color: #22d3ee; mix-blend-mode: screen;
    animation: glitchFatiaA .55s steps(1, end);
  }
  .judul-glitch::after {
    opacity: 1; color: #ec4899; mix-blend-mode: screen;
    animation: glitchFatiaB .55s steps(1, end);
  }
  @keyframes glitchFatiaA {
    0%   { clip-path: inset(0 0 84% 0);   transform: translate(0, 0); }
    16%  { clip-path: inset(8% 0 62% 0);  transform: translate(-7px, 0); }
    32%  { clip-path: inset(38% 0 32% 0); transform: translate(6px, 0); }
    48%  { clip-path: inset(58% 0 12% 0); transform: translate(-5px, 0); }
    64%  { clip-path: inset(18% 0 58% 0); transform: translate(5px, 0); }
    80%  { clip-path: inset(45% 0 25% 0); transform: translate(-3px, 0); }
    100% { clip-path: inset(0 0 100% 0);  transform: translate(0, 0); opacity: 0; }
  }
  @keyframes glitchFatiaB {
    0%   { clip-path: inset(70% 0 4% 0);  transform: translate(0, 0); }
    16%  { clip-path: inset(28% 0 42% 0); transform: translate(7px, 0); }
    32%  { clip-path: inset(52% 0 18% 0); transform: translate(-6px, 0); }
    48%  { clip-path: inset(12% 0 68% 0); transform: translate(5px, 0); }
    64%  { clip-path: inset(60% 0 10% 0); transform: translate(-5px, 0); }
    80%  { clip-path: inset(32% 0 40% 0); transform: translate(3px, 0); }
    100% { clip-path: inset(100% 0 0 0); transform: translate(0, 0); opacity: 0; }
  }
  @keyframes judulGlitch {
    0%   { transform: translate(0, 0); opacity: 1; }
    16%  { transform: translate(-4px, 1px); opacity: .8; }
    32%  { transform: translate(4px, -1px); opacity: .85; }
    48%  { transform: translate(-3px, 0); opacity: .7; }
    64%  { transform: translate(3px, 1px); opacity: .9; }
    100% { transform: translate(0, 0); opacity: 1; }
  }
  .judul-glitch { animation: judulGlitch .55s steps(1, end); }
  @media (prefers-reduced-motion: reduce) {
    .judul-glitch, .judul-glitch::before, .judul-glitch::after { animation: none; opacity: 0; }
  }
  .lead { font-size: clamp(1rem, 2.1vw, 1.15rem); color: var(--muted); margin: 0 auto 34px; max-width: 60ch; }
  .lead b { color: var(--text); font-weight: 600; }
  .aksi { display: flex; gap: 12px; flex-wrap: wrap; justify-content: center; }

  .angka { display: flex; gap: clamp(20px, 6vw, 56px); justify-content: center; flex-wrap: wrap; margin-top: 58px; }
  .angka .satu { text-align: center; }
  .angka .nilai { font-family: var(--tulis); font-size: clamp(1.9rem, 5vw, 2.7rem); font-weight: 800;
    line-height: 1; background: linear-gradient(180deg, #fff, #93a3bd); -webkit-background-clip: text;
    background-clip: text; color: transparent; }
  .angka .ket { font-size: 10.5px; letter-spacing: .16em; text-transform: uppercase; color: var(--muted);
    margin-top: 9px; font-weight: 600; }
  .pemisah-angka { width: 1px; background: var(--line); align-self: stretch; }
  @media (max-width: 620px) { .pemisah-angka { display: none; } }

  .bonus-baris { display: inline-flex; align-items: center; gap: 8px; margin: 22px 0 0;
    font-size: 13px; font-weight: 600; font-family: var(--tulis); color: var(--teal-2);
    background: rgba(34,211,238,.07); border: 1px solid rgba(34,211,238,.26);
    border-radius: 99px; padding: 7px 16px; }

  .panah-bawah { display: block; margin: 54px auto 0; color: var(--muted); background: none; border: 0;
    cursor: pointer; animation: turun 2.2s ease-in-out infinite; }
  @keyframes turun { 0%, 100% { transform: translateY(0); opacity: .5; } 50% { transform: translateY(7px); opacity: 1; } }
  @media (prefers-reduced-motion: reduce) {
    .panah-bawah, .pil .titik, .bar i { animation: none !important; }
  }

  /* ============ bagian umum ============ */
  section { padding: 76px 0; position: relative; }
  .kepala { max-width: 62ch; margin: 0 0 42px; }
  .kepala.tengah { margin-left: auto; margin-right: auto; text-align: center; }
  .kapital { font-size: 11.5px; font-weight: 700; letter-spacing: .18em; text-transform: uppercase;
    margin: 0 0 12px; font-family: var(--tulis);
    background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
  .kepala h2 { font-size: clamp(1.6rem, 4vw, 2.35rem); font-weight: 800; margin: 0 0 14px; }
  .kepala p { color: var(--muted); margin: 0; }

  /* ============ demo dengar ============ */
  /* Dulu var(--bg-2) yang pekat. Diganti lapisan putih tipis: warnanya di mata
     hampir sama, tapi cahaya lampu kursor di belakangnya jadi tembus. */
  #dengar { background: rgba(255,255,255,.02); border-block: 1px solid var(--line); }
  .duo { display: grid; grid-template-columns: 1fr auto 1fr; gap: 20px; align-items: stretch; }
  @media (max-width: 860px) { .duo { grid-template-columns: 1fr; } }

  .vs { display: grid; place-items: center; align-self: center; }
  .vs span { font-family: var(--tulis); font-weight: 800; font-size: 12px; letter-spacing: .08em;
    color: var(--muted); border: 1px solid var(--line-2); border-radius: 99px; width: 46px; height: 46px;
    display: grid; place-items: center; background: var(--panel); }

  .kartu-suara { background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
    padding: 20px; display: flex; flex-direction: column; transition: border-color .2s, box-shadow .2s; }
  .kartu-suara.main { border-color: rgba(34,211,238,.45); box-shadow: 0 0 0 1px rgba(34,211,238,.12), 0 12px 40px rgba(34,211,238,.10); }
  .kartu-suara.kloning.main { border-color: rgba(168,85,247,.45); box-shadow: 0 0 0 1px rgba(168,85,247,.12), 0 12px 40px rgba(168,85,247,.10); }

  /* Judul kartu diberi latar berwarna (bukan cuma garis kiri) supaya lebih
     menonjol, senada dengan warna label "Suara Asli" / "Suara Hasil Kloning"
     yang dulu ada sebagai pil terpisah. */
  .kartu-suara h3 { display: inline-block; font-size: 17px; margin: 0 0 14px; font-weight: 700;
    padding: 7px 14px; border-radius: 8px; border-left: 3px solid var(--teal);
    background: rgba(34,211,238,.12); }
  .kartu-suara.kloning h3 { border-left-color: var(--ungu-2); background: rgba(232,121,249,.12); }
  .kartu-suara .sub { color: var(--muted); font-size: 13px; margin: 0 0 14px; }
  .ucapan { font-size: 13.5px; line-height: 1.65; color: var(--muted); background: var(--panel-2);
    border: 1px solid var(--line); border-left: 2px solid var(--teal); border-radius: 0 10px 10px 0;
    padding: 11px 14px; margin: 0 0 16px; font-style: italic; }
  .kloning .ucapan { border-left-color: var(--ungu); }

  .pemutar { display: flex; align-items: center; gap: 14px; margin-top: auto; }
  .putar { width: 50px; height: 50px; border-radius: 50%; flex: none; border: 0; cursor: pointer;
    display: grid; place-items: center; color: #05070d; background: linear-gradient(135deg, var(--teal), #38bdf8);
    box-shadow: 0 5px 20px rgba(34,211,238,.32); transition: transform .12s, filter .15s; }
  .kloning .putar { background: linear-gradient(135deg, var(--ungu), var(--ungu-2)); box-shadow: 0 5px 20px rgba(168,85,247,.32); }
  .putar:hover:not(:disabled) { filter: brightness(1.1); transform: scale(1.05); }
  .putar:disabled { background: var(--panel-2); color: var(--muted); box-shadow: none; cursor: default;
    border: 1px solid var(--line); }

  .bar { display: flex; align-items: center; gap: 3px; height: 34px; flex: 1; overflow: hidden; }
  .bar i { display: block; width: 3px; border-radius: 99px; flex: none; height: 18%;
    background: linear-gradient(180deg, var(--teal-2), var(--teal)); opacity: .32; }
  .kloning .bar i { background: linear-gradient(180deg, var(--ungu-2), var(--ungu)); }
  .bar.jalan i { opacity: 1; animation: gelombang 1.05s ease-in-out infinite; }
  @keyframes gelombang { 0%, 100% { height: 16%; } 50% { height: var(--h, 80%); } }

  .durasi { font-size: 12px; color: var(--muted); font-variant-numeric: tabular-nums; flex: none;
    min-width: 42px; text-align: right; }
  .belum { font-size: 12.5px; color: var(--warn); margin: 12px 0 0; line-height: 1.55; }

  /* ============ kartu fitur ============ */
  .kisi { display: grid; grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); gap: 16px; }
  .kartu { background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
    padding: 24px; transition: border-color .2s, transform .2s; }
  .kartu:hover { border-color: var(--line-2); transform: translateY(-3px); }
  .kartu .ikon { width: 42px; height: 42px; border-radius: 12px; display: grid; place-items: center;
    font-size: 19px; background: var(--grad-lembut); border: 1px solid var(--line-2); margin-bottom: 16px; }
  .kartu h3 { font-size: 16.5px; font-weight: 700; margin: 0 0 8px; }
  .kartu p { font-size: 13.8px; color: var(--muted); margin: 0; line-height: 1.68; }

  /* ============ langkah ============ */
  .langkah { counter-reset: n; display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 30px; }
  .langkah > div { position: relative; padding-top: 52px; }
  .langkah > div::before { counter-increment: n; content: "0" counter(n);
    position: absolute; top: 0; left: 0; font-family: var(--tulis); font-weight: 800; font-size: 22px;
    background: var(--grad); -webkit-background-clip: text; background-clip: text; color: transparent; }
  .langkah h3 { font-size: 16px; margin: 0 0 7px; }
  .langkah p { font-size: 13.8px; color: var(--muted); margin: 0; }

  /* ============ panel penawaran di bagian Beli Akses ============
     Satu panel menggantikan lima kartu.

     Lima kartu terlihat rapi tapi bekerja melawan tujuannya: mata pembeli
     berpindah-pindah, tidak ada yang menonjol, dan tombolnya terlempar jauh
     di bawah setelah semua kartu terbaca. Satu panel memberi satu tempat
     untuk berhenti, satu daftar untuk dibaca, dan satu hal untuk dilakukan —
     dengan tombolnya berada di dalam panel yang sama, bukan sesudahnya.

     Garis tepinya gradien. Dikerjakan dengan dua lapis (luar gradien,
     dalam warna panel) karena border-image tidak melengkung mengikuti
     border-radius di semua browser. */
  .tawar { max-width: 680px; margin: 26px auto 0; padding: 1px;
    border-radius: 22px; background: var(--grad);
    box-shadow: 0 18px 60px rgba(99,102,241,.22); }
  .tawar-isi { border-radius: 21px; padding: 32px 30px 26px;
    background: linear-gradient(180deg, rgba(255,255,255,.055),
      rgba(255,255,255,0) 42%), var(--panel); }
  @media (max-width: 520px) { .tawar-isi { padding: 26px 19px 22px; } }

  .tawar-lencana { display: inline-flex; align-items: center; gap: 7px;
    border: 1px solid var(--line-2); background: var(--grad-lembut);
    border-radius: 99px; padding: 5px 13px; font-family: var(--tulis);
    font-size: 11px; font-weight: 700; letter-spacing: .14em;
    text-transform: uppercase; color: var(--teal-2); }
  .tawar-judul { font-family: var(--tulis); font-weight: 800;
    font-size: clamp(1.3rem, 3.6vw, 1.7rem); letter-spacing: -.02em;
    margin: 16px 0 7px; }
  .tawar-sub { color: var(--muted); font-size: 14px; line-height: 1.6;
    margin: 0 0 24px; }

  .tawar-daftar { list-style: none; display: grid; gap: 15px;
    margin: 0 0 26px; padding: 0; }
  @media (min-width: 640px) {
    .tawar-daftar { grid-template-columns: 1fr 1fr; gap: 17px 24px; }
    /* Butir terakhir dibiarkan melebar penuh supaya baris bawah tidak
       menyisakan satu kolom kosong yang menganga. */
    .tawar-daftar li:last-child { grid-column: 1 / -1; }
  }
  .tawar-daftar li { display: flex; gap: 11px; align-items: flex-start; }
  .tawar-centang { flex: 0 0 auto; width: 21px; height: 21px; border-radius: 50%;
    background: var(--grad); display: grid; place-items: center; margin-top: 2px; }
  .tawar-centang::after { content: ""; width: 5px; height: 9px;
    border: solid #05070d; border-width: 0 2px 2px 0;
    transform: rotate(45deg) translate(-1px, -1px); }
  .tawar-daftar b { display: block; font-family: var(--tulis); font-size: 14.5px;
    font-weight: 700; margin-bottom: 3px; }
  .tawar-daftar span { color: var(--muted); font-size: 13px; line-height: 1.55; }

  .tawar-aksi .btn { width: 100%; padding: 15px 22px; font-size: 16px; }
  .tawar-catatan { color: var(--muted); font-size: 12px; text-align: center;
    margin: 13px 0 0; line-height: 1.6; }

  /* ============ harga ============ */
  .paket-kisi { display: grid; grid-template-columns: repeat(auto-fit, minmax(250px, 1fr)); gap: 18px;
    align-items: start; }
  .paket { background: var(--panel); border: 1px solid var(--line); border-radius: 18px;
    padding: 26px 24px; display: flex; flex-direction: column; position: relative; height: 100%; }
  .paket.sorot { border-color: transparent; background:
    linear-gradient(var(--panel), var(--panel)) padding-box,
    var(--grad) border-box; border: 1px solid transparent;
    box-shadow: 0 14px 44px rgba(99,102,241,.18); }
  .paket .label { position: absolute; top: -11px; left: 24px; font-family: var(--tulis);
    font-size: 10.5px; font-weight: 800; letter-spacing: .1em; text-transform: uppercase;
    background: var(--grad); color: #05070d; border-radius: 99px; padding: 4px 12px; }
  .paket h3 { font-size: 15px; font-weight: 700; margin: 0 0 12px; color: var(--muted);
    letter-spacing: .06em; text-transform: uppercase; }
  .paket .harga { font-family: var(--tulis); font-size: clamp(1.7rem, 4vw, 2.2rem); font-weight: 800;
    line-height: 1.1; letter-spacing: -.03em; }
  .paket .periode { color: var(--muted); font-size: 13px; margin: 4px 0 20px; }
  .paket ul { list-style: none; margin: 0 0 22px; padding: 0; display: grid; gap: 10px; }
  .paket li { display: flex; gap: 10px; font-size: 13.8px; line-height: 1.5; color: var(--text); }
  .paket li svg { flex: none; margin-top: 3px; }
  .paket li.tidak { color: var(--muted); }
  .paket .catatan { color: var(--muted); font-size: 11.5px; line-height: 1.5; margin: 12px 0 0; }
  .paket .btn { margin-top: auto; }

  /* ============ FAQ ============ */
  details { border: 1px solid var(--line); border-radius: 12px; background: var(--panel);
    padding: 0 18px; margin-bottom: 10px; }
  details[open] { border-color: var(--line-2); }
  summary { cursor: pointer; padding: 17px 0; font-weight: 600; font-size: 15px; list-style: none;
    display: flex; align-items: center; justify-content: space-between; gap: 14px; font-family: var(--tulis); }
  summary::-webkit-details-marker { display: none; }
  summary::after { content: "+"; color: var(--teal); font-size: 21px; font-weight: 400; flex: none; }
  details[open] summary::after { content: "\2212"; color: var(--ungu-2); }
  details p { margin: 0 0 17px; color: var(--muted); font-size: 14px; }

  /* ============ penutup & kaki ============ */
  .penutup { background: var(--grad-lembut); border: 1px solid var(--line-2); border-radius: 20px;
    padding: 52px 34px; text-align: center; }
  .penutup h2 { font-size: clamp(1.5rem, 3.6vw, 2.2rem); font-weight: 800; margin: 0 0 12px; }
  .penutup p { color: var(--muted); margin: 0 auto 26px; max-width: 50ch; }

  /* ============ sosial media ============ */
  .sosial { display: flex; gap: 16px; justify-content: center; flex-wrap: wrap; margin-top: 4px; }
  .sosial a { display: flex; align-items: center; text-decoration: none;
    background: var(--panel); border: 1px solid var(--line); border-radius: var(--r);
    padding: 15px 22px; min-width: 246px; color: var(--text);
    transition: border-color .18s, transform .18s, background .18s; }
  .sosial a:hover { border-color: var(--line-2); transform: translateY(-3px); background: var(--panel-2); }
  /* Lencana ikon memakai warna resmi masing-masing platform supaya langsung
     dikenali; sisa kartunya tetap mengikuti tema gelap halaman ini. */
  /* Jaraknya memakai margin, bukan `gap`, supaya tetap benar di browser lama
     yang belum mendukung gap pada flexbox. */
  .sosial .lencana { flex: none; margin-right: 13px; width: 42px; height: 42px; border-radius: 12px;
    display: flex; align-items: center; justify-content: center; }
  .sosial .lencana svg { width: 22px; height: 22px; fill: #fff; }
  .sosial .ig .lencana { background: linear-gradient(45deg, #f9ce34, #ee2a7b 52%, #6228d7); }
  .sosial .tt .lencana { background: #010101; border: 1px solid var(--line-2); }
  .sosial .nama { font-family: var(--tulis); font-weight: 700; font-size: 15px; line-height: 1.3; }
  .sosial .pegangan { color: var(--muted); font-size: 13px; }
  @media (max-width: 560px) {
    .sosial { flex-direction: column; align-items: stretch; }
    .sosial a { min-width: 0; }
  }

  footer { border-top: 1px solid var(--line); padding: 34px 0 48px; }
  footer .wrap { display: flex; gap: 18px; flex-wrap: wrap; align-items: center;
    justify-content: space-between; font-size: 13px; color: var(--muted); }
  footer img { height: 26px; width: auto; opacity: .85; }
  footer a { color: var(--muted); text-decoration: none; margin-left: 18px; }
  footer a:first-child { margin-left: 0; }
  footer a:hover { color: var(--teal-2); }

  /* ============ jendela ============ */
  .tirai { position: fixed; inset: 0; background: rgba(4,6,12,.88); backdrop-filter: blur(6px);
    display: none; align-items: center; justify-content: center; z-index: 100; padding: 18px; overflow-y: auto; }
  .tirai.tampil { display: flex; }
  .kotak { background: var(--panel); border: 1px solid var(--line-2); border-radius: 18px; padding: 28px;
    width: 100%; max-width: 390px; position: relative; margin: auto; }
  .kotak h3 { margin: 0 0 5px; font-size: 20px; font-weight: 800; }
  .kotak .sub { color: var(--muted); font-size: 13.5px; margin: 0 0 22px; }
  .tutup { position: absolute; top: 13px; right: 15px; background: none; border: none; color: var(--muted);
    font-size: 23px; cursor: pointer; line-height: 1; padding: 4px; }
  .tutup:hover { color: var(--text); }

  .kotak input { width: 100%; background: var(--panel-2); color: var(--text); border: 1px solid var(--line-2);
    border-radius: 10px; padding: 12px; font-size: 15px; font-family: inherit; margin-bottom: 13px; }
  .kotak input:focus { outline: none; border-color: var(--teal); }

  .pwrap { position: relative; margin-bottom: 13px; }
  .pwrap > input { margin-bottom: 0; padding-right: 44px; }
  .pwrap .mata { position: absolute; top: 0; right: 0; height: 100%; width: 42px; display: grid;
    place-items: center; background: none; border: 0; color: var(--muted); cursor: pointer; }
  .pwrap .mata:hover { color: var(--teal-2); }

  .bonus-kecil { display: block; font-size: 12px; color: var(--teal-2); margin: -6px 0 14px;
    line-height: 1.5; font-weight: 500; }

  .pisah { display: flex; align-items: center; gap: 12px; margin: 20px 0; color: var(--muted); font-size: 12px; }
  .pisah::before, .pisah::after { content: ""; flex: 1; height: 1px; background: var(--line); }

  .tab2 { display: flex; gap: 4px; background: var(--panel-2); border: 1px solid var(--line);
    border-radius: 11px; padding: 4px; margin-bottom: 22px; }
  .tab2 button { flex: 1; background: none; border: none; color: var(--muted); font-family: var(--tulis);
    font-size: 13.5px; font-weight: 700; padding: 9px; border-radius: 8px; cursor: pointer; }
  .tab2 button.aktif { background: var(--panel); color: var(--text); box-shadow: 0 1px 8px rgba(0,0,0,.35); }

  .pesan { font-size: 13px; margin-top: 12px; min-height: 18px; color: var(--muted); line-height: 1.55; }
  .pesan.err { color: var(--danger); }
  .pesan.ok { color: var(--ok); }
  .kabar { border-radius: 11px; padding: 12px 14px; font-size: 13px; line-height: 1.55; margin-bottom: 18px; }
  .kabar.info { background: rgba(34,211,238,.07); border: 1px solid rgba(34,211,238,.26); color: var(--teal-2); }
  .kabar.warn { background: rgba(251,191,36,.07); border: 1px solid rgba(251,191,36,.28); color: var(--warn); }
  .kabar.err { background: rgba(251,113,133,.07); border: 1px solid rgba(251,113,133,.28); color: var(--danger); }
  .kabar b { display: block; }

  .perangkat { border: 1px solid var(--line); border-radius: 11px; background: var(--panel-2);
    padding: 12px 13px; margin-bottom: 8px; display: flex; align-items: center; gap: 11px; }
  .perangkat .isi { flex: 1; min-width: 0; }
  .perangkat b { display: block; font-size: 13.5px; font-weight: 600; }
  .perangkat small { color: var(--muted); font-size: 11.5px; }

  .lingkar { width: 64px; height: 64px; border-radius: 50%; margin: 0 auto 16px; display: grid;
    place-items: center; font-size: 28px; background: var(--grad-lembut); border: 1px solid var(--line-2); }
  .lingkar.tunggu { background: rgba(251,191,36,.1); border-color: rgba(251,191,36,.3); }
  .lingkar.tolak { background: rgba(251,113,133,.1); border-color: rgba(251,113,133,.3); }
  .lingkar.baik { background: rgba(52,211,153,.1); border-color: rgba(52,211,153,.3); }
</style>
</head>
<body>
<!-- Lampu yang mengikuti kursor. Satu lapisan tetap seukuran layar, dipakai
     seluruh halaman. Kosong dari isi apa pun dan tidak bisa diklik. -->
<div class="lampu" id="lampu" aria-hidden="true">
  <div class="kilau"></div>
  <div class="jala-lampu"></div>
</div>
<!-- ================= bilah atas ================= -->
<nav class="nav" id="nav">
  <div class="wrap">
    <a class="merek" href="/">
      <img src="data:image/webp;base64,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" alt="Voice Promax" id="logo-atas" />
      <span class="merek-teks" id="merek-teks">Voice&nbsp;Promax</span>
    </a>
    <a class="navlink" href="#dengar">Dengar contoh</a>
    <a class="navlink" href="#fitur">Fitur</a>
    <a class="navlink" href="#cara">Cara kerja</a>
    <a class="navlink" href="#akses">Beli Akses</a>
    <a class="navlink" href="#tanya">Tanya jawab</a>
    <button class="btn hantu kecil" id="nav-masuk">Masuk</button>
    <a class="btn utama kecil" id="nav-beli" href="https://lynk.id/voicepromax/6r49qwe4ypx1" target="_blank" rel="noopener">Beli Akses <span class="panah">&rarr;</span></a>
    <a class="btn utama kecil sembunyi" id="nav-buka" href="/dashboard">Buka dashboard <span class="panah">&rarr;</span></a>
  </div>
</nav>

<!-- ================= hero ================= -->
<header class="hero">
  <div class="cahaya a"></div>
  <div class="cahaya b"></div>
  <div class="jala"></div>
  <div class="wrap">
    <!-- =====================================================================
         JUDUL UTAMA — ini satu-satunya tempat teksnya ditulis. Sengaja TIDAK
         digambar JavaScript: <h1> adalah elemen terpenting bagi mesin pencari,
         dan kalau dibuat skrip ada peluang perayap tidak melihatnya sama sekali.
         ===================================================================== -->
    <h1 class="judul" id="judul-hero" data-muncul="naik">
      <span class="baris"><span id="judul-l1" data-text="Voice Cloning">Voice Cloning</span></span>
      <span class="baris baris2"><span id="judul-l2" data-text="No. 1 di Indonesia">No. 1 di Indonesia</span></span>
    </h1>

    <p class="lead" data-muncul="naik" data-teks>
      Kloning suara siapa pun dari rekaman pendek, lalu ubah tulisan apa saja menjadi suara yang
      terdengar manusiawi &mdash; lengkap dengan <b>emosi, jeda, dan tarikan napas</b>.
      Untuk konten YouTube, TikTok, dan Instagram Unlimited.
    </p>
    <div class="aksi" data-muncul="naik">
      <button class="btn utama" id="hero-masuk">Masuk ke aplikasi <span class="panah">&rarr;</span></button>
      <a class="btn hantu" href="#dengar">Dengarkan dulu</a>
    </div>
    <p class="bonus-baris sembunyi" id="bonus-hero" data-muncul="skala"></p>

    <div class="angka" data-muncul="naik">
      <div class="satu"><div class="nilai"><span data-hitung="600">0</span>+</div><div class="ket">Bahasa</div></div>
      <div class="pemisah-angka"></div>
      <div class="satu"><div class="nilai"><span data-hitung="20">0</span><span style="font-size:.46em">dtk</span></div><div class="ket">Untuk kloning</div></div>
      <div class="pemisah-angka"></div>
      <div class="satu"><div class="nilai"><span data-hitung="5">0</span></div><div class="ket">Fitur AI</div></div>
      <div class="pemisah-angka"></div>
      <div class="satu"><div class="nilai">Unlimited</div><div class="ket">Generate</div></div>
    </div>

    <button class="panah-bawah" id="ke-demo" aria-label="Turun ke contoh suara">
      <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
        stroke-linecap="round" stroke-linejoin="round"><path d="M12 5v14M19 12l-7 7-7-7"/></svg>
    </button>
  </div>
</header>
<!-- ================= dengar sendiri ================= -->
<section id="dengar">
  <!-- Bentuk hias. Muncul dengan membesar pelan saat bagian ini digulir, supaya
       peralihan antar bagian tidak terasa seperti berpindah halaman. -->
  <div class="bentuk" aria-hidden="true">
    <b class="o1" data-muncul="skala"></b>
    <b class="cincin" data-muncul="pegas"></b>
    <b class="o2" data-muncul="skala"></b>
  </div>
  <div class="wrap">
    <div class="kepala tengah" data-muncul="naik">
      <p class="kapital">Dengar bedanya</p>
      <h2 id="dengar-judul" data-teks>Mana yang manusia, mana yang mesin?</h2>
      <p id="dengar-sub">Satu rekaman asli. Satu kalimat yang tidak pernah diucapkan siapa pun. Tekan keduanya.</p>
    </div>

    <div class="duo">
      <!-- kartu: suara asli -->
      <div class="kartu-suara" id="kartu-asli" data-muncul="kiri">
        <h3>Rekaman manusia</h3>
        <p class="sub">8 detik narasi yang benar-benar direkam. Inilah bahan mentah kloningnya.</p>
        <p class="ucapan" id="teks-asli"></p>
        <div class="pemutar">
          <button class="putar" data-putar="asli" aria-label="Putar suara asli">
            <svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
          </button>
          <div class="bar" data-bar="asli" aria-hidden="true"></div>
          <span class="durasi" data-durasi="asli">0:00</span>
        </div>
        <p class="belum sembunyi" data-belum="asli"></p>
      </div>

      <div class="vs" data-muncul="pegas"><span>VS</span></div>

      <!-- kartu: hasil kloning -->
      <div class="kartu-suara kloning" id="kartu-kloning" data-muncul="kanan">
        <h3>Dibuat Voice Promax</h3>
        <p class="sub">Suara yang sama, kalimat yang berbeda. Tidak ada satu pun yang direkam.</p>
        <p class="ucapan" id="teks-kloning"></p>
        <div class="pemutar">
          <button class="putar" data-putar="kloning" aria-label="Putar suara hasil kloning">
            <svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>
          </button>
          <div class="bar" data-bar="kloning" aria-hidden="true"></div>
          <span class="durasi" data-durasi="kloning">0:00</span>
        </div>
        <p class="belum sembunyi" data-belum="kloning"></p>
      </div>
    </div>
  </div>
</section><!-- ================= fitur ================= -->
<section id="fitur">
  <div class="wrap">
    <div class="kepala" data-muncul="naik">
      <p class="kapital">Yang bisa dikerjakan</p>
      <h2 data-teks>Satu alat untuk semua kebutuhan suara</h2>
      <p>Lima mode kerja yang dirancang untuk pekerjaan nyata, bukan sekadar demo.</p>
    </div>
    <div class="kisi">
      <div class="kartu" data-muncul="naik"><div class="ikon">&#128483;</div><h3>TTS</h3>
        <p>Tempel tulisan, pilih suara, dapatkan audio. Lebih dari 600 bahasa dan dialek, termasuk Indonesia yang terdengar wajar.</p></div>
      <div class="kartu" data-muncul="naik"><div class="ikon">&#127911;</div><h3>Kloning</h3>
        <p>Unggah rekaman bersih sekitar 20 detik, lalu sistem menirukan warna dan irama bicaranya untuk teks apa pun.</p></div>
      <div class="kartu" data-muncul="naik"><div class="ikon">&#127916;</div><h3>Dubbing</h3>
        <p>Unggah audio sumber, pilih suara target, sistem membacakan ulang isinya dengan suara baru &mdash; cocok untuk mengganti suara video atau podcast yang sudah direkam.</p></div>
      <div class="kartu" data-muncul="naik"><div class="ikon">&#127908;</div><h3>Podcast</h3>
        <p>Tulis naskah dengan beberapa tokoh, beri tiap tokoh suaranya sendiri, lalu rangkai jadi satu berkas percakapan.</p></div>
      <div class="kartu" data-muncul="naik"><div class="ikon">&#127917;</div><h3>Voice Design</h3>
        <p>Rancang karakter suara baru dari nol tanpa audio referensi &mdash; atur gaya, nada, dan bahasanya sendiri.</p></div>
    </div>
  </div>
</section>
<!-- ================= cara kerja ================= -->
<section id="cara" style="background:rgba(255,255,255,.02);border-block:1px solid var(--line)">
  <div class="wrap">
    <div class="kepala" data-muncul="naik">
      <p class="kapital">Cara kerja</p>
      <h2 data-teks>Tiga langkah, sekali siapkan</h2>
    </div>
    <div class="langkah">
      <div data-muncul="naik"><h3>Unggah sampel</h3>
        <p>Satu rekaman bersih dari satu pembicara, 5 detik sampai 20 detik. Format MP3, WAV,
        atau M4A.</p></div>
      <div data-muncul="naik"><h3>Masukkan naskah</h3>
        <p>Tidak ada batasan karakter pada naskah.</p></div>
      <div data-muncul="naik"><h3>Mulai generate</h3>
        <p>Pakai suara tersebut dan masukkan naskah, lalu generate tanpa batas.</p></div>
    </div>
  </div>
</section>

<!-- =====================================================================
     BELI AKSES
     Tidak ada pendaftaran mandiri di sistem ini: akun dibuat admin lewat
     public/users.json atau /api/admin/users. Pembelian sekarang lewat
     Lynk.id — tombol Beli Akses mengarah langsung ke halaman produknya.
     Chat admin dan grup pengguna sengaja tidak ditampilkan di sini lagi;
     keduanya sudah ada di sidebar dashboard.
     ===================================================================== -->
<section id="akses">
  <div class="wrap" style="max-width:820px">
    <div class="kepala tengah" data-muncul="naik">
      <p class="kapital">Beli Akses</p>
      <h2 data-teks>Satu kali beli, akun langsung disiapkan</h2>
    </div>

    <div class="tawar" data-muncul="naik">
      <div class="tawar-isi">
        <span class="tawar-lencana">Sekali bayar</span>
        <h3 class="tawar-judul">Semua yang Anda butuhkan, dalam satu akun</h3>
        <p class="tawar-sub">Tidak ada langganan, tidak ada kuota karakter, dan
        tidak ada tagihan yang datang lagi bulan depan.</p>

        <ul class="tawar-daftar">
          <li><i class="tawar-centang"></i><div>
            <b>Akun pribadi</b>
            <span>Username dan password khusus untuk Anda sendiri.</span>
          </div></li>
          <li><i class="tawar-centang"></i><div>
            <b>5 Fitur AI</b>
            <span>TTS, Kloning, Podcast, dan Voice Design. Fitur Dubbing dijual terpisah.</span>
          </div></li>
          <li><i class="tawar-centang"></i><div>
            <b>Tanpa hitungan kredit</b>
            <span>Tidak ada biaya per karakter. Pakai sebanyak yang Anda perlukan.</span>
          </div></li>
          <li><i class="tawar-centang"></i><div>
            <b>1 jam sekali proses</b>
            <span>Naskah panjang tidak perlu dipotong-potong dulu.</span>
          </div></li>
          <li><i class="tawar-centang"></i><div>
            <b>600+ bahasa</b>
            <span>Termasuk Bahasa Indonesia yang terdengar wajar, plus dialek dan aksen lainnya.</span>
          </div></li>
        </ul>

        <div class="tawar-aksi">
          <a class="btn utama" id="beli-akses" href="https://lynk.id/voicepromax/6r49qwe4ypx1" target="_blank" rel="noopener">Beli Akses Sekarang <span class="panah">&rarr;</span></a>
        </div>
        <p class="tawar-catatan">Pembayaran lewat Lynk.id. Akun dibuatkan dan
        dikirimkan setelah pembayaran diterima.</p>
      </div>
    </div>

    <p style="color:var(--muted);font-size:12.5px;text-align:center;margin:26px 0 0">
      Sudah punya akun? <button class="btn hantu kecil" id="akses-masuk">Masuk di sini</button>
    </p>
  </div>
</section>

<!-- ================= tanya jawab ================= -->
<section id="tanya">
  <div class="bentuk" aria-hidden="true">
    <b class="o2" data-muncul="skala" style="right:auto;left:-140px;top:10%"></b>
  </div>
  <div class="wrap" style="max-width:800px">
    <div class="kepala tengah" data-muncul="naik">
      <p class="kapital">Tanya jawab</p>
      <h2 data-teks>Hal yang sering ditanyakan</h2>
    </div>

    <details open data-muncul="naik"><summary>Bagaimana cara mendapatkan akun?</summary>
      <p>Lewat tombol <a href="#akses">Beli Akses</a>. Selesaikan pembelian di Lynk.id,
      lalu username dan password Anda dibuatkan dan dikirimkan untuk langsung dipakai
      masuk.</p></details>

    <details data-muncul="naik"><summary>Apakah alamat engine saya terlihat orang lain?</summary>
      <p>Tidak. Semua lalu lintas melewati gateway di domain ini, dan alamat server engine
      tidak pernah dikirim ke browser. Yang terlihat hanya alamat situs ini.</p></details>

    <details data-muncul="naik"><summary>Bisa dipakai di HP?</summary>
      <p>Bisa. Tampilan dashboard dioptimalkan juga untuk browser HP, meski layarnya lebih
      sempit. Panduan langkah demi langkah untuk HP ada di panel Tutorial di dalam
      dashboard.</p></details>

    <details data-muncul="naik"><summary>Apakah suara yang saya unggah disimpan?</summary>
      <p>Berkas audio untuk kloning diproses di sesi engine Anda sendiri dan ikut hilang
      begitu sesi itu berakhir. Rinciannya ada di halaman
      <a href="/kebijakan.html">Syarat &amp; Kebijakan Privasi</a>.</p></details>

    <details data-muncul="naik"><summary>Bolehkah hasilnya dipakai untuk pekerjaan?</summary>
      <p>Boleh. Tanggung jawab atas isi teks, suara yang ditiru, dan penggunaannya sepenuhnya
      ada pada Anda &mdash; termasuk memastikan Anda punya izin dari pemilik suara yang dikloning.</p></details>
  </div>
</section>
<!-- ================= penutup ================= -->
<section style="padding-top:0">
  <div class="wrap">
    <div class="penutup">
      <h2 data-teks>Siap mendengar tulisan Anda?</h2>
      <p id="penutup-sub">Unggah sampel suara, masukkan naskah, dan suara pertama jadi dalam hitungan menit.</p>
      <button class="btn utama" id="penutup-masuk">Masuk ke aplikasi <span class="panah">&rarr;</span></button>
    </div>
  </div>
</section>
<!-- ================= sosial media ================= -->
<section id="sosial" style="padding-top:46px">
  <div class="wrap" style="max-width:820px">
    <div class="kepala tengah" data-muncul="naik">
      <p class="kapital">Follow our Social Media</p>
      <h2 data-teks>Ikuti kami di Instagram dan TikTok</h2>
      <p>Contoh hasil, tips memakai tool, dan kabar fitur baru dibagikan lebih dulu di sana.</p>
    </div>
    <div class="sosial" data-muncul="naik">
      <a class="ig" href="https://www.instagram.com/voicepromax/" target="_blank" rel="noopener">
        <span class="lencana" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M12 2.16c3.2 0 3.58.01 4.85.07 1.17.05 1.8.25 2.23.41.56.22.96.48 1.38.9.42.42.68.82.9 1.38.16.42.36 1.06.41 2.23.06 1.27.07 1.65.07 4.85s-.01 3.58-.07 4.85c-.05 1.17-.25 1.8-.41 2.23-.22.56-.48.96-.9 1.38-.42.42-.82.68-1.38.9-.42.16-1.06.36-2.23.41-1.27.06-1.65.07-4.85.07s-3.58-.01-4.85-.07c-1.17-.05-1.8-.25-2.23-.41-.56-.22-.96-.48-1.38-.9-.42-.42-.68-.82-.9-1.38-.16-.42-.36-1.06-.41-2.23-.06-1.27-.07-1.65-.07-4.85s.01-3.58.07-4.85c.05-1.17.25-1.8.41-2.23.22-.56.48-.96.9-1.38.42-.42.82-.68 1.38-.9.42-.16 1.06-.36 2.23-.41 1.27-.06 1.65-.07 4.85-.07M12 0C8.74 0 8.33.01 7.05.07 5.78.13 4.9.33 4.14.63c-.79.3-1.46.72-2.12 1.39C1.35 2.68.93 3.35.63 4.14.33 4.9.13 5.78.07 7.05.01 8.33 0 8.74 0 12s.01 3.67.07 4.95c.06 1.27.26 2.15.56 2.91.3.79.72 1.46 1.39 2.12.66.67 1.33 1.09 2.12 1.39.76.3 1.64.5 2.91.56C8.33 23.99 8.74 24 12 24s3.67-.01 4.95-.07c1.27-.06 2.15-.26 2.91-.56.79-.3 1.46-.72 2.12-1.39.67-.66 1.09-1.33 1.39-2.12.3-.76.5-1.64.56-2.91.06-1.28.07-1.69.07-4.95s-.01-3.67-.07-4.95c-.06-1.27-.26-2.15-.56-2.91-.3-.79-.72-1.46-1.39-2.12C21.32 1.35 20.65.93 19.86.63 19.1.33 18.22.13 16.95.07 15.67.01 15.26 0 12 0z"/><path d="M12 5.84A6.16 6.16 0 1 0 18.16 12 6.16 6.16 0 0 0 12 5.84zM12 16a4 4 0 1 1 4-4 4 4 0 0 1-4 4z"/><circle cx="18.41" cy="5.59" r="1.44"/></svg>
        </span>
        <span>
          <span class="nama">Instagram</span><br>
          <span class="pegangan">@voicepromax</span>
        </span>
      </a>
      <a class="tt" href="https://www.tiktok.com/@voicepromaxofficial" target="_blank" rel="noopener">
        <span class="lencana" aria-hidden="true">
          <svg viewBox="0 0 24 24"><path d="M16.6 5.82A4.28 4.28 0 0 1 15.54 3h-3.09v12.4a2.59 2.59 0 0 1-2.59 2.5 2.59 2.59 0 1 1 .76-5.07v-3.1a5.66 5.66 0 0 0-.76-.05 5.68 5.68 0 1 0 5.68 5.68V9.01a7.35 7.35 0 0 0 4.28 1.37V7.29a4.28 4.28 0 0 1-3.22-1.47z"/></svg>
        </span>
        <span>
          <span class="nama">TikTok</span><br>
          <span class="pegangan">@voicepromaxofficial</span>
        </span>
      </a>
    </div>
  </div>
</section>

<footer>
  <div class="wrap">
    <div style="display:flex;align-items:center;gap:14px;flex-wrap:wrap">
      <img src="data:image/webp;base64,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" alt="Voice Promax" id="logo-kaki" />
      <span>&copy; <span id="tahun"></span> <span id="merek-kaki">Voice Promax</span></span>
    </div>
    <div>
      <a href="/kebijakan.html">Syarat &amp; Kebijakan</a>
      <a href="#dengar">Contoh suara</a>
      <a href="#akses">Beli Akses</a>
      <a href="#tanya">Tanya jawab</a>
      <a href="https://www.instagram.com/voicepromax/" target="_blank" rel="noopener">Instagram</a>
      <a href="https://www.tiktok.com/@voicepromaxofficial" target="_blank" rel="noopener">TikTok</a>
    </div>
  </div>
</footer>

<!-- ================= jendela: masuk ================= -->
<div class="tirai" id="jendela-masuk">
  <div class="kotak">
    <button class="tutup" data-tutup="jendela-masuk" aria-label="Tutup">&times;</button>

    <div id="kabar-masuk"></div>

    <h3>Selamat datang kembali</h3>
    <p class="sub">Masuk memakai username dan password dari admin.</p>

    <div id="kotak-password">
      <input type="text" id="f-user" placeholder="Username" autocomplete="username" autocapitalize="none" spellcheck="false" />
      <div class="pwrap">
        <input type="password" id="f-pass" placeholder="Password" autocomplete="current-password" />
        <button class="mata" type="button" data-mata="f-pass" aria-label="Tampilkan password">
          <svg width="19" height="19" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"
            stroke-linecap="round" stroke-linejoin="round"><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>
        </button>
      </div>
      <button class="btn utama penuh" id="kirim-masuk">Masuk</button>
    </div>

    <p class="pesan" id="pesan-masuk"></p>

    <div class="pisah">belum punya akun?</div>
    <a class="btn hantu penuh" id="beli-akses-modal" href="https://lynk.id/voicepromax/6r49qwe4ypx1" target="_blank" rel="noopener">Beli Akses</a>
  </div>
</div>
<script>
"use strict";

/* ============================================================
   Halaman depan Voice Promax.

   Yang dipakai dari gateway (_worker.js) hanya tiga alamat:
     GET  /api/config    merek + tautan WhatsApp, boleh gagal
     GET  /api/session   apakah pengunjung sudah masuk
     POST /api/auth/login   { username, password }
   Tidak ada pendaftaran mandiri di sistem ini, jadi tidak ada
   /api/signup, tidak ada verifikasi email, tidak ada kredit.
   ============================================================ */

const $ = (id) => document.getElementById(id);
const esc = (t) => String(t ?? "").replace(/[&<>"']/g, (c) =>
  ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", "'": "&#39;", '"': "&quot;" }[c]));
const angkaId = (n) => Number(n || 0).toLocaleString("id-ID");

// Ke mana pengunjung diantar setelah berhasil masuk. Hanya jalur di situs ini
// yang diterima, supaya ?next= tidak bisa dipakai memantulkan orang ke luar.
const TUJUAN = (() => {
  const n = new URLSearchParams(location.search).get("next") || "/dashboard";
  return n.startsWith("/") && !n.startsWith("//") && !n.includes("\\") ? n : "/dashboard";
})();

// Cadangan kalau /api/config tidak terbaca.
// Chat admin & grup pengguna sengaja tidak lagi dipakai di halaman depan;
// keduanya hanya tampil di sidebar dashboard.
let CFG = {
  brand: "Voice Promax",
};

$("tahun").textContent = new Date().getFullYear();

// Jaring pengaman: logo sekarang data URI yang ditanam langsung di HTML,
// jadi baris ini praktis tidak akan pernah terpicu. Tetap dijaga untuk
// kasus yang sangat jarang saat browser gagal mendekode gambarnya.
$("logo-atas").addEventListener("error", () => {
  $("logo-atas").remove();
  $("merek-teks").classList.remove("sembunyi");
});
$("logo-kaki").addEventListener("error", () => $("logo-kaki").remove());
addEventListener("scroll", () => $("nav").classList.toggle("turun", scrollY > 8), { passive: true });
$("ke-demo").addEventListener("click", () => $("dengar").scrollIntoView({ behavior: "smooth" }));

/* ============================================================
   JUDUL HERO — bergantian setiap beberapa detik dengan efek glitch.
   Teks tetap ASLI (bukan digambar canvas/SVG) supaya tetap terbaca
   mesin pencari, hanya isinya yang berganti lewat textContent.
   ============================================================ */
const FRASA_JUDUL = [
  ["Voice Cloning", "No. 1 di Indonesia"],
  ["Kloning Suara", "Tanpa Batas Karakter"],
];
let indeksFrasa = 0;
if (!matchMedia("(prefers-reduced-motion: reduce)").matches) {
  setInterval(() => {
    indeksFrasa = (indeksFrasa + 1) % FRASA_JUDUL.length;
    const [l1, l2] = FRASA_JUDUL[indeksFrasa];
    const el1 = $("judul-l1"), el2 = $("judul-l2");
    el1.classList.add("judul-glitch");
    el2.classList.add("judul-glitch");
    setTimeout(() => {
      el1.textContent = l1; el1.setAttribute("data-text", l1);
      el2.textContent = l2; el2.setAttribute("data-text", l2);
    }, 160);
    setTimeout(() => { el1.classList.remove("judul-glitch"); el2.classList.remove("judul-glitch"); }, 520);
  }, 4200);
}

/* ============================================================
   CONTOH SUARA
   Teks di bawah harus SAMA dengan yang benar-benar terdengar di
   berkas audionya. Kalau audionya diganti, ganti juga teks ini.
   Taruh berkasnya di public/demo/. Lihat public/demo/README.txt
   ============================================================ */
const DEMO = {
  asli: {
    src: "/demo/asli.mp3",
    teks: "Bayangkan sejenak\u2026 Kali Bekasi, pada suatu malam di tahun 1945, dipenuhi warna merah darah.",
  },
  kloning: {
    src: "/demo/kloning.mp3",
    teks: "Kalimat ini tidak pernah direkam siapa pun. Seluruhnya dibuat Voice Promax hanya dari suara asli kamu, tunggu apa lagi? cobain sekarang!",
  },
};

$("teks-asli").textContent = "\u201C" + DEMO.asli.teks + "\u201D";
$("teks-kloning").textContent = "\u201C" + DEMO.kloning.teks + "\u201D";

// Batang gelombang. Tingginya acak tapi TETAP (dari benih), supaya kedua kartu
// terlihat seperti dua rekaman berbeda dan bukan pola sama yang diulang.
function gambarBar(nama, benih) {
  const wadah = document.querySelector('[data-bar="' + nama + '"]');
  let x = benih;
  const acak = () => { x = (x * 1103515245 + 12345) % 2147483648; return x / 2147483648; };
  wadah.innerHTML = Array.from({ length: 34 }, (_, i) =>
    '<i style="animation-delay:' + (i * 0.045).toFixed(3) + 's;--h:' + (24 + acak() * 72).toFixed(0) + '%"></i>').join("");
}
gambarBar("asli", 7);
gambarBar("kloning", 91);

const menit = (d) => {
  if (!isFinite(d) || d <= 0) return "0:00";
  const m = Math.floor(d / 60), s = Math.floor(d % 60);
  return m + ":" + String(s).padStart(2, "0");
};

const IKON_MAIN = '<svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M8 5v14l11-7z"/></svg>';
const IKON_JEDA = '<svg width="19" height="19" viewBox="0 0 24 24" fill="currentColor"><path d="M7 5h4v14H7zM13 5h4v14h-4z"/></svg>';

const AUDIO = {};
let SEDANG = null;   // nama contoh yang sedang berbunyi

function siapkanContoh(nama) {
  const info = DEMO[nama];
  const tombol = document.querySelector('[data-putar="' + nama + '"]');
  const durasi = document.querySelector('[data-durasi="' + nama + '"]');
  const belum = document.querySelector('[data-belum="' + nama + '"]');

  const audio = new Audio();
  audio.preload = "metadata";
  AUDIO[nama] = audio;

  audio.addEventListener("loadedmetadata", () => { durasi.textContent = menit(audio.duration); });
  audio.addEventListener("timeupdate", () => {
    if (SEDANG === nama) durasi.textContent = menit(audio.duration - audio.currentTime);
  });
  audio.addEventListener("ended", () => hentikan(nama, true));
  audio.addEventListener("error", () => {
    // Dicatat ke console supaya gampang dilacak lain kali: kode error MediaError
    // (1=dibatalkan, 2=jaringan, 3=decode gagal/berkas rusak, 4=format/URL tidak
    // didukung) jauh lebih jelas daripada cuma "belum tersedia" di layar.
    console.warn("[demo]", nama, "gagal dimuat:", info.src,
      "kode:", audio.error && audio.error.code,
      "pesan:", audio.error && audio.error.message);
    tombol.disabled = true;
    durasi.textContent = "\u2014";
    belum.textContent = nama === "kloning"
      ? "Contoh hasil kloning sedang disiapkan."
      : "Contoh suara belum tersedia.";
    belum.classList.remove("sembunyi");
    // Judul bagian ini menantang pengunjung membandingkan dua suara. Kalau salah
    // satunya tidak ada, janji itu ditarik daripada dibiarkan menggantung.
    $("dengar-judul").textContent = "Dengar suaranya";
    $("dengar-sub").textContent =
      "Contoh pembanding sedang disiapkan. Yang tersedia sekarang bisa Anda putar di bawah.";
  });

  // Tanda versi di alamatnya (?rev=) supaya browser/CDN yang sempat menyimpan
  // respons gagal dari percobaan sebelumnya (mis. sebelum berkasnya di-deploy)
  // tidak terus dipakai ulang — naikkan angka ini kalau berkas demo diganti.
  audio.src = info.src + "?rev=2";
  tombol.addEventListener("click", () => (SEDANG === nama ? hentikan(nama) : mainkan(nama)));
}

function hentikan(nama, keAwal) {
  const audio = AUDIO[nama];
  if (!audio) return;
  audio.pause();
  if (keAwal) {
    audio.currentTime = 0;
    document.querySelector('[data-durasi="' + nama + '"]').textContent = menit(audio.duration);
  }
  document.querySelector('[data-putar="' + nama + '"]').innerHTML = IKON_MAIN;
  document.querySelector('[data-bar="' + nama + '"]').classList.remove("jalan");
  $("kartu-" + nama).classList.remove("main");
  if (SEDANG === nama) SEDANG = null;
}

async function mainkan(nama) {
  // Dua contoh yang berbunyi bersamaan tidak bisa dibandingkan.
  if (SEDANG && SEDANG !== nama) hentikan(SEDANG, true);
  try {
    await AUDIO[nama].play();
  } catch {
    return; // browser menolak pemutaran; klik berikutnya akan berhasil
  }
  SEDANG = nama;
  document.querySelector('[data-putar="' + nama + '"]').innerHTML = IKON_JEDA;
  document.querySelector('[data-bar="' + nama + '"]').classList.add("jalan");
  $("kartu-" + nama).classList.add("main");
}

siapkanContoh("asli");
siapkanContoh("kloning");


/* ================= jendela ================= */
function buka(id) {
  if (SEDANG) hentikan(SEDANG, true);   // jangan biarkan contoh berbunyi di balik jendela
  $(id).classList.add("tampil");
  document.body.style.overflow = "hidden";
}
function tutup(id) {
  $(id).classList.remove("tampil");
  if (!document.querySelector(".tirai.tampil")) document.body.style.overflow = "";
}
document.querySelectorAll("[data-tutup]").forEach((b) =>
  b.addEventListener("click", () => tutup(b.dataset.tutup)));
document.querySelectorAll(".tirai").forEach((t) =>
  t.addEventListener("click", (e) => { if (e.target === t) tutup(t.id); }));
addEventListener("keydown", (e) => {
  if (e.key !== "Escape") return;
  const t = document.querySelector(".tirai.tampil");
  if (t) tutup(t.id);
});
document.querySelectorAll("[data-mata]").forEach((b) =>
  b.addEventListener("click", () => {
    const i = $(b.dataset.mata);
    i.type = i.type === "password" ? "text" : "password";
    b.setAttribute("aria-label", i.type === "password" ? "Tampilkan password" : "Sembunyikan password");
    i.focus();
  }));

function pesan(el, teks, jenis) {
  el.textContent = teks || "";
  el.className = "pesan" + (jenis ? " " + jenis : "");
}
function kabar(html, jenis) {
  $("kabar-masuk").innerHTML = html
    ? '<div class="kabar ' + (jenis || "info") + '">' + html + "</div>" : "";
}

function bukaMasuk() {
  buka("jendela-masuk");
  $("f-user").focus();
}
["nav-masuk", "hero-masuk", "penutup-masuk", "akses-masuk"].forEach((id) => {
  const el = $(id);
  if (el) el.addEventListener("click", (e) => { e.preventDefault(); bukaMasuk(); });
});

/* ================= masuk ================= */
let SEDANG_KIRIM = false;

async function kirimMasuk() {
  if (SEDANG_KIRIM) return;
  const user = $("f-user").value.trim().toLowerCase();
  const pass = $("f-pass").value;
  const kotakPesan = $("pesan-masuk");

  if (!user || !pass) {
    pesan(kotakPesan, "Username dan password harus diisi.", "err");
    return;
  }

  SEDANG_KIRIM = true;
  const tombol = $("kirim-masuk");
  const teksAsli = tombol.textContent;
  tombol.disabled = true;
  tombol.textContent = "Memeriksa\u2026";
  pesan(kotakPesan, "");

  try {
    const res = await fetch("/api/auth/login", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      credentials: "same-origin",
      body: JSON.stringify({ username: user, password: pass }),
    });
    const j = await res.json().catch(() => ({}));
    if (!res.ok || !j.ok) throw new Error(j.error || "Terjadi kesalahan. Coba lagi.");

    // Notebook Colab TIDAK dibuka otomatis di sini.
    //
    // Dulu satu tab Colab langsung terbuka begitu password benar. Bagi orang
    // yang cuma mau melihat dashboard, atau yang Colab-nya memang sudah
    // berjalan di tab lain, itu satu tab liar yang tidak diminta — dan di HP
    // ia merebut layar. Tombol "Buka Colab" sudah tersedia di panel
    // Sambungkan Engine pada dashboard, jadi yang membutuhkannya tetap
    // menemukannya satu ketukan kemudian.
    pesan(kotakPesan, "Berhasil. Membuka dashboard\u2026", "ok");
    location.href = j.redirect || TUJUAN;
    return;
  } catch (err) {
    pesan(kotakPesan, err.message, "err");
    $("f-pass").value = "";
    $("f-pass").focus();
  } finally {
    SEDANG_KIRIM = false;
    tombol.disabled = false;
    tombol.textContent = teksAsli;
  }
}

$("kirim-masuk").addEventListener("click", kirimMasuk);
["f-user", "f-pass"].forEach((id) =>
  $(id).addEventListener("keydown", (e) => { if (e.key === "Enter") kirimMasuk(); }));

/* ================= setelan dari gateway ================= */
function pasangTautan() {
  if (CFG.brand) {
    $("merek-teks").innerHTML = esc(CFG.brand).replace(/ /g, "&nbsp;");
    $("merek-kaki").textContent = CFG.brand;
    $("logo-atas").alt = CFG.brand;
    $("logo-kaki").alt = CFG.brand;
  }
}

// Pengunjung yang sudah punya cookie sesi tidak perlu ditawari kotak masuk.
function tampilSudahMasuk(nama) {
  $("nav-masuk").classList.add("sembunyi");
  $("nav-beli").classList.add("sembunyi");
  $("nav-buka").classList.remove("sembunyi");

  const sapa = nama ? "Masuk sebagai " + esc(nama) + ". " : "";
  $("bonus-hero").innerHTML = sapa + '<a href="/dashboard">Lanjutkan ke dashboard &rarr;</a>';
  $("bonus-hero").classList.remove("sembunyi");

  // Tombol yang tadinya membuka kotak masuk sekarang langsung ke dashboard.
  ["hero-masuk", "penutup-masuk", "akses-masuk"].forEach((id) => {
    const el = $(id);
    if (!el) return;
    const baru = el.cloneNode(true);
    baru.textContent = "Buka dashboard";
    baru.addEventListener("click", () => { location.href = "/dashboard"; });
    el.replaceWith(baru);
  });
}

async function muatSetelan() {
  try {
    const res = await fetch("/api/config", { credentials: "same-origin" });
    if (res.ok) {
      const j = await res.json();
      // Hanya kolom yang benar-benar terisi yang menimpa cadangan di atas.
      for (const k of ["brand"]) {
        if (j && j[k]) CFG[k] = j[k];
      }
      if (j && j.masuk) tampilSudahMasuk(j.username);
    }
  } catch (e) {
    // Gateway tidak menjawab. Halaman tetap utuh dengan nilai cadangan —
    // tautan WhatsApp jauh lebih baik ada dan mungkin basi daripada hilang.
    console.warn("[VPM] /api/config tidak terbaca:", e.message);
  }
  pasangTautan();
}

/* ================= pesan setelah keluar ================= */
function pesanPembuka() {
  const q = new URLSearchParams(location.search);
  if (q.get("keluar") === "1") {
    kabar("Anda sudah keluar. Sampai jumpa lagi.", "info");
    bukaMasuk();
  } else if (q.get("sesi") === "habis") {
    kabar("<b>Sesi Anda sudah berakhir.</b> Silakan masuk lagi.", "warn");
    bukaMasuk();
  } else if (q.get("masuk") === "1") {
    bukaMasuk();
  }
}

/* ================= cahaya mengikuti kursor ================= */
// Posisi kursor hanya ditulis ke dua custom property CSS. Semua penggambaran
// dikerjakan CSS (lihat .sorot dan .jala-terang), jadi JavaScript di sini tidak
// pernah menyentuh tata letak.
//
// requestAnimationFrame dipakai supaya pembaruan terjadi paling banyak sekali
// per bingkai. Tanpa itu, pointermove bisa menembak puluhan kali per bingkai di
// mouse ber-polling tinggi, dan hasilnya justru tersendat.
// matchMedia dirujuk lewat window dan diperiksa dulu. Merujuknya langsung
// sebagai variabel global akan melempar ReferenceError di lingkungan yang tidak
// punya — dan karena kode ini berjalan di tingkat atas, satu galat di sini
// menghentikan SELURUH sisa skrip, termasuk pemuatan setelan dan tombol
// pendaftaran. Hiasan tidak boleh bisa mematikan halaman.
const tanyaMedia = (q) =>
  (typeof window.matchMedia === "function" ? window.matchMedia(q) : { matches: false });
const gerakBoleh = () => !tanyaMedia("(prefers-reduced-motion: reduce)").matches;

function cahayaKursor() {
  const lampu = $("lampu");
  if (!lampu) return;

  // Layar sentuh tidak punya kursor untuk diikuti. CSS sudah menyembunyikan
  // lapisannya; di sini pendengar peristiwanya sekalian tidak dipasang.
  if (!tanyaMedia("(hover: hover)").matches || !gerakBoleh()) return;

  // Karena lapisannya position:fixed, koordinat kursor terhadap layar sudah
  // langsung benar — tidak ada posisi gulir yang perlu ikut dihitung. Itu
  // sebabnya efek ini sekarang jalan di seluruh halaman, bukan cuma di hero.
  let x = 0, y = 0, menunggu = false;
  const gambar = () => {
    menunggu = false;
    lampu.style.setProperty("--lx", x + "px");
    lampu.style.setProperty("--ly", y + "px");
  };

  // requestAnimationFrame dipakai supaya pembaruan terjadi paling banyak sekali
  // per bingkai. Tanpa itu, pointermove bisa menembak puluhan kali per bingkai
  // di mouse ber-polling tinggi, dan hasilnya justru tersendat.
  addEventListener("pointermove", (e) => {
    x = e.clientX;
    y = e.clientY;
    if (!menunggu) { menunggu = true; requestAnimationFrame(gambar); }
    if (!document.body.classList.contains("terang")) document.body.classList.add("terang");
  }, { passive: true });

  // Kursor keluar dari jendela -> cahayanya meredup, bukan membeku di tepi.
  document.addEventListener("mouseleave", () => document.body.classList.remove("terang"));
  document.addEventListener("mouseenter", () => document.body.classList.add("terang"));
}

// Dibungkus try/catch dengan alasan yang sama: efek ini murni hiasan, dan
// kegagalannya tidak boleh berakibat apa pun pada halaman.

/* ================= muncul saat digulir ================= */
// Satu pengamat untuk seluruh halaman. IntersectionObserver dipakai, bukan
// pendengar peristiwa scroll: pengamat ini dijalankan browser di luar alur
// utama, jadi menggulir tetap ringan walau ada puluhan elemen yang diawasi.
let PENGAMAT = null;

// true  = animasi berjalan LAGI setiap kali elemen masuk layar, termasuk saat
//         digulir kembali ke atas.
// false = sekali saja; setelah muncul, elemen dibiarkan tetap tampil.
//
// Kalau suatu saat terasa terlalu ramai saat halaman digulir bolak-balik, ini
// satu-satunya tempat yang perlu diubah.
const ULANGI_ANIMASI = true;

// Dua ambang yang berbeda, dan itu penting. Elemen DITAMPILKAN begitu 5%
// bagiannya terlihat, tapi baru DIKEMBALIKAN ke keadaan awal setelah benar-benar
// hilang dari layar (rasio 0). Jarak antara keduanya mencegah kedipan: dengan
// satu ambang saja, elemen yang berhenti tepat di garis batas akan menyala dan
// mati berulang-ulang mengikuti gerak gulir sekecil apa pun.
const AMBANG_TAMPIL = 0.05;

const ANIMASI_HIDUP = () => document.documentElement.classList.contains("animasi");

// ---- memotong teks jadi kata ----
// Setiap kata dibungkus <span class="kata"><i>kata</i></span>. Spasinya
// DITINGGAL di luar span supaya baris tetap bisa terpotong secara alami di
// tempat yang benar; kalau spasi ikut masuk ke dalam kotak inline-block,
// pemotongan barisnya jadi aneh pada layar sempit.
//
// Hanya simpul teks yang disentuh, jadi <b> dan <a> di dalam kalimat tetap utuh
// beserta gayanya.
function pecahTeks(el) {
  if (!el || el.dataset.terpecah === "1") return;

  const simpul = [];
  (function jelajah(n) {
    for (const anak of n.childNodes) {
      if (anak.nodeType === 3) simpul.push(anak);
      else if (anak.nodeType === 1 && !anak.classList.contains("kata")) jelajah(anak);
    }
  })(el);

  let w = 0;
  for (const simpulTeks of simpul) {
    const isi = simpulTeks.nodeValue;
    if (!isi || !isi.trim()) continue;

    const kumpulan = document.createDocumentFragment();
    // Dipisah dengan menangkap spasinya, supaya spasi asli bisa dikembalikan
    // apa adanya dan tidak ada kata yang menempel.
    for (const bagian of isi.split(/(\s+)/)) {
      if (!bagian) continue;
      if (/^\s+$/.test(bagian)) {
        kumpulan.appendChild(document.createTextNode(bagian));
        continue;
      }
      const kotak = document.createElement("span");
      kotak.className = "kata";
      const dalam = document.createElement("i");
      dalam.textContent = bagian;
      dalam.style.setProperty("--w", String(w++));
      kotak.appendChild(dalam);
      kumpulan.appendChild(kotak);
    }
    simpulTeks.parentNode.replaceChild(kumpulan, simpulTeks);
  }
  el.dataset.terpecah = "1";
}

// ---- angka yang berhitung naik ----
// Dihitung dengan requestAnimationFrame terhadap waktu sungguhan, bukan dengan
// menambah nilai per bingkai. Kalau per bingkai, durasinya jadi bergantung pada
// kecepatan perangkat: cepat di layar 120Hz, lambat di ponsel lemah.
let GEN_HITUNG = 0;

function hitungNaik(el) {
  const tujuan = Number(el.dataset.hitung) || 0;
  if (!ANIMASI_HIDUP()) { el.textContent = angkaId(tujuan); return; }

  // Nomor giliran. Kalau pengguna menggulir bolak-balik cepat, hitungan lama
  // masih punya requestAnimationFrame yang berjalan; tanpa penanda ini, dua
  // hitungan akan saling menimpa angka yang sama dan hasilnya berkedut.
  const giliran = ++GEN_HITUNG;
  el.dataset.giliran = String(giliran);

  const lama = 1100;
  const mulaiPada = performance.now();
  const langkah = (kini) => {
    // Giliran sudah berganti -> hitungan ini sudah tidak berlaku, berhenti.
    if (el.dataset.giliran !== String(giliran)) return;
    const t = Math.min(1, (kini - mulaiPada) / lama);
    // Melambat di ujung, jadi angka terakhirnya terasa mendarat.
    const halus = 1 - Math.pow(1 - t, 3);
    el.textContent = angkaId(Math.round(tujuan * halus));
    if (t < 1) requestAnimationFrame(langkah);
  };
  requestAnimationFrame(langkah);
}

// Mengembalikan angka ke 0 supaya bisa dihitung dari awal saat masuk layar lagi.
function nolkanHitung(el) {
  el.dataset.giliran = "";   // hentikan hitungan yang mungkin masih berjalan
  el.textContent = angkaId(0);
}

// ---- garis yang menggambar dirinya di bawah kepala bagian ----
function pasangGaris() {
  for (const kepala of document.querySelectorAll(".kepala")) {
    if (kepala.querySelector(".garis")) continue;
    const garis = document.createElement("span");
    garis.className = "garis";
    garis.setAttribute("aria-hidden", "true");
    garis.setAttribute("data-muncul", "garis");
    kepala.appendChild(garis);
  }
}

function siapkanMuncul(akar) {
  if (!ANIMASI_HIDUP()) {
    // Tanpa animasi, angka tetap harus menampilkan nilai akhirnya.
    document.querySelectorAll("[data-hitung]").forEach(hitungNaik);
    return;
  }

  if (!PENGAMAT) {
    PENGAMAT = new IntersectionObserver((masuk) => {
      for (const e of masuk) {
        const el = e.target;
        const rasio = e.intersectionRatio;

        if (e.isIntersecting && rasio >= AMBANG_TAMPIL) {
          if (el.classList.contains("tampil")) continue;
          el.classList.add("tampil");
          // Angka di dalamnya mulai berhitung begitu bloknya terlihat.
          el.querySelectorAll("[data-hitung]").forEach(hitungNaik);

          // Mode sekali-jalan: berhenti mengawasi supaya tidak ada pekerjaan
          // sisa untuk elemen yang sudah selesai.
          if (!ULANGI_ANIMASI) PENGAMAT.unobserve(el);
          continue;
        }

        // Benar-benar hilang dari layar -> dikembalikan ke keadaan awal, siap
        // dianimasikan lagi saat pengguna menggulir kembali ke sini. Rasio harus
        // tepat 0, bukan sekadar "di bawah ambang" — lihat catatan pada
        // AMBANG_TAMPIL soal kedipan.
        if (ULANGI_ANIMASI && rasio === 0 && el.classList.contains("tampil")) {
          el.classList.remove("tampil");
          el.querySelectorAll("[data-hitung]").forEach(nolkanHitung);
        }
      }
    }, {
      // Dipicu sedikit SEBELUM elemennya benar-benar masuk layar, supaya saat
      // pembaca sampai ke sana animasinya sudah berjalan, bukan baru dimulai.
      rootMargin: "0px 0px -10% 0px",
      // Dua ambang: 0 untuk mengetahui saat elemen benar-benar keluar layar,
      // AMBANG_TAMPIL untuk saat ia mulai masuk.
      threshold: [0, AMBANG_TAMPIL],
    });
  }

  const wadah = akar || document;

  // Teks dipotong lebih dulu, sebelum apa pun diawasi.
  wadah.querySelectorAll("[data-teks]").forEach(pecahTeks);

  // Judul hero: nomor urut per baris.
  wadah.querySelectorAll(".judul .baris").forEach((b, i) => b.style.setProperty("--l", String(i)));

  // [data-teks] ikut diawasi, bukan hanya [data-muncul]. Kata-katanya menunggu
  // kelas .tampil pada elemennya sendiri; kalau tidak diawasi, kelas itu tidak
  // pernah datang dan teksnya tinggal tersembunyi selamanya.
  const daftar = wadah.querySelectorAll("[data-muncul]:not(.tampil), [data-teks]:not(.tampil)");
  // Nomor urut per saudara sekandung dipakai CSS sebagai jeda animasi, supaya
  // kartu-kartu dalam satu baris masuk berurutan, bukan serempak.
  const hitung = new Map();
  for (const el of daftar) {
    const induk = el.parentElement;
    const n = hitung.get(induk) || 0;
    hitung.set(induk, n + 1);
    el.style.setProperty("--i", String(n));
    PENGAMAT.observe(el);
  }
}

/* ================= hiasan ================= */
// Dipanggil di sini, bukan di dekat definisinya, karena siapkanMuncul() memakai
// variabel PENGAMAT yang dideklarasikan dengan `let` lebih ke bawah. Memanggilnya
// lebih awal melempar galat "Cannot access before initialization".
//
// Ketiganya murni hiasan, jadi dibungkus try/catch: kegagalannya tidak boleh
// berakibat apa pun pada halaman.
try { cahayaKursor(); } catch (e) { console.warn("[VPM] cahaya kursor dimatikan:", e.message); }
try { pasangGaris(); } catch (e) { console.warn("[VPM] garis hias dilewati:", e.message); }
try { siapkanMuncul(); } catch (e) {
  console.warn("[VPM] animasi gulir dimatikan:", e.message);
  // Kalau pengamatnya gagal dibuat, kelas penyembunyi HARUS dilepas — kalau
  // tidak, isi halaman tinggal di opacity 0 dan tidak akan pernah muncul.
  document.documentElement.classList.remove("animasi");
}

pesanPembuka();
muatSetelan();
</script>
</body>
</html>
