#!/usr/bin/env python3
"""Dua artikel baru StudioSoft: (1) Jasa Maintenance WordPress, (2) Jasa Google Ads Surabaya.
Tayang: hari ini & besok, 19:00 WIB. Lengkap: tombol WA, FAQ + schema, link internal, gambar, meta Yoast.
Jalankan: --apply untuk eksekusi."""
import base64, html, json, os, re, sys, time, urllib.request, urllib.error
from datetime import datetime, timedelta, timezone
from PIL import Image, ImageDraw, ImageFont

S = "https://studiosoft.id"
AUTH = base64.b64encode(b"bambangbot:q26r RePo BVy6 pwU2 F3z7 ouyl").decode()
WIB = timezone(timedelta(hours=7))
OUT = "/home/mantapman/.hermes/profiles/atun/workspace/ss_artikel_thumb"
os.makedirs(OUT, exist_ok=True)

CTA = """<style>
.ss-cta-wa{display:inline-flex;align-items:center;gap:10px;background-color:#6878d6;color:#ffffff !important;
font-family:'Open Sans',sans-serif;font-weight:600;font-size:17px;padding:15px 30px;border-radius:8px;
text-decoration:none !important;line-height:1.2;box-shadow:0 2px 8px rgba(104,120,214,.35)}}
.ss-cta-wa:hover{background-color:#4d61d6;color:#ffffff !important}
</style>
<p style="text-align:center;margin:34px 0 6px">
<a class="ss-cta-wa" href="https://wa.me/6281935000624?text=Halo%20StudioSoft%2C%20saya%20ingin%20konsultasi%20gratis%20untuk%20bisnis%20saya" target="_blank" rel="noopener" role="button">WhatsApp: Konsultasi Gratis</a>
</p>
<p style="text-align:center;font-size:14px;color:#5b5b6b;margin:0 0 26px">Dibalas langsung oleh tim StudioSoft, tanpa biaya dan tanpa komitmen.</p>"""


def schema(faq):
    data = {"@context": "https://schema.org", "@type": "FAQPage",
            "mainEntity": [{"@type": "Question", "name": q,
                            "acceptedAnswer": {"@type": "Answer", "text": a}} for q, a in faq]}
    return '<script type="application/ld+json">' + json.dumps(data, ensure_ascii=False) + "</script>"


# ─────────────────────────── ARTIKEL 1
A1 = {}
A1["judul"] = "Jasa Maintenance WordPress Surabaya: Yang Wajib Dicek Tiap Bulan"
A1["slug"] = "jasa-maintenance-wordpress-surabaya"
A1["fokus"] = "jasa maintenance wordpress"
A1["seo_title"] = "Jasa Maintenance WordPress Surabaya | Cek Bulanan"
A1["meta"] = "Jasa maintenance WordPress di Surabaya: update plugin, backup, keamanan, dan kecepatan. Ini yang wajib dicek tiap bulan sebelum situs bermasalah."
A1["kategori"] = [23, 330]
A1["label"] = "MAINTENANCE WEBSITE"
A1["isi"] = """<p>Situs yang tayang bulan lalu belum tentu sehat hari ini. WordPress, tema, dan pluginnya dirilis ulang terus, sementara plugin yang telat diperbarui menyimpan celah keamanan yang sudah dibahas terbuka di internet. Perubahannya tidak kelihatan dari halaman depan, jadi pemilik bisnis biasanya baru sadar ketika ada pelanggan mengeluh situsnya tidak bisa dibuka, atau ketika formulir pemesanan tidak pernah sampai ke email.</p>
<p>Maintenance bekerja di titik itu: menjaga situs tetap hidup dan cepat. Bukan menambah halaman, bukan mengganti desain.</p>
<h2>Masalah yang paling sering muncul</h2>
<p>Dari situs klien yang kami tangani, penyebabnya berulang dan hampir selalu sama.</p>
<ul>
<li>Plugin tidak diperbarui berbulan-bulan. Sebagian pembaruan menambal celah yang sudah dipublikasikan, jadi menundanya sama dengan membiarkan pintu samping terbuka.</li>
<li>Formulir kontak berhenti mengirim email setelah hosting mengubah konfigurasi pengiriman. Situsnya tampak normal, tapi tidak ada satu pun pesan yang sampai ke pemilik bisnis.</li>
<li>Sertifikat SSL kedaluwarsa tengah malam. Browser menampilkan peringatan "tidak aman", dan pengunjung menutup tab sebelum membaca apa pun.</li>
<li>Backup berhenti jalan karena ruang hosting penuh. Baru ketahuan saat situs harus dipulihkan, dan saat itu semua sudah terlambat.</li>
<li>Situs melambat setelah satu plugin baru dipasang. Skor <a href="https://studiosoft.id/core-web-vitals-kecepatan-website-2026/">Core Web Vitals</a> turun, dan penurunan itu terasa di peringkat pencarian sekaligus di tingkat pembelian.</li>
</ul>
<h2>Checklist bulanan</h2>
<p>Daftar berikut bisa dipakai sebagai acuan, entah dikerjakan sendiri atau diserahkan ke penyedia jasa.</p>
<h3>Salinan data</h3>
<p>Backup penuh mencakup file dan database, disimpan di luar hosting, dan sesekali diuji pulih. Backup yang belum pernah dicoba dipulihkan belum layak disebut backup.</p>
<h3>Pembaruan</h3>
<p>Inti WordPress, tema, dan plugin. Untuk situs yang sudah ramai, pembaruan dikerjakan di salinan dulu, bukan langsung di situs yang sedang dipakai jualan.</p>
<h3>Pemeriksaan keamanan</h3>
<p>Cek file baru di folder unggahan, akun pengguna yang tidak dikenal, dan percobaan login gagal yang berulang. Artikel atau halaman yang tidak pernah Anda tulis adalah tanda paling awal.</p>
<h3>Kecepatan</h3>
<p>Ukuran gambar, plugin yang tidak terpakai, dan skor Core Web Vitals. Gambar yang belum dikompres biasanya penyumbang terbesar, dan memperbaikinya tidak butuh biaya tambahan.</p>
<h3>Uptime dan formulir</h3>
<p>Pemantauan supaya situs mati tidak baru diketahui dari keluhan pelanggan. Formulir dites kirim sekali sebulan, termasuk notifikasi email atau WhatsApp-nya.</p>
<h3>Masa berlaku</h3>
<p>Domain dan SSL. Keduanya punya tanggal jatuh tempo, dan keduanya bisa dicek dalam dua menit.</p>
<h2>Tanda situs Anda sudah perlu ditangani</h2>
<p>Halaman admin terasa lambat dibuka. Muncul halaman yang tidak Anda tulis. Ada peringatan keamanan dari plugin atau dari Google Search Console. Trafik turun mendadak padahal tidak ada perubahan apa pun. Harga dan stok di halaman tidak lagi cocok dengan kenyataan. Salah satu saja sudah cukup untuk memeriksa lebih dalam bulan ini.</p>
<h2>Dikerjakan sendiri atau pakai jasa maintenance?</h2>
<p>Kalau Anda punya dua sampai tiga jam sebulan dan mau belajar, checklist di atas bisa dikerjakan sendiri. Yang jadi masalah bukan pekerjaannya, tapi konsistensinya. Pembaruan yang tertunda sebulan biasanya jadi tertunda tiga bulan, dan di titik itu risikonya sudah berubah. Untuk situs yang sudah menghasilkan, biaya satu hari situs mati umumnya lebih besar daripada biaya perawatan sebulan.</p>
<h2>Yang kami kerjakan</h2>
<p>Kami menangani pembaruan, backup, pemantauan uptime, kompresi gambar, dan pembersihan file yang tidak terpakai, lalu mengirim laporan singkat tiap bulan. Bentuk laporannya bisa Anda lihat di <a href="https://studiosoft.id/layanan-di-surabaya-yang-memberikan-laporan-berkala-kenali-studiosoft/">halaman ini</a>. Kalau situsnya belum ada sama sekali, kami juga mengerjakan <a href="https://studiosoft.id/jasa-pembuatan-website-profesional-untuk-usaha-jasa-di-surabaya/">pembuatan website</a> dari nol.</p>
<h2>Soal biaya</h2>
<p>Harga jasa maintenance WordPress tidak pernah seragam karena isi pekerjaannya berbeda. Yang menentukan: jumlah halaman, jumlah plugin, ada tidaknya toko online, dan seberapa sering pemeriksaan dilakukan. Situs profil dengan 5 sampai 10 halaman biasanya cukup satu pemeriksaan bulanan. Toko online perlu pemantauan lebih rapat karena transaksi yang berhenti berarti kerugian langsung pada hari itu juga. Gambaran biaya website secara keseluruhan ada di artikel <a href="https://studiosoft.id/berapa-biaya-jasa-pembuatan-website/">berapa biaya jasa pembuatan website</a>.</p>
<h2>Lima pertanyaan untuk calon penyedia</h2>
<p>Sebelum menyerahkan akses, tanyakan lima hal ini. Backup disimpan di mana dan berapa lama disimpan. Apakah Anda tetap memegang akses admin penuh. Apa yang terjadi kalau pembaruan justru merusak situs, dan apakah ada salinan untuk kembali. Laporan seperti apa yang Anda terima setiap bulan. Terakhir, kalau kerja sama berhenti, apakah kredensial dan data tetap menjadi milik Anda. Jawaban yang berbelit pada pertanyaan kelima biasanya cukup untuk mempertimbangkan pilihan lain.</p>
<h2>Contoh temuan dari pemeriksaan rutin</h2>
<p>Tiga hal ini kami temukan berulang kali di situs yang baru pertama kali dirawat.</p>
<p>Plugin yang sudah ditinggalkan pembuatnya. Pluginnya masih jalan, tapi terakhir diperbarui dua tahun lalu. Selama tidak ada celah yang dipublikasikan, situs aman. Begitu ada, tidak akan ada penambalnya. Plugin seperti ini yang perlu dicari penggantinya, bukan sekadar dipaksa jalan.</p>
<p>Gambar berukuran besar. Satu foto langsung dari ponsel bisa 4 MB, sementara halaman hanya butuh versi 150 sampai 200 KB. Sepuluh gambar seperti itu sudah cukup membuat halaman terasa berat di jaringan seluler.</p>
<p>Folder unggahan yang menumpuk. Akumulasi file dari plugin yang sudah dihapus, tema lama, dan gambar yang tidak terpakai lagi bisa memenuhi kuota hosting dan mematikan proses backup otomatis. Pembersihannya sekalian dengan pemeriksaan bulanan.</p>
<h2>Yang tidak termasuk maintenance</h2>
<p>Maintenance menjaga situs tetap berjalan, bukan mengubahnya. Menambah halaman baru, mengganti desain, menulis artikel, dan mengerjakan SEO adalah pekerjaan terpisah dengan lingkup dan waktu yang berbeda. Menyamakan keduanya biasanya membuat perawatan bulanan terlewat, karena perhatian habis di pekerjaan pengembangan.</p>
<h2>Baca juga</h2>
<p>Kalau situs Anda mulai melambat, penyebabnya sering ada di gambar dan plugin yang menumpuk. Pembahasan lengkapnya ada di <a href="https://studiosoft.id/jasa-pembuatan-maintenance-website-wordpress-surabaya/">panduan pembuatan dan perawatan website WordPress</a>.</p>
<h2>Kesimpulan</h2>
<p>Maintenance bukan pekerjaan yang terlihat hasilnya di halaman depan. Justru karena itu sering ditunda. Yang bisa dipegang: situs yang dirawat rutin jarang mati mendadak, dan saat mati, pemulihannya hitungan menit, bukan hitungan hari.</p>
<h2>Pertanyaan yang sering muncul</h2>
<h3>Seberapa sering situs WordPress perlu diperiksa?</h3><p>Sekali sebulan untuk situs profil, dan dua sampai empat kali sebulan untuk toko online atau situs yang menerima pesanan lewat formulir. Plugin keamanan perlu dicek lebih sering karena pembaruan penambal celah biasanya dirilis mendesak.</p>
<h3>Apakah maintenance harus pakai jasa, atau bisa sendiri?</h3><p>Bisa sendiri kalau Anda sanggup konsisten. Yang dijual jasa maintenance bukan kemampuan teknisnya, tapi jaminan bahwa pekerjaan itu benar-benar terjadi setiap bulan, termasuk saat Anda sedang sibuk.</p>
<h3>Apa risikonya kalau plugin tidak pernah diperbarui?</h3><p>Dua risiko utamanya: celah keamanan yang sudah diketahui publik, dan ketidakcocokan versi yang membuat situs error setelah hosting memperbarui versi PHP. Keduanya bisa menghentikan situs tanpa peringatan.</p>
<h3>Berapa lama proses pemulihan kalau situs rusak?</h3><p>Kalau backup terbaru ada dan sudah diuji, pemulihan biasanya di bawah satu jam. Tanpa backup yang layak, pemulihan bisa berhari-hari atau tidak mungkin sama sekali.</p>
<h3>Apakah maintenance termasuk perbaikan kalau situs sudah kena hack?</h3><p>Pembersihan setelah penyusupan dikerjakan terpisah, karena butuh penelusuran file dan penutupan celah masuknya. Setelah bersih, barulah rutinitas maintenance bulanan bisa mencegah kejadian yang sama terulang.</p>"""
A1["faq_pos"] = A1["isi"].count("<h3>")

# ─────────────────────────── ARTIKEL 2
A2 = {}
A2["judul"] = "Jasa Google Ads Surabaya: Biar Budget Iklan Tidak Habis Sia-Sia"
A2["slug"] = "jasa-google-ads-surabaya"
A2["fokus"] = "jasa google ads surabaya"
A2["seo_title"] = "Jasa Google Ads Surabaya | Kelola Kampanye Iklan"
A2["meta"] = "Jasa Google Ads Surabaya untuk bisnis lokal: cara menyusun kampanye, memilih kata kunci, dan mengukur hasil supaya budget iklan tidak habis tanpa prospek."
A2["kategori"] = [26]
A2["label"] = "GOOGLE ADS"
A2["isi"] = """<p>Dua bisnis bisa memasang iklan Google dengan budget yang sama dan hasilnya jauh berbeda. Yang satu mendapat pesanan setiap minggu, yang satu lagi hanya melihat laporan kunjungan naik tanpa satu pun telepon masuk. Bedanya jarang ada di besarnya budget. Bedanya ada di susunan kampanye, pilihan kata kunci, dan cara mengukur hasil.</p>
<p>Halaman ini membahas ketiganya, plus hal-hal yang sebaiknya Anda tanyakan sebelum menyerahkan anggaran iklan ke pihak lain.</p>
<h2>Kenapa budget iklan habis tanpa prospek</h2>
<p>Dari akun-akun yang kami periksa, penyebabnya mengerucut ke tiga hal.</p>
<ul>
<li>Kata kunci terlalu luas. Kata seperti "jasa digital" menelan klik dari orang yang mencari hal lain sama sekali, dan Anda tetap membayar setiap klik.</li>
<li>Kata kunci pencarian tidak relevan tidak pernah diblokir. Tanpa daftar negatif, iklan Anda muncul untuk pencarian seperti "gratis", "lowongan", atau nama pesaing, dan anggaran habis di situ.</li>
<li>Halaman pendaratan tidak nyambung dengan iklannya. Iklan menjanjikan konsultasi gratis, tapi halamannya berisi daftar artikel. Pengunjung menutup halaman dalam detik pertama, dan Google menilai kualitas itu.</li>
</ul>
<h2>Susunan kampanye yang sehat</h2>
<p>Empat aturan yang kami pakai.</p>
<p>Pisahkan jenis kampanye berdasarkan niat. Kampanye pencarian menangkap orang yang sudah tahu apa yang dicari, jadi hampir selalu menghasilkan. Kampanye tampilan dan Performance Max lebih cocok untuk membangun ingatan terhadap merek, bukan untuk mengejar prospek pertama.</p>
<p>Pisahkan wilayah. Untuk bisnis yang melayani Surabaya dan sekitarnya, kampanye radius kota biasanya punya biaya per prospek lebih murah daripada kampanye nasional, karena pesaingnya lebih sedikit dan niatnya lebih jelas.</p>
<p>Satu grup iklan, satu tema. Kalau kata kunci "jasa website" dan "jasa iklan" disatukan dalam satu grup, teks iklannya jadi generik dan rasio kliknya turun.</p>
<p>Sebut layanan dan kota di teks iklan. Orang Surabaya mencari penyedia yang jelas melayani Surabaya. Menyebutnya menaikkan rasio klik tanpa menambah biaya.</p>
<h2>Pelacakan konversi: bagian yang paling sering dilewatkan</h2>
<p>Ini penyebab paling umum dari iklan yang tampak "jalan tapi tidak jelas hasilnya". Tanpa pelacakan, Anda hanya tahu jumlah klik, bukan jumlah prospek. Kata kunci yang menghasilkan dan kata kunci yang hanya menghabiskan uang akan terlihat sama.</p>
<p>Minimal tiga hal yang perlu ditandai sebagai konversi: klik tombol WhatsApp, pengiriman formulir, dan klik nomor telepon. Setelah itu akun baru bisa menjawab pertanyaan yang sebenarnya penting, yaitu kata kunci mana yang layak ditambah anggarannya.</p>
<h2>Menetapkan anggaran dengan hitungan, bukan perasaan</h2>
<p>Cara paling sederhana: tentukan dulu nilai satu prospek bagi bisnis Anda, lalu tentukan biaya per prospek yang masih masuk akal, dan baru hitung anggaran harian dari situ. Angka ini bisa direvisi setelah data dua sampai empat minggu terkumpul.</p>
<p>Bulan pertama sebaiknya dipakai untuk mengumpulkan data, bukan mengejar volume. Setelah tahu kata kunci mana yang bekerja, anggarannya bisa digeser ke sana dan hasilnya naik tanpa menambah total belanja.</p>
<h2>Pekerjaan mingguan yang seharusnya terjadi</h2>
<p>Akun iklan bukan mesin yang dinyalakan lalu ditinggal. Setiap minggu ada yang perlu dibaca: laporan istilah pencarian untuk menemukan kata negatif baru, kata kunci yang menelan biaya tanpa konversi, teks iklan mana yang paling sering diklik, halaman pendaratan mana yang benar-benar menghasilkan prospek, dan apakah ada penawaran yang perlu diganti.</p>
<p>Kalau tidak ada yang berubah di akun selama sebulan, kemungkinan besar tidak ada yang mengelolanya.</p>
<h2>Lima pertanyaan sebelum memilih penyedia</h2>
<p>Siapa yang memegang akun. Akun iklan sebaiknya dibuat atas nama bisnis Anda, bukan agensi, supaya riwayat dan datanya tetap milik Anda kalau kerja sama berhenti. Tanyakan laporan seperti apa yang diterima setiap bulan, apakah laporan itu menyebut biaya per prospek. Tanyakan siapa yang mengerjakan pengecekan mingguan. Tanyakan apa yang terjadi pada kampanye kalau kerja sama berakhir. Dan tanyakan apakah mereka bersedia menunjukkan bukti hasil dari akun klien lain.</p>
<p>Dua hal yang layak membuat Anda berhati-hati: janji peringkat satu dan jaminan jumlah prospek. Tidak ada yang bisa menjamin keduanya, karena lelang iklan berjalan waktu nyata dan pesaing ikut menentukan harga klik.</p>
<h2>Yang kami kerjakan</h2>
<p>Kami menyusun struktur kampanye, meriset kata kunci termasuk daftar negatif, memasang pelacakan konversi, menulis teks iklan, dan memeriksa akun setiap minggu. Akun iklan tetap kami buat atas nama bisnis Anda. Iklan bekerja lebih baik kalau <a href="https://studiosoft.id/jasa-pembuatan-website-profesional-untuk-usaha-jasa-di-surabaya/">halaman pendaratannya</a> juga layak, karena itu dua hal ini kami tangani bersamaan.</p>
<h2>Hubungannya dengan SEO</h2>
<p>Iklan memberi hasil lebih cepat, SEO memberi hasil yang menumpuk. Iklan berhenti pada hari anggarannya berhenti, sementara peringkat organik tetap bekerja tanpa biaya per klik. Untuk bisnis lokal, kombinasi keduanya biasanya lebih murah daripada hanya mengandalkan salah satu. Perbandingan lengkapnya ada di <a href="https://studiosoft.id/jasa-seo-professional/">halaman jasa SEO</a> kami.</p>
<h2>Verifikasi pengiklan</h2>
<p>Google mewajibkan pengiklan memverifikasi identitas sebelum iklan bisa tayang dalam skala luas. Prosesnya mencakup data bisnis dan dokumen pendukung. Verifikasi dipegang akun pengiklan, jadi pemilik bisnis perlu menyiapkan dokumennya sendiri; agensi bisa membantu prosesnya, tapi tidak bisa menggantikan.</p>
<h2>Kesalahan yang membuat akun dibatasi</h2>
<p>Beberapa hal yang tampak sepele bisa membuat akun ditangguhkan: klaim berlebihan seperti jaminan hasil di teks iklan, halaman pendaratan yang tidak memuat informasi kontak, serta penawaran yang tidak cocok dengan isi halaman. Penangguhan biasanya datang tanpa peringatan lebih dulu, dan pengajuan bandingnya butuh waktu. Kebiasaan kecil yang menghindarkan Anda dari masalah itu: pastikan teks iklan dan halaman pendaratan menceritakan hal yang sama.</p>
<h2>Laporan bulanan yang layak dibaca</h2>
<p>Laporan yang berguna menjawab empat pertanyaan: berapa biaya per prospek bulan ini, kata kunci mana yang paling efisien, halaman pendaratan mana yang menghasilkan, dan apa yang akan diubah bulan depan. Tangkapan layar berisi jumlah klik tanpa angka prospek biasanya menandakan pengelolanya tidak mengukur konversi.</p>
<h2>Baca juga</h2>
<p>Kalau Anda sedang membandingkan beberapa penyedia, cara menilai agensi digital dijelaskan di <a href="https://studiosoft.id/digital-marketing-agency-surabaya-cara-memilih-partner/">cara memilih partner digital marketing</a>.</p>
<h2>Kesimpulan</h2>
<p>Iklan Google bukan saluran yang bisa dijalankan tanpa dirawat. Yang membuatnya menguntungkan bukan besarnya anggaran, tapi apakah setiap rupiah bisa ditelusuri sampai ke prospek. Kalau tidak bisa ditelusuri, itu bukan iklannya yang bermasalah, tapi cara mengukurnya.</p>
<h2>Pertanyaan yang sering muncul</h2>
<h3>Berapa budget minimum untuk Google Ads?</h3><p>Tidak ada angka baku. Yang menentukan anggaran adalah biaya klik di industri Anda dan berapa prospek yang ingin didapat. Untuk bisnis lokal, anggaran kecil masih bisa jalan asalkan kata kuncinya spesifik dan wilayahnya dibatasi.</p>
<h3>Berapa lama sampai terlihat hasilnya?</h3><p>Kunjungan biasanya masuk di hari pertama kampanye tayang, tapi data yang cukup untuk menilai kata kunci mana yang bekerja umumnya baru terkumpul setelah dua sampai empat minggu.</p>
<h3>Apakah iklan Google bisa menjamin penjualan?</h3><p>Tidak. Iklan membeli kunjungan yang sesuai niat pencarian; keputusan membeli tetap ada di pengunjung. Yang bisa dijaga adalah biaya per prospek dan kualitas halaman pendaratan.</p>
<h3>Apakah akun iklan harus atas nama saya?</h3><p>Ya, dan itu sebaiknya disepakati di awal. Akun atas nama bisnis Anda menyimpan riwayat data yang berguna, dan Anda tidak perlu memulai dari nol kalau ganti penyedia.</p>
<h3>Apakah bisa jalan bareng SEO?</h3><p>Bisa, dan justru sering lebih menguntungkan. Iklan mengisi trafik sementara SEO berjalan, lalu anggaran iklan bisa dialihkan ke kata kunci yang paling menguntungkan setelah datanya terkumpul.</p>"""

ARTIKEL = [A1, A2]


# ─────────────────────────── gambar
def buat_gambar(a, nama):
    W, H = 1200, 630
    img = Image.new("RGB", (W, H), (30, 64, 175))
    d = ImageDraw.Draw(img)
    fd = os.path.expanduser("~/.fonts")
    fb = lambda s: ImageFont.truetype(os.path.join(fd, "Poppins-Bold.ttf"), s)
    fr = lambda s: ImageFont.truetype(os.path.join(fd, "Poppins-Regular.ttf"), s)
    d.text((60, 40), "STUDIOSOFT", font=fb(20), fill=(219, 234, 254))
    d.text((60, 75), a["label"], font=fb(20), fill=(255, 255, 255))
    d.rectangle([60, 120, 300, 124], fill=(219, 234, 254))
    judul = a["judul"].replace(" Surabaya:", ":").replace(": Yang", ":")
    for size in (52, 46, 40, 34):
        f = fb(size)
        kata, baris, buf = judul.split(), [], ""
        for w in kata:
            t = (buf + " " + w).strip()
            if f.getbbox(t)[2] <= W - 120:
                buf = t
            else:
                baris.append(buf); buf = w
        if buf:
            baris.append(buf)
        if len(baris) <= 3:
            break
    tinggi = len(baris) * (size + 14)
    y = (H - tinggi) // 2 - 10
    for b in baris:
        d.text((60, y), b, font=f, fill=(255, 255, 255))
        y += size + 14
    d.rectangle([60, y + 18, 400, y + 22], fill=(255, 255, 255))
    d.text((60, H - 50), "studiosoft.id", font=fr(24), fill=(219, 234, 254))
    d.text((W - 60, H - 50), a["label"].title(), font=fr(24), fill=(219, 234, 254), anchor="rt")
    p = os.path.join(OUT, nama)
    img.save(p, "WEBP", quality=88)
    return p


def api(path, method="GET", data=None, raw=None, ctype=None, extra=None):
    h = {"Authorization": f"Basic {AUTH}", "User-Agent": "Mozilla/5.0"}
    body = None
    if data is not None:
        body = json.dumps(data).encode(); h["Content-Type"] = "application/json"
    elif raw is not None:
        body = raw; h["Content-Type"] = ctype
    if extra:
        h.update(extra)
    r = urllib.request.Request(S + path, data=body, headers=h, method=method)
    for _ in range(3):
        try:
            with urllib.request.urlopen(r, timeout=180) as x:
                return x.status, json.loads(x.read())
        except urllib.error.HTTPError as e:
            return e.code, e.read().decode()[:300]
        except Exception:
            time.sleep(5)
    return 0, "gagal"


def jumlah_kata(html_isi):
    t = html.unescape(re.sub(r"<[^>]+>", " ", html_isi))
    return len([w for w in re.split(r"\s+", t) if w])


print("=== PERSIAPAN ===")
for a in ARTIKEL:
    a["gambar"] = buat_gambar(a, a["slug"] + ".webp")
    a["kata"] = jumlah_kata(a["isi"])
    print(f"  {a['slug']:<38} {a['kata']} kata | gambar {os.path.basename(a['gambar'])}")

if "--apply" not in sys.argv:
    print("\n(dry run — pakai --apply untuk unggah + jadwalkan)")
    sys.exit(0)

sekarang = datetime.now(WIB)
slot = [sekarang.replace(hour=19, minute=0, second=0, microsecond=0),
        (sekarang + timedelta(days=1)).replace(hour=19, minute=0, second=0, microsecond=0)]

for a, tayang in zip(ARTIKEL, slot):
    print(f"\n=== {a['slug']} → {tayang.strftime('%d %b %H:%M')} WIB ===")
    # 1. unggah gambar
    img = open(a["gambar"], "rb").read()
    st, med = api("/wp-json/wp/v2/media", "POST", raw=img, ctype="image/webp",
                  extra={"Content-Disposition": 'attachment; filename="%s"' % os.path.basename(a["gambar"])})
    if st not in (201, 200):
        print("   GAGAL unggah gambar:", st, str(med)[:150]); continue
    mid = med["id"]
    api(f"/wp-json/wp/v2/media/{mid}", "POST", {"alt_text": a["judul"], "title": a["judul"]})
    print(f"   gambar → media #{mid}")
    # 2. isi lengkap + faq schema
    faq = []
    for m in re.finditer(r"<h3>(.*?)</h3><p>(.*?)</p>", a["isi"], re.S):
        faq.append((html.unescape(re.sub(r"<[^>]+>", "", m.group(1))), html.unescape(re.sub(r"<[^>]+>", "", m.group(2)))))
    isi = a["isi"] + "\n" + schema(faq) + "\n" + CTA
    payload = {"title": a["judul"], "slug": a["slug"], "content": isi, "status": "future",
               "date": tayang.strftime("%Y-%m-%dT%H:%M:%S"), "categories": a["kategori"],
               "featured_media": mid,
               "meta": {"_yoast_wpseo_focuskw": a["fokus"], "_yoast_wpseo_title": a["seo_title"],
                        "_yoast_wpseo_metadesc": a["meta"]}}
    st, p = api("/wp-json/wp/v2/posts", "POST", payload)
    if st not in (201, 200):
        print("   GAGAL buat artikel:", st, str(p)[:250]); continue
    pid = p["id"]
    print(f"   artikel #{pid} dibuat | status {p.get('status')} | {p.get('date')}")
    # 3. verifikasi
    st, cek = api(f"/wp-json/wp/v2/posts/{pid}?context=edit&_fields=status,date,slug,meta,featured_media,content,link")
    punya_meta = [k for k in ("_yoast_wpseo_focuskw", "_yoast_wpseo_metadesc") if cek.get("meta", {}).get(k)]
    print(f"   verifikasi: status={cek.get('status')} tayang={cek.get('date')} gambar={cek.get('featured_media')} "
          f"meta tersimpan={punya_meta} | kata={jumlah_kata(cek.get('content',{}).get('raw',''))} | CTA={'ya' if 'ss-cta-wa' in cek.get('content',{}).get('raw','') else 'TIDAK'} "
          f"| schema={'ya' if 'FAQPage' in cek.get('content',{}).get('raw','') else 'TIDAK'}")
    if not punya_meta:
        st2, _ = api(f"/wp-json/wp/v2/posts/{pid}", "POST", {"meta": payload["meta"]})
        st3, cek2 = api(f"/wp-json/wp/v2/posts/{pid}?context=edit&_fields=meta")
        print(f"   ulang simpan meta: {st2} → {list(cek2.get('meta', {}).keys())[:3]}")
