Menguasai Dunia Digital: Bikin Script Otomatisasi Python untuk Bot & Web Scraping

ikramlink Maret 26, 2026
Menguasai Dunia Digital: Bikin Script Otomatisasi Python untuk Bot & Web Scraping

Halo, bro dan sist para ksatria keyboard! Apa kabar? Semoga kodingan lancar dan kopi selalu siap di samping ya. Kali ini, kita akan ngulik topik yang bikin banyak programmer "wow" saat pertama kali kenal: otomatisasi dengan Python! Khususnya, kita bakal bedah tuntas cara bikin script otomatisasi Python buat bot dan web scraping. Siap-siap, karena skill ini bisa jadi game changer buat banyak banget proyek kalian!

Sebagai seorang tech-blogger dan juga programmer yang tiap hari nyemplung di lautan kode, saya sering banget pakai Python buat ngurusin hal-hal repetitif. Mulai dari ngumpulin data dari website, nge-monitor harga barang, sampai otomatisasi interaksi di situs web. Rasanya kayak punya asisten digital super cerdas yang enggak pernah ngeluh disuruh kerja. Dan percaya deh, feeling pas script kalian jalan dan ngelakuin tugasnya sendiri itu... aduh, nagih banget!

Kenapa Harus Python untuk Otomatisasi?

Mungkin ada yang nanya, "Kenapa Python, Bang? Kan ada bahasa lain?" Nah, ini dia alasannya kenapa Python jadi primadona di dunia otomatisasi, web scraping, dan bahkan AI:

  • Simplicity & Readability: Sintaksis Python itu ramah banget, mirip bahasa Inggris. Bikin kodingan jadi gampang dibaca dan ditulis, bahkan buat pemula sekalipun.
  • Ekosistem Library yang Kaya: Ini dia MVP-nya! Python punya segudang library powerful yang siap pakai. Buat web scraping ada requests dan BeautifulSoup. Buat otomatisasi browser ada Selenium. Buat data analysis ada Pandas. Tinggal pip install, langsung gas!
  • Komunitas Besar: Kalau mentok ada bug atau butuh ide, komunitas Python itu super aktif. Forum, Stack Overflow, GitHub, semua siap bantu. Jadi, enggak bakal berasa jalan sendirian.
  • Cross-Platform: Script Python yang kalian tulis di Windows bisa jalan di macOS atau Linux tanpa masalah berarti. Praktis banget kan?

Apa Itu Web Scraping?

Oke, mari kita mulai dari dasar, yaitu web scraping. Secara sederhana, web scraping itu proses "mengambil" atau "menarik" data dari halaman web secara otomatis menggunakan script. Bayangkan kalian buka sebuah website, terus kalian butuh semua judul berita di halaman itu. Daripada capek copy-paste satu per satu, kita bisa bikin script Python untuk ngambilin data itu semua dalam sekejap mata!

Peralatan Tempur Web Scraping: Requests & BeautifulSoup

Untuk web scraping, dua library ini adalah duet maut yang paling sering saya pakai:

  • requests: Library ini berfungsi untuk mengirim permintaan HTTP (GET, POST, dll.) ke server web, persis kayak browser kalian minta halaman web. Ini yang bakal ngambil 'mentahan' HTML dari sebuah URL.
  • BeautifulSoup (bs4): Setelah dapat mentahan HTML-nya, BeautifulSoup bertugas sebagai "pakar bedah" yang akan mem-parsing (mengurai) HTML tersebut agar kita bisa dengan mudah menemukan dan mengekstrak data yang kita inginkan (misalnya, semua tag

    , tag dengan kelas tertentu, atau teks di dalam

    ).

Praktik Web Scraping Sederhana

Mari kita coba contoh simpel. Anggaplah kita mau mengambil judul halaman dari sebuah website. Di sini saya akan pakai website blog saya sendiri sebagai target (supaya aman dan etis, ya bro!).


import requests
from bs4 import BeautifulSoup

def scrape_judul_website(url):
    try:
        # Kirim permintaan GET ke URL
        response = requests.get(url)
        response.raise_for_status() # Cek jika ada error HTTP

        # Parse konten HTML menggunakan BeautifulSoup
        soup = BeautifulSoup(response.text, 'html.parser')

        # Ambil judul halaman (biasanya di dalam tag )
        judul = soup.title.string
        print(f"Judul website '{url}': {judul}")

        # Contoh lain: ambil semua link di halaman
        print("\nBeberapa link yang ditemukan:")
        links = soup.find_all('a')
        for i, link in enumerate(links[:5]): # Ambil 5 link pertama saja
            href = link.get('href')
            text = link.get_text(strip=True)
            print(f"- {text} ({href})")

    except requests.exceptions.RequestException as e:
        print(f"Error saat melakukan permintaan: {e}")
    except AttributeError:
        print("Judul tidak ditemukan atau format HTML tidak sesuai.")
    except Exception as e:
        print(f"Terjadi error tak terduga: {e}")

# Panggil fungsi dengan URL target
target_url = "https://example.com" # Ganti dengan URL yang valid dan punya izin
scrape_judul_website(target_url)
</code></pre>

<p><strong>Penjelasan Kodingan:</strong></p>
<ul>
    <li><code>requests.get(url)</code>: Ngirim permintaan ke server. Kalau berhasil, kita dapat respons berisi HTML-nya.</li>
    <li><code>response.raise_for_status()</code>: Ini penting buat nangkep error kayak 404 Not Found atau 500 Server Error.</li>
    <li><code>BeautifulSoup(response.text, 'html.parser')</code>: Ini yang ngubah teks HTML jadi objek <code>BeautifulSoup</code> yang gampang dimanipulasi.</li>
    <li><code>soup.title.string</code>: Cara gampang buat ngambil teks di dalam tag <code><title></code>.</li>
    <li><code>soup.find_all('a')</code>: Ini buat nyari semua tag <code><a></code> (link) di halaman. Setelah itu, kita bisa iterasi untuk ngambil atribut <code>href</code> dan teks di dalamnya.</li>
</ul>

<p>Tips dari saya: Selalu pakai <code>try-except</code> block. Karena dunia web itu dinamis, ada aja hal tak terduga yang bisa bikin script kalian error, kayak server down, koneksi putus, atau struktur HTML website yang berubah. Dengan <code>try-except</code>, script kalian jadi lebih robust!</p>

<h2>Mengenal Bot Otomatisasi dengan Selenium</h2>
<p>Kalau web scraping itu cuma "ngebaca" data, <strong>bot otomatisasi</strong> itu lebih ke arah "berinteraksi" dengan website. Contohnya: login ke akun, mengisi formulir, klik tombol, scroll halaman, atau bahkan main game berbasis web secara otomatis. Nah, untuk tugas-tugas ini, kita butuh tool yang bisa mengendalikan browser sungguhan. Di sinilah <strong>Selenium</strong> beraksi!</p>

<h3>Kenapa Selenium?</h3>
<p>Selenium ini bukan cuma buat otomatisasi, tapi juga sering dipakai buat <em>testing</em> web. Dia bisa mengontrol browser populer seperti Chrome, Firefox, Edge, Safari, seolah-olah ada manusia yang sedang memakainya. Jadi, website yang punya JavaScript kompleks atau butuh interaksi aktif, bisa banget diatasi sama Selenium.</p>

<p><strong>Hal yang Perlu Disiapkan Sebelum Pakai Selenium:</strong></p>
<ol>
    <li><strong>Browser:</strong> Chrome, Firefox, dll.</li>
    <li><strong>WebDriver:</strong> Ini adalah "jembatan" antara script Python kalian dengan browser. Tiap browser punya WebDriver-nya sendiri (misalnya ChromeDriver untuk Chrome, GeckoDriver untuk Firefox). Kalian harus download dan pastikan lokasinya terdaftar di PATH sistem atau sebutkan path-nya di script.</li>
    <li><strong>Library Selenium Python:</strong> Install dengan <code>pip install selenium</code>.</li>
</ol>

<h3>Praktik Otomatisasi Login Sederhana</h3>
<p>Mari kita bikin script yang bisa otomatis login ke sebuah halaman. Saya akan pakai demo login dari <code>the-internet.herokuapp.com</code> karena ini situs yang dibuat khusus buat testing.</p>

<pre><code>
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
import time

def otomatisasi_login(username, password):
    # Setup WebDriver secara otomatis dengan webdriver_manager
    service = ChromeService(executable_path=ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)

    try:
        # Buka halaman login
        driver.get("https://the-internet.herokuapp.com/login")
        print(f"Berhasil membuka halaman: {driver.title}")
        time.sleep(2) # Kasih jeda biar elemen keload sempurna

        # Temukan elemen input username dan isi
        # Bisa pakai ID, NAME, CLASS_NAME, CSS_SELECTOR, XPATH
        username_field = driver.find_element(By.ID, "username")
        username_field.send_keys(username)
        print("Username berhasil diisi.")
        time.sleep(1)

        # Temukan elemen input password dan isi
        password_field = driver.find_element(By.ID, "password")
        password_field.send_keys(password)
        print("Password berhasil diisi.")
        time.sleep(1)

        # Temukan tombol login dan klik
        login_button = driver.find_element(By.CSS_SELECTOR, "button[type='submit']")
        login_button.click()
        print("Tombol login diklik.")
        time.sleep(3) # Tunggu beberapa saat setelah login

        # Cek apakah login berhasil
        if "secure" in driver.current_url:
            print("Login berhasil! Anda berada di halaman secure.")
            # Ambil pesan flash success
            success_message = driver.find_element(By.ID, "flash").text
            print(f"Pesan: {success_message}")
        else:
            print("Login gagal. Kembali ke halaman login.")
            error_message = driver.find_element(By.ID, "flash").text
            print(f"Pesan error: {error_message}")

    except Exception as e:
        print(f"Terjadi error: {e}")
    finally:
        # Penting: Tutup browser setelah selesai
        print("Menutup browser...")
        driver.quit()

# Panggil fungsi otomatisasi login
otomatisasi_login("tomsmith", "SuperSecretPassword!")
</code></pre>

<p><strong>Penjelasan Kodingan:</strong></p>
<ul>
    <li><code>ChromeDriverManager().install()</code>: Ini library tambahan (<code>webdriver-manager</code>) yang sangat membantu karena dia akan otomatis mendownload dan mengatur ChromeDriver yang sesuai dengan versi Chrome kalian. Jadi kalian enggak perlu pusing download manual lagi!</li>
    <li><code>driver = webdriver.Chrome(service=service)</code>: Menginisialisasi browser Chrome.</li>
    <li><code>driver.get(url)</code>: Membuka URL yang dituju.</li>
    <li><code>driver.find_element(By.ID, "username")</code>: Mencari elemen di halaman web berdasarkan ID-nya. Ada banyak metode pencarian lain seperti <code>By.NAME</code>, <code>By.CLASS_NAME</code>, <code>By.CSS_SELECTOR</code>, <code>By.XPATH</code>. Kalian bisa pakai <em>Inspect Element</em> di browser untuk tahu ID/Class/Selector elemen.</li>
    <li><code>username_field.send_keys(username)</code>: Mengirim teks ke input field.</li>
    <li><code>login_button.click()</code>: Mengklik sebuah elemen.</li>
    <li><code>time.sleep(2)</code>: Ini jeda biar script enggak terlalu cepat dan memberi kesempatan halaman atau elemen untuk loading. Penting untuk menghindari error <code>ElementNotInteractableException</code>.</li>
    <li><code>driver.quit()</code>: Wajib dipanggil untuk menutup browser dan membersihkan resource setelah selesai. Jangan sampai lupa, nanti banyak browser "ghost" yang nyangkut di background!</li>
</ul>

<h3>Etika dan Batasan dalam Web Scraping dan Bot</h3>
<p>Sebelum kalian mulai "menyerbu" website dengan script, ada beberapa hal etis dan legal yang WAJIB kalian perhatikan:</p>
<ol>
    <li><strong><code>robots.txt</code>:</strong> Ini adalah file di website (misalnya <code>https://example.com/robots.txt</code>) yang memberitahu bot area mana yang boleh di-crawl dan mana yang tidak. SELALU patuhi ini.</li>
    <li><strong>Terms of Service (ToS):</strong> Baca ToS website yang ingin kalian scrape. Beberapa website secara eksplisit melarang scraping.</li>
    <li><strong>Jangan Bebani Server:</strong> Jangan kirim permintaan terlalu cepat dan banyak. Gunakan <code>time.sleep()</code> untuk memberi jeda. Kalau kalian bikin server website tujuan jadi lambat atau bahkan down, itu bisa dianggap <em>Denial of Service (DoS) attack</em> dan bisa kena masalah hukum.</li>
    <li><strong>Hindari Data Pribadi:</strong> Jangan pernah scrape atau simpan data pribadi pengguna tanpa izin. Ini bukan cuma tidak etis, tapi juga ilegal di banyak negara (GDPR, UU ITE).</li>
    <li><strong>Identifikasi Bot Kalian:</strong> Beberapa website memperbolehkan scraping jika kalian mengidentifikasi bot kalian dengan <em>User-Agent</em> yang jelas (misalnya "MyBot/1.0 (contact@email.com)").</li>
</ol>
<p>Intinya, jadilah programmer yang bertanggung jawab. Tujuan otomatisasi adalah efisiensi, bukan eksploitasi.</p>

<div style="text-align:center;margin:30px 0;"><script>atOptions={'key':'7d25a94073d391d12386aaa75b909463','format':'iframe','height':90,'width':728,'params':{}};</script><script src="https://www.highperformanceformat.com/7d25a94073d391d12386aaa75b909463/invoke.js"></script></div>

<h2>Kombinasi Kekuatan: Scraping & Bot untuk Skenario Lebih Kompleks</h2>
<p>Gimana kalau kita gabungin kekuatan <code>BeautifulSoup</code> dan <code>Selenium</code>? Contohnya, kita mau login ke sebuah forum, terus masuk ke sub-forum tertentu, lalu scrape semua judul thread di sana. Ini skenario yang sering banget terjadi di dunia nyata.</p>

<p>Pada dasarnya, <code>Selenium</code> akan kita pakai untuk navigasi dan interaksi (login, klik tombol, scroll). Setelah kita sampai di halaman yang kita inginkan dan semua konten JavaScript sudah ter-render, kita bisa ambil <code>page_source</code> dari <code>Selenium</code>, lalu oper ke <code>BeautifulSoup</code> untuk parsing data yang lebih efisien dan akurat.</p>

<pre><code>
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.chrome.service import Service as ChromeService
from webdriver_manager.chrome import ChromeDriverManager
from bs4 import BeautifulSoup
import time

def bot_scrape_forum(username, password, forum_url):
    service = ChromeService(executable_path=ChromeDriverManager().install())
    driver = webdriver.Chrome(service=service)

    try:
        # 1. Navigasi ke halaman login (contoh sederhana)
        driver.get("https://the-internet.herokuapp.com/login")
        time.sleep(2)

        # 2. Lakukan login
        driver.find_element(By.ID, "username").send_keys(username)
        driver.find_element(By.ID, "password").send_keys(password)
        driver.find_element(By.CSS_SELECTOR, "button[type='submit']").click()
        time.sleep(3) # Tunggu login selesai

        if "secure" not in driver.current_url:
            print("Login gagal. Menghentikan bot.")
            return

        print("Login berhasil! Sekarang menuju halaman forum.")

        # 3. Navigasi ke URL forum yang ditargetkan
        driver.get(forum_url)
        time.sleep(5) # Kasih waktu lebih lama kalau forumnya berat JavaScript

        # 4. Ambil source halaman setelah JavaScript di-render oleh Selenium
        page_source = driver.page_source

        # 5. Gunakan BeautifulSoup untuk parse data dari page_source
        soup = BeautifulSoup(page_source, 'html.parser')

        print(f"\nScraping judul thread dari forum: {driver.title}")
        # Contoh: Mencari semua elemen yang mungkin berisi judul thread
        # Ini sangat tergantung struktur HTML forum tujuan.
        # Anggaplah judul thread ada di dalam tag <h3> dengan class 'thread-title'
        thread_titles = soup.find_all('h3', class_='thread-title') 
        if not thread_titles:
            # Jika tidak ada h3.thread-title, coba cari dengan pola lain, misal <a> dalam <div> tertentu
            print("Tidak ditemukan 'h3' dengan class 'thread-title'. Mencoba pola lain...")
            thread_containers = soup.find_all('div', class_='thread-item') # Contoh
            for i, container in enumerate(thread_containers[:10]): # Ambil 10 saja
                title_link = container.find('a', class_='thread-link') # Contoh
                if title_link:
                    print(f"- {title_link.get_text(strip=True)} ({title_link.get('href')})")
                else:
                    print(f"- [Tidak ada link judul di kontainer {i+1}]")
        else:
            for i, title_tag in enumerate(thread_titles[:10]): # Ambil 10 judul pertama
                print(f"- {title_tag.get_text(strip=True)}")

    except Exception as e:
        print(f"Terjadi error: {e}")
    finally:
        print("\nMenutup browser...")
        driver.quit()

# Panggil fungsi dengan kredensial dan URL forum
# NOTE: Ganti forum_target_url dengan URL forum yang valid dan izinkan scraping.
# Karena saya tidak punya forum demo yang bisa diakses publik, ini hanya contoh pola.
# Anggap saja ada halaman forum yang bisa diakses setelah login di the-internet.herokuapp.com/secure
bot_scrape_forum("tomsmith", "SuperSecretPassword!", "https://the-internet.herokuapp.com/secure") 
# Perlu diperhatikan, halaman /secure tidak memiliki struktur forum, jadi contoh scrapingnya akan gagal
# Ini hanya untuk demonstrasi alur kombinasi Selenium dan BS4
</code></pre>

<p><strong>Penting:</strong> Contoh di atas untuk bagian scraping judul thread akan sangat bergantung pada struktur HTML dari forum yang kalian tuju. Kalian perlu pakai fitur <em>Inspect Element</em> di browser kalian untuk benar-benar mengidentifikasi <em>tag</em> HTML, <em>class</em>, atau <em>ID</em> yang berisi judul thread. Ini adalah bagian yang paling banyak memakan waktu saat web scraping: "reverse engineering" struktur website.</p>

<h2>Tips dan Trik Lanjutan Anti-Deteksi Bot</h2>
<p>Situs web modern semakin pintar mendeteksi bot. Berikut beberapa tips supaya bot kalian tidak gampang ketahuan:</p>
<ul>
    <li><strong>Gunakan User-Agent Random:</strong> Ganti User-Agent browser kalian setiap beberapa permintaan. User-Agent adalah string yang mengidentifikasi browser kalian. Bot biasanya pakai User-Agent default Selenium yang gampang dikenali.</li>
    <li><strong>Proxy Rotasi:</strong> Jika kalian mengirim banyak permintaan dari satu IP, server bisa memblokir IP kalian. Gunakan daftar proxy yang berbeda-beda untuk menyembunyikan IP asli.</li>
    <li><strong>Headless Mode OFF (kadang-kadang):</strong> Selenium bisa dijalankan dalam mode headless (tanpa GUI browser terbuka). Namun, beberapa situs bisa mendeteksi mode headless. Sesekali jalankan dengan GUI terbuka untuk tes atau jika memang diperlukan.</li>
    <li><strong>Mimik Perilaku Manusia:</strong> Randomize <code>time.sleep()</code>, lakukan scroll acak, klik beberapa link tak penting sebelum ke target. Ini meniru perilaku manusia yang tidak linear.</li>
    <li><strong>Tangani CAPTCHA:</strong> Ini adalah tantangan terbesar. Untuk CAPTCHA sederhana, ada library OCR (Optical Character Recognition) atau API pihak ketiga. Untuk reCAPTCHA v3 yang canggih, seringkali butuh integrasi dengan layanan pemecah CAPTCHA.</li>
    <li><strong>Simpan Cookies dan Session:</strong> Setelah login, simpan cookies atau session jika memungkinkan, agar tidak perlu login ulang setiap kali script dijalankan.</li>
</ul>

<h2>Curhatan Programmer: Kenapa Web Scraping itu Bikin Deg-Degan?</h2>
<p>Bro, percaya deh, pengalaman ngoding web scraping ini seringkali bikin deg-degan kayak lagi nunggu hasil tes. Ada kalanya, pas script udah jalan mulus berhari-hari, eh tiba-tiba website yang di-scrape ganti layout! Semua selector CSS atau XPath yang udah kita atur rapih, langsung bubar jalan. Akhirnya, harus balik lagi ke "medan perang" Inspect Element. Rasanya kayak lagi main kucing-kucingan sama developer web lain.</p>

<p>Tapi justru di situ seninya. Setiap kali berhasil mecahin pola baru atau berhasil ngakalin anti-bot system yang canggih, rasanya kayak dapat harta karun. Apalagi kalau data yang berhasil di-scrape itu bener-bener berguna, kayak buat analisis pasar, riset harga, atau bahkan buat bikin produk baru. Kepuasan itu lho, enggak ada duanya!</p>

<h2>Penutup: Ayo Mulai Berpetualang!</h2>
<p>Gimana, bro dan sist? Udah kebayang kan betapa powerful-nya Python buat otomatisasi dan web scraping? Dari sekadar ngambil judul website sampai otomatisasi interaksi kompleks, semuanya bisa kita taklukkan. Kuncinya ada di pemahaman dasar, latihan yang rajin, dan kemauan untuk terus belajar dari setiap tantangan.</p>

<p>Dunia digital itu luas banget, dan kemampuan otomatisasi ini adalah salah satu kunci untuk menjelajahinya lebih efisien. Jadi, jangan ragu untuk mulai bereksperimen, bikin proyek-proyek kecil kalian sendiri, dan rasakan sensasi punya "asisten digital" yang siap sedia 24/7!</p>

<p>Kalau ada pertanyaan atau mau sharing pengalaman ngoding bot, jangan sungkan tinggalkan komentar di bawah ya! Sampai jumpa di artikel selanjutnya, salam koding!</p>
        
</div>
<div class='mt-8 pt-4 border-t border-gray-200 dark:border-dark-border flex flex-wrap gap-2'>
<a class='px-2.5 py-1 bg-gray-50 dark:bg-dark-700 border border-gray-200 dark:border-dark-border text-blue-600 dark:text-dark-link text-xs font-medium rounded-full hover:bg-gray-100 dark:hover:bg-dark-border transition-colors' href='https://script.pintarapp.com/search/label/Bot'>Bot</a>
<a class='px-2.5 py-1 bg-gray-50 dark:bg-dark-700 border border-gray-200 dark:border-dark-border text-blue-600 dark:text-dark-link text-xs font-medium rounded-full hover:bg-gray-100 dark:hover:bg-dark-border transition-colors' href='https://script.pintarapp.com/search/label/Python'>Python</a>
<a class='px-2.5 py-1 bg-gray-50 dark:bg-dark-700 border border-gray-200 dark:border-dark-border text-blue-600 dark:text-dark-link text-xs font-medium rounded-full hover:bg-gray-100 dark:hover:bg-dark-border transition-colors' href='https://script.pintarapp.com/search/label/Web%20Scraping'>Web Scraping</a>
</div>
<div class='mt-8 pt-4 border-t border-gray-200 dark:border-dark-border flex justify-center w-full'>
<script async='async' data-cfasync='false' src='https://pl28962147.profitablecpmratenetwork.com/7797da862e5153c59970a9d83ab07d97/invoke.js'></script>
<div id='container-7797da862e5153c59970a9d83ab07d97'></div>
</div>
</div>
</div></div>
</main>
<!-- KOLOM 3: SIDEBAR KANAN (Tetap 3 Kolom / 25%) -->
<aside class='lg:col-span-3 w-full order-3 sticky top-20'>
<!-- Slot Iklan Atas Sidebar Kanan -->
<div class='w-full mb-4 flex justify-center items-center overflow-hidden adsterra-slot' data-h='250' data-key='017ff5a2024d32a8d605ed757a4d6002' data-w='300'>
<div class='no-items section' id='ad-sidebar-right'></div>
</div>
<div class='sidebar-right section' id='sidebar-right'><div class='widget PopularPosts' data-version='2' id='PopularPosts1'>
<div class='widget bg-white dark:bg-dark-800 border border-gray-200 dark:border-dark-border p-4 rounded-md shadow-sm'>
<h2>Trending</h2>
<div class='space-y-3'>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/dari-nol-jadi-pro-bangun-dapps-smart.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_uYMqfvt0wTrZ_KUwaN7ipzgj2sy1RQxMydNBZabB6fk1F02KWKgRtir3KSG7v8ukz7JqkqKoV2SpEhF0aikxvKUJlO1XPuiEHvL5o'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Dari Nol Jadi Pro: Bangun dApps & Smart Contract di Solana yang Ngebut Abis!</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/otomatisasi-kompresi-gambar-massal-ke.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://blogger.googleusercontent.com/img/b/R29vZ2xl/AVvXsEiYDgcW77dIwQ7Fn8qRlFuAhiTxJ0mIpi2gvJ1ZAZhhjppBXp9F0yF6zzOIyOJqoiyWqubxaQhI6HJwoeyhsG8XDBRZhpsZ7NBTltmGv8sibMyKJgGnj9l7fkrTlqSHueUoNOcU7NLJOQbdx5vJPj4_DHrWxmlVtsdjaKsT0pC63WM8UJtLtJQpFtprsls/s400/compress%20massal%20webp.jpg'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Otomatisasi Kompresi Gambar Massal ke format WebP</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/rahasia-bikin-hidup-lebih-mudah-python.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_tZSYSD_cHRpfo7UpsHHqvS5LBmb_ompfarKWMstMjX7LKMbVvFZdaj45JuAru7nr68CaCQFRo9NvyxhfGSvjCZji1oIKslYBvKgz4'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Rahasia Bikin Hidup Lebih Mudah: Python untuk Bot & Web Scraping Otomatis!</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/python-jadi-kuncian-bikin-bot-otomasi.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_tp0FmvAeBQ7q2UUJ7XlJBnP310SI4yqLz0sgkbbjWPSZ6l-JJlPiuUepCDLcpc9zVA9a59ahi9qgOkd5q3q-zU1Y05PFX_zeNRm6Ngz3a1NBSUGtH9'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Python Jadi Kuncian! Bikin Bot Otomasi & Web Scraping Anti Ribet (Panduan Lengkap)</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/dari-nol-sampai-pro-bikin-kalkulator.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_tug2PafYUA_OKgdEfjpEh_8T4xj8QB0qAmoiboQmZOBhlgesLnbYoaudNAm7Nz_zuuDMgZZJWQJGOjhLNY5ipxyrdbWJ4J2_4Stpr7mrlJimbeQw'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Dari Nol Sampai Pro: Bikin Kalkulator dan Page Builder Interaktif Pakai HTML, CSS, JS!</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/dari-nol-sampai-pro-panduan-lengkap.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_vG8Wayv4-T59T7jF9TjxOq7IkJp3DujH00L6w_g2xHEYAasuhTIRZg2q6iC6bz8jrmwB2FRsM7RNUK4pWDc3UcDNmsI4rQYiv2bdb3H47BbVf3mA'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Dari Nol Sampai Pro: Panduan Lengkap Simulasi Jaringan Pakai GNS3 dan Packet Tracer</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/memaksimalkan-mikrotik-chr-panduan.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_vYHYVmjeTineVCgaec-lZZz5Jg3KDJWIZvmdJc8QKu2-7sQ3w95FHglrrS-5JzHSGAkxu7ZW2Ky54kGWhDIuX34ZyQeljgZcLdXOTu8wu9u4jw'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Memaksimalkan MikroTik CHR: Panduan Lengkap Manajemen Bandwidth dan Server Impianmu</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/jago-jaringan-tanpa-nombok-hardware.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_taBw_Wx4JOO-MO8jLQFCNTrI7g1r9SN-DBo8pBi0sFQe0FaL4pRylMPI9HfaHvSBgOlkrdJrU6r13hYWFf9Vzdql6RZysCUkm6u6UEdDN3OUk2Qw'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Jago Jaringan Tanpa Nombok Hardware: Panduan Lengkap Simulasi dengan GNS3 dan Cisco Packet Tracer</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/membangun-kalkulator-dan-page-builder.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_v9JcHTA5wK7PW-ySlTmBGBnI05_eWWKkkPHvZMgpG0r3CJglHld02tmTn0mfbecDkNen5u5kZg_gyuFt0LL0xGeb5M7FfKNaPqUMxdvg'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Membangun Kalkulator dan Page Builder Interaktif dengan HTML, CSS, dan JavaScript Murni</h4>
</div>
</a>
<a class='group flex gap-3 items-start' href='https://script.pintarapp.com/2026/03/bedah-solana-web3-panduan-lengkap.html'>
<div class='w-10 h-10 flex-shrink-0 bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border rounded-md overflow-hidden'>
<img class='w-full h-full object-cover transition-transform duration-500 group-hover:scale-110' loading='lazy' src='https://lh3.googleusercontent.com/blogger_img_proxy/AEn0k_vRdzOefCV9wr3B_KjhZdgbuyeeduJblLfazmbk9_vswxVbzPqz7Z_yNKDrGau8GgcFBG2Cr1AyyvwbAjnepgPoMXrQdOcHoxpDfc7652phB2-G'/>
</div>
<div class='flex-1'>
<h4 class='text-xs font-semibold text-gray-800 dark:text-dark-text group-hover:text-blue-600 dark:group-hover:text-dark-link line-clamp-2 leading-snug'>Bedah Solana Web3: Panduan Lengkap Ngoding dApps dan Smart Contract dari Nol (Pakai Anchor, Bro!)</h4>
</div>
</a>
</div>
</div>
</div></div>
</aside>
</div>
</div>
<!-- FOOTER -->
<footer class='w-full border-t border-gray-200 dark:border-dark-border bg-gray-50 dark:bg-dark-900 mt-auto'>
<div class='max-w-[1200px] mx-auto px-4 py-6 flex flex-col md:flex-row justify-between items-center gap-4 text-xs text-gray-500 dark:text-dark-muted'>
<div class='flex items-center gap-2'>
<svg class='w-5 h-5 text-gray-400 dark:text-dark-muted' fill='currentColor' viewBox='0 0 24 24'><path clip-rule='evenodd' d='M12 2C6.477 2 2 6.484 2 12.017c0 4.425 2.865 8.18 6.839 9.504.5.092.682-.217.682-.483 0-.237-.008-.868-.013-1.703-2.782.605-3.369-1.343-3.369-1.343-.454-1.158-1.11-1.466-1.11-1.466-.908-.62.069-.608.069-.608 1.003.07 1.531 1.032 1.531 1.032.892 1.53 2.341 1.088 2.91.832.092-.647.35-1.088.636-1.338-2.22-.253-4.555-1.113-4.555-4.951 0-1.093.39-1.988 1.029-2.688-.103-.253-.446-1.272.098-2.65 0 0 .84-.27 2.75 1.026A9.564 9.564 0 0112 6.844c.85.004 1.705.115 2.504.337 1.909-1.296 2.747-1.027 2.747-1.027.546 1.379.202 2.398.1 2.651.64.7 1.028 1.595 1.028 2.688 0 3.848-2.339 4.695-4.566 4.943.359.309.678.92.678 1.855 0 1.338-.012 2.419-.012 2.747 0 .268.18.58.688.482A10.019 10.019 0 0022 12.017C22 6.484 17.522 2 12 2z' fill-rule='evenodd'></path></svg>
<span>© 2026 PintarApp Script. All rights reserved.</span>
</div>
<div class='flex space-x-4'>
<a class='hover:text-blue-600 dark:hover:text-dark-link transition-colors' href='/p/about.html#tentang'>About</a>
<a class='hover:text-blue-600 dark:hover:text-dark-link transition-colors' href='/p/about.html#privasi'>Privacy Policy</a>
<a class='hover:text-blue-600 dark:hover:text-dark-link transition-colors' href='/p/about.html#syarat'>Terms of Service</a>
</div>
</div>
</footer>
<!-- FLOATING SHARE (BOTTOM LEFT) -->
<div class='fixed bottom-20 md:bottom-6 left-4 md:left-6 z-50 flex flex-col items-center gap-3' id='floating-share'>
<div class='flex flex-col gap-3 opacity-0 invisible translate-y-4 transition-all duration-300' id='share-icons'>
<a class='w-10 h-10 rounded-full bg-white dark:bg-dark-800 border border-gray-200 dark:border-dark-border text-gray-700 dark:text-dark-text flex items-center justify-center hover:text-white hover:bg-[#1877F2] transition-colors shadow-lg' href='#' onclick='window.open("https://www.facebook.com/sharer.php?u="+encodeURIComponent(window.location.href), "_blank"); return false;'>
<svg class='w-5 h-5' fill='currentColor' viewBox='0 0 24 24'><path d='M24 12.073c0-6.627-5.373-12-12-12s-12 5.373-12 12c0 5.99 4.388 10.954 10.125 11.854v-8.385H7.078v-3.469h3.047V9.43c0-3.007 1.792-4.669 4.533-4.669 1.312 0 2.686.235 2.686.235v2.953H15.83c-1.491 0-1.956.925-1.956 1.874v2.25h3.328l-.532 3.469h-2.796v8.385C19.612 23.027 24 18.062 24 12.073z'></path></svg>
</a>
<a class='w-10 h-10 rounded-full bg-white dark:bg-dark-800 border border-gray-200 dark:border-dark-border text-gray-700 dark:text-dark-text flex items-center justify-center hover:text-white hover:bg-[#25D366] transition-colors shadow-lg' href='#' onclick='window.open("https://api.whatsapp.com/send?text="+encodeURIComponent(document.title + " " + window.location.href), "_blank"); return false;'>
<svg class='w-5 h-5' fill='currentColor' viewBox='0 0 24 24'><path d='M17.472 14.382c-.297-.149-1.758-.867-2.03-.967-.273-.099-.471-.148-.67.15-.197.297-.767.966-.94 1.164-.173.199-.347.223-.644.075-.297-.15-1.255-.463-2.39-1.475-.883-.788-1.48-1.761-1.653-2.059-.173-.297-.018-.458.13-.606.134-.133.298-.347.446-.52.149-.174.198-.298.298-.497.099-.198.05-.371-.025-.52-.075-.149-.669-1.612-.916-2.207-.242-.579-.487-.5-.669-.51a12.8 12.8 0 00-.57-.01c-.198 0-.52.074-.792.372-.272.297-1.04 1.016-1.04 2.479 0 1.462 1.065 2.875 1.213 3.074.149.198 2.096 3.2 5.077 4.487.709.306 1.262.489 1.694.625.712.227 1.36.195 1.871.118.571-.085 1.758-.719 2.006-1.413.248-.694.248-1.289.173-1.413-.074-.124-.272-.198-.57-.347m-5.421 7.403h-.004a9.87 9.87 0 01-5.031-1.378l-.361-.214-3.741.982.998-3.648-.235-.374a9.86 9.86 0 01-1.51-5.26c.001-5.45 4.436-9.884 9.888-9.884 2.64 0 5.122 1.03 6.988 2.898a9.825 9.825 0 012.893 6.994c-.003 5.45-4.437 9.884-9.885 9.884m8.413-18.297A11.815 11.815 0 0012.05 0C5.495 0 .16 5.335.157 11.892c0 2.096.547 4.142 1.588 5.945L.057 24l6.305-1.654a11.882 11.882 0 005.683 1.448h.005c6.554 0 11.89-5.335 11.893-11.893a11.821 11.821 0 00-3.48-8.413Z'></path></svg>
</a>
</div>
<button aria-label='Share' class='w-12 h-12 rounded-full bg-hacker-500 border border-transparent text-white flex items-center justify-center shadow-[0_4px_10px_rgba(35,134,54,0.4)] hover:bg-hacker-600 transition-colors' id='share-toggle-btn'>
<svg class='w-5 h-5 ml-[-2px]' fill='none' stroke='currentColor' viewBox='0 0 24 24'><path d='M8.684 13.342C8.886 12.938 9 12.482 9 12c0-.482-.114-.938-.316-1.342m0 2.684a3 3 0 110-2.684m0 2.684l6.632 3.316m-6.632-6l6.632-3.316m0 0a3 3 0 105.367-2.684 3 3 0 00-5.367 2.684zm0 9.316a3 3 0 105.368 2.684 3 3 0 00-5.368-2.684z' stroke-linecap='round' stroke-linejoin='round' stroke-width='2'></path></svg>
</button>
</div>
<!-- HIGHLIGHT.JS SCRIPT -->
<script src='https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.8.0/highlight.min.js'></script>
<script>hljs.highlightAll();</script>
<!-- CUSTOM LOGIC (SEARCH, SHARE, ADS, FETCHERS) -->
<script>
    //<![CDATA[
        document.addEventListener('DOMContentLoaded', () => {

            // Dark Mode Toggle Logic
            const themeToggleBtn = document.getElementById('theme-toggle');
            const darkIcon = document.getElementById('theme-toggle-dark-icon');
            const lightIcon = document.getElementById('theme-toggle-light-icon');

            if (darkIcon && lightIcon) {
                if (document.documentElement.classList.contains('dark')) {
                    lightIcon.classList.remove('hidden');
                } else {
                    darkIcon.classList.remove('hidden');
                }
            }

            function toggleTheme() {
                if (darkIcon) darkIcon.classList.toggle('hidden');
                if (lightIcon) lightIcon.classList.toggle('hidden');

                if (localStorage.getItem('color-theme')) {
                    if (localStorage.getItem('color-theme') === 'light') {
                        document.documentElement.classList.add('dark');
                        localStorage.setItem('color-theme', 'dark');
                    } else {
                        document.documentElement.classList.remove('dark');
                        localStorage.setItem('color-theme', 'light');
                    }
                } else {
                    if (document.documentElement.classList.contains('dark')) {
                        document.documentElement.classList.remove('dark');
                        localStorage.setItem('color-theme', 'light');
                    } else {
                        document.documentElement.classList.add('dark');
                        localStorage.setItem('color-theme', 'dark');
                    }
                }
            }

            if (themeToggleBtn) themeToggleBtn.addEventListener('click', toggleTheme);

            // Hamburger Mobile Menu Logic
            const mobileMenuBtn = document.getElementById('mobile-menu-btn');
            const mobileNavMenu = document.getElementById('mobile-nav-menu');
            if (mobileMenuBtn && mobileNavMenu) {
                mobileMenuBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    mobileNavMenu.classList.toggle('hidden');
                });
                document.addEventListener('click', (e) => {
                    if (!mobileNavMenu.contains(e.target) && !mobileMenuBtn.contains(e.target)) {
                        mobileNavMenu.classList.add('hidden');
                    }
                });
            }

            // 1. SEARCH TOGGLE LOGIC
            const searchBtn = document.getElementById('search-toggle');
            const searchBox = document.getElementById('search-box');
            if (searchBtn && searchBox) {
                searchBtn.addEventListener('click', (e) => {
                    e.stopPropagation();
                    if (searchBox.classList.contains('opacity-0')) {
                        searchBox.classList.remove('opacity-0', 'invisible', 'translate-y-1');
                        searchBox.querySelector('input').focus();
                    } else {
                        searchBox.classList.add('opacity-0', 'invisible', 'translate-y-1');
                    }
                });
                document.addEventListener('click', (e) => {
                    if (!searchBox.contains(e.target) && !searchBtn.contains(e.target)) {
                        searchBox.classList.add('opacity-0', 'invisible', 'translate-y-1');
                    }
                });
            }

            // 2. FLOATING SHARE TOGGLE
            const shareBtn = document.getElementById('share-toggle-btn');
            const shareIcons = document.getElementById('share-icons');
            if (shareBtn && shareIcons) {
                shareBtn.addEventListener('click', () => {
                    shareIcons.classList.toggle('opacity-0');
                    shareIcons.classList.toggle('invisible');
                    shareIcons.classList.toggle('translate-y-4');
                });
            }

            // 3. AUTO INJECT COPY BUTTON TO PRE CODE
            document.querySelectorAll('pre').forEach(pre => {
                const btn = document.createElement('button');
                btn.className = 'copy-btn';
                btn.innerHTML = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 012 2v2m-6 12h8a2 2 0 002-2v-8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg> Copy';
                btn.onclick = () => {
                    const code = pre.querySelector('code');
                    if (code) {
                        navigator.clipboard.writeText(code.innerText).then(() => {
                            btn.innerHTML = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 13l4 4L19 7"></path></svg> Copied!';
                            btn.classList.add('copied');
                            setTimeout(() => {
                                btn.innerHTML = '<svg class="w-3.5 h-3.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M8 16H6a2 2 0 01-2-2V6a2 2 0 012-2h8a2 2 0 00-2-2h-8a2 2 0 00-2 2v8a2 2 0 002 2z"></path></svg> Copy';
                                btn.classList.remove('copied');
                            }, 2000);
                        });
                    }
                };
                pre.appendChild(btn);
            });

            // 4. PARALLAX AD & IMAGE PROXY (Inside Post)
            const postBodies = document.querySelectorAll('.post-body');
            postBodies.forEach(body => {
                // Auto Photon Proxy for ibb
                const imgs = body.querySelectorAll('img');
                imgs.forEach(img => {
                    if (img.src && img.src.includes('ibb.co')) {
                        let cleanUrl = img.src.replace(/^https?:\/\//, '');
                        img.src = 'https://i0.wp.com/' + cleanUrl + '?quality=80&strip=all';
                    }
                });

                // Inject Parallax Ad at middle of post
                let pTags = body.querySelectorAll('br, p');
                if (pTags.length > 0) {
                    let mid = Math.floor(pTags.length / 2);
                    let target = pTags[mid];
                    if (target) {
                        const adHtml = '<div class="scriptParallax"><div class="ptx1"><div class="ptx2"><div class="flex justify-center items-center adsterra-slot w-[300px] h-[250px] lg:mr-[350px]" data-w="300" data-h="250" data-key="017ff5a2024d32a8d605ed757a4d6002"></div></div></div></div></div>';
                        target.insertAdjacentHTML('afterend', adHtml);
                    }
                }
            });

            // 5. FETCH DATA FOR HOMEPAGE MODULES
            if (document.getElementById('home-modules')) {
                fetchHomeModules();
            }

            // 6. ADSTERRA OBSERVER LAZY LOAD
            if ('IntersectionObserver' in window) {
                const adObserver = new IntersectionObserver((entries, observer) => {
                    entries.forEach(entry => {
                        if (entry.isIntersecting) {
                            const el = entry.target;
                            const k = el.getAttribute('data-key');
                            const w = el.getAttribute('data-w');
                            const h = el.getAttribute('data-h');
                            if (k && !el.hasAttribute('data-loaded')) {
                                el.setAttribute('data-loaded', 'true');
                                const iframe = document.createElement('iframe');
                                iframe.width = w; iframe.height = h; iframe.frameBorder = '0'; iframe.scrolling = 'no';
                                iframe.style.border = 'none'; iframe.style.overflow = 'hidden'; iframe.style.background = 'transparent';
                                iframe.style.margin = 'auto'; // Menggantikan absolute inset-0 agar tidak keluar jalur
                                el.appendChild(iframe);
                                const doc = iframe.contentWindow.document;
                                doc.open();
                                doc.write('<!DOCTYPE html><html><head><style>body{margin:0;padding:0;overflow:hidden;background:transparent;display:flex;justify-content:center;align-items:center;}</style></head><body>');
                                doc.write('<scr'+'ipt type="text/javascript">atOptions={"key":"'+k+'","format":"iframe","height":'+h+',"width":'+w+',"params":{}};</scr'+'ipt>');
                                doc.write('<scr'+'ipt type="text/javascript" src="https://www.highperformanceformat.com/'+k+'/invoke.js"></scr'+'ipt>');
                                doc.write('</body></html>');
                                doc.close();
                            }
                            observer.unobserve(el);
                        }
                    });
                }, { rootMargin: '200px' });

                document.querySelectorAll('.adsterra-slot').forEach(slot => adObserver.observe(slot));
            }
        });

        // === MOBILE TOP AD FUNCTIONS ===
        function minimizeMobileTopAd() {
            const ad = document.getElementById('mobile-top-ad');
            const btn = document.getElementById('show-mobile-top-ad-btn');
            if(ad && btn) {
                ad.style.transform = 'translateY(-100%)';
                setTimeout(() => { btn.classList.remove('hidden'); }, 300);
            }
        }
        function maximizeMobileTopAd() {
            const ad = document.getElementById('mobile-top-ad');
            const btn = document.getElementById('show-mobile-top-ad-btn');
            if(ad && btn) {
                ad.style.transform = 'translateY(0)';
                btn.classList.add('hidden');
            }
        }

        // === MOBILE BOTTOM STICKY AD FUNCTIONS ===
        function toggleMobileBottomAd() {
            const wrapper = document.getElementById('mobile-bottom-ad-wrapper');
            const icon = document.getElementById('bottom-ad-icon');
            // Jika posisinya di 0 (tampil), kita dorong ke bawah sejauh tingginya content (50px) + border (1px) = 51px
            if(wrapper.style.transform === 'translateY(0px)' || wrapper.style.transform === '') {
                wrapper.style.transform = 'translateY(51px)';
                icon.style.transform = 'rotate(180deg)'; // Panah naik
            } else {
                wrapper.style.transform = 'translateY(0px)';
                icon.style.transform = 'rotate(0deg)'; // Panah turun
            }
        }

        // === HOMEPAGE FETCHERS ===
        function fetchHomeModules() {
            // Fetch Sub Category 1 (List Style - Ex: Blogger)
            fetch('/feeds/posts/default/-/Blogger?alt=json&max-results=3')
            .then(res => res.json())
            .then(data => {
                let html = '';
                if(data.feed && data.feed.entry) {
                    data.feed.entry.forEach(post => {
                        const title = post.title.$t;
                        let link = post.link.find(l => l.rel === 'alternate')?.href || '#';
                        html += `<a href="${link}" class="flex gap-2 items-center group py-1">
                            <svg class="w-4 h-4 text-gray-400 dark:text-dark-muted group-hover:text-blue-600 dark:group-hover:text-dark-link transition-colors flex-shrink-0" fill="currentColor" viewBox="0 0 16 16"><path d="M2 2.5A2.5 2.5 0 0 1 4.5 0h8.75a.75.75 0 0 1 .75.75v12.5a.75.75 0 0 1-.75.75h-2.5a.75.75 0 0 1 0-1.5h1.75v-2h-8a1 1 0 0 0-.714 1.7.75.75 0 1 1-1.072 1.05A2.495 2.495 0 0 1 2 11.5Zm10.5-1h-8a1 1 0 0 0-1 1v6.708A2.486 2.486 0 0 1 4.5 9h8ZM5 12.25a.25.25 0 0 1 .25-.25h3.5a.25.25 0 0 1 .25.25v3.25a.25.25 0 0 1-.4.2l-1.45-1.087a.249.249 0 0 0-.3 0L5.4 15.7a.25.25 0 0 1-.4-.2Z"></path></svg>
                            <span class="text-blue-600 dark:text-dark-link group-hover:underline truncate">${title}</span>
                        </a>`;
                    });
                } else { html = '<span class="text-gray-500 dark:text-dark-muted">No scripts found.</span>'; }
                let container = document.getElementById('cat-blogger');
                if(container) container.innerHTML = html;
            }).catch(() => {});

            // Fetch Sub Category 2 (Grid Box Style - Ex: Tools)
            fetch('/feeds/posts/default/-/Tools?alt=json&max-results=4') 
            .then(res => res.json())
            .then(data => {
                let html = '';
                if(data.feed && data.feed.entry) {
                    data.feed.entry.forEach(post => {
                        const title = post.title.$t;
                        let link = post.link.find(l => l.rel === 'alternate')?.href || '#';
                        let snip = post.summary ? post.summary.$t : (post.content ? post.content.$t.replace(/<[^>]+>/g, '').substring(0, 60) + '...' : '');
                        html += `<a href="${link}" class="block bg-gray-50 dark:bg-dark-900 border border-gray-200 dark:border-dark-border p-4 rounded-md hover:border-gray-400 dark:hover:border-dark-muted transition-colors group">
                            <h3 class="text-blue-600 dark:text-dark-link font-semibold mb-1 group-hover:underline line-clamp-1">${title}</h3>
                            <p class="text-xs text-gray-500 dark:text-dark-muted line-clamp-2">${snip}</p>
                        </a>`;
                    });
                } else { html = '<span class="text-gray-500 dark:text-dark-muted">No data found.</span>'; }
                let container = document.getElementById('cat-tools');
                if(container) container.innerHTML = html;
            }).catch(() => {});
        }

        // Utility to extract images (incl. ImgBB to Photon)
        function extractThumb(post, w, h) {
            let thumbUrl = `https://via.placeholder.com/${w}x${h}/f3f4f6/9ca3af?text=[code]`;
            if (document.documentElement.classList.contains('dark')) {
                thumbUrl = `https://via.placeholder.com/${w}x${h}/161b22/30363d?text=[code]`;
            }
            if (post.media$thumbnail && post.media$thumbnail.url) {
                thumbUrl = post.media$thumbnail.url.replace(/\/s[0-9]+(\-c)?/, `/w${w}-h${h}-c`);
            } else if (post.content && post.content.$t) {
                const imgMatch = post.content.$t.match(/<img[^>]+src=['"]([^'"]+)['"]/i);
                if (imgMatch && imgMatch[1]) {
                    thumbUrl = imgMatch[1];
                    if (thumbUrl.includes('ibb.co')) {
                        let cleanUrl = thumbUrl.replace(/^https?:\/\//, '');
                        thumbUrl = `https://i0.wp.com/${cleanUrl}?resize=${w},${h}&quality=80&strip=all`;
                    }
                }
            }
            return thumbUrl;
        }
    //]]>
    </script>

</body>
</html>