{"id":324,"date":"2016-09-22T02:19:07","date_gmt":"2016-09-22T02:19:07","guid":{"rendered":"http:\/\/arpus.acehprov.go.id\/?page_id=324"},"modified":"2026-07-15T15:00:39","modified_gmt":"2026-07-15T08:00:39","slug":"katalog-online","status":"publish","type":"page","link":"https:\/\/arpus.acehprov.go.id\/?page_id=324","title":{"rendered":"OPAC"},"content":{"rendered":"\n<script src=\"https:\/\/cdn.tailwindcss.com\"><\/script>\n\n<div class=\"opac-wrapper antialiased bg-gray-50 min-h-screen py-8 px-4 sm:px-6 lg:px-8 font-sans\" style=\"color: #1f2937;\">\n    <div class=\"max-w-4xl mx-auto\">\n        \n        <div class=\"text-center mb-10 font-sans\">\n            <h1 class=\"text-3xl sm:text-4xl font-extrabold text-gray-900 tracking-tight mb-2\" style=\"margin-bottom: 8px; font-weight: 800;\">\n                &#x1f4d6; Online Public Access Catalog (OPAC)\n            <\/h1>\n            <p class=\"text-sm sm:text-base text-gray-600\" style=\"color: #4b5563;\">Cari katalog buku secara online, cepat, dan mudah.<\/p>\n        <\/div>\n\n        <div class=\"bg-white rounded-2xl shadow-sm p-4 sm:p-6 mb-6 border border-gray-100 font-sans\" style=\"background: #ffffff; border-radius: 1rem; padding: 1.5rem; border: 1px solid #f3f4f6; margin-bottom: 1.5rem; box-shadow: 0 1px 3px rgba(0,0,0,0.05);\">\n            <div class=\"flex flex-col sm:flex-row gap-3 items-stretch mb-4\" style=\"display: flex; gap: 12px; margin-bottom: 1rem;\">\n                <div class=\"relative flex-grow\" style=\"position: relative; flex-grow: 1; min-width: 0;\">\n                    <span class=\"absolute inset-y-0 left-0 flex items-center pl-3 pointer-events-none text-gray-400\" style=\"position: absolute; left: 12px; top: 50%; transform: translateY(-50%); z-index: 10; font-size: 16px;\">\n                        &#x1f50d;\n                    <\/span>\n                    <input type=\"text\" id=\"inputCari\" placeholder=\"Ketik judul buku atau penulis...\" class=\"w-full border border-gray-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:border-indigo-500 text-sm outline-none transition-all\" style=\"width: 100% !important; padding: 12px 12px 12px 40px !important; border: 1px solid #e5e7eb !important; border-radius: 0.75rem !important; font-size: 14px !important; height: 48px !important; box-sizing: border-box !important;\">\n                <\/div>\n                <button type=\"button\" id=\"tombolCari\" class=\"bg-indigo-600 hover:bg-indigo-700 text-white font-medium px-6 py-3 rounded-xl text-sm transition-colors shadow-sm\" style=\"background-color: #4f46e5 !important; color: #ffffff !important; font-weight: 500 !important; padding: 0 24px !important; border-radius: 0.75rem !important; font-size: 14px !important; height: 48px !important; cursor: pointer !important; border: none !important; white-space: nowrap;\">\n                    Cari Buku\n                <\/button>\n            <\/div>\n\n            <div class=\"flex border-b border-gray-200\" style=\"display: flex; border-bottom: 1px solid #e5e7eb;\">\n                <button type=\"button\" id=\"tabDigital\" class=\"flex-1 py-2.5 text-sm font-semibold text-indigo-600 border-b-2 border-indigo-600 focus:outline-none transition-all\" style=\"flex: 1; padding: 10px 0; font-size: 14px; font-weight: 600; text-align: center; border: none; background: transparent; cursor: pointer; border-bottom: 2px solid #4f46e5; color: #4f46e5;\">\n                    &#x1f4f1; iPustaka (Digital)\n                <\/button>\n                <button type=\"button\" id=\"tabFisik\" class=\"flex-1 py-2.5 text-sm font-medium text-gray-500 hover:text-gray-700 focus:outline-none transition-all\" style=\"flex: 1; padding: 10px 0; font-size: 14px; font-weight: 500; text-align: center; border: none; background: transparent; cursor: pointer; border-bottom: 2px solid transparent; color: #6b7280;\">\n                    &#x1f4da; Perpustakaan Fisik\n                <\/button>\n            <\/div>\n        <\/div>\n\n        <div id=\"wrapperRekomendasi\" class=\"mb-8 font-sans\">\n            <h2 id=\"labelGimmick\" class=\"text-base font-bold text-gray-700 mb-4 flex items-center gap-2\" style=\"font-weight: 700; color: #4b5563; margin-bottom: 16px;\">\n                Memuat rekomendasi buku populer&#8230;\n            <\/h2>\n            <div id=\"gridRekomendasi\" class=\"grid grid-cols-1 sm:grid-cols-2 gap-4\" style=\"display: grid; gap: 16px;\"><\/div>\n        <\/div>\n\n        <div id=\"statusSistem\" class=\"text-center py-6 text-sm text-gray-500 hidden font-sans\" style=\"text-align: center; padding: 1.5rem 0; font-size: 14px; color: #6b7280;\"><\/div>\n\n        <div id=\"gridBuku\" class=\"grid grid-cols-1 sm:grid-cols-2 gap-4 font-sans\" style=\"display: grid; gap: 16px;\"><\/div>\n\n        <div id=\"panelPaginasi\" class=\"flex justify-between items-center mt-8 pt-4 border-t border-gray-200 hidden font-sans\" style=\"display: none; justify-content: space-between; align-items: center; margin-top: 2rem; padding-top: 1rem; border-top: 1px solid #e5e7eb;\">\n            <button type=\"button\" id=\"btnPrev\" style=\"background-color: #ffffff; border: 1px solid #d1d5db; color: #374151; padding: 8px 16px; border-radius: 0.5rem; font-size: 13px; font-weight: 500; cursor: pointer;\">\n                \u2190\n            <\/button>\n            <span id=\"infoHalaman\" style=\"font-size: 13px; color: #4b5563; font-weight: 500;\">\n                Halaman 1 dari 1\n            <\/span>\n            <button type=\"button\" id=\"btnNext\" style=\"background-color: #ffffff; border: 1px solid #d1d5db; color: #374151; padding: 8px 16px; border-radius: 0.5rem; font-size: 13px; font-weight: 500; cursor: pointer;\">\n                \u2192\n            <\/button>\n        <\/div>\n\n    <\/div>\n<\/div>\n\n<div id=\"modalDetail\" class=\"fixed inset-0 bg-black bg-opacity-50 flex items-center justify-center p-4 z-50 hidden font-sans\" style=\"position: fixed; inset: 0; background-color: rgba(0,0,0,0.5); display: none; items-center: center; justify-content: center; padding: 1rem; z-index: 99999;\">\n    <div class=\"bg-white rounded-2xl max-w-2xl w-full max-h-[85vh] overflow-y-auto p-6 shadow-xl relative\" style=\"background: #ffffff; border-radius: 1rem; max-width: 42rem; width: 100%; max-height: 85vh; overflow-y: auto; padding: 1.5rem; box-shadow: 0 20px 25px -5px rgba(0,0,0,0.1); position: relative;\">\n        <button type=\"button\" id=\"tombolTutupSilang\" style=\"position: absolute; top: 16px; right: 16px; background: none; border: none; font-size: 24px; cursor: pointer; color: #9ca3af;\">\u00d7<\/button>\n        <div id=\"kontenModal\"><\/div>\n        <div style=\"margin-top: 1.5rem; border-top: 1px solid #f3f4f6; padding-top: 1rem; text-align: right;\">\n            <button type=\"button\" id=\"tombolTutupBawah\" style=\"background-color: #ef4444; color: white; padding: 8px 20px; border-radius: 0.5rem; font-size: 14px; cursor: pointer; border: none; font-weight: 500;\">Tutup Detail<\/button>\n        <\/div>\n    <\/div>\n<\/div>\n\n<script>\ndocument.addEventListener(\"DOMContentLoaded\", function() {\n    const inputCari = document.getElementById('inputCari');\n    const tombolCari = document.getElementById('tombolCari');\n    const statusSistem = document.getElementById('statusSistem');\n    const gridBuku = document.getElementById('gridBuku');\n    \n    const wrapperRekomendasi = document.getElementById('wrapperRekomendasi');\n    const labelGimmick = document.getElementById('labelGimmick');\n    const gridRekomendasi = document.getElementById('gridRekomendasi');\n\n    const panelPaginasi = document.getElementById('panelPaginasi');\n    const btnPrev = document.getElementById('btnPrev');\n    const btnNext = document.getElementById('btnNext');\n    const infoHalaman = document.getElementById('infoHalaman');\n\n    const modalDetail = document.getElementById('modalDetail');\n    const kontenModal = document.getElementById('kontenModal');\n    const tombolTutupSilang = document.getElementById('tombolTutupSilang');\n    const tombolTutupBawah = document.getElementById('tombolTutupBawah');\n\n    const tabFisik = document.getElementById('tabFisik');\n    const tabDigital = document.getElementById('tabDigital');\n\n    let semuaBuku = [];\n    let bukuRekomendasi = [];\n    let halamanSekarang = 1;\n    const itemPerHalaman = 10;\n    \n    let tabAktif = 'digital'; \n\n    function escapeHtml(teks) {\n        if (teks === null || teks === undefined) return '';\n        return String(teks)\n            .replace(\/&\/g, '&amp;')\n            .replace(\/<\/g, '&lt;')\n            .replace(\/>\/g, '&gt;')\n            .replace(\/\"\/g, '&quot;')\n            .replace(\/'\/g, '&#039;');\n    }\n\n    tabFisik.addEventListener('click', function() {\n        if (tabAktif === 'fisik') return;\n        tabAktif = 'fisik';\n        \n        tabFisik.className = \"flex-1 py-2.5 text-sm font-semibold text-indigo-600 border-b-2 border-indigo-600 focus:outline-none transition-all\";\n        tabFisik.style.borderBottom = \"2px solid #4f46e5\";\n        tabFisik.style.color = \"#4f46e5\";\n        \n        tabDigital.className = \"flex-1 py-2.5 text-sm font-medium text-gray-500 hover:text-gray-700 focus:outline-none transition-all\";\n        tabDigital.style.borderBottom = \"2px solid transparent\";\n        tabDigital.style.color = \"#6b7280\";\n\n        gridBuku.innerHTML = '';\n        panelPaginasi.style.display = \"none\";\n        statusSistem.style.display = \"none\";\n        statusSistem.classList.add('hidden');\n        inputCari.value = '';\n        muatRekomendasiBuku();\n    });\n\n    tabDigital.addEventListener('click', function() {\n        if (tabAktif === 'digital') return;\n        tabAktif = 'digital';\n\n        tabDigital.className = \"flex-1 py-2.5 text-sm font-semibold text-indigo-600 border-b-2 border-indigo-600 focus:outline-none transition-all\";\n        tabDigital.style.borderBottom = \"2px solid #4f46e5\";\n        tabDigital.style.color = \"#4f46e5\";\n        \n        tabFisik.className = \"flex-1 py-2.5 text-sm font-medium text-gray-500 hover:text-gray-700 focus:outline-none transition-all\";\n        tabFisik.style.borderBottom = \"2px solid transparent\";\n        tabFisik.style.color = \"#6b7280\";\n\n        gridBuku.innerHTML = '';\n        panelPaginasi.style.display = \"none\";\n        statusSistem.style.display = \"none\";\n        statusSistem.classList.add('hidden');\n        inputCari.value = '';\n        muatRekomendasiBuku();\n    });\n\n    async function muatRekomendasiBuku() {\n        wrapperRekomendasi.style.display = 'block';\n        labelGimmick.innerText = \"Memuat rekomendasi buku populer...\";\n        gridRekomendasi.innerHTML = '';\n\n        if (tabAktif === 'fisik') {\n            const acakOffset = Math.floor(Math.random() * 50);\n            const urlRekomendasi = `\/?opac_proxy=rekomendasi_fisik&offset=${acakOffset}`;\n            \n            try {\n                const response = await fetch(urlRekomendasi, { method: 'GET' });\n                if (response.ok) {\n                    const resJson = await response.json();\n                    if (resJson && resJson.length > 0) {\n                        labelGimmick.innerText = \"Rekomendasi Buku Populer Hari Ini:\";\n                        bukuRekomendasi = resJson;\n                        tampilkanRekomendasi();\n                    } else {\n                        wrapperRekomendasi.style.display = 'none';\n                    }\n                } else {\n                    wrapperRekomendasi.style.display = 'none';\n                }\n            } catch (e) {\n                wrapperRekomendasi.style.display = 'none';\n                console.log(\"Gagal memuat gimmick rekomendasi fisik.\");\n            }\n        } else {\n            const kataKunciGimmick = [\"indonesia\", \"sistem\", \"data\", \"pintar\", \"aplikasi\", \"manajemen\"][Math.floor(Math.random() * 6)];\n            const urlDigitalRec = `\/?opac_proxy=rekomendasi_digital&q=${encodeURIComponent(kataKunciGimmick)}`;\n            \n            try {\n                const response = await fetch(urlDigitalRec, { method: 'GET' });\n                if (response.ok) {\n                    const resJson = await response.json();\n                    if (resJson && resJson.data && resJson.data.length > 0) {\n                        labelGimmick.innerText = \"Rekomendasi E-Book Populer iPustaka:\";\n                        bukuRekomendasi = resJson.data;\n                        tampilkanRekomendasi();\n                    } else {\n                        wrapperRekomendasi.style.display = 'none';\n                    }\n                } else {\n                    wrapperRekomendasi.style.display = 'none';\n                }\n            } catch (e) {\n                wrapperRekomendasi.style.display = 'none';\n                console.log(\"Gagal memuat gimmick rekomendasi digital.\");\n            }\n        }\n    }\n\n    muatRekomendasiBuku();\n\n    let hitungCoverGoogleBooks = 0;\n\n    async function muatCoverDariGoogleBooks(elemId, isbn, className) {\n        try {\n            const isbnBersih = (isbn || '').toString().replace(\/[^0-9Xx]\/g, '');\n            if (!isbnBersih) return;\n\n            \/\/ Fallback format ISBN (10\/13 digit) dan cache sudah ditangani di server (proxy),\n            \/\/ jadi di sini cukup satu kali request saja.\n            const response = await fetch(`\/?opac_proxy=cover_googlebooks&q=${isbnBersih}`);\n            if (!response.ok) return;\n\n            const hasil = await response.json();\n            const thumb = hasil && hasil.thumbnail;\n\n            if (!thumb) return;\n\n            const elemTarget = document.getElementById(elemId);\n            if (!elemTarget) return;\n\n            const imgBaru = document.createElement('img');\n            imgBaru.src = thumb;\n            imgBaru.className = className;\n            imgBaru.onerror = function() { this.style.display = 'none'; };\n            elemTarget.replaceWith(imgBaru);\n        } catch (err) {\n            \/\/ Diamkan saja; cover placeholder tetap ditampilkan jika Google Books gagal\/tidak menemukan sampul.\n        }\n    }\n\n    function buatKomponenCover(id, title, coverUrl, isbn) {\n        if (coverUrl) {\n            return `<img decoding=\"async\" src=\"${escapeHtml(coverUrl)}\" class=\"w-16 h-24 object-cover rounded-xl shadow-sm mr-4 flex-shrink-0\" onerror=\"this.src='https:\/\/placehold.co\/70x100\/e0e0e0\/666666?text=Buku'\">`;\n        }\n        const daftarGradasi = [\n            'from-pink-500 to-rose-600', \n            'from-purple-500 to-indigo-600', \n            'from-blue-500 to-cyan-600', \n            'from-teal-500 to-emerald-600', \n            'from-amber-500 to-orange-600'\n        ];\n        const indeksWarna = (id || 0) % daftarGradasi.length;\n        const warnaPilihan = daftarGradasi[indeksWarna];\n        const potonganJudul = title ? title.substring(0, 2).toUpperCase() : 'BK';\n\n        const isbnCek = (isbn || '').toString().trim();\n        let atributIdCover = '';\n        if (isbnCek && isbnCek !== '-') {\n            const covId = 'gcover-' + (hitungCoverGoogleBooks++);\n            atributIdCover = ` id=\"${covId}\"`;\n            muatCoverDariGoogleBooks(covId, isbnCek, 'w-16 h-24 object-cover rounded-xl shadow-sm mr-4 flex-shrink-0');\n        }\n\n        return `\n            <div${atributIdCover} class=\"w-16 h-24 bg-gradient-to-br ${warnaPilihan} rounded-xl flex flex-col justify-between p-2 text-white mr-4 shadow-sm flex-shrink-0 relative overflow-hidden select-none\">\n                <span class=\"opacity-60 uppercase tracking-wider font-mono block text-right\" style=\"font-size: 8px;\">OPAC<\/span>\n                <span class=\"text-base font-black tracking-tight block text-center my-auto\">${escapeHtml(potonganJudul)}<\/span>\n                <div class=\"absolute bottom-0 left-0 right-0 h-1 bg-black bg-opacity-20\"><\/div>\n            <\/div>\n        `;\n    }\n\n    function buatHTMLCard(book, index, isRekomendasi = false) {\n        const callNo = book.CallNumber ? `Rak: ${book.CallNumber}` : 'Umum';\n        const title = book.Title || 'Judul Tidak Tersedia';\n        const author = book.Author || 'Anonim';\n        const publisher = book.Publisher || '-';\n        const isbn = book.ISBN || '-';\n        \n        const coverImg = buatKomponenCover(book.ID, title, book.CoverURL, book.ISBN);\n        const prefixDataAttr = isRekomendasi ? 'data-rec-index' : 'data-index';\n\n        return `\n            <div class=\"kartu-buku bg-white rounded-2xl shadow-sm p-4 border border-gray-100 flex flex-col justify-between transition-all\" \n                 ${prefixDataAttr}=\"${index}\"\n                 style=\"background: #ffffff; border-radius: 1rem; padding: 1.25rem; border: 1px solid #f3f4f6; box-shadow: 0 1px 3px rgba(0,0,0,0.05); display: flex; flex-direction: column; justify-content: space-between; min-height: 190px; cursor: pointer;\"\n                 onmouseover=\"this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(0,0,0,0.05)';\" \n                 onmouseout=\"this.style.transform='none'; this.style.boxShadow='0 1px 3px rgba(0,0,0,0.05)';\">\n                <div>\n                    <div class=\"flex items-start mb-2\">\n                        ${coverImg}\n                        <div class=\"flex-1 min-w-0\">\n                            <span class=\"inline-block bg-indigo-50 text-indigo-700 text-xs px-2.5 py-0.5 rounded-full font-semibold mb-1\" style=\"background-color: #e0e7ff; color: #4338ca; font-size: 11px; font-weight: 600;\">${escapeHtml(callNo)}<\/span>\n                            <h3 class=\"font-bold text-gray-900 text-sm sm:text-base line-clamp-2\" style=\"font-weight: 700; color: #111827; line-height: 1.4;\">${escapeHtml(title)}<\/h3>\n                        <\/div>\n                    <\/div>\n                    <p class=\"text-xs text-gray-500 mb-2\" style=\"font-size: 12px; color: #6b7280;\">Oleh: <span style=\"font-weight: 500; color: #374151;\">${escapeHtml(author)}<\/span><\/p>\n                <\/div>\n                <div class=\"border-t border-gray-100 pt-2 flex justify-between items-center text-xs text-gray-500\" style=\"border-top: 1px solid #f3f4f6; padding-top: 8px;\">\n                    <div class=\"truncate max-w-[180px]\">Penerbit: <span style=\"font-weight: 500;\">${escapeHtml(publisher)}<\/span><\/div>\n                    <div style=\"font-family: monospace; color: #9ca3af;\">ISBN: ${escapeHtml(isbn)}<\/div>\n                <\/div>\n            <\/div>\n        `;\n    }\n\n    function buatHTMLCardDigital(book, index, isRekomendasi = false) {\n        const title = book.book_title || 'Judul Tidak Tersedia';\n        const author = book.author_name || 'Anonim';\n        const coverImg = book.cover_url ? `<img decoding=\"async\" src=\"${escapeHtml(book.cover_url)}\" class=\"w-16 h-24 object-cover rounded-xl shadow-sm mr-4 flex-shrink-0\" onerror=\"this.src='https:\/\/placehold.co\/70x100\/e0e0e0\/666666?text=Ebook'\">` : buatKomponenCover(index, title, null);\n        const prefixDataAttr = isRekomendasi ? 'data-rec-digital-index' : 'data-digital-index';\n\n        return `\n            <div class=\"kartu-buku bg-white rounded-2xl shadow-sm p-4 border border-gray-100 flex flex-col justify-between transition-all\" \n                 ${prefixDataAttr}=\"${index}\"\n                 style=\"background: #ffffff; border-radius: 1rem; padding: 1.25rem; border: 1px solid #f3f4f6; box-shadow: 0 1px 3px rgba(0,0,0,0.05); display: flex; flex-direction: column; justify-content: space-between; min-height: 190px; cursor: pointer;\"\n                 onmouseover=\"this.style.transform='translateY(-2px)'; this.style.boxShadow='0 4px 12px rgba(0,0,0,0.05)';\" \n                 onmouseout=\"this.style.transform='none'; this.style.boxShadow='0 1px 3px rgba(0,0,0,0.05)';\">\n                <div>\n                    <div class=\"flex items-start mb-2\">\n                        ${coverImg}\n                        <div class=\"flex-1 min-w-0\">\n                            <span class=\"inline-block bg-teal-50 text-teal-700 text-xs px-2.5 py-0.5 rounded-full font-semibold mb-1\" style=\"background-color: #ccfbf1; color: #0f766e; font-size: 11px; font-weight: 600;\">&#x1f4f1; E-Book Digital<\/span>\n                            <h3 class=\"font-bold text-gray-900 text-sm sm:text-base line-clamp-2\" style=\"font-weight: 700; color: #111827; line-height: 1.4;\">${escapeHtml(title)}<\/h3>\n                        <\/div>\n                    <\/div>\n                    <p class=\"text-xs text-gray-500 mb-2\" style=\"font-size: 12px; color: #6b7280;\">Oleh: <span style=\"font-weight: 500; color: #374151;\">${escapeHtml(author)}<\/span><\/p>\n                <\/div>\n                <div class=\"border-t border-gray-100 pt-2 flex justify-end items-center text-xs text-gray-500\" style=\"border-top: 1px solid #f3f4f6; padding-top: 8px;\">\n                    <div style=\"font-weight: 600; color: #0f766e; font-size: 11px;\">Lihat Ketersediaan \u2192<\/div>\n                <\/div>\n            <\/div>\n        `;\n    }\n\n    function tampilkanRekomendasi() {\n        gridRekomendasi.innerHTML = '';\n        if (tabAktif === 'fisik') {\n            bukuRekomendasi.forEach((book, idx) => {\n                gridRekomendasi.insertAdjacentHTML('beforeend', buatHTMLCard(book, idx, true));\n            });\n\n            document.querySelectorAll('[data-rec-index]').forEach(card => {\n                card.addEventListener('click', function() {\n                    const idx = this.getAttribute('data-rec-index');\n                    bukaPopUpDetail(bukuRekomendasi[idx]);\n                });\n            });\n        } else {\n            bukuRekomendasi.forEach((book, idx) => {\n                gridRekomendasi.insertAdjacentHTML('beforeend', buatHTMLCardDigital(book, idx, true));\n            });\n\n            document.querySelectorAll('[data-rec-digital-index]').forEach(card => {\n                card.addEventListener('click', function() {\n                    const idx = this.getAttribute('data-rec-digital-index');\n                    bukaPopUpDetailDigital(bukuRekomendasi[idx]);\n                });\n            });\n        }\n    }\n\n    async function cariFisikSekaliJalan(uris, timeoutMs) {\n        for (let urlTarget of uris) {\n            try {\n                const controller = new AbortController();\n                const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n\n                const response = await fetch(urlTarget, { method: 'GET', signal: controller.signal });\n                clearTimeout(timeoutId);\n\n                if (response.ok) {\n                    const resJson = await response.json();\n                    if (resJson && resJson.length > 0) {\n                        return resJson;\n                    }\n                }\n            } catch (err) {\n                console.warn(\"Kueri dialihkan otomatis untuk menjaga kestabilan server.\");\n            }\n        }\n        return null;\n    }\n\n    async function cariDigitalSekaliJalan(urlDigital, timeoutMs) {\n        const controller = new AbortController();\n        const timeoutId = setTimeout(() => controller.abort(), timeoutMs);\n        try {\n            const response = await fetch(urlDigital, { method: 'GET', signal: controller.signal });\n            clearTimeout(timeoutId);\n            if (response.ok) {\n                const resJson = await response.json();\n                if (resJson && resJson.data && resJson.data.length > 0) {\n                    return { status: 'ok', data: resJson.data };\n                }\n                return { status: 'kosong' };\n            }\n            return { status: 'error_http' };\n        } catch (err) {\n            clearTimeout(timeoutId);\n            return { status: 'error_network' };\n        }\n    }\n\n    async function aksiPencarian() {\n        const q = inputCari.value.trim();\n        if (!q) return;\n\n        wrapperRekomendasi.style.display = 'none';\n        gridBuku.innerHTML = '';\n        semuaBuku = [];\n        halamanSekarang = 1;\n        panelPaginasi.style.display = \"none\";\n        \n        statusSistem.classList.remove('hidden');\n        statusSistem.style.display = \"block\";\n        statusSistem.style.color = \"#4f46e5\";\n\n        if (tabAktif === 'fisik') {\n            statusSistem.innerText = 'Menghubungkan ke database perpustakaan...';\n\n            const uris = [\n                `\/?opac_proxy=cari_fisik_title&q=${encodeURIComponent(q)}`,\n                `\/?opac_proxy=cari_fisik_author&q=${encodeURIComponent(q)}`\n            ];\n\n            \/\/ Percobaan pertama dengan timeout yang lebih longgar, karena Supabase\n            \/\/ yang baru \"bangun\" dari idle\/cold start bisa butuh waktu lebih lama untuk merespons.\n            let dataBerhasil = await cariFisikSekaliJalan(uris, 15000);\n\n            if (!dataBerhasil) {\n                \/\/ Coba ulang otomatis satu kali (biasanya server sudah \"bangun\" di percobaan ini)\n                \/\/ supaya pengguna tidak perlu menekan tombol Cari dua kali secara manual.\n                statusSistem.innerText = 'Menyiapkan database perpustakaan, mohon tunggu sebentar...';\n                dataBerhasil = await cariFisikSekaliJalan(uris, 15000);\n            }\n\n            if (dataBerhasil && dataBerhasil.length > 0) {\n                statusSistem.style.display = \"none\";\n                statusSistem.classList.add('hidden');\n                semuaBuku = dataBerhasil;\n                tampilkanDataHalaman();\n            } else {\n                statusSistem.style.color = \"#d97706\";\n                statusSistem.innerText = 'Buku tidak ditemukan. Coba masukkan judul lain yang lebih spesifik.';\n            }\n\n        } else {\n            statusSistem.innerText = 'Menghubungkan ke server ePustaka digital...';\n            const urlDigital = `\/?opac_proxy=cari_digital&q=${encodeURIComponent(q)}`;\n\n            \/\/ Percobaan pertama dengan timeout yang lebih longgar untuk mengakomodasi cold start.\n            let hasilDigital = await cariDigitalSekaliJalan(urlDigital, 15000);\n\n            if (hasilDigital.status !== 'ok') {\n                \/\/ Coba ulang otomatis satu kali sebelum menampilkan pesan gagal\/tidak ditemukan.\n                statusSistem.innerText = 'Menyiapkan server ePustaka digital, mohon tunggu sebentar...';\n                hasilDigital = await cariDigitalSekaliJalan(urlDigital, 15000);\n            }\n\n            if (hasilDigital.status === 'ok') {\n                statusSistem.style.display = \"none\";\n                statusSistem.classList.add('hidden');\n                semuaBuku = hasilDigital.data;\n                tampilkanDataHalaman();\n            } else if (hasilDigital.status === 'kosong') {\n                statusSistem.style.color = \"#d97706\";\n                statusSistem.innerText = 'E-Book tidak ditemukan di iPustaka Aceh. Coba kata kunci lain.';\n            } else if (hasilDigital.status === 'error_network') {\n                statusSistem.style.color = \"#ef4444\";\n                statusSistem.innerText = 'Terjadi kesalahan koneksi jaringan sistem digital.';\n            } else {\n                statusSistem.style.color = \"#ef4444\";\n                statusSistem.innerText = 'Gagal memuat data dari iPustaka Aceh.';\n            }\n        }\n    }\n\n    function tampilkanDataHalaman() {\n        gridBuku.innerHTML = '';\n        const indeksAwal = (halamanSekarang - 1) * itemPerHalaman;\n        const indeksAkhir = indeksAwal + itemPerHalaman;\n        const dataPotong = semuaBuku.slice(indeksAwal, indeksAkhir);\n\n        if (tabAktif === 'fisik') {\n            dataPotong.forEach((book, posisi) => {\n                const indeksAsli = indeksAwal + posisi;\n                gridBuku.insertAdjacentHTML('beforeend', buatHTMLCard(book, indeksAsli, false));\n            });\n\n            document.querySelectorAll('[data-index]').forEach(card => {\n                card.addEventListener('click', function() {\n                    const idx = this.getAttribute('data-index');\n                    bukaPopUpDetail(semuaBuku[idx]);\n                });\n            });\n        } else {\n            dataPotong.forEach((book, posisi) => {\n                const indeksAsli = indeksAwal + posisi;\n                gridBuku.insertAdjacentHTML('beforeend', buatHTMLCardDigital(book, indeksAsli));\n            });\n\n            document.querySelectorAll('[data-digital-index]').forEach(card => {\n                card.addEventListener('click', function() {\n                    const idx = this.getAttribute('data-digital-index');\n                    bukaPopUpDetailDigital(semuaBuku[idx]);\n                });\n            });\n        }\n\n        perbaruiPanelPaginasi();\n    }\n\n    function perbaruiPanelPaginasi() {\n        const totalHalaman = Math.ceil(semuaBuku.length \/ itemPerHalaman);\n        if (totalHalaman > 1) {\n            panelPaginasi.style.display = \"flex\";\n            panelPaginasi.classList.remove('hidden');\n            infoHalaman.innerText = `Halaman ${halamanSekarang} dari ${totalHalaman}`;\n            btnPrev.disabled = (halamanSekarang === 1);\n            btnNext.disabled = (halamanSekarang === totalHalaman);\n            btnPrev.style.opacity = (halamanSekarang === 1) ? \"0.5\" : \"1\";\n            btnPrev.style.cursor = (halamanSekarang === 1) ? \"not-allowed\" : \"pointer\";\n            btnNext.style.opacity = (halamanSekarang === totalHalaman) ? \"0.5\" : \"1\";\n            btnNext.style.cursor = (halamanSekarang === totalHalaman) ? \"not-allowed\" : \"pointer\";\n        } else {\n            panelPaginasi.style.display = \"none\";\n        }\n    }\n\n    btnPrev.addEventListener('click', function() {\n        if (halamanSekarang > 1) {\n            halamanSekarang--;\n            tampilkanDataHalaman();\n            window.scrollTo({ top: panelPaginasi.offsetTop - 500, behavior: 'smooth' });\n        }\n    });\n\n    btnNext.addEventListener('click', function() {\n        const totalHalaman = Math.ceil(semuaBuku.length \/ itemPerHalaman);\n        if (halamanSekarang < totalHalaman) {\n            halamanSekarang++;\n            tampilkanDataHalaman();\n            window.scrollTo({ top: panelPaginasi.offsetTop - 500, behavior: 'smooth' });\n        }\n    });\n\n    function bukaPopUpDetail(buku) {\n        const daftarGradasi = ['from-pink-500 to-rose-600', 'from-purple-500 to-indigo-600', 'from-blue-500 to-cyan-600', 'from-teal-500 to-emerald-600', 'from-amber-500 to-orange-600'];\n        const warnaPilihan = daftarGradasi[(buku.ID || 0) % daftarGradasi.length];\n        \n        let modalCoverHtml = '';\n        if (buku.CoverURL) {\n            modalCoverHtml = `<div class=\"w-full sm:w-1\/4 mb-4 sm:mb-0 flex justify-center\"><img decoding=\"async\" src=\"${escapeHtml(buku.CoverURL)}\" class=\"w-32 h-44 object-cover rounded-xl shadow-md\" onerror=\"this.style.display='none'\"><\/div>`;\n        } else {\n            const isbnModalCek = (buku.ISBN || '').toString().trim();\n            const isbnModalValid = isbnModalCek && isbnModalCek !== '-';\n            const covIdModal = isbnModalValid ? ('gcover-modal-' + (hitungCoverGoogleBooks++)) : '';\n            const atributIdCoverModal = isbnModalValid ? ` id=\"${covIdModal}\"` : '';\n\n            modalCoverHtml = `\n                <div${atributIdCoverModal} class=\"w-32 h-44 bg-gradient-to-br ${warnaPilihan} rounded-xl flex flex-col justify-between p-3 text-white mx-auto sm:mx-0 shadow-md flex-shrink-0 relative overflow-hidden select-none\">\n                    <span class=\"opacity-60 uppercase tracking-wider font-mono block text-right\" style=\"font-size: 10px;\">OPAC<\/span>\n                    <span class=\"text-2xl font-black tracking-tight block text-center my-auto\">${escapeHtml((buku.Title || 'BK').substring(0,2).toUpperCase())}<\/span>\n                    <div class=\"absolute bottom-0 left-0 right-0 h-2 bg-black bg-opacity-20\"><\/div>\n                <\/div>\n            `;\n\n            if (isbnModalValid) {\n                muatCoverDariGoogleBooks(covIdModal, isbnModalCek, 'w-32 h-44 object-cover rounded-xl shadow-md mx-auto sm:mx-0 flex-shrink-0');\n            }\n        }\n\n        kontenModal.innerHTML = `\n            <span style=\"display: inline-block; background-color: #e0e7ff; color: #4338ca; font-size: 11px; padding: 4px 12px; border-radius: 9999px; font-weight: 600; margin-bottom: 12px;\">\n                &#x1f4da; ${buku.CallNumber ? `Rak: ${escapeHtml(buku.CallNumber)}` : 'Umum'}\n            <\/span>\n            <div class=\"flex flex-col sm:flex-row gap-6 items-center sm:items-start mb-4\">\n                ${modalCoverHtml}\n                <div class=\"flex-1 text-center sm:text-left\">\n                    <h2 style=\"font-size: 20px; font-weight: 800; color: #111827; margin-bottom: 8px; line-height: 1.4;\">${escapeHtml(buku.Title || 'Judul Tidak Tersedia')}<\/h2>\n                    <p style=\"font-size: 14px; color: #4b5563; margin-bottom: 20px;\">Utama Penulis: <strong style=\"color: #111827;\">${escapeHtml(buku.Author || 'Anonim')}<\/strong><\/p>\n                <\/div>\n            <\/div>\n            \n            <div style=\"display: grid; grid-template-columns: 1fr; gap: 12px; font-size: 13px; border-top: 1px solid #f3f4f6; padding-top: 16px;\">\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Edisi<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.Edition || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Penerbit<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.Publisher || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Kota Terbit<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.PublishLocation || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Tahun Terbit<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.PublishYear || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">ISBN \/ ISSN<\/div><div style=\"color: #111827; font-family: monospace;\">: ${escapeHtml(buku.ISBN || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Deskripsi Fisik<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.PhysicalDescription || '-')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Bahasa<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.Languages || 'Indonesia')}<\/div><\/div>\n                <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Subjek<\/div><div style=\"color: #111827;\">: ${escapeHtml(buku.Subject || '-')}<\/div><\/div>\n                <div style=\"display: flex; flex-direction: column; padding-top: 4px;\">\n                    <div style=\"color: #6b7280; font-weight: 500; margin-bottom: 4px;\">Catatan \/ Info Tambahan:<\/div>\n                    <div style=\"color: #4b5563; background-color: #f9fafb; padding: 10px; border-radius: 0.5rem; font-style: italic; font-size: 12px; line-height: 1.5;\">\n                        ${escapeHtml(buku.Note || 'Tidak ada catatan tambahan untuk katalog ini.')}\n                    <\/div>\n                <\/div>\n            <\/div>\n        `;\n        modalDetail.style.display = \"flex\";\n        modalDetail.classList.remove('hidden');\n    }\n\n    function amankanUrlHref(url) {\n        const bersih = (url || '').toString().trim();\n        return \/^https?:\\\/\\\/\/i.test(bersih) ? escapeHtml(bersih) : '';\n    }\n\n    async function bukaPopUpDetailDigital(bukuRingkas) {\n        kontenModal.innerHTML = `<div style=\"text-align:center; padding: 20px; color: #4f46e5; font-size: 14px;\">Memuat informasi detail e-book dari iPustaka...<\/div>`;\n        modalDetail.style.display = \"flex\";\n        modalDetail.classList.remove('hidden');\n\n        const urlDetailDigital = `\/?opac_proxy=detail_digital&book_id=${encodeURIComponent(bukuRingkas.id || bukuRingkas.book_id)}`;\n\n        try {\n            const response = await fetch(urlDetailDigital);\n            if (response.ok) {\n                const resJson = await response.json();\n                const b = resJson.data;\n\n                const coverHtml = b.cover_url ? `<div class=\"w-full sm:w-1\/4 mb-4 sm:mb-0 flex justify-center\"><img decoding=\"async\" src=\"${escapeHtml(b.cover_url)}\" class=\"w-32 h-44 object-cover rounded-xl shadow-md\"><\/div>` : `<div class=\"w-32 h-44 bg-gradient-to-br from-teal-500 to-emerald-600 rounded-xl flex items-center justify-center text-white font-bold text-2xl shadow-md\">E-BK<\/div>`;\n\n                const urlBaca = amankanUrlHref(b.share_url);\n\n                kontenModal.innerHTML = `\n                    <span style=\"display: inline-block; background-color: #ccfbf1; color: #0f766e; font-size: 11px; padding: 4px 12px; border-radius: 9999px; font-weight: 600; margin-bottom: 12px;\">\n                        &#x1f4f1; Kategori: ${escapeHtml(b.category_name || '-')}\n                    <\/span>\n                    <div class=\"flex flex-col sm:flex-row gap-6 items-center sm:items-start mb-4\">\n                        ${coverHtml}\n                        <div class=\"flex-1 text-center sm:text-left\">\n                            <h2 style=\"font-size: 20px; font-weight: 800; color: #111827; margin-bottom: 8px; line-height: 1.4;\">${escapeHtml(b.book_title || 'Judul Tidak Tersedia')}<\/h2>\n                            <p style=\"font-size: 14px; color: #4b5563; margin-bottom: 16px;\">Penulis: <strong style=\"color: #111827;\">${escapeHtml(b.book_author || 'Anonim')}<\/strong><\/p>\n                            \n                            ${urlBaca ? `<a href=\"${urlBaca}\" target=\"_blank\" rel=\"noopener noreferrer\" style=\"display: inline-block; background-color: #0f766e; color: white; padding: 8px 16px; border-radius: 0.5rem; font-size: 13px; font-weight: 600; text-decoration: none; box-shadow: 0 1px 2px rgba(0,0,0,0.05);\">&#x1f4d6; Pinjam & Baca E-Book<\/a>` : ''}\n                        <\/div>\n                    <\/div>\n                    \n                    <div style=\"display: grid; grid-template-columns: 1fr; gap: 12px; font-size: 13px; border-top: 1px solid #f3f4f6; padding-top: 16px;\">\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Status Salinan<\/div><div style=\"color: #111827; font-weight: 600;\">: Tersedia ${escapeHtml(b.available_qty)} dari ${escapeHtml(b.all_qty)} Eksemplar<\/div><\/div>\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Sedang Dipinjam<\/div><div style=\"color: #111827;\">: ${escapeHtml(b.active_borrow_count)} user<\/div><\/div>\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Jumlah Antrean<\/div><div style=\"color: #111827;\">: ${escapeHtml(b.queue_count)}<\/div><\/div>\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Tahun Terbit<\/div><div style=\"color: #111827;\">: ${escapeHtml(b.publish_date || '-')}<\/div><\/div>\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">ISBN<\/div><div style=\"color: #111827; font-family: monospace;\">: ${escapeHtml(b.book_isbn || '-')}<\/div><\/div>\n                        <div style=\"display: flex; border-bottom: 1px solid #f9fafb; padding-bottom: 6px;\"><div style=\"width: 140px; color: #6b7280; font-weight: 500;\">Ukuran File<\/div><div style=\"color: #111827;\">: ${escapeHtml(b.file_size_info || '-')} (${escapeHtml(b.file_ext)})<\/div><\/div>\n                        <div style=\"display: flex; flex-direction: column; padding-top: 4px;\">\n                            <div style=\"color: #6b7280; font-weight: 500; margin-bottom: 4px;\">Sinopsis \/ Deskripsi Buku:<\/div>\n                            <div style=\"color: #4b5563; background-color: #f9fafb; padding: 10px; border-radius: 0.5rem; font-size: 12px; line-height: 1.5; max-height: 150px; overflow-y: auto;\">\n                                ${escapeHtml(b.book_description || 'Tidak ada deskripsi\/sinopsis untuk buku digital ini.')}\n                            <\/div>\n                        <\/div>\n                    <\/div>\n                `;\n            } else {\n                kontenModal.innerHTML = `<div style=\"text-align:center; padding: 20px; color: #ef4444; font-size: 14px;\">Gagal memuat detail buku digital dari iPustaka.<\/div>`;\n            }\n        } catch (err) {\n            kontenModal.innerHTML = `<div style=\"text-align:center; padding: 20px; color: #ef4444; font-size: 14px;\">Terjadi kesalahan sistem saat mengambil data detail.<\/div>`;\n        }\n    }\n\n    function tutupPopUpDetail() {\n        modalDetail.style.display = \"none\";\n        modalDetail.classList.add('hidden');\n    }\n\n    tombolTutupSilang.addEventListener('click', tutupPopUpDetail);\n    tombolTutupBawah.addEventListener('click', tutupPopUpDetail);\n    modalDetail.addEventListener('click', function(e) { if (e.target === modalDetail) tutupPopUpDetail(); });\n\n    tombolCari.addEventListener('click', function(e) { e.preventDefault(); aksiPencarian(); });\n    inputCari.addEventListener('keypress', function(e) { if (e.key === 'Enter') { e.preventDefault(); aksiPencarian(); } });\n});\n<\/script>\n\n<style>\n.opac-wrapper .grid { display: grid !important; }\n@media (min-width: 640px) {\n    .opac-wrapper .sm\\:grid-cols-2 { grid-template-columns: repeat(2, minmax(0, 1fr)) !important; }\n}\n<\/style>\n","protected":false},"excerpt":{"rendered":"<p>&#x1f4d6; Online Public Access Catalog (OPAC) Cari katalog buku secara online, cepat, dan mudah. &#x1f50d; Cari Buku &#x1f4f1; iPustaka (Digital)&hellip;<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":98,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-324","page","type-page","status-publish","hentry"],"jetpack_sharing_enabled":true,"jetpack_shortlink":"https:\/\/wp.me\/P7EXk0-5e","_links":{"self":[{"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/pages\/324","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=324"}],"version-history":[{"count":13,"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/pages\/324\/revisions"}],"predecessor-version":[{"id":5917,"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/pages\/324\/revisions\/5917"}],"up":[{"embeddable":true,"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=\/wp\/v2\/pages\/98"}],"wp:attachment":[{"href":"https:\/\/arpus.acehprov.go.id\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=324"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}