{"id":20996,"date":"2026-10-21T11:23:09","date_gmt":"2026-10-21T03:23:09","guid":{"rendered":"https:\/\/www.ukm.my\/citra\/?page_id=20996"},"modified":"2026-10-21T16:24:39","modified_gmt":"2026-10-21T08:24:39","slug":"prosiding-icitra-2026","status":"publish","type":"page","link":"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/","title":{"rendered":"Prosiding iCitra 2026"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"20996\" class=\"elementor elementor-20996\" data-elementor-post-type=\"page\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-446d9eb elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"446d9eb\" data-element_type=\"section\" data-e-type=\"section\" data-settings=\"{&quot;animation&quot;:&quot;none&quot;}\">\n\t\t\t\t\t\t<div class=\"elementor-container elementor-column-gap-default\">\n\t\t\t\t\t<div class=\"elementor-column elementor-col-100 elementor-top-column elementor-element elementor-element-0665449\" data-id=\"0665449\" data-element_type=\"column\" data-e-type=\"column\">\n\t\t\t<div class=\"elementor-widget-wrap elementor-element-populated\">\n\t\t\t\t\t\t<div class=\"elementor-element elementor-element-65392bb elementor-widget elementor-widget-html\" data-id=\"65392bb\" data-element_type=\"widget\" data-e-type=\"widget\" data-settings=\"{&quot;ekit_we_effect_on&quot;:&quot;none&quot;}\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t\r\n<!-- LAZY LOADING 3D PDF FLIPBOOK FOR ELEMENTOR -->\r\n\r\n<div id=\"lf-search-info\" role=\"status\" aria-live=\"polite\"><\/div>\r\n<div id=\"lf-search-results\"><\/div>\r\n\r\n<div id=\"lazy-pdf-flipbook\"\r\n     data-pdf=\"https:\/\/www.ukm.my\/citra\/wp-content\/uploads\/2026\/10\/PROCEEDINGS-iCITRA2026.pdf\">\r\n\r\n  <div class=\"lf-header\">\r\n    <h3>Prosiding iCitra<\/h3>\r\n    <p>2026<\/p>\r\n  <\/div>\r\n\r\n\r\n<!-- PDF SEARCH BAR -->\r\n<div class=\"lf-search\">\r\n  <input\r\n    type=\"search\"\r\n    id=\"lf-search-input\"\r\n    placeholder=\"Search words inside PDF...\"\r\n    aria-label=\"Search PDF text\"\r\n  \/>\r\n  <button type=\"button\" id=\"lf-search-btn\">\r\n    \ud83d\udd0d Search\r\n  <\/button>\r\n  <button type=\"button\" id=\"lf-search-clear\">\r\n    Clear\r\n  <\/button>\r\n<\/div>\r\n\r\n<div id=\"lf-search-info\" role=\"status\" aria-live=\"polite\"><\/div>\r\n<div id=\"lf-search-results\"><\/div>\r\n\r\n  <div class=\"lf-stage\">\r\n    <div id=\"lf-book\"><\/div>\r\n    <div id=\"lf-loading\" class=\"lf-loading\">\r\n      Loading PDF...\r\n    <\/div>\r\n  <\/div>\r\n\r\n  <div class=\"lf-toolbar\">\r\n    <button id=\"lf-prev\" disabled>\u276e Previous<\/button>\r\n    <span id=\"lf-counter\">0 \/ 0<\/span>\r\n    <button id=\"lf-next\" disabled>Next \u276f<\/button>\r\n    <button id=\"lf-fullscreen\">\u26f6 Fullscreen<\/button>\r\n    <a id=\"lf-pdf-link\" target=\"_blank\"\r\n       rel=\"noopener noreferrer\">Open PDF \u2197<\/a>\r\n  <\/div>\r\n\r\n  <div id=\"lf-status\" role=\"status\" aria-live=\"polite\">\r\n    Preparing document...\r\n  <\/div>\r\n<\/div>\r\n\r\n<style>\r\n\r\n\/* PDF SEARCH *\/\r\n#lazy-pdf-flipbook .lf-search {\r\n  display: flex;\r\n  flex-wrap: wrap;\r\n  gap: 8px;\r\n  margin: 15px 0;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-search-input {\r\n  flex: 1;\r\n  min-width: 160px;\r\n  padding: 12px 14px;\r\n  border: 1px solid #ccd3df;\r\n  border-radius: 8px;\r\n  background: #fff;\r\n  color: #222;\r\n  font-size: 14px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-search button {\r\n  background: #17365d;\r\n  color: white;\r\n  border: 0;\r\n  border-radius: 8px;\r\n  padding: 11px 15px;\r\n  cursor: pointer;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-search-info {\r\n  font-size: 13px;\r\n  color: #666;\r\n  margin: 8px 0;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-search-results {\r\n  max-height: 230px;\r\n  overflow-y: auto;\r\n  margin-bottom: 12px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-result {\r\n  display: block;\r\n  width: 100%;\r\n  text-align: left;\r\n  padding: 12px;\r\n  margin-bottom: 6px;\r\n  border: 1px solid #e1e5ed;\r\n  border-radius: 7px;\r\n  background: white;\r\n  color: #222;\r\n  cursor: pointer;\r\n  font-size: 13px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-result:hover {\r\n  background: #eaf1fa;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-result strong {\r\n  display: block;\r\n  margin-bottom: 5px;\r\n  color: #17365d;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-result mark {\r\n  background: #ffe28a;\r\n  color: #222;\r\n  border-radius: 2px;\r\n}\r\n\r\n@media(max-width:767px) {\r\n  #lazy-pdf-flipbook .lf-search button {\r\n    padding: 10px;\r\n  }\r\n}\r\n\r\n#lazy-pdf-flipbook {\r\n  --lf-primary: #17365d;\r\n  width: 100%;\r\n  max-width: 1200px;\r\n  margin: auto;\r\n  padding: 18px;\r\n  background: #f5f6f9;\r\n  border-radius: 15px;\r\n  font-family: Arial, sans-serif;\r\n  color: #222;\r\n  box-sizing: border-box;\r\n}\r\n\r\n#lazy-pdf-flipbook * {\r\n  box-sizing: border-box;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-header {\r\n  text-align: center;\r\n  margin-bottom: 15px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-header h3 {\r\n  font-size: 22px;\r\n  font-weight: 700;\r\n  margin: 0 0 5px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-header p {\r\n  font-size: 13px;\r\n  color: #777;\r\n  margin: 0;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-stage {\r\n  position: relative;\r\n  width: 100%;\r\n  height: 650px;\r\n  background: #e4e6ec;\r\n  border-radius: 10px;\r\n  overflow: hidden;\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-book {\r\n  width: 100%;\r\n  height: 100%;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-page {\r\n  background: white;\r\n  overflow: hidden;\r\n  position: relative;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-page canvas {\r\n  display: block;\r\n  width: 100%;\r\n  height: 100%;\r\n  object-fit: fill;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-placeholder {\r\n  display: flex;\r\n  align-items: center;\r\n  justify-content: center;\r\n  height: 100%;\r\n  font-size: 13px;\r\n  color: #888;\r\n  background: #fff;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-loading {\r\n  position: absolute;\r\n  z-index: 20;\r\n  background: rgba(23,54,93,.9);\r\n  color: white;\r\n  padding: 12px 20px;\r\n  border-radius: 8px;\r\n  font-size: 13px;\r\n  pointer-events: none;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-loading.hidden {\r\n  display: none;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-toolbar {\r\n  display: flex;\r\n  flex-wrap: wrap;\r\n  align-items: center;\r\n  justify-content: center;\r\n  gap: 8px;\r\n  margin-top: 15px;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-toolbar button,\r\n#lazy-pdf-flipbook .lf-toolbar a {\r\n  background: var(--lf-primary);\r\n  color: white;\r\n  padding: 10px 14px;\r\n  border: 0;\r\n  border-radius: 7px;\r\n  font-size: 13px;\r\n  line-height: 1.4;\r\n  text-decoration: none;\r\n  cursor: pointer;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-toolbar button:hover,\r\n#lazy-pdf-flipbook .lf-toolbar a:hover {\r\n  background: #315c91;\r\n  color: white;\r\n}\r\n\r\n#lazy-pdf-flipbook .lf-toolbar button:disabled {\r\n  opacity: .4;\r\n  cursor: not-allowed;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-counter {\r\n  min-width: 70px;\r\n  text-align: center;\r\n  font-weight: 600;\r\n  font-size: 14px;\r\n}\r\n\r\n#lazy-pdf-flipbook #lf-status {\r\n  text-align: center;\r\n  font-size: 12px;\r\n  color: #777;\r\n  margin-top: 12px;\r\n}\r\n\r\n#lazy-pdf-flipbook:fullscreen {\r\n  max-width: none;\r\n  height: 100vh;\r\n  border-radius: 0;\r\n  display: flex;\r\n  flex-direction: column;\r\n}\r\n\r\n#lazy-pdf-flipbook:fullscreen .lf-stage {\r\n  flex: 1;\r\n  height: auto;\r\n  min-height: 0;\r\n}\r\n\r\n@media(max-width:767px) {\r\n  #lazy-pdf-flipbook {\r\n    padding: 10px;\r\n  }\r\n\r\n  #lazy-pdf-flipbook .lf-stage {\r\n    height: 65vh;\r\n    min-height: 350px;\r\n  }\r\n\r\n  #lazy-pdf-flipbook .lf-header h3 {\r\n    font-size: 18px;\r\n  }\r\n\r\n  #lazy-pdf-flipbook .lf-toolbar button,\r\n  #lazy-pdf-flipbook .lf-toolbar a {\r\n    font-size: 12px;\r\n    padding: 9px 11px;\r\n  }\r\n\r\n  #lazy-pdf-flipbook:fullscreen .lf-stage {\r\n    height: auto;\r\n    min-height: 0;\r\n  }\r\n}\r\n<\/style>\r\n\r\n<!-- REQUIRED LIBRARIES -->\r\n<script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js\"><\/script>\r\n<script src=\"https:\/\/cdn.jsdelivr.net\/npm\/page-flip@2.0.7\/dist\/js\/page-flip.browser.min.js\"><\/script>\r\n\r\n<script>\r\n(function () {\r\n  \"use strict\";\r\n\r\n  const root = document.getElementById(\"lazy-pdf-flipbook\");\r\n  if (!root || root.dataset.initialized) return;\r\n  root.dataset.initialized = \"true\";\r\n\r\n  const pdfURL = root.dataset.pdf;\r\n  const bookEl = root.querySelector(\"#lf-book\");\r\n  const loadingEl = root.querySelector(\"#lf-loading\");\r\n  const counterEl = root.querySelector(\"#lf-counter\");\r\n  const statusEl = root.querySelector(\"#lf-status\");\r\n\r\n  const prevBtn = root.querySelector(\"#lf-prev\");\r\n  const nextBtn = root.querySelector(\"#lf-next\");\r\n  const fullscreenBtn = root.querySelector(\"#lf-fullscreen\");\r\n  const pdfLink = root.querySelector(\"#lf-pdf-link\");\r\n\r\n  pdfLink.href = pdfURL;\r\n\r\n  \/\/ LAZY LOADING SETTINGS\r\n  const CONFIG = {\r\n    preloadBefore: 3,\r\n    preloadAfter: 5,\r\n    cacheLimit: 16,\r\n    renderScale: 1.5,\r\n    flipSpeed: 750,\r\n    concurrency: 2\r\n  };\r\n\r\n  let pdf = null;\r\n  let flipbook = null;\r\n  let totalPages = 0;\r\n  let pageElements = [];\r\n\r\n  const rendered = new Map();\r\n  const pending = new Map();\r\n  const queue = [];\r\n\r\n  let activeRenders = 0;\r\n  let currentIndex = 0;\r\n  let generation = 0;\r\n\r\n  function setLoading(text, visible = true) {\r\n    loadingEl.textContent = text;\r\n    loadingEl.classList.toggle(\"hidden\", !visible);\r\n  }\r\n\r\n  function setStatus(text) {\r\n    statusEl.textContent = text;\r\n  }\r\n\r\n  function updateNavigation() {\r\n    counterEl.textContent =\r\n      (currentIndex + 1) + \" \/ \" + totalPages;\r\n\r\n    prevBtn.disabled = currentIndex === 0;\r\n    nextBtn.disabled = currentIndex >= totalPages - 1;\r\n  }\r\n\r\n  function createPages() {\r\n    const fragment = document.createDocumentFragment();\r\n\r\n    for (let i = 0; i < totalPages; i++) {\r\n      const page = document.createElement(\"div\");\r\n      page.className = \"lf-page\";\r\n      page.dataset.page = i;\r\n\r\n      const placeholder = document.createElement(\"div\");\r\n      placeholder.className = \"lf-placeholder\";\r\n      placeholder.textContent = \"Page \" + (i + 1);\r\n\r\n      page.appendChild(placeholder);\r\n      fragment.appendChild(page);\r\n      pageElements.push(page);\r\n    }\r\n\r\n    bookEl.appendChild(fragment);\r\n  }\r\n\r\n  function isNeeded(index) {\r\n    return (\r\n      index >= Math.max(0, currentIndex - CONFIG.preloadBefore) &&\r\n      index <= Math.min(\r\n        totalPages - 1,\r\n        currentIndex + CONFIG.preloadAfter\r\n      )\r\n    );\r\n  }\r\n\r\n  function enqueue(index) {\r\n    if (\r\n      index < 0 ||\r\n      index >= totalPages ||\r\n      rendered.has(index) ||\r\n      pending.has(index)\r\n    ) return;\r\n\r\n    \/\/ One queue entry per page\r\n    pending.set(index, \"queued\");\r\n    queue.push(index);\r\n    processQueue();\r\n  }\r\n\r\n  function processQueue() {\r\n    while (\r\n      activeRenders < CONFIG.concurrency &&\r\n      queue.length > 0\r\n    ) {\r\n      const index = queue.shift();\r\n\r\n      if (!pending.has(index)) continue;\r\n\r\n      if (!isNeeded(index)) {\r\n        pending.delete(index);\r\n        continue;\r\n      }\r\n\r\n      activeRenders++;\r\n      pending.set(index, \"rendering\");\r\n\r\n      renderPage(index)\r\n        .catch(function (error) {\r\n          console.error(\"Page render failed:\", index + 1, error);\r\n          setStatus(\"Unable to render page \" + (index + 1));\r\n        })\r\n        .finally(function () {\r\n          pending.delete(index);\r\n          activeRenders--;\r\n          processQueue();\r\n        });\r\n    }\r\n  }\r\n\r\n  async function renderPage(index) {\r\n    const page = await pdf.getPage(index + 1);\r\n\r\n    const baseViewport = page.getViewport({ scale: 1 });\r\n\r\n    \/\/ Limit pixel dimensions for memory safety\r\n    const maxDimension = 1800;\r\n\r\n    const scale = Math.min(\r\n      CONFIG.renderScale,\r\n      maxDimension \/ Math.max(\r\n        baseViewport.width,\r\n        baseViewport.height\r\n      )\r\n    );\r\n\r\n    const viewport = page.getViewport({ scale });\r\n\r\n    const canvas = document.createElement(\"canvas\");\r\n    const context = canvas.getContext(\"2d\", {\r\n      alpha: false\r\n    });\r\n\r\n    canvas.width = Math.ceil(viewport.width);\r\n    canvas.height = Math.ceil(viewport.height);\r\n\r\n    await page.render({\r\n      canvasContext: context,\r\n      viewport,\r\n      background: \"rgb(255,255,255)\"\r\n    }).promise;\r\n\r\n    \/\/ Do not keep pages that are no longer nearby\r\n    if (!isNeeded(index)) {\r\n      canvas.width = 0;\r\n      canvas.height = 0;\r\n      page.cleanup();\r\n      return;\r\n    }\r\n\r\n    const element = pageElements[index];\r\n\r\n    element.replaceChildren(canvas);\r\n    rendered.set(index, canvas);\r\n\r\n    page.cleanup();\r\n\r\n    trimCache();\r\n\r\n    if (index === currentIndex) {\r\n      setLoading(\"\", false);\r\n    }\r\n\r\n    setStatus(\r\n      \"Page \" + (currentIndex + 1) +\r\n      \" of \" + totalPages +\r\n      \" \u00b7 \" + rendered.size + \" pages cached\"\r\n    );\r\n  }\r\n\r\n  function trimCache() {\r\n    if (rendered.size <= CONFIG.cacheLimit) return;\r\n\r\n    const candidates = [...rendered.keys()]\r\n      .filter(function (index) {\r\n        return !isNeeded(index);\r\n      })\r\n      .sort(function (a, b) {\r\n        return Math.abs(b - currentIndex) -\r\n               Math.abs(a - currentIndex);\r\n      });\r\n\r\n    for (const index of candidates) {\r\n      if (rendered.size <= CONFIG.cacheLimit) break;\r\n\r\n      const canvas = rendered.get(index);\r\n      rendered.delete(index);\r\n\r\n      const placeholder = document.createElement(\"div\");\r\n      placeholder.className = \"lf-placeholder\";\r\n      placeholder.textContent = \"Page \" + (index + 1);\r\n\r\n      pageElements[index].replaceChildren(placeholder);\r\n\r\n      canvas.width = 0;\r\n      canvas.height = 0;\r\n    }\r\n  }\r\n\r\n  function loadNearbyPages() {\r\n    generation++;\r\n\r\n    \/\/ Prioritize current page, then nearby pages\r\n    const indices = [];\r\n\r\n    for (\r\n      let distance = 0;\r\n      distance <= Math.max(\r\n        CONFIG.preloadBefore,\r\n        CONFIG.preloadAfter\r\n      );\r\n      distance++\r\n    ) {\r\n      const right = currentIndex + distance;\r\n      const left = currentIndex - distance;\r\n\r\n      if (\r\n        right < totalPages &&\r\n        right <= currentIndex + CONFIG.preloadAfter\r\n      ) indices.push(right);\r\n\r\n      if (\r\n        distance > 0 &&\r\n        left >= 0 &&\r\n        left >= currentIndex - CONFIG.preloadBefore\r\n      ) indices.push(left);\r\n    }\r\n\r\n    \/\/ Remove obsolete queued work\r\n    for (let i = queue.length - 1; i >= 0; i--) {\r\n      const index = queue[i];\r\n      if (!isNeeded(index)) {\r\n        queue.splice(i, 1);\r\n        if (pending.get(index) === \"queued\") {\r\n          pending.delete(index);\r\n        }\r\n      }\r\n    }\r\n\r\n    \/\/ Put the newest nearby pages first\r\n    for (const index of indices.reverse()) {\r\n      if (!rendered.has(index) && !pending.has(index)) {\r\n        pending.set(index, \"queued\");\r\n        queue.unshift(index);\r\n      }\r\n    }\r\n\r\n    if (!rendered.has(currentIndex)) {\r\n      setLoading(\"Loading page \" + (currentIndex + 1));\r\n    } else {\r\n      setLoading(\"\", false);\r\n    }\r\n\r\n    processQueue();\r\n    trimCache();\r\n  }\r\n\r\n  async function init() {\r\n    try {\r\n      if (!window.pdfjsLib || !window.St?.PageFlip) {\r\n        throw new Error(\"PDF or flipbook library unavailable.\");\r\n      }\r\n\r\n      if (!pdfURL || pdfURL.includes(\"yourwebsite.com\")) {\r\n        throw new Error(\"Please replace the example PDF URL.\");\r\n      }\r\n\r\n      pdfjsLib.GlobalWorkerOptions.workerSrc =\r\n        \"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.worker.min.js\";\r\n\r\n      setLoading(\"Opening PDF...\");\r\n\r\n      pdf = await pdfjsLib.getDocument({\r\n        url: pdfURL,\r\n        disableAutoFetch: true,\r\n        disableStream: true\r\n      }).promise;\r\n\r\n      totalPages = pdf.numPages;\r\n\r\n      if (totalPages < 1) {\r\n        throw new Error(\"PDF has no pages.\");\r\n      }\r\n\r\n      const firstPage = await pdf.getPage(1);\r\n      const firstViewport = firstPage.getViewport({\r\n        scale: 1\r\n      });\r\n\r\n      const pageWidth = 440;\r\n      const ratio = firstViewport.height \/ firstViewport.width;\r\n      const pageHeight = Math.round(pageWidth * ratio);\r\n\r\n      createPages();\r\n\r\n      flipbook = new St.PageFlip(bookEl, {\r\n        width: pageWidth,\r\n        height: pageHeight,\r\n        size: \"stretch\",\r\n        minWidth: 150,\r\n        maxWidth: 600,\r\n        minHeight: Math.round(150 * ratio),\r\n        maxHeight: Math.round(600 * ratio),\r\n        showCover: true,\r\n        drawShadow: true,\r\n        maxShadowOpacity: 0.5,\r\n        flippingTime: CONFIG.flipSpeed,\r\n        usePortrait: true,\r\n        mobileScrollSupport: true,\r\n        autoSize: false\r\n      });\r\n\r\n      flipbook.loadFromHTML(pageElements);\r\n\r\n      flipbook.on(\"flip\", function (event) {\r\n        currentIndex = event.data;\r\n        updateNavigation();\r\n        loadNearbyPages();\r\n      });\r\n\r\n      flipbook.on(\"changeOrientation\", function () {\r\n        loadNearbyPages();\r\n      });\r\n\r\n      updateNavigation();\r\n\r\n      \/\/ Initial loading only renders nearby pages\r\n      loadNearbyPages();\r\n\r\n      setStatus(\r\n        totalPages + \" pages detected \u00b7 Lazy loading enabled\"\r\n      );\r\n\r\n    } catch (error) {\r\n      console.error(\"Flipbook initialization failed:\", error);\r\n      setLoading(\"Unable to load PDF\");\r\n      setStatus(error.message);\r\n    }\r\n  }\r\n\r\n  prevBtn.addEventListener(\"click\", function () {\r\n    if (flipbook) flipbook.flipPrev();\r\n  });\r\n\r\n  nextBtn.addEventListener(\"click\", function () {\r\n    if (flipbook) flipbook.flipNext();\r\n  });\r\n\r\n  fullscreenBtn.addEventListener(\"click\", async function () {\r\n    try {\r\n      if (!document.fullscreenElement) {\r\n        await root.requestFullscreen();\r\n      } else {\r\n        await document.exitFullscreen();\r\n      }\r\n    } catch (error) {\r\n      setStatus(\"Fullscreen is not supported here.\");\r\n    }\r\n  });\r\n\r\n  document.addEventListener(\"fullscreenchange\", function () {\r\n    fullscreenBtn.textContent = document.fullscreenElement\r\n      ? \"\u2715 Exit Fullscreen\"\r\n      : \"\u26f6 Fullscreen\";\r\n\r\n    if (flipbook) {\r\n      requestAnimationFrame(function () {\r\n        flipbook.update();\r\n      });\r\n    }\r\n  });\r\n\r\n\r\n\/\/ =======================================\r\n\/\/ SEARCH INSIDE PDF - LAZY TEXT EXTRACTION\r\n\/\/ =======================================\r\n\r\nconst searchInput = root.querySelector(\"#lf-search-input\");\r\nconst searchBtn = root.querySelector(\"#lf-search-btn\");\r\nconst clearBtn = root.querySelector(\"#lf-search-clear\");\r\nconst searchInfo = root.querySelector(\"#lf-search-info\");\r\nconst searchResults = root.querySelector(\"#lf-search-results\");\r\n\r\n\/\/ Text is extracted on demand, not at initial PDF load.\r\nconst textCache = new Map();\r\nlet searchToken = 0;\r\nlet searchBusy = false;\r\n\r\nfunction normalizeSearchText(value) {\r\n  return value\r\n    .normalize(\"NFKC\")\r\n    .replace(\/\\s+\/g, \" \")\r\n    .toLocaleLowerCase();\r\n}\r\n\r\nasync function getPageText(pageIndex) {\r\n  if (textCache.has(pageIndex)) {\r\n    return textCache.get(pageIndex);\r\n  }\r\n\r\n  const page = await pdf.getPage(pageIndex + 1);\r\n  const content = await page.getTextContent();\r\n\r\n  const text = content.items\r\n    .map(item => item.str || \"\")\r\n    .join(\" \")\r\n    .replace(\/\\s+\/g, \" \")\r\n    .trim();\r\n\r\n  textCache.set(pageIndex, text);\r\n\r\n  return text;\r\n}\r\n\r\nfunction makeSnippet(text, position, length) {\r\n  const start = Math.max(0, position - 55);\r\n  const end = Math.min(\r\n    text.length,\r\n    position + length + 85\r\n  );\r\n\r\n  return (\r\n    (start > 0 ? \"...\" : \"\") +\r\n    text.slice(start, end) +\r\n    (end < text.length ? \"...\" : \"\")\r\n  );\r\n}\r\n\r\nfunction createSearchResult(pageIndex, snippet) {\r\n  const button = document.createElement(\"button\");\r\n  button.type = \"button\";\r\n  button.className = \"lf-result\";\r\n\r\n  const heading = document.createElement(\"strong\");\r\n  heading.textContent = \"Page \" + (pageIndex + 1);\r\n\r\n  const description = document.createElement(\"span\");\r\n  description.textContent = snippet;\r\n\r\n  button.appendChild(heading);\r\n  button.appendChild(description);\r\n\r\n  button.addEventListener(\"click\", function () {\r\n    if (!flipbook) return;\r\n\r\n    \/\/ Navigate directly to matching page.\r\n    \/\/ Existing flip event triggers nearby-page rendering.\r\n    flipbook.turnToPage(pageIndex);\r\n\r\n    \/\/ Scroll the reader into view.\r\n    root.querySelector(\".lf-stage\").scrollIntoView({\r\n      behavior: \"smooth\",\r\n      block: \"center\"\r\n    });\r\n  });\r\n\r\n  searchResults.appendChild(button);\r\n}\r\n\r\nasync function searchPDF() {\r\n  if (!pdf || !flipbook) {\r\n    searchInfo.textContent = \"PDF is still loading.\";\r\n    return;\r\n  }\r\n\r\n  const query = searchInput.value.trim();\r\n\r\n  if (!query) {\r\n    searchInfo.textContent = \"Enter a word or phrase.\";\r\n    return;\r\n  }\r\n\r\n  \/\/ Cancel previous search.\r\n  const token = ++searchToken;\r\n\r\n  searchResults.replaceChildren();\r\n  searchBusy = true;\r\n  searchBtn.disabled = true;\r\n\r\n  const needle = normalizeSearchText(query);\r\n\r\n  let matchingPages = 0;\r\n  let totalMatches = 0;\r\n\r\n  try {\r\n    for (let i = 0; i < totalPages; i++) {\r\n      if (token !== searchToken) return;\r\n\r\n      searchInfo.textContent =\r\n        \"Searching page \" + (i + 1) +\r\n        \" of \" + totalPages + \"...\";\r\n\r\n      const text = await getPageText(i);\r\n\r\n      if (token !== searchToken) return;\r\n\r\n      const normalized = normalizeSearchText(text);\r\n\r\n      let position = normalized.indexOf(needle);\r\n      let pageMatches = 0;\r\n\r\n      while (position !== -1) {\r\n        pageMatches++;\r\n        position = normalized.indexOf(\r\n          needle,\r\n          position + Math.max(1, needle.length)\r\n        );\r\n      }\r\n\r\n      if (pageMatches > 0) {\r\n        matchingPages++;\r\n        totalMatches += pageMatches;\r\n\r\n        const firstPosition = normalized.indexOf(needle);\r\n        const snippet = makeSnippet(\r\n          text,\r\n          firstPosition,\r\n          query.length\r\n        );\r\n\r\n        createSearchResult(\r\n          i,\r\n          snippet + \" (\" + pageMatches + \" match(es))\"\r\n        );\r\n      }\r\n\r\n      \/\/ Give the browser time to update the interface.\r\n      if (i % 5 === 0) {\r\n        await new Promise(resolve => setTimeout(resolve, 0));\r\n      }\r\n    }\r\n\r\n    if (token !== searchToken) return;\r\n\r\n    searchInfo.textContent =\r\n      totalMatches + \" match(es) across \" +\r\n      matchingPages + \" page(s).\";\r\n\r\n    if (matchingPages === 0) {\r\n      searchResults.textContent = \"No matching text found.\";\r\n    }\r\n\r\n  } catch (error) {\r\n    if (token === searchToken) {\r\n      console.error(\"PDF search error:\", error);\r\n      searchInfo.textContent =\r\n        \"Search failed. Please try again.\";\r\n    }\r\n  } finally {\r\n    if (token === searchToken) {\r\n      searchBusy = false;\r\n      searchBtn.disabled = false;\r\n    }\r\n  }\r\n}\r\n\r\nsearchBtn.addEventListener(\"click\", searchPDF);\r\n\r\nsearchInput.addEventListener(\"keydown\", function (event) {\r\n  if (event.key === \"Enter\") {\r\n    event.preventDefault();\r\n    searchPDF();\r\n  }\r\n});\r\n\r\nclearBtn.addEventListener(\"click\", function () {\r\n  ++searchToken;\r\n  searchBusy = false;\r\n  searchBtn.disabled = false;\r\n  searchInput.value = \"\";\r\n  searchInfo.textContent = \"\";\r\n  searchResults.replaceChildren();\r\n});\r\n\r\n  init();\r\n})();\r\n<\/script>\r\n\t\t\t\t<\/div>\n\t\t\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/div>\n\t\t\t\t\t<\/div>\n\t\t<\/section>\n\t\t\t\t<\/div>\n\t\t","protected":false},"excerpt":{"rendered":"<p>Prosiding iCitra 2026 \ud83d\udd0d Search Clear Loading PDF&#8230; \u276e Previous 0 \/ 0 Next \u276f \u26f6 Fullscreen Open PDF \u2197 Preparing document&#8230;<\/p>\n","protected":false},"author":101015,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"footnotes":""},"class_list":["post-20996","page","type-page","status-publish","hentry"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.5.1 - aioseo.com -->\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<link rel=\"canonical\" href=\"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.5.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"en_US\" \/>\n\t\t<meta property=\"og:site_name\" content=\"Fakulti Pengajian Sains Citra | Bijaksana Untuk Memperkasa | Wisdom To\u00a0Empower\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"Prosiding iCitra 2026 | Fakulti Pengajian Sains Citra\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2026-10-21T03:23:09+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2026-10-21T08:24:39+00:00\" \/>\n\t\t<meta name=\"twitter:card\" content=\"summary\" \/>\n\t\t<meta name=\"twitter:title\" content=\"Prosiding iCitra 2026 | Fakulti Pengajian Sains Citra\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"Prosiding iCitra 2026 | Fakulti Pengajian Sains Citra","description":"","canonical_url":"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"en_US","og:site_name":"Fakulti Pengajian Sains Citra | Bijaksana Untuk Memperkasa | Wisdom To\u00a0Empower","og:type":"article","og:title":"Prosiding iCitra 2026 | Fakulti Pengajian Sains Citra","og:url":"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/","article:published_time":"2026-10-21T03:23:09+00:00","article:modified_time":"2026-10-21T08:24:39+00:00","twitter:card":"summary","twitter:title":"Prosiding iCitra 2026 | Fakulti Pengajian Sains Citra"},"aioseo_meta_data":{"post_id":"20996","title":null,"description":null,"keywords":null,"keyphrases":{"focus":{"keyphrase":"","score":0,"analysis":{"keyphraseInTitle":{"score":0,"maxScore":9,"error":1}}},"additional":[]},"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":"","og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"WebPage","isEnabled":true},"graphs":[]},"schema_type":"default","schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":"-1","robots_max_videopreview":"-1","robots_max_imagepreview":"large","priority":null,"frequency":"default","location":null,"local_seo":null,"breadcrumb_settings":null,"limit_modified_date":false,"ai":{"faqs":[],"keyPoints":[],"schemas":[],"titles":[],"descriptions":[],"socialPosts":{"email":{"subject":"","preview":"","content":""},"linkedin":[],"twitter":[],"facebook":[],"instagram":[]}},"created":"2026-10-21 03:22:40","updated":"2026-10-21 09:57:08","seo_analyzer_scan_date":null,"focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"aioseo_breadcrumb":"<div class=\"aioseo-breadcrumbs\"><span class=\"aioseo-breadcrumb\">\n\t\t\t<a href=\"https:\/\/www.ukm.my\/citra\/\" title=\"Home\">Home<\/a>\n\t\t<\/span><span class=\"aioseo-breadcrumb-separator\">\u00bb<\/span><span class=\"aioseo-breadcrumb\">\n\t\t\tProsiding iCitra 2026\n\t\t<\/span><\/div>","aioseo_breadcrumb_json":[{"label":"Home","link":"https:\/\/www.ukm.my\/citra\/"},{"label":"Prosiding iCitra 2026","link":"https:\/\/www.ukm.my\/citra\/prosiding-icitra-2026\/"}],"_links":{"self":[{"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/pages\/20996","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/users\/101015"}],"replies":[{"embeddable":true,"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/comments?post=20996"}],"version-history":[{"count":10,"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/pages\/20996\/revisions"}],"predecessor-version":[{"id":21041,"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/pages\/20996\/revisions\/21041"}],"wp:attachment":[{"href":"https:\/\/www.ukm.my\/citra\/wp-json\/wp\/v2\/media?parent=20996"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}