{"id":1901,"date":"2026-07-29T21:10:56","date_gmt":"2026-07-29T14:10:56","guid":{"rendered":"https:\/\/aipnemaptma.org\/?page_id=1901"},"modified":"2026-08-13T15:53:16","modified_gmt":"2026-08-13T08:53:16","slug":"jurnal-ilmiah","status":"publish","type":"page","link":"https:\/\/aipnemaptma.org\/en\/jurnal-ilmiah\/","title":{"rendered":"Jurnal Ilmiah"},"content":{"rendered":"\t\t<div data-elementor-type=\"wp-page\" data-elementor-id=\"1901\" class=\"elementor elementor-1901\">\n\t\t\t\t\t\t<section class=\"elementor-section elementor-top-section elementor-element elementor-element-095984e elementor-section-boxed elementor-section-height-default elementor-section-height-default\" data-id=\"095984e\" data-element_type=\"section\" data-e-type=\"section\">\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-d62100e\" data-id=\"d62100e\" 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-ae7dfee elementor-widget elementor-widget-html\" data-id=\"ae7dfee\" data-element_type=\"widget\" data-e-type=\"widget\" data-widget_type=\"html.default\">\n\t\t\t\t<div class=\"elementor-widget-container\">\n\t\t\t\t\t<!-- =========================================================\nPAGE JURNAL ILMIAH AIPNEMA\nData dibaca langsung dari Google Sheets melalui Google Visualization API.\n\nSpreadsheet:\nhttps:\/\/docs.google.com\/spreadsheets\/d\/12_7KlAYEih5KElVm9n3zgq6HITNFufYLlVVfE4UP4lU\/edit?usp=sharing\n\nTampilan publik:\n- Cover jurnal\n- Nama jurnal\n- Institusi\/penerbit bila tersedia\n- Peringkat Sinta\n- Status DOAJ\n- Status Scopus\n- Cover dan judul menuju website jurnal\n\nTidak menyertakan header dan footer.\n========================================================= -->\n\n<style>\n:root{\n  --aip-journal-primary:#094528;\n  --aip-journal-primary-2:#0C5A35;\n  --aip-journal-dark:#052F1D;\n  --aip-journal-deep:#032416;\n  --aip-journal-gold:#D8B25C;\n  --aip-journal-gold-soft:#F5E9C9;\n  --aip-journal-blue:#1E6292;\n  --aip-journal-text:#1C2822;\n  --aip-journal-muted:#647169;\n  --aip-journal-border:#DCE5E0;\n  --aip-journal-soft:#EAF3EE;\n  --aip-journal-bg:#F7F9F8;\n  --aip-journal-white:#FFFFFF;\n  --aip-journal-container:1200px;\n  --aip-journal-shadow:0 18px 48px rgba(5,47,29,.10);\n  --aip-journal-shadow-sm:0 10px 28px rgba(5,47,29,.08);\n  --aip-journal-header-offset:120px;\n}\n\n.aip-journal-page,\n.aip-journal-page *{\n  box-sizing:border-box;\n}\n\n.aip-journal-page{\n  position:relative;\n  left:50%;\n  z-index:1;\n  width:100vw;\n  max-width:100vw;\n  margin:0 0 0 -50vw;\n  padding:0!important;\n  overflow-x:clip;\n  color:var(--aip-journal-text);\n  background:var(--aip-journal-white);\n  font-family:Inter,ui-sans-serif,system-ui,-apple-system,BlinkMacSystemFont,\"Segoe UI\",Arial,sans-serif;\n}\n\n.aip-journal-page [id]{\n  scroll-margin-top:var(--aip-journal-header-offset);\n}\n\n.aip-journal-page img{\n  display:block;\n  max-width:100%;\n}\n\n.aip-journal-page a{\n  color:inherit;\n}\n\n.aip-journal-page h1,\n.aip-journal-page h2,\n.aip-journal-page h3,\n.aip-journal-page p{\n  margin-top:0;\n}\n\n.aip-journal-container{\n  width:min(var(--aip-journal-container),calc(100% - 48px));\n  margin-inline:auto;\n}\n\n.aip-journal-section{\n  position:relative;\n  padding:86px 0;\n}\n\n.aip-journal-section-soft{\n  background:var(--aip-journal-bg);\n}\n\n.aip-journal-kicker{\n  display:inline-flex;\n  align-items:center;\n  gap:10px;\n  margin:0 0 15px;\n  color:var(--aip-journal-primary);\n  font-size:13px;\n  font-weight:800;\n  letter-spacing:.12em;\n  text-transform:uppercase;\n}\n\n.aip-journal-kicker::before{\n  content:\"\";\n  width:30px;\n  height:3px;\n  border-radius:999px;\n  background:var(--aip-journal-gold);\n}\n\n.aip-journal-heading{\n  max-width:900px;\n  margin:0;\n  color:var(--aip-journal-text);\n  font-size:clamp(32px,4.4vw,52px);\n  font-weight:820;\n  letter-spacing:-.04em;\n  line-height:1.12;\n}\n\n\/* HERO *\/\n.aip-journal-hero{\n  position:relative;\n  isolation:isolate;\n  overflow:hidden;\n  color:var(--aip-journal-white);\n  background:\n    radial-gradient(circle at 85% 16%,rgba(216,178,92,.20),transparent 24%),\n    linear-gradient(125deg,var(--aip-journal-deep),var(--aip-journal-primary) 60%,#0A5A34);\n}\n\n.aip-journal-hero::before{\n  content:\"\";\n  position:absolute;\n  inset:0;\n  z-index:-1;\n  opacity:.11;\n  background-image:\n    linear-gradient(rgba(255,255,255,.28) 1px,transparent 1px),\n    linear-gradient(90deg,rgba(255,255,255,.28) 1px,transparent 1px);\n  background-size:54px 54px;\n  mask-image:linear-gradient(to right,#000,transparent 82%);\n}\n\n.aip-journal-hero-grid{\n  display:grid;\n  grid-template-columns:minmax(0,1.08fr) minmax(330px,.92fr);\n  align-items:center;\n  gap:62px;\n  padding:82px 0 76px;\n}\n\n.aip-journal-breadcrumb{\n  display:flex;\n  flex-wrap:wrap;\n  gap:8px;\n  margin-bottom:24px;\n  color:#CFE0D6;\n  font-size:13px;\n}\n\n.aip-journal-breadcrumb a{\n  text-decoration:none;\n}\n\n.aip-journal-breadcrumb a:hover{\n  color:#F0D99F;\n}\n\n.aip-journal-hero-badge{\n  display:inline-flex;\n  align-items:center;\n  gap:9px;\n  margin-bottom:22px;\n  padding:8px 13px;\n  border:1px solid rgba(255,255,255,.20);\n  border-radius:999px;\n  color:#F7EBCB;\n  background:rgba(255,255,255,.08);\n  font-size:13px;\n  font-weight:760;\n}\n\n.aip-journal-hero-badge::before{\n  content:\"\";\n  width:8px;\n  height:8px;\n  border-radius:50%;\n  background:var(--aip-journal-gold);\n}\n\n.aip-journal-hero h1{\n  max-width:800px;\n  margin:0;\n  color:var(--aip-journal-white);\n  font-size:clamp(42px,5.2vw,66px);\n  font-weight:830;\n  letter-spacing:-.05em;\n  line-height:1.04;\n}\n\n.aip-journal-hero h1 span{\n  color:#F1D99D;\n}\n\n.aip-journal-hero-text{\n  max-width:740px;\n  margin:24px 0 0;\n  color:#DCEBE2;\n  font-size:17px;\n  line-height:1.78;\n}\n\n.aip-journal-hero-panel{\n  padding:30px;\n  border:1px solid rgba(255,255,255,.16);\n  border-radius:24px;\n  background:rgba(255,255,255,.08);\n  box-shadow:0 28px 64px rgba(0,0,0,.17);\n  backdrop-filter:blur(8px);\n}\n\n.aip-journal-logo-wrap{\n  display:inline-flex;\n  padding:13px 15px;\n  border-radius:14px;\n  background:var(--aip-journal-white);\n  box-shadow:0 12px 30px rgba(0,0,0,.15);\n}\n\n.aip-journal-logo-wrap img{\n  width:190px;\n  height:auto;\n}\n\n.aip-journal-hero-panel p{\n  margin:20px 0 0;\n  color:#DCEBE2;\n  font-size:14px;\n  line-height:1.75;\n}\n\n.aip-journal-stat-grid{\n  display:grid;\n  grid-template-columns:repeat(2,1fr);\n  gap:12px;\n  margin-top:22px;\n}\n\n.aip-journal-stat{\n  padding:17px;\n  border:1px solid rgba(255,255,255,.13);\n  border-radius:14px;\n  background:rgba(255,255,255,.07);\n}\n\n.aip-journal-stat strong{\n  display:block;\n  color:#F2DCA5;\n  font-size:25px;\n  font-weight:840;\n  line-height:1.05;\n}\n\n.aip-journal-stat span{\n  display:block;\n  margin-top:7px;\n  color:#DCEBE2;\n  font-size:12px;\n  line-height:1.45;\n}\n\n\/* TOOLBAR *\/\n\n\/* =========================================================\n   JOURNAL ANALYTICS DASHBOARD\n========================================================= *\/\n.aip-journal-analytics{\n  overflow:hidden;\n  background:\n    radial-gradient(circle at 7% 8%,rgba(216,178,92,.11),transparent 22%),\n    radial-gradient(circle at 92% 3%,rgba(9,69,40,.09),transparent 25%),\n    var(--aip-journal-white);\n}\n\n.aip-journal-analytics-head{\n  display:flex;\n  align-items:flex-end;\n  justify-content:space-between;\n  gap:32px;\n}\n\n.aip-journal-analytics-copy{\n  max-width:820px;\n}\n\n.aip-journal-analytics-lead{\n  max-width:790px;\n  margin:17px 0 0;\n  color:var(--aip-journal-muted);\n  font-size:15px;\n  line-height:1.76;\n}\n\n.aip-journal-analytics-note{\n  display:inline-flex;\n  align-items:center;\n  gap:9px;\n  flex:0 0 auto;\n  padding:9px 12px;\n  border:1px solid rgba(216,178,92,.30);\n  border-radius:999px;\n  color:#765916;\n  background:var(--aip-journal-gold-soft);\n  font-size:9px;\n  font-weight:800;\n  line-height:1.4;\n}\n\n.aip-journal-analytics-note::before{\n  content:\"\";\n  width:7px;\n  height:7px;\n  flex:0 0 7px;\n  border-radius:50%;\n  background:var(--aip-journal-gold);\n  box-shadow:0 0 0 4px rgba(216,178,92,.14);\n}\n\n.aip-journal-analytics-grid{\n  display:grid;\n  grid-template-columns:minmax(0,1.05fr) minmax(0,.95fr);\n  gap:18px;\n  margin-top:34px;\n}\n\n.aip-journal-analytics-card{\n  min-width:0;\n  overflow:hidden;\n  border:1px solid var(--aip-journal-border);\n  border-radius:20px;\n  background:rgba(255,255,255,.96);\n  box-shadow:0 15px 38px rgba(5,47,29,.08);\n}\n\n.aip-journal-analytics-card-wide{\n  grid-column:1\/-1;\n}\n\n.aip-journal-analytics-card-head{\n  display:flex;\n  align-items:flex-start;\n  justify-content:space-between;\n  gap:20px;\n  padding:22px 23px 18px;\n  border-bottom:1px solid var(--aip-journal-border);\n  background:\n    linear-gradient(135deg,rgba(234,243,238,.74),rgba(255,255,255,.98));\n}\n\n.aip-journal-analytics-card-head h3{\n  margin:0;\n  color:var(--aip-journal-text);\n  font-size:18px;\n  letter-spacing:-.02em;\n  line-height:1.35;\n}\n\n.aip-journal-analytics-card-head p{\n  margin:6px 0 0;\n  color:var(--aip-journal-muted);\n  font-size:10px;\n  line-height:1.55;\n}\n\n.aip-journal-analytics-card-head span{\n  flex:0 0 auto;\n  color:var(--aip-journal-primary);\n  font-size:9px;\n  font-weight:800;\n  line-height:1.4;\n}\n\n\/* SINTA HORIZONTAL BAR *\/\n.aip-journal-sinta-chart{\n  display:grid;\n  gap:11px;\n  min-height:290px;\n  padding:22px 23px 24px;\n}\n\n.aip-journal-chart-bar{\n  position:relative;\n  display:grid;\n  grid-template-columns:92px minmax(180px,1fr) 76px;\n  align-items:center;\n  gap:13px;\n  min-height:45px;\n  padding:7px 8px;\n  border:1px solid transparent;\n  border-radius:11px;\n  cursor:pointer;\n  transition:\n    transform .2s ease,\n    background .2s ease,\n    border-color .2s ease,\n    box-shadow .2s ease;\n}\n\n.aip-journal-chart-bar:hover,\n.aip-journal-chart-bar:focus-visible{\n  transform:translateY(-2px);\n  border-color:rgba(9,69,40,.20);\n  background:var(--aip-journal-bg);\n  box-shadow:0 9px 22px rgba(5,47,29,.07);\n  outline:none;\n}\n\n.aip-journal-chart-bar.is-active{\n  border-color:var(--aip-journal-primary);\n  background:var(--aip-journal-soft);\n  box-shadow:0 0 0 3px rgba(9,69,40,.07);\n}\n\n.aip-journal-chart-bar-label{\n  color:var(--aip-journal-text);\n  font-size:10.5px;\n  font-weight:760;\n}\n\n.aip-journal-chart-track{\n  position:relative;\n  height:15px;\n  overflow:hidden;\n  border-radius:999px;\n  background:#E5ECE8;\n  box-shadow:inset 0 1px 2px rgba(5,47,29,.07);\n}\n\n.aip-journal-chart-fill{\n  position:absolute;\n  inset:0 auto 0 0;\n  width:0;\n  min-width:0;\n  overflow:hidden;\n  border-radius:inherit;\n  background:\n    linear-gradient(\n      90deg,\n      var(--chart-color-start,var(--aip-journal-primary)),\n      var(--chart-color-end,var(--aip-journal-primary-2))\n    );\n  transition:\n    width .9s cubic-bezier(.18,.76,.22,1),\n    transform .2s ease,\n    filter .2s ease;\n}\n\n.aip-journal-chart-fill::after{\n  content:\"\";\n  position:absolute;\n  inset:0;\n  transform:translateX(-110%);\n  background:\n    linear-gradient(\n      105deg,\n      transparent 20%,\n      rgba(255,255,255,.36) 48%,\n      transparent 75%\n    );\n  animation:aipJournalChartShine 3.1s ease-in-out 1.2s infinite;\n}\n\n.aip-journal-chart-bar:hover .aip-journal-chart-fill,\n.aip-journal-chart-bar.is-active .aip-journal-chart-fill{\n  transform:scaleY(1.15);\n  filter:saturate(1.12) brightness(1.03);\n}\n\n@keyframes aipJournalChartShine{\n  0%,58%,100%{transform:translateX(-110%)}\n  78%{transform:translateX(110%)}\n}\n\n.aip-journal-chart-value{\n  display:flex;\n  justify-content:flex-end;\n  align-items:baseline;\n  gap:5px;\n  white-space:nowrap;\n}\n\n.aip-journal-chart-value strong{\n  color:var(--aip-journal-text);\n  font-size:13px;\n  font-weight:850;\n}\n\n.aip-journal-chart-value span{\n  color:var(--aip-journal-primary);\n  font-size:9px;\n  font-weight:780;\n}\n\n\/* INDEXING DONUT *\/\n.aip-journal-index-layout{\n  display:grid;\n  grid-template-columns:repeat(2,minmax(0,1fr));\n  gap:16px;\n  padding:24px 22px 26px;\n}\n\n.aip-journal-index-card{\n  display:grid;\n  justify-items:center;\n  gap:14px;\n  min-width:0;\n  padding:18px 13px;\n  border:1px solid var(--aip-journal-border);\n  border-radius:16px;\n  background:var(--aip-journal-bg);\n}\n\n.aip-journal-index-card h4{\n  margin:0;\n  color:var(--aip-journal-text);\n  font-size:12px;\n  font-weight:780;\n}\n\n.aip-journal-donut{\n  position:relative;\n  width:126px;\n  height:126px;\n  border-radius:50%;\n  opacity:0;\n  transform:scale(.80) rotate(-16deg);\n  background:var(--donut-gradient,#E4EBE7);\n  box-shadow:0 11px 25px rgba(5,47,29,.08);\n  transition:\n    opacity .35s ease,\n    transform .75s cubic-bezier(.18,.78,.22,1);\n}\n\n.aip-journal-donut.is-ready{\n  opacity:1;\n  transform:scale(1) rotate(0);\n}\n\n.aip-journal-donut::before{\n  content:\"\";\n  position:absolute;\n  inset:18px;\n  border-radius:50%;\n  background:\n    radial-gradient(circle at 34% 28%,#FFFFFF,#F9FBFA 76%);\n  box-shadow:inset 0 0 0 1px rgba(9,69,40,.04);\n}\n\n.aip-journal-donut-center{\n  position:absolute;\n  inset:0;\n  z-index:2;\n  display:grid;\n  place-content:center;\n  text-align:center;\n  pointer-events:none;\n}\n\n.aip-journal-donut-center strong{\n  color:var(--aip-journal-text);\n  font-size:22px;\n  font-weight:850;\n  letter-spacing:-.04em;\n  line-height:1;\n}\n\n.aip-journal-donut-center span{\n  display:block;\n  margin-top:5px;\n  color:var(--aip-journal-muted);\n  font-size:8px;\n  font-weight:700;\n}\n\n.aip-journal-index-summary{\n  color:var(--aip-journal-muted);\n  font-size:9.5px;\n  font-weight:680;\n  line-height:1.5;\n  text-align:center;\n}\n\n\/* INSTITUTION DISTRIBUTION *\/\n.aip-journal-institution-chart{\n  display:grid;\n  gap:10px;\n  min-height:270px;\n  padding:23px 24px 26px;\n}\n\n.aip-journal-institution-row{\n  display:grid;\n  grid-template-columns:minmax(175px,.34fr) minmax(220px,.56fr) 38px;\n  align-items:center;\n  gap:13px;\n}\n\n.aip-journal-institution-label{\n  min-width:0;\n  overflow:hidden;\n  color:var(--aip-journal-text);\n  font-size:10px;\n  font-weight:690;\n  line-height:1.35;\n  text-overflow:ellipsis;\n  white-space:nowrap;\n}\n\n.aip-journal-institution-track{\n  height:11px;\n  overflow:hidden;\n  border-radius:999px;\n  background:#E5ECE8;\n}\n\n.aip-journal-institution-fill{\n  display:block;\n  width:0;\n  height:100%;\n  border-radius:inherit;\n  background:\n    linear-gradient(\n      90deg,\n      var(--aip-journal-primary),\n      var(--aip-journal-gold)\n    );\n  transition:width .9s cubic-bezier(.18,.76,.22,1);\n}\n\n.aip-journal-institution-row:hover .aip-journal-institution-fill{\n  filter:saturate(1.14) brightness(1.03);\n}\n\n.aip-journal-institution-value{\n  color:var(--aip-journal-primary);\n  font-size:11px;\n  font-weight:850;\n  text-align:right;\n}\n\n.aip-journal-chart-empty{\n  display:grid;\n  place-items:center;\n  min-height:220px;\n  padding:30px;\n  color:var(--aip-journal-muted);\n  text-align:center;\n  font-size:12px;\n  line-height:1.7;\n}\n\n\/* COVER IMPROVEMENTS *\/\n.aip-journal-cover{\n  transition:\n    transform .42s cubic-bezier(.18,.76,.22,1),\n    filter .35s ease,\n    opacity .25s ease;\n}\n\n.aip-journal-cover-link:hover .aip-journal-cover{\n  transform:scale(1.035);\n  filter:saturate(1.05) contrast(1.02);\n}\n\n.aip-journal-cover.is-loading{\n  opacity:.25;\n}\n\n.aip-journal-cover.is-ready{\n  opacity:1;\n}\n\n.aip-journal-card{\n  transition:\n    transform .25s ease,\n    border-color .25s ease,\n    box-shadow .25s ease;\n}\n\n.aip-journal-card:hover{\n  transform:translateY(-5px);\n  border-color:rgba(9,69,40,.28);\n  box-shadow:0 18px 40px rgba(5,47,29,.12);\n}\n\n@media(max-width:920px){\n  .aip-journal-analytics-head{\n    align-items:flex-start;\n    flex-direction:column;\n  }\n\n  .aip-journal-analytics-grid{\n    grid-template-columns:1fr;\n  }\n\n  .aip-journal-analytics-card-wide{\n    grid-column:auto;\n  }\n}\n\n@media(max-width:680px){\n  .aip-journal-index-layout{\n    grid-template-columns:1fr;\n  }\n\n  .aip-journal-chart-bar{\n    grid-template-columns:82px minmax(110px,1fr) 70px;\n    gap:9px;\n  }\n\n  .aip-journal-institution-row{\n    grid-template-columns:minmax(115px,.42fr) minmax(110px,.48fr) 30px;\n    gap:8px;\n  }\n\n  .aip-journal-analytics-note{\n    white-space:normal;\n  }\n}\n\n\n.aip-journal-toolbar{\n  display:grid;\n  grid-template-columns:minmax(260px,1fr) 160px 155px 155px 175px;\n  gap:13px;\n  margin-top:38px;\n}\n\n.aip-journal-search-wrap{\n  position:relative;\n}\n\n.aip-journal-search-icon{\n  position:absolute;\n  top:50%;\n  left:15px;\n  width:20px;\n  height:20px;\n  transform:translateY(-50%);\n  color:var(--aip-journal-primary);\n  pointer-events:none;\n}\n\n.aip-journal-input,\n.aip-journal-select{\n  width:100%;\n  min-height:50px;\n  border:1px solid var(--aip-journal-border);\n  border-radius:12px;\n  color:var(--aip-journal-text);\n  background:var(--aip-journal-white);\n  font-family:inherit;\n  font-size:14px;\n  outline:none;\n}\n\n.aip-journal-input{\n  padding:0 15px 0 44px;\n}\n\n.aip-journal-select{\n  padding:0 38px 0 14px;\n}\n\n.aip-journal-input:focus,\n.aip-journal-select:focus{\n  border-color:var(--aip-journal-primary);\n  box-shadow:0 0 0 4px rgba(9,69,40,.10);\n}\n\n.aip-journal-result-bar{\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  gap:20px;\n  margin:25px 0 20px;\n  color:var(--aip-journal-muted);\n  font-size:13px;\n}\n\n.aip-journal-reset{\n  border:0;\n  color:var(--aip-journal-primary);\n  background:transparent;\n  font-family:inherit;\n  font-size:12px;\n  font-weight:760;\n  cursor:pointer;\n}\n\n\/* GRID *\/\n.aip-journal-grid{\n  display:grid;\n  grid-template-columns:repeat(4,minmax(0,1fr));\n  gap:20px;\n}\n\n.aip-journal-card{\n  display:flex;\n  flex-direction:column;\n  min-width:0;\n  overflow:hidden;\n  border:1px solid var(--aip-journal-border);\n  border-radius:19px;\n  background:var(--aip-journal-white);\n  transition:.22s ease;\n}\n\n.aip-journal-card:hover{\n  transform:translateY(-6px);\n  border-color:rgba(9,69,40,.30);\n  box-shadow:var(--aip-journal-shadow-sm);\n}\n\n.aip-journal-cover-link{\n  position:relative;\n  display:block;\n  overflow:hidden;\n  aspect-ratio:3\/4;\n  padding:18px;\n  background:\n    radial-gradient(circle at 50% 25%,rgba(216,178,92,.14),transparent 28%),\n    linear-gradient(145deg,#EEF5F1,#DCEAE2);\n  text-decoration:none!important;\n}\n\n.aip-journal-cover-link::after{\n  content:\"Buka Jurnal\";\n  position:absolute;\n  inset:auto 16px 16px;\n  display:flex;\n  align-items:center;\n  justify-content:center;\n  min-height:42px;\n  border-radius:10px;\n  color:var(--aip-journal-white);\n  background:rgba(5,47,29,.92);\n  font-size:12px;\n  font-weight:780;\n  opacity:0;\n  transform:translateY(8px);\n  transition:.22s ease;\n}\n\n.aip-journal-card:hover .aip-journal-cover-link::after{\n  opacity:1;\n  transform:translateY(0);\n}\n\n.aip-journal-cover{\n  width:100%;\n  height:100%;\n  object-fit:contain;\n  object-position:center;\n  border-radius:7px;\n  background:var(--aip-journal-white);\n  box-shadow:0 16px 30px rgba(5,47,29,.16);\n  transition:transform .35s ease;\n}\n\n.aip-journal-card:hover .aip-journal-cover{\n  transform:scale(1.025);\n}\n\n.aip-journal-cover-fallback{\n  position:absolute;\n  inset:18px;\n  display:none;\n  place-items:center;\n  border-radius:7px;\n  background:\n    radial-gradient(circle at 50% 25%,rgba(216,178,92,.17),transparent 24%),\n    linear-gradient(145deg,var(--aip-journal-dark),var(--aip-journal-primary));\n  box-shadow:0 16px 30px rgba(5,47,29,.16);\n}\n\n.aip-journal-cover-fallback img{\n  width:70%;\n  height:auto;\n  object-fit:contain;\n  filter:drop-shadow(0 10px 20px rgba(0,0,0,.16));\n}\n\n.aip-journal-card-body{\n  display:flex;\n  flex-direction:column;\n  flex:1;\n  padding:22px;\n}\n\n.aip-journal-card h3{\n  overflow-wrap:anywhere;\n  margin:0;\n  color:var(--aip-journal-text);\n  font-size:18px;\n  line-height:1.48;\n}\n\n.aip-journal-card h3 a{\n  text-decoration:none!important;\n}\n\n.aip-journal-card h3 a:hover{\n  color:var(--aip-journal-primary);\n}\n\n.aip-journal-institution{\n  margin:9px 0 0;\n  color:var(--aip-journal-muted);\n  font-size:12px;\n  line-height:1.58;\n}\n\n.aip-journal-badges{\n  display:flex;\n  flex-wrap:wrap;\n  gap:8px;\n  margin-top:18px;\n}\n\n.aip-journal-badge{\n  display:inline-flex;\n  align-items:center;\n  justify-content:center;\n  min-height:31px;\n  padding:0 10px;\n  border:1px solid transparent;\n  border-radius:999px;\n  font-size:10px;\n  font-weight:820;\n  letter-spacing:.04em;\n  line-height:1.2;\n  text-transform:uppercase;\n}\n\n.aip-journal-sinta-1{\n  color:#FFFFFF;\n  background:#7B1FA2;\n}\n\n.aip-journal-sinta-2{\n  color:#FFFFFF;\n  background:#1565C0;\n}\n\n.aip-journal-sinta-3{\n  color:#FFFFFF;\n  background:#00897B;\n}\n\n.aip-journal-sinta-4{\n  color:#FFFFFF;\n  background:#558B2F;\n}\n\n.aip-journal-sinta-5{\n  color:#4B3A0A;\n  background:#F2D16B;\n}\n\n.aip-journal-sinta-6{\n  color:#5F420D;\n  background:#E8C38B;\n}\n\n.aip-journal-sinta-other{\n  color:var(--aip-journal-primary);\n  background:var(--aip-journal-soft);\n  border-color:rgba(9,69,40,.18);\n}\n\n.aip-journal-doaj-active{\n  color:#FFFFFF;\n  background:#F47920;\n}\n\n.aip-journal-scopus-active{\n  color:#FFFFFF;\n  background:#1E6292;\n}\n\n.aip-journal-index-inactive{\n  color:#7A8580;\n  background:#EEF1EF;\n  border-color:#D8DFDB;\n}\n\n.aip-journal-card-link{\n  display:inline-flex;\n  align-items:center;\n  gap:7px;\n  width:max-content;\n  margin-top:auto;\n  padding-top:22px;\n  color:var(--aip-journal-primary)!important;\n  text-decoration:none!important;\n  font-size:12px;\n  font-weight:780;\n}\n\n.aip-journal-card-link svg{\n  width:16px;\n  height:16px;\n  transition:transform .2s ease;\n}\n\n.aip-journal-card-link:hover svg{\n  transform:translateX(4px);\n}\n\n\/* STATE *\/\n.aip-journal-state{\n  grid-column:1\/-1;\n  padding:43px;\n  border:1px dashed var(--aip-journal-border);\n  border-radius:17px;\n  color:var(--aip-journal-muted);\n  background:var(--aip-journal-white);\n  text-align:center;\n  font-size:14px;\n  line-height:1.75;\n}\n\n.aip-journal-state strong{\n  display:block;\n  margin-bottom:7px;\n  color:var(--aip-journal-text);\n  font-size:17px;\n}\n\n.aip-journal-loading{\n  display:inline-flex;\n  gap:5px;\n  margin-top:13px;\n}\n\n.aip-journal-loading span{\n  width:7px;\n  height:7px;\n  border-radius:50%;\n  background:var(--aip-journal-primary);\n  animation:aipJournalPulse 1.15s infinite ease-in-out;\n}\n\n.aip-journal-loading span:nth-child(2){\n  animation-delay:.16s;\n}\n\n.aip-journal-loading span:nth-child(3){\n  animation-delay:.32s;\n}\n\n@keyframes aipJournalPulse{\n  0%,80%,100%{opacity:.25;transform:scale(.8)}\n  40%{opacity:1;transform:scale(1)}\n}\n\n\/* CTA *\/\n.aip-journal-cta{\n  padding-top:0;\n  background:var(--aip-journal-bg);\n}\n\n.aip-journal-cta-card{\n  display:flex;\n  align-items:center;\n  justify-content:space-between;\n  gap:38px;\n  padding:38px 42px;\n  border:1px solid var(--aip-journal-border);\n  border-radius:20px;\n  background:var(--aip-journal-white);\n  box-shadow:var(--aip-journal-shadow-sm);\n}\n\n.aip-journal-cta-card h2{\n  margin:0;\n  color:var(--aip-journal-text);\n  font-size:clamp(26px,3.2vw,38px);\n  letter-spacing:-.03em;\n  line-height:1.2;\n}\n\n.aip-journal-cta-card p{\n  margin:10px 0 0;\n  color:var(--aip-journal-muted);\n  font-size:15px;\n  line-height:1.7;\n}\n\n.aip-journal-cta-button{\n  display:inline-flex;\n  align-items:center;\n  justify-content:center;\n  min-height:50px;\n  padding:0 22px;\n  border:1px solid var(--aip-journal-primary);\n  border-radius:11px;\n  color:var(--aip-journal-white)!important;\n  background:var(--aip-journal-primary);\n  text-decoration:none!important;\n  font-size:15px;\n  font-weight:760;\n  transition:.22s ease;\n}\n\n.aip-journal-cta-button:hover{\n  transform:translateY(-2px);\n  background:var(--aip-journal-primary-2);\n}\n\n@media(max-width:1100px){\n  .aip-journal-grid{\n    grid-template-columns:repeat(3,minmax(0,1fr));\n  }\n\n  .aip-journal-toolbar{\n    grid-template-columns:minmax(240px,1fr) repeat(4,minmax(145px,.42fr));\n  }\n}\n\n@media(max-width:920px){\n  .aip-journal-hero-grid{\n    grid-template-columns:1fr;\n  }\n\n  .aip-journal-hero-grid{\n    gap:38px;\n    padding:68px 0;\n  }\n\n  .aip-journal-hero-panel{\n    max-width:620px;\n  }\n\n  .aip-journal-section{\n    padding:68px 0;\n  }\n\n  .aip-journal-toolbar{\n    grid-template-columns:1fr 1fr;\n  }\n\n  .aip-journal-search-wrap{\n    grid-column:1\/-1;\n  }\n}\n\n@media(max-width:760px){\n  .aip-journal-grid{\n    grid-template-columns:repeat(2,minmax(0,1fr));\n  }\n}\n\n@media(max-width:560px){\n  :root{\n    --aip-journal-header-offset:105px;\n  }\n\n  .aip-journal-container{\n    width:min(var(--aip-journal-container),calc(100% - 30px));\n  }\n\n  .aip-journal-section{\n    padding:56px 0;\n  }\n\n  .aip-journal-hero-grid{\n    padding:52px 0;\n  }\n\n  .aip-journal-hero h1{\n    font-size:clamp(38px,11vw,50px);\n  }\n\n  .aip-journal-hero-text{\n    font-size:16px;\n  }\n\n  .aip-journal-toolbar{\n    grid-template-columns:1fr;\n  }\n\n  .aip-journal-search-wrap{\n    grid-column:auto;\n  }\n\n  .aip-journal-result-bar{\n    align-items:flex-start;\n    flex-direction:column;\n  }\n\n  .aip-journal-grid{\n    grid-template-columns:1fr;\n  }\n\n  .aip-journal-cover-link{\n    max-height:560px;\n  }\n\n  .aip-journal-cta-card{\n    flex-direction:column;\n    align-items:flex-start;\n    padding:30px 24px;\n  }\n\n  .aip-journal-cta-button{\n    width:100%;\n  }\n}\n<\/style>\n\n<main class=\"aip-journal-page\" id=\"aipJournalPage\">\n\n  <header class=\"aip-journal-hero\">\n    <div class=\"aip-journal-container aip-journal-hero-grid\">\n      <div>\n        <nav class=\"aip-journal-breadcrumb\" aria-label=\"Breadcrumb\">\n          <a href=\"https:\/\/aipnemaptma.org\/\">Beranda<\/a>\n          <span>\/<\/span>\n          <span>Jurnal Ilmiah<\/span>\n        <\/nav>\n\n        <div class=\"aip-journal-hero-badge\">\n          Publikasi Ilmiah Institusi Anggota\n        <\/div>\n\n        <h1>\n          Jurnal Ilmiah <span>AIPNEMA<\/span>\n        <\/h1>\n\n        <p class=\"aip-journal-hero-text\">\n          Direktori jurnal ilmiah yang dikelola oleh institusi anggota,\n          dilengkapi peringkat Sinta serta informasi pengindeksan DOAJ\n          dan Scopus.\n        <\/p>\n      <\/div>\n\n      <aside class=\"aip-journal-hero-panel\">\n        <div class=\"aip-journal-logo-wrap\">\n          <img fetchpriority=\"high\" decoding=\"async\"\n            src=\"https:\/\/aipnemaptma.org\/wp-content\/uploads\/2023\/07\/logo-aipnema2.png\"\n            alt=\"Logo AIPNEMA\"\n            width=\"500\"\n            height=\"150\"\n            loading=\"eager\"\n          >\n        <\/div>\n\n        <p>\n          Klik cover atau nama jurnal untuk mengunjungi website jurnal\n          dan membaca informasi publikasi secara langsung.\n        <\/p>\n\n        <div class=\"aip-journal-stat-grid\">\n          <div class=\"aip-journal-stat\">\n            <strong id=\"aipJournalTotal\">\u2013<\/strong>\n            <span>Jurnal terdata<\/span>\n          <\/div>\n\n          <div class=\"aip-journal-stat\">\n            <strong id=\"aipJournalSintaCount\">\u2013<\/strong>\n            <span>Terakreditasi Sinta<\/span>\n          <\/div>\n\n          <div class=\"aip-journal-stat\">\n            <strong id=\"aipJournalDoajCount\">\u2013<\/strong>\n            <span>Terindeks DOAJ<\/span>\n          <\/div>\n\n          <div class=\"aip-journal-stat\">\n            <strong id=\"aipJournalScopusCount\">\u2013<\/strong>\n            <span>Terindeks Scopus<\/span>\n          <\/div>\n        <\/div>\n      <\/aside>\n    <\/div>\n  <\/header>\n\n\n  <section\n    class=\"aip-journal-section aip-journal-analytics\"\n    id=\"profil-jurnal\"\n  >\n    <div class=\"aip-journal-container\">\n      <div class=\"aip-journal-analytics-head\">\n        <div class=\"aip-journal-analytics-copy\">\n          <p class=\"aip-journal-kicker\">Profil Jurnal<\/p>\n\n          <h2 class=\"aip-journal-heading\">\n            Profil mutu dan indeksasi jurnal AIPNEMA.\n          <\/h2>\n\n          <p class=\"aip-journal-analytics-lead\">\n            Ringkasan visual ini memperlihatkan komposisi peringkat Sinta,\n            status indeksasi, dan sebaran jurnal berdasarkan institusi pengelola.\n          <\/p>\n        <\/div>\n\n        <span class=\"aip-journal-analytics-note\">\n          Grafik dihitung otomatis dari seluruh jurnal terdata\n        <\/span>\n      <\/div>\n\n      <div class=\"aip-journal-analytics-grid\">\n        <article class=\"aip-journal-analytics-card\">\n          <header class=\"aip-journal-analytics-card-head\">\n            <div>\n              <h3>Distribusi Peringkat Sinta<\/h3>\n              <p>Frekuensi dan persentase jurnal pada setiap peringkat.<\/p>\n            <\/div>\n\n            <span>Klik batang untuk memfilter<\/span>\n          <\/header>\n\n          <div\n            class=\"aip-journal-sinta-chart\"\n            id=\"aipJournalSintaChart\"\n            aria-live=\"polite\"\n          >\n            <div class=\"aip-journal-chart-empty\">\n              Menyiapkan grafik Sinta\u2026\n            <\/div>\n          <\/div>\n        <\/article>\n\n        <article class=\"aip-journal-analytics-card\">\n          <header class=\"aip-journal-analytics-card-head\">\n            <div>\n              <h3>Status Indeksasi<\/h3>\n              <p>Proporsi jurnal yang telah terindeks DOAJ dan Scopus.<\/p>\n            <\/div>\n\n            <span>Persentase seluruh jurnal<\/span>\n          <\/header>\n\n          <div class=\"aip-journal-index-layout\" id=\"aipJournalIndexChart\">\n            <div class=\"aip-journal-chart-empty\">\n              Menyiapkan grafik indeksasi\u2026\n            <\/div>\n          <\/div>\n        <\/article>\n\n        <article class=\"aip-journal-analytics-card aip-journal-analytics-card-wide\">\n          <header class=\"aip-journal-analytics-card-head\">\n            <div>\n              <h3>Sebaran Jurnal Berdasarkan Institusi<\/h3>\n              <p>Institusi dengan jumlah jurnal terdata terbanyak.<\/p>\n            <\/div>\n\n            <span>Maksimal 10 institusi<\/span>\n          <\/header>\n\n          <div\n            class=\"aip-journal-institution-chart\"\n            id=\"aipJournalInstitutionChart\"\n            aria-live=\"polite\"\n          >\n            <div class=\"aip-journal-chart-empty\">\n              Menyiapkan grafik institusi\u2026\n            <\/div>\n          <\/div>\n        <\/article>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"aip-journal-section aip-journal-section-soft\" id=\"direktori-jurnal\">\n    <div class=\"aip-journal-container\">\n      <p class=\"aip-journal-kicker\">Direktori Jurnal<\/p>\n\n      <h2 class=\"aip-journal-heading\">\n        Jurnal diurutkan dari peringkat terbaik hingga terendah.\n      <\/h2>\n\n      <div class=\"aip-journal-toolbar\">\n        <div class=\"aip-journal-search-wrap\">\n          <svg class=\"aip-journal-search-icon\" viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n            <circle cx=\"11\" cy=\"11\" r=\"7\" stroke=\"currentColor\" stroke-width=\"2\"\/>\n            <path d=\"M20 20l-3.5-3.5\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\"\/>\n          <\/svg>\n\n          <input\n            class=\"aip-journal-input\"\n            id=\"aipJournalSearch\"\n            type=\"search\"\n            placeholder=\"Cari nama jurnal atau institusi\u2026\"\n            autocomplete=\"off\"\n          >\n        <\/div>\n\n        <select class=\"aip-journal-select\" id=\"aipJournalSinta\">\n          <option value=\"\">Semua Sinta<\/option>\n        <\/select>\n\n        <select class=\"aip-journal-select\" id=\"aipJournalDoaj\">\n          <option value=\"\">Semua DOAJ<\/option>\n          <option value=\"indexed\">Terindeks DOAJ<\/option>\n          <option value=\"not-indexed\">Belum\/Tidak terindeks<\/option>\n        <\/select>\n\n        <select class=\"aip-journal-select\" id=\"aipJournalScopus\">\n          <option value=\"\">Semua Scopus<\/option>\n          <option value=\"indexed\">Terindeks Scopus<\/option>\n          <option value=\"not-indexed\">Belum\/Tidak terindeks<\/option>\n        <\/select>\n\n        <select class=\"aip-journal-select\" id=\"aipJournalSort\">\n          <option value=\"ranking\">Peringkat Terbaik<\/option>\n          <option value=\"name-asc\">Nama A\u2013Z<\/option>\n          <option value=\"name-desc\">Nama Z\u2013A<\/option>\n        <\/select>\n      <\/div>\n\n      <div class=\"aip-journal-result-bar\">\n        <span id=\"aipJournalResultText\">Memuat data jurnal\u2026<\/span>\n\n        <button\n          class=\"aip-journal-reset\"\n          id=\"aipJournalReset\"\n          type=\"button\"\n        >\n          Reset pencarian\n        <\/button>\n      <\/div>\n\n      <div class=\"aip-journal-grid\" id=\"aipJournalGrid\" aria-live=\"polite\">\n        <div class=\"aip-journal-state\">\n          <strong>Memuat daftar jurnal\u2026<\/strong>\n          Sistem sedang membaca data jurnal.\n          <span class=\"aip-journal-loading\" aria-hidden=\"true\">\n            <span><\/span><span><\/span><span><\/span>\n          <\/span>\n        <\/div>\n      <\/div>\n    <\/div>\n  <\/section>\n\n  <section class=\"aip-journal-section aip-journal-cta\">\n    <div class=\"aip-journal-container\">\n      <div class=\"aip-journal-cta-card\">\n        <div>\n          <h2>Data jurnal belum tercantum atau perlu diperbarui?<\/h2>\n\n          <p>\n            Sampaikan pemutakhiran cover, website, peringkat Sinta,\n            atau status pengindeksan kepada sekretariat AIPNEMA.\n          <\/p>\n        <\/div>\n\n        <a\n          class=\"aip-journal-cta-button\"\n          href=\"https:\/\/aipnemaptma.org\/kontak\/\"\n        >\n          Hubungi Sekretariat\n        <\/a>\n      <\/div>\n    <\/div>\n  <\/section>\n\n<\/main>\n\n<script>\n(function(){\n  \/* =======================================================\n     KONFIGURASI GOOGLE SHEETS\n     Ganti SHEET_GID apabila data berada pada tab selain tab pertama.\n  ======================================================= *\/\n  const SHEET_ID =\n    '12_7KlAYEih5KElVm9n3zgq6HITNFufYLlVVfE4UP4lU';\n  const SHEET_NAME = 'Sheet1';\n\n  const SHEET_BASE =\n    'https:\/\/docs.google.com\/spreadsheets\/d\/' +\n    SHEET_ID +\n    '\/gviz\/tq';\n\n  \/*\n    Tampilan halaman dipertahankan.\n    Perubahan hanya pada sumber Cover dan Homepage Jurnal.\n  *\/\n  const COVER_METADATA_ENDPOINT =\n    'https:\/\/api.microlink.io\/';\n  const COVER_METADATA_LOOKUP = true;\n  const COVER_CACHE_KEY =\n    'aipnemaJournalCoverResolverV4';\n  const COVER_CACHE_TTL =\n    7 * 24 * 60 * 60 * 1000;\n  const COVER_MAX_CONCURRENT = 3;\n\n  const FALLBACK_LOGO =\n    'https:\/\/aipnemaptma.org\/wp-content\/uploads\/2023\/07\/logo-aipnema2.png';\n\n  const FIELD_ALIASES = {\n    name:[\n      'nama jurnal','jurnal','journal name','nama journal',\n      'title','judul jurnal'\n    ],\n    institution:[\n      'institusi','institusi pengelola','penerbit','publisher',\n      'perguruan tinggi','afiliasi','pengelola','universitas',\n      'asal institusi'\n    ],\n    cover:[\n      'Cover',\n      'cover','cover jurnal','sampul','sampul jurnal',\n      'thumbnail','tumbnail','gambar cover','url cover',\n      'link cover','logo jurnal','image','gambar'\n    ],\n    website:[\n      'Homepage Jurnal',\n      'homepage jurnal','homepage',\n      'website jurnal','website','url jurnal','link jurnal',\n      'alamat jurnal','journal website','ojs','website ojs'\n    ],\n    sinta:[\n      'sinta','peringkat sinta','rank sinta','sinta rank',\n      'akreditasi sinta','peringkat','sinta score'\n    ],\n    doaj:[\n      'doaj','indexing doaj','indeks doaj',\n      'terindeks doaj','status doaj'\n    ],\n    scopus:[\n      'scopus','indexing scopus','indeks scopus',\n      'terindeks scopus','status scopus'\n    ]\n  };\n\n  const pageElement = document.getElementById('aipJournalPage');\n  const gridElement = document.getElementById('aipJournalGrid');\n  const searchElement = document.getElementById('aipJournalSearch');\n  const sintaElement = document.getElementById('aipJournalSinta');\n  const doajElement = document.getElementById('aipJournalDoaj');\n  const scopusElement = document.getElementById('aipJournalScopus');\n  const sortElement = document.getElementById('aipJournalSort');\n  const resetElement = document.getElementById('aipJournalReset');\n  const resultTextElement =\n    document.getElementById('aipJournalResultText');\n\n  const totalElement = document.getElementById('aipJournalTotal');\n  const sintaCountElement =\n    document.getElementById('aipJournalSintaCount');\n  const doajCountElement =\n    document.getElementById('aipJournalDoajCount');\n  const scopusCountElement =\n    document.getElementById('aipJournalScopusCount');\n\n  const sintaChartElement =\n    document.getElementById('aipJournalSintaChart');\n  const indexChartElement =\n    document.getElementById('aipJournalIndexChart');\n  const institutionChartElement =\n    document.getElementById('aipJournalInstitutionChart');\n\n  let headers = [];\n  let records = [];\n\n  const coverMetadataMemory = new Map();\n  const coverMetadataQueue = [];\n  let coverMetadataActive = 0;\n  let coverObserver = null;\n\n  let keys = {\n    name:null,\n    institution:null,\n    cover:null,\n    website:null,\n    sinta:null,\n    doaj:null,\n    scopus:null\n  };\n\n  function setHeaderOffset(){\n    if(!pageElement) return;\n\n    const header =\n      document.getElementById('aipnemaSiteHeader') ||\n      document.querySelector('#masthead, .site-header');\n\n    if(!header) return;\n\n    const style = window.getComputedStyle(header);\n    const overlay =\n      ['absolute','fixed','sticky'].includes(style.position);\n\n    pageElement.style.paddingTop =\n      overlay ? header.offsetHeight + 'px' : '0px';\n\n    pageElement.style.setProperty(\n      '--aip-journal-header-offset',\n      (header.offsetHeight + 24) + 'px'\n    );\n  }\n\n  function normalize(value){\n    return String(value || '')\n      .trim()\n      .toLowerCase()\n      .normalize('NFD')\n      .replace(\/[\\u0300-\\u036f]\/g,'')\n      .replace(\/[^a-z0-9]+\/g,' ')\n      .replace(\/\\s+\/g,' ')\n      .trim();\n  }\n\n  function escapeHtml(value){\n    return String(value || '')\n      .replace(\/&\/g,'&amp;')\n      .replace(\/<\/g,'&lt;')\n      .replace(\/>\/g,'&gt;')\n      .replace(\/\"\/g,'&quot;')\n      .replace(\/'\/g,'&#039;');\n  }\n\n  function valueOf(value){\n    if(value === null || typeof value === 'undefined') return '';\n    return String(value).trim();\n  }\n\n  function ensureUrl(value){\n    const text = valueOf(value);\n\n    if(!text) return '';\n\n    if(\/^https?:\\\/\\\/\/i.test(text)){\n      return text;\n    }\n\n    if(\/^www\\.\/i.test(text)){\n      return 'https:\/\/' + text;\n    }\n\n    if(\n      \/^[a-z0-9.-]+\\.[a-z]{2,}(\\\/.*)?$\/i.test(text)\n    ){\n      return 'https:\/\/' + text;\n    }\n\n    return '';\n  }\n\n  function splitCoverValues(value){\n    const text = valueOf(value);\n\n    if(!text) return [];\n\n    return text\n      .split(\/[\\n,;|]+\/)\n      .map(function(item){\n        return item.trim();\n      })\n      .filter(Boolean);\n  }\n\n  function driveFileId(value){\n    const text = valueOf(value);\n\n    if(!text) return '';\n\n    let match = text.match(\n      \/drive\\.google\\.com\\\/file\\\/d\\\/([^\/?#]+)\/\n    );\n\n    if(!match){\n      match = text.match(\/[?&]id=([^&#]+)\/);\n    }\n\n    if(!match){\n      match = text.match(\n        \/drive\\.google\\.com\\\/(?:open|uc)\\?[^#]*\\bid=([^&#]+)\/\n      );\n    }\n\n    return match && match[1]\n      ? match[1]\n      : '';\n  }\n\n  function normalizeCoverUrl(value){\n    const url = ensureUrl(value);\n\n    if(!url) return '';\n\n    const fileId = driveFileId(url);\n\n    if(fileId){\n      return 'https:\/\/drive.google.com\/thumbnail?id=' +\n        encodeURIComponent(fileId) +\n        '&sz=w1200';\n    }\n\n    if(\/dropbox\\.com\/i.test(url)){\n      return url\n        .replace(\n          'www.dropbox.com',\n          'dl.dropboxusercontent.com'\n        )\n        .replace(\/[?&]dl=0\\b\/i,'')\n        .replace(\/[?&]raw=1\\b\/i,'');\n    }\n\n    return url;\n  }\n\n  function looksLikeImageFile(value){\n    const url = ensureUrl(value);\n\n    if(!url) return false;\n\n    return (\n      \/\\.(?:avif|bmp|gif|jpe?g|png|svg|webp)(?:[?#].*)?$\/i.test(url) ||\n      \/\\\/public\\\/(?:journals|site)\\\/.*\\.(?:avif|gif|jpe?g|png|svg|webp)(?:[?#].*)?$\/i.test(url) ||\n      \/googleusercontent\\.com\/i.test(url) ||\n      \/ggpht\\.com\/i.test(url) ||\n      \/drive\\.google\\.com\/i.test(url) ||\n      \/dl\\.dropboxusercontent\\.com\/i.test(url) ||\n      \/\\\/thumbnail(?:\\?|\\\/)\/i.test(url) ||\n      \/[?&](?:image|img|photo|cover|thumbnail)=\/i.test(url)\n    );\n  }\n\n  function coverCandidates(value){\n    const candidates = [];\n\n    splitCoverValues(value).forEach(function(raw){\n      const url = ensureUrl(raw);\n\n      if(!url) return;\n\n      const fileId = driveFileId(url);\n\n      if(fileId){\n        const driveCandidates = [\n          'https:\/\/drive.google.com\/thumbnail?id=' +\n            encodeURIComponent(fileId) +\n            '&sz=w1200',\n          'https:\/\/drive.google.com\/uc?export=view&id=' +\n            encodeURIComponent(fileId)\n        ];\n\n        driveCandidates.forEach(function(candidate){\n          if(!candidates.includes(candidate)){\n            candidates.push(candidate);\n          }\n        });\n\n        return;\n      }\n\n      if(looksLikeImageFile(url)){\n        const candidate = normalizeCoverUrl(url);\n\n        if(\n          candidate &&\n          !candidates.includes(candidate)\n        ){\n          candidates.push(candidate);\n        }\n      }\n    });\n\n    return candidates;\n  }\n\n  function coverPageCandidates(value){\n    const pages = [];\n\n    splitCoverValues(value).forEach(function(raw){\n      const url = ensureUrl(raw);\n\n      if(\n        url &&\n        !driveFileId(url) &&\n        !looksLikeImageFile(url) &&\n        !pages.includes(url)\n      ){\n        pages.push(url);\n      }\n    });\n\n    return pages;\n  }\n\n  function readCoverCache(){\n    try{\n      const stored = JSON.parse(\n        localStorage.getItem(COVER_CACHE_KEY) ||\n        '{}'\n      );\n\n      return stored && typeof stored === 'object'\n        ? stored\n        : {};\n    }catch(error){\n      return {};\n    }\n  }\n\n  function getCachedCover(pageUrl){\n    if(coverMetadataMemory.has(pageUrl)){\n      return coverMetadataMemory.get(pageUrl);\n    }\n\n    const stored = readCoverCache();\n    const entry = stored[pageUrl];\n\n    if(\n      !entry ||\n      !entry.time ||\n      Date.now() - entry.time > COVER_CACHE_TTL\n    ){\n      return null;\n    }\n\n    const value =\n      typeof entry.url === 'string'\n        ? entry.url\n        : '';\n\n    coverMetadataMemory.set(pageUrl,value);\n    return value;\n  }\n\n  function setCachedCover(pageUrl,imageUrl){\n    const value = ensureUrl(imageUrl) || '';\n\n    coverMetadataMemory.set(pageUrl,value);\n\n    try{\n      const stored = readCoverCache();\n\n      stored[pageUrl] = {\n        url:value,\n        time:Date.now()\n      };\n\n      const entries = Object.entries(stored)\n        .sort(function(a,b){\n          return (b[1].time || 0) -\n            (a[1].time || 0);\n        })\n        .slice(0,180);\n\n      localStorage.setItem(\n        COVER_CACHE_KEY,\n        JSON.stringify(\n          Object.fromEntries(entries)\n        )\n      );\n    }catch(error){\n      \/* cache bersifat opsional *\/\n    }\n  }\n\n  function canLoadCover(imageUrl){\n    return new Promise(function(resolve){\n      const tester = new Image();\n      let completed = false;\n\n      const finish = function(result){\n        if(completed) return;\n\n        completed = true;\n        window.clearTimeout(timeout);\n        tester.onload = null;\n        tester.onerror = null;\n        resolve(result);\n      };\n\n      const timeout = window.setTimeout(function(){\n        finish(false);\n      },8000);\n\n      tester.referrerPolicy = 'no-referrer';\n      tester.onload = function(){\n        finish(true);\n      };\n      tester.onerror = function(){\n        finish(false);\n      };\n      tester.src = imageUrl;\n    });\n  }\n\n  async function metadataCoverFromPage(pageUrl){\n    const cached = getCachedCover(pageUrl);\n\n    if(cached !== null){\n      return cached;\n    }\n\n    if(!COVER_METADATA_LOOKUP){\n      return '';\n    }\n\n    try{\n      const endpoint = new URL(\n        COVER_METADATA_ENDPOINT\n      );\n\n      endpoint.searchParams.set('url',pageUrl);\n      endpoint.searchParams.set(\n        'filter',\n        'image.url'\n      );\n      endpoint.searchParams.set('meta','false');\n      endpoint.searchParams.set(\n        'screenshot',\n        'false'\n      );\n\n      const response = await fetch(\n        endpoint.toString(),\n        {\n          method:'GET',\n          mode:'cors',\n          cache:'force-cache'\n        }\n      );\n\n      if(!response.ok){\n        throw new Error(\n          'Metadata cover tidak tersedia.'\n        );\n      }\n\n      const payload = await response.json();\n\n      const imageUrl = ensureUrl(\n        payload &&\n        payload.data &&\n        payload.data.image\n          ? payload.data.image.url\n          : ''\n      );\n\n      if(!imageUrl){\n        setCachedCover(pageUrl,'');\n        return '';\n      }\n\n      const valid =\n        await canLoadCover(imageUrl);\n\n      setCachedCover(\n        pageUrl,\n        valid ? imageUrl : ''\n      );\n\n      return valid ? imageUrl : '';\n    }catch(error){\n      setCachedCover(pageUrl,'');\n      return '';\n    }\n  }\n\n  async function resolvePageCover(image){\n    let pages = [];\n\n    try{\n      pages = JSON.parse(\n        decodeURIComponent(\n          image.dataset.coverPages ||\n          '%5B%5D'\n        )\n      );\n    }catch(error){\n      pages = [];\n    }\n\n    for(const pageUrl of pages){\n      const imageUrl =\n        await metadataCoverFromPage(\n          pageUrl\n        );\n\n      if(!imageUrl) continue;\n\n      image.dataset.coverIndex = '0';\n      image.dataset.coverCandidates =\n        encodeURIComponent(\n          JSON.stringify([imageUrl])\n        );\n\n      image.classList.add('is-loading');\n      image.style.display = 'block';\n      image.src = imageUrl;\n\n      return true;\n    }\n\n    return false;\n  }\n\n  function processCoverMetadataQueue(){\n    while(\n      coverMetadataActive < COVER_MAX_CONCURRENT &&\n      coverMetadataQueue.length\n    ){\n      const image =\n        coverMetadataQueue.shift();\n\n      if(\n        !image ||\n        image.dataset.coverResolution === 'done'\n      ){\n        continue;\n      }\n\n      image.dataset.coverResolution =\n        'working';\n\n      coverMetadataActive += 1;\n\n      resolvePageCover(image)\n        .finally(function(){\n          image.dataset.coverResolution =\n            'done';\n\n          coverMetadataActive -= 1;\n          processCoverMetadataQueue();\n        });\n    }\n  }\n\n  function queueCoverMetadata(image){\n    if(\n      !COVER_METADATA_LOOKUP ||\n      !image ||\n      image.dataset.coverResolution ===\n        'queued' ||\n      image.dataset.coverResolution ===\n        'working' ||\n      image.dataset.coverResolution ===\n        'done'\n    ){\n      return;\n    }\n\n    let pages = [];\n\n    try{\n      pages = JSON.parse(\n        decodeURIComponent(\n          image.dataset.coverPages ||\n          '%5B%5D'\n        )\n      );\n    }catch(error){\n      pages = [];\n    }\n\n    if(!pages.length){\n      image.dataset.coverResolution =\n        'done';\n      return;\n    }\n\n    image.dataset.coverResolution =\n      'queued';\n\n    coverMetadataQueue.push(image);\n    processCoverMetadataQueue();\n  }\n\n  function prepareCoverMetadata(root){\n    const images = Array.from(\n      root.querySelectorAll(\n        '[data-cover-resolver]'\n      )\n    );\n\n    if(!images.length) return;\n\n    if('IntersectionObserver' in window){\n      if(coverObserver){\n        coverObserver.disconnect();\n      }\n\n      coverObserver =\n        new IntersectionObserver(\n          function(entries){\n            entries.forEach(function(entry){\n              if(!entry.isIntersecting) return;\n\n              coverObserver.unobserve(\n                entry.target\n              );\n\n              const image = entry.target;\n\n              if(\n                image.dataset.coverState ===\n                  'fallback' ||\n                image.dataset.coverState ===\n                  'empty'\n              ){\n                queueCoverMetadata(image);\n              }\n            });\n          },\n          {\n            rootMargin:'220px 0px',\n            threshold:.01\n          }\n        );\n\n      images.forEach(function(image){\n        coverObserver.observe(image);\n      });\n    }else{\n      images.forEach(function(image){\n        if(\n          image.dataset.coverState ===\n            'fallback' ||\n          image.dataset.coverState ===\n            'empty'\n        ){\n          queueCoverMetadata(image);\n        }\n      });\n    }\n  }\n\n  window.aipJournalCoverLoaded =\n    function(image){\n      image.dataset.coverState = 'loaded';\n      image.classList.remove('is-loading');\n      image.classList.add('is-ready');\n      image.style.display = 'block';\n\n      const fallback =\n        image.nextElementSibling;\n\n      if(fallback){\n        fallback.style.display = 'none';\n      }\n    };\n\n  window.aipJournalTryNextCover =\n    function(image){\n      let candidates = [];\n\n      try{\n        candidates = JSON.parse(\n          decodeURIComponent(\n            image.dataset.coverCandidates ||\n            '%5B%5D'\n          )\n        );\n      }catch(error){\n        candidates = [];\n      }\n\n      const currentIndex =\n        Number(\n          image.dataset.coverIndex || 0\n        );\n\n      const nextIndex =\n        currentIndex + 1;\n\n      if(\n        nextIndex < candidates.length &&\n        candidates[nextIndex]\n      ){\n        image.dataset.coverIndex =\n          String(nextIndex);\n\n        image.classList.add('is-loading');\n        image.src = candidates[nextIndex];\n        return;\n      }\n\n      image.dataset.coverState =\n        'fallback';\n\n      image.classList.remove(\n        'is-loading',\n        'is-ready'\n      );\n\n      image.style.display = 'none';\n\n      const fallback =\n        image.nextElementSibling;\n\n      if(fallback){\n        fallback.style.display = 'grid';\n      }\n\n      queueCoverMetadata(image);\n    };\n\n  function cellValue(cell){\n    if(!cell) return '';\n\n    if(cell.f !== null && typeof cell.f !== 'undefined'){\n      return valueOf(cell.f);\n    }\n\n    if(cell.v !== null && typeof cell.v !== 'undefined'){\n      return valueOf(cell.v);\n    }\n\n    return '';\n  }\n\n  function rowHeaderScore(row){\n    const allAliases = Object.values(FIELD_ALIASES)\n      .flat()\n      .map(normalize);\n\n    return row.reduce(function(score,value){\n      const normalizedValue = normalize(value);\n\n      if(!normalizedValue) return score;\n\n      const matched = allAliases.some(function(alias){\n        return normalizedValue === alias ||\n          normalizedValue.includes(alias) ||\n          alias.includes(normalizedValue);\n      });\n\n      return score + (matched ? 1 : 0);\n    },0);\n  }\n\n  function uniqueHeaders(headerRow){\n    const used = {};\n\n    return headerRow.map(function(header,index){\n      let candidate =\n        valueOf(header) || 'Kolom ' + (index + 1);\n\n      const normalizedCandidate =\n        normalize(candidate) || 'kolom';\n\n      used[normalizedCandidate] =\n        (used[normalizedCandidate] || 0) + 1;\n\n      if(used[normalizedCandidate] > 1){\n        candidate += ' ' + used[normalizedCandidate];\n      }\n\n      return candidate;\n    });\n  }\n\n  function parseResponse(response){\n    if(!response || response.status === 'error'){\n      throw new Error(\n        response &&\n        response.errors &&\n        response.errors[0]\n          ? response.errors[0].detailed_message ||\n            response.errors[0].message\n          : 'Spreadsheet tidak dapat dibaca.'\n      );\n    }\n\n    const table = response.table;\n\n    if(\n      !table ||\n      !Array.isArray(table.cols) ||\n      !Array.isArray(table.rows)\n    ){\n      throw new Error('Format spreadsheet tidak dikenali.');\n    }\n\n    const columnCount = table.cols.length;\n\n    const matrix = table.rows.map(function(row){\n      return Array.from({length:columnCount},function(_,index){\n        return cellValue(row.c ? row.c[index] : null);\n      });\n    });\n\n    if(!matrix.length){\n      throw new Error('Spreadsheet belum berisi data jurnal.');\n    }\n\n    let bestIndex = 0;\n    let bestScore = -1;\n\n    matrix.slice(0,15).forEach(function(row,index){\n      const score = rowHeaderScore(row);\n\n      if(score > bestScore){\n        bestScore = score;\n        bestIndex = index;\n      }\n    });\n\n    if(bestScore < 2){\n      bestIndex = matrix.findIndex(function(row){\n        return row.filter(Boolean).length >= 2;\n      });\n\n      if(bestIndex < 0) bestIndex = 0;\n    }\n\n    const parsedHeaders =\n      uniqueHeaders(matrix[bestIndex]);\n\n    const parsedRecords = matrix\n      .slice(bestIndex + 1)\n      .map(function(row){\n        const record = {};\n\n        parsedHeaders.forEach(function(header,index){\n          record[header] = valueOf(row[index]);\n        });\n\n        return record;\n      })\n      .filter(function(record){\n        return parsedHeaders.some(function(header){\n          return valueOf(record[header]);\n        });\n      });\n\n    return {\n      headers:parsedHeaders,\n      records:parsedRecords\n    };\n  }\n\n  function parseVisualizationText(text){\n    const match = String(text || '').match(\n      \/google\\.visualization\\.Query\\.setResponse\\(([\\s\\S]*)\\);?\\s*$\/\n    );\n\n    if(!match){\n      throw new Error(\n        'Respons Google Sheets tidak dapat diproses.'\n      );\n    }\n\n    return JSON.parse(match[1]);\n  }\n\n  function findHeader(aliases){\n    const normalizedAliases = aliases.map(normalize);\n\n    const exact = headers.find(function(header){\n      return normalizedAliases.includes(normalize(header));\n    });\n\n    if(exact) return exact;\n\n    return headers.find(function(header){\n      const normalizedHeader = normalize(header);\n\n      return normalizedAliases.some(function(alias){\n        return normalizedHeader.includes(alias) ||\n          alias.includes(normalizedHeader);\n      });\n    }) || null;\n  }\n\n  function configureFields(){\n    const exactHeader = function(label){\n      return headers.find(function(header){\n        return normalize(header) ===\n          normalize(label);\n      }) || null;\n    };\n\n    keys.name =\n      exactHeader('Nama Jurnal') ||\n      findHeader(FIELD_ALIASES.name);\n\n    keys.institution =\n      exactHeader('Institusi') ||\n      findHeader(\n        FIELD_ALIASES.institution\n      );\n\n    \/*\n      Dikunci sesuai permintaan:\n      cover hanya dari kolom Cover.\n    *\/\n    keys.cover =\n      exactHeader('Cover');\n\n    \/*\n      Dikunci sesuai permintaan:\n      link website hanya dari kolom\n      Homepage Jurnal.\n    *\/\n    keys.website =\n      exactHeader('Homepage Jurnal');\n\n    keys.sinta =\n      exactHeader('Sinta') ||\n      findHeader(FIELD_ALIASES.sinta);\n\n    keys.doaj =\n      exactHeader('DOAJ') ||\n      findHeader(FIELD_ALIASES.doaj);\n\n    keys.scopus =\n      exactHeader('Scopus') ||\n      findHeader(FIELD_ALIASES.scopus);\n\n    if(!keys.name){\n      keys.name = headers[0] || null;\n    }\n  }\n\n  function normalizeSinta(value){\n    const text = valueOf(value);\n\n    if(!text) return '';\n\n    const normalizedText = normalize(text);\n    const rankMatch = normalizedText.match(\n      \/(?:sinta|s)\\s*([1-6])\\b\/\n    ) || normalizedText.match(\/^([1-6])$\/);\n\n    if(rankMatch){\n      return 'Sinta ' + rankMatch[1];\n    }\n\n    if(\n      \/tidak|belum|non|none|n\\\/a|na\/.test(normalizedText)\n    ){\n      return '';\n    }\n\n    return text;\n  }\n\n  function sintaRank(value){\n    const normalizedValue = normalizeSinta(value);\n    const match = normalizedValue.match(\/([1-6])\/);\n\n    return match ? Number(match[1]) : 99;\n  }\n\n  function indexStatus(value){\n    const text = valueOf(value);\n    const normalizedText = normalize(text);\n\n    if(!text) return false;\n\n    if(\n      \/tidak|belum|no|non|not indexed|none|n\\\/a|na|-\/.test(\n        normalizedText\n      )\n    ){\n      return false;\n    }\n\n    if(\n      \/^https?:\\\/\\\/\/i.test(text) ||\n      \/ya|yes|terindeks|indexed|aktif|scopus|doaj\/.test(\n        normalizedText\n      )\n    ){\n      return true;\n    }\n\n    return false;\n  }\n\n  function sintaClass(value){\n    const rank = sintaRank(value);\n\n    return rank >= 1 && rank <= 6\n      ? 'aip-journal-sinta-' + rank\n      : 'aip-journal-sinta-other';\n  }\n\n\n  function percentage(count){\n    return records.length\n      ? (count \/ records.length) * 100\n      : 0;\n  }\n\n  function formatPercentage(value){\n    const rounded = Math.round(value * 10) \/ 10;\n\n    return Number.isInteger(rounded)\n      ? rounded + '%'\n      : rounded.toFixed(1).replace('.',',') + '%';\n  }\n\n  function sintaDistribution(){\n    const labels = [\n      'Sinta 1',\n      'Sinta 2',\n      'Sinta 3',\n      'Sinta 4',\n      'Sinta 5',\n      'Sinta 6',\n      'Belum Sinta'\n    ];\n\n    const counts = new Map(\n      labels.map(function(label){\n        return [label,0];\n      })\n    );\n\n    records.forEach(function(record){\n      const sinta =\n        keys.sinta\n          ? normalizeSinta(record[keys.sinta])\n          : '';\n\n      const label =\n        sinta && \/^Sinta [1-6]$\/.test(sinta)\n          ? sinta\n          : 'Belum Sinta';\n\n      counts.set(\n        label,\n        (counts.get(label) || 0) + 1\n      );\n    });\n\n    return labels.map(function(label){\n      const count = counts.get(label) || 0;\n\n      return {\n        label:label,\n        count:count,\n        percentage:percentage(count)\n      };\n    });\n  }\n\n  function institutionDistribution(){\n    if(!keys.institution) return [];\n\n    const counts = new Map();\n\n    records.forEach(function(record){\n      const institution =\n        valueOf(record[keys.institution]) ||\n        'Belum Dicantumkan';\n\n      counts.set(\n        institution,\n        (counts.get(institution) || 0) + 1\n      );\n    });\n\n    return Array.from(counts.entries())\n      .map(function(entry){\n        return {\n          label:entry[0],\n          count:entry[1]\n        };\n      })\n      .sort(function(a,b){\n        if(a.count !== b.count){\n          return b.count - a.count;\n        }\n\n        return a.label.localeCompare(\n          b.label,\n          'id',\n          {sensitivity:'base'}\n        );\n      })\n      .slice(0,10);\n  }\n\n  function renderSintaChart(){\n    if(!sintaChartElement) return;\n\n    const data = sintaDistribution();\n    const maximum = Math.max.apply(\n      null,\n      data.map(function(item){\n        return item.count;\n      }).concat([1])\n    );\n\n    const colors = [\n      ['#094528','#0C6A3C'],\n      ['#1E6292','#3688B5'],\n      ['#6651BD','#806BDD'],\n      ['#B68B27','#D8B25C'],\n      ['#D5651C','#E9914E'],\n      ['#9B5C85','#BA7DA3'],\n      ['#7E8C85','#A3B0A9']\n    ];\n\n    sintaChartElement.innerHTML =\n      data.map(function(item,index){\n        const width =\n          item.count\n            ? Math.max(\n                3,\n                (item.count \/ maximum) * 100\n              )\n            : 0;\n\n        const active =\n          item.label === 'Belum Sinta'\n            ? !sintaElement.value\n            : sintaElement.value === item.label;\n\n        return `\n          <div\n            class=\"aip-journal-chart-bar${active && sintaElement.value ? ' is-active' : ''}\"\n            role=\"button\"\n            tabindex=\"0\"\n            data-sinta-chart=\"${escapeHtml(item.label)}\"\n            aria-label=\"${escapeHtml(\n              item.label +\n              ': ' +\n              item.count +\n              ' jurnal, ' +\n              formatPercentage(item.percentage)\n            )}\"\n          >\n            <span class=\"aip-journal-chart-bar-label\">\n              ${escapeHtml(item.label)}\n            <\/span>\n\n            <span class=\"aip-journal-chart-track\">\n              <span\n                class=\"aip-journal-chart-fill\"\n                data-chart-width=\"${width.toFixed(3)}\"\n                style=\"\n                  --chart-color-start:${colors[index][0]};\n                  --chart-color-end:${colors[index][1]};\n                \"\n              ><\/span>\n            <\/span>\n\n            <span class=\"aip-journal-chart-value\">\n              <strong>${item.count}<\/strong>\n              <span>${formatPercentage(item.percentage)}<\/span>\n            <\/span>\n          <\/div>\n        `;\n      }).join('');\n\n    const bars = Array.from(\n      sintaChartElement.querySelectorAll(\n        '[data-sinta-chart]'\n      )\n    );\n\n    bars.forEach(function(bar,index){\n      const fill =\n        bar.querySelector('[data-chart-width]');\n\n      if(fill){\n        window.setTimeout(function(){\n          fill.style.width =\n            fill.dataset.chartWidth + '%';\n        },80 + (index * 80));\n      }\n\n      function activate(){\n        const label =\n          bar.dataset.sintaChart || '';\n\n        \/*\n          Klik \"Belum Sinta\" belum dipakai sebagai filter karena\n          filter publik hanya menyimpan nilai peringkat Sinta.\n        *\/\n        if(label === 'Belum Sinta'){\n          sintaElement.value = '';\n        }else{\n          sintaElement.value =\n            sintaElement.value === label\n              ? ''\n              : label;\n        }\n\n        render();\n        renderSintaChart();\n\n        document\n          .getElementById('direktori-jurnal')\n          .scrollIntoView({\n            behavior:'smooth',\n            block:'start'\n          });\n      }\n\n      bar.addEventListener('click',activate);\n\n      bar.addEventListener('keydown',function(event){\n        if(\n          event.key === 'Enter' ||\n          event.key === ' '\n        ){\n          event.preventDefault();\n          activate();\n        }\n      });\n    });\n  }\n\n  function renderIndexChart(){\n    if(!indexChartElement) return;\n\n    const doajCount =\n      keys.doaj\n        ? records.filter(function(record){\n            return indexStatus(record[keys.doaj]);\n          }).length\n        : 0;\n\n    const scopusCount =\n      keys.scopus\n        ? records.filter(function(record){\n            return indexStatus(record[keys.scopus]);\n          }).length\n        : 0;\n\n    const metrics = [\n      {\n        label:'DOAJ',\n        count:doajCount,\n        percentage:percentage(doajCount),\n        active:'#1E6292',\n        inactive:'#E3EAE6'\n      },\n      {\n        label:'Scopus',\n        count:scopusCount,\n        percentage:percentage(scopusCount),\n        active:'#094528',\n        inactive:'#E3EAE6'\n      }\n    ];\n\n    indexChartElement.innerHTML =\n      metrics.map(function(item){\n        const degree =\n          Math.max(\n            0,\n            Math.min(100,item.percentage)\n          ) * 3.6;\n\n        return `\n          <article class=\"aip-journal-index-card\">\n            <h4>${escapeHtml(item.label)}<\/h4>\n\n            <div\n              class=\"aip-journal-donut\"\n              data-journal-donut\n              style=\"\n                --donut-gradient:\n                conic-gradient(\n                  ${item.active} 0deg ${degree.toFixed(2)}deg,\n                  ${item.inactive} ${degree.toFixed(2)}deg 360deg\n                );\n              \"\n            >\n              <div class=\"aip-journal-donut-center\">\n                <strong>${formatPercentage(item.percentage)}<\/strong>\n                <span>${item.count} jurnal<\/span>\n              <\/div>\n            <\/div>\n\n            <div class=\"aip-journal-index-summary\">\n              ${item.count} dari ${records.length} jurnal terdata\n            <\/div>\n          <\/article>\n        `;\n      }).join('');\n\n    window.requestAnimationFrame(function(){\n      window.requestAnimationFrame(function(){\n        indexChartElement\n          .querySelectorAll('[data-journal-donut]')\n          .forEach(function(donut){\n            donut.classList.add('is-ready');\n          });\n      });\n    });\n  }\n\n  function renderInstitutionChart(){\n    if(!institutionChartElement) return;\n\n    const data = institutionDistribution();\n\n    if(!data.length){\n      institutionChartElement.innerHTML = `\n        <div class=\"aip-journal-chart-empty\">\n          Data institusi pengelola belum tersedia.\n        <\/div>\n      `;\n      return;\n    }\n\n    const maximum = Math.max.apply(\n      null,\n      data.map(function(item){\n        return item.count;\n      }).concat([1])\n    );\n\n    institutionChartElement.innerHTML =\n      data.map(function(item,index){\n        const width =\n          (item.count \/ maximum) * 100;\n\n        return `\n          <div class=\"aip-journal-institution-row\">\n            <span\n              class=\"aip-journal-institution-label\"\n              title=\"${escapeHtml(item.label)}\"\n            >\n              ${escapeHtml(item.label)}\n            <\/span>\n\n            <span class=\"aip-journal-institution-track\">\n              <span\n                class=\"aip-journal-institution-fill\"\n                data-institution-width=\"${width.toFixed(3)}\"\n              ><\/span>\n            <\/span>\n\n            <strong class=\"aip-journal-institution-value\">\n              ${item.count}\n            <\/strong>\n          <\/div>\n        `;\n      }).join('');\n\n    institutionChartElement\n      .querySelectorAll('[data-institution-width]')\n      .forEach(function(fill,index){\n        window.setTimeout(function(){\n          fill.style.width =\n            fill.dataset.institutionWidth + '%';\n        },90 + (index * 65));\n      });\n  }\n\n  function renderAnalytics(){\n    renderSintaChart();\n    renderIndexChart();\n    renderInstitutionChart();\n  }\n\n  function showAnalyticsError(){\n    if(sintaChartElement){\n      sintaChartElement.innerHTML = `\n        <div class=\"aip-journal-chart-empty\">\n          Grafik Sinta belum dapat ditampilkan.\n        <\/div>\n      `;\n    }\n\n    if(indexChartElement){\n      indexChartElement.innerHTML = `\n        <div class=\"aip-journal-chart-empty\">\n          Grafik indeksasi belum dapat ditampilkan.\n        <\/div>\n      `;\n    }\n\n    if(institutionChartElement){\n      institutionChartElement.innerHTML = `\n        <div class=\"aip-journal-chart-empty\">\n          Grafik institusi belum dapat ditampilkan.\n        <\/div>\n      `;\n    }\n  }\n\n  function cardMarkup(record){\n    const name =\n      valueOf(record[keys.name]) ||\n      'Nama jurnal belum dicantumkan';\n\n    const institution =\n      keys.institution\n        ? valueOf(record[keys.institution])\n        : '';\n\n    const website =\n      keys.website\n        ? ensureUrl(\n            record[keys.website]\n          )\n        : '';\n\n    const coverRaw =\n      keys.cover\n        ? valueOf(\n            record[keys.cover]\n          )\n        : '';\n\n    const coverList =\n      coverCandidates(\n        coverRaw\n      );\n\n    const coverPages =\n      coverPageCandidates(\n        coverRaw\n      );\n\n    const cover =\n      coverList.length\n        ? coverList[0]\n        : '';\n\n    const encodedCoverCandidates =\n      encodeURIComponent(\n        JSON.stringify(\n          coverList\n        )\n      );\n\n    const encodedCoverPages =\n      encodeURIComponent(\n        JSON.stringify(\n          coverPages\n        )\n      );\n\n    const sinta =\n      keys.sinta\n        ? normalizeSinta(record[keys.sinta])\n        : '';\n\n    const doaj =\n      keys.doaj\n        ? indexStatus(record[keys.doaj])\n        : false;\n\n    const scopus =\n      keys.scopus\n        ? indexStatus(record[keys.scopus])\n        : false;\n\n    const link =\n      website || '#';\n\n    const linkAttributes = website\n      ? 'target=\"_blank\" rel=\"noopener\"'\n      : 'aria-disabled=\"true\" onclick=\"return false\"';\n\n    return `\n      <article class=\"aip-journal-card\">\n        <a\n          class=\"aip-journal-cover-link\"\n          href=\"${escapeHtml(link)}\"\n          ${linkAttributes}\n          aria-label=\"Buka website ${escapeHtml(name)}\"\n        >\n          <img decoding=\"async\"\n            class=\"aip-journal-cover${cover ? ' is-loading' : ''}\"\n            src=\"${cover ? escapeHtml(cover) : ''}\"\n            alt=\"Cover ${escapeHtml(name)}\"\n            loading=\"lazy\"\n            referrerpolicy=\"no-referrer\"\n            data-cover-resolver\n            data-cover-state=\"${cover ? 'loading' : 'empty'}\"\n            data-cover-index=\"0\"\n            data-cover-candidates=\"${escapeHtml(encodedCoverCandidates)}\"\n            data-cover-pages=\"${escapeHtml(encodedCoverPages)}\"\n            style=\"${cover ? '' : 'display:none'}\"\n            onload=\"window.aipJournalCoverLoaded(this)\"\n            onerror=\"window.aipJournalTryNextCover(this)\"\n          >\n\n          <span\n            class=\"aip-journal-cover-fallback\"\n            style=\"${cover ? '' : 'display:grid'}\"\n          >\n            <img decoding=\"async\"\n              src=\"${FALLBACK_LOGO}\"\n              alt=\"Logo AIPNEMA\"\n            >\n          <\/span>\n        <\/a>\n\n        <div class=\"aip-journal-card-body\">\n          <h3>\n            <a\n              href=\"${escapeHtml(link)}\"\n              ${linkAttributes}\n            >\n              ${escapeHtml(name)}\n            <\/a>\n          <\/h3>\n\n          ${institution\n            ? `\n              <p class=\"aip-journal-institution\">\n                ${escapeHtml(institution)}\n              <\/p>\n            `\n            : ''\n          }\n\n          <div class=\"aip-journal-badges\">\n            ${sinta\n              ? `\n                <span class=\"aip-journal-badge ${sintaClass(sinta)}\">\n                  ${escapeHtml(sinta)}\n                <\/span>\n              `\n              : `\n                <span class=\"aip-journal-badge aip-journal-index-inactive\">\n                  Sinta \u2014\n                <\/span>\n              `\n            }\n\n            <span class=\"aip-journal-badge ${\n              doaj\n                ? 'aip-journal-doaj-active'\n                : 'aip-journal-index-inactive'\n            }\">\n              ${doaj ? 'DOAJ' : 'DOAJ \u2014'}\n            <\/span>\n\n            <span class=\"aip-journal-badge ${\n              scopus\n                ? 'aip-journal-scopus-active'\n                : 'aip-journal-index-inactive'\n            }\">\n              ${scopus ? 'Scopus' : 'Scopus \u2014'}\n            <\/span>\n          <\/div>\n\n          ${website\n            ? `\n              <a\n                class=\"aip-journal-card-link\"\n                href=\"${escapeHtml(website)}\"\n                target=\"_blank\"\n                rel=\"noopener\"\n              >\n                Kunjungi website jurnal\n                <svg viewBox=\"0 0 24 24\" fill=\"none\" aria-hidden=\"true\">\n                  <path d=\"M5 12h14M13 6l6 6-6 6\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\"\/>\n                <\/svg>\n              <\/a>\n            `\n            : ''\n          }\n        <\/div>\n      <\/article>\n    `;\n  }\n\n  function uniqueSintaValues(){\n    if(!keys.sinta) return [];\n\n    return Array.from(\n      new Set(\n        records\n          .map(function(record){\n            return normalizeSinta(record[keys.sinta]);\n          })\n          .filter(Boolean)\n      )\n    ).sort(function(a,b){\n      return sintaRank(a) - sintaRank(b);\n    });\n  }\n\n  function populateSintaFilter(){\n    sintaElement.innerHTML =\n      '<option value=\"\">Semua Sinta<\/option>';\n\n    uniqueSintaValues().forEach(function(value){\n      sintaElement.insertAdjacentHTML(\n        'beforeend',\n        '<option value=\"' +\n        escapeHtml(value) +\n        '\">' +\n        escapeHtml(value) +\n        '<\/option>'\n      );\n    });\n  }\n\n  function updateStats(){\n    totalElement.textContent = records.length;\n\n    sintaCountElement.textContent =\n      keys.sinta\n        ? records.filter(function(record){\n            return Boolean(normalizeSinta(record[keys.sinta]));\n          }).length\n        : '\u2013';\n\n    doajCountElement.textContent =\n      keys.doaj\n        ? records.filter(function(record){\n            return indexStatus(record[keys.doaj]);\n          }).length\n        : '\u2013';\n\n    scopusCountElement.textContent =\n      keys.scopus\n        ? records.filter(function(record){\n            return indexStatus(record[keys.scopus]);\n          }).length\n        : '\u2013';\n  }\n\n  function journalRankData(record){\n    const sinta =\n      keys.sinta\n        ? normalizeSinta(record[keys.sinta])\n        : '';\n\n    const scopus =\n      keys.scopus\n        ? indexStatus(record[keys.scopus])\n        : false;\n\n    const doaj =\n      keys.doaj\n        ? indexStatus(record[keys.doaj])\n        : false;\n\n    \/*\n      Hierarki urutan default:\n      1. Scopus\n      2. Sinta 1\u20136\n      3. DOAJ tanpa Scopus\/Sinta\n      4. Belum memiliki indeks\/peringkat tersebut\n    *\/\n    let group = 3;\n\n    if(scopus){\n      group = 0;\n    }else if(sinta){\n      group = 1;\n    }else if(doaj){\n      group = 2;\n    }\n\n    return {\n      group:group,\n      sintaRank:sintaRank(sinta),\n      scopus:scopus,\n      doaj:doaj\n    };\n  }\n\n  function compareByRanking(a,b){\n    const rankA = journalRankData(a);\n    const rankB = journalRankData(b);\n\n    if(rankA.group !== rankB.group){\n      return rankA.group - rankB.group;\n    }\n\n    \/*\n      Di dalam kelompok Scopus, jurnal dengan peringkat Sinta lebih baik\n      ditempatkan lebih dahulu bila datanya tersedia.\n    *\/\n    if(rankA.sintaRank !== rankB.sintaRank){\n      return rankA.sintaRank - rankB.sintaRank;\n    }\n\n    \/*\n      DOAJ menjadi pembeda tambahan bila posisi Scopus\/Sinta setara.\n    *\/\n    if(rankA.doaj !== rankB.doaj){\n      return rankA.doaj ? -1 : 1;\n    }\n\n    return valueOf(recordName(a)).localeCompare(\n      valueOf(recordName(b)),\n      'id',\n      {sensitivity:'base'}\n    );\n  }\n\n  function filteredRecords(){\n    const term = normalize(searchElement.value);\n    const selectedSinta = sintaElement.value;\n    const selectedDoaj = doajElement.value;\n    const selectedScopus = scopusElement.value;\n\n    const filtered = records.filter(function(record){\n      const name = valueOf(record[keys.name]);\n      const institution =\n        keys.institution\n          ? valueOf(record[keys.institution])\n          : '';\n\n      const sinta =\n        keys.sinta\n          ? normalizeSinta(record[keys.sinta])\n          : '';\n\n      const doaj =\n        keys.doaj\n          ? indexStatus(record[keys.doaj])\n          : false;\n\n      const scopus =\n        keys.scopus\n          ? indexStatus(record[keys.scopus])\n          : false;\n\n      const matchesSearch =\n        !term ||\n        normalize(name + ' ' + institution).includes(term);\n\n      const matchesSinta =\n        !selectedSinta ||\n        sinta === selectedSinta;\n\n      const matchesDoaj =\n        !selectedDoaj ||\n        (\n          selectedDoaj === 'indexed'\n            ? doaj\n            : !doaj\n        );\n\n      const matchesScopus =\n        !selectedScopus ||\n        (\n          selectedScopus === 'indexed'\n            ? scopus\n            : !scopus\n        );\n\n      return matchesSearch &&\n        matchesSinta &&\n        matchesDoaj &&\n        matchesScopus;\n    });\n\n    filtered.sort(function(a,b){\n      if(sortElement.value === 'name-asc'){\n        return valueOf(recordName(a)).localeCompare(\n          valueOf(recordName(b)),\n          'id',\n          {sensitivity:'base'}\n        );\n      }\n\n      if(sortElement.value === 'name-desc'){\n        return valueOf(recordName(b)).localeCompare(\n          valueOf(recordName(a)),\n          'id',\n          {sensitivity:'base'}\n        );\n      }\n\n      return compareByRanking(a,b);\n    });\n\n    return filtered;\n  }\n\n  function recordName(record){\n    return record[keys.name] || '';\n  }\n\n  function render(){\n    const items = filteredRecords();\n\n    resultTextElement.textContent =\n      items.length +\n      ' dari ' +\n      records.length +\n      ' jurnal ditampilkan';\n\n    if(!items.length){\n      gridElement.innerHTML = `\n        <div class=\"aip-journal-state\">\n          <strong>Jurnal tidak ditemukan.<\/strong>\n          Ubah kata kunci atau pilihan filter.\n        <\/div>\n      `;\n      return;\n    }\n\n    gridElement.innerHTML =\n      items.map(cardMarkup).join('');\n\n    prepareCoverMetadata(\n      gridElement\n    );\n  }\n\n  function initializeData(parsed){\n    headers = parsed.headers;\n    records = parsed.records;\n\n    configureFields();\n\n    records = records.filter(function(record){\n      return valueOf(record[keys.name]);\n    });\n\n    if(!records.length){\n      throw new Error(\n        'Spreadsheet belum berisi daftar jurnal.'\n      );\n    }\n\n    populateSintaFilter();\n    updateStats();\n    renderAnalytics();\n    render();\n  }\n\n  function showError(error){\n    const message =\n      error && error.message\n        ? error.message\n        : 'Data jurnal belum dapat dibaca.';\n\n    gridElement.innerHTML = `\n      <div class=\"aip-journal-state\">\n        <strong>Data jurnal belum dapat dimuat.<\/strong>\n        ${escapeHtml(message)}\n        <br><br>\n        Pastikan spreadsheet dapat dilihat oleh\n        siapa saja yang memiliki link dan data berada\n        pada tab dengan GID ${escapeHtml(SHEET_GID)}.\n      <\/div>\n    `;\n\n    resultTextElement.textContent =\n      'Data jurnal belum dapat dimuat';\n\n    showAnalyticsError();\n\n    totalElement.textContent = '\u2013';\n    sintaCountElement.textContent = '\u2013';\n    doajCountElement.textContent = '\u2013';\n    scopusCountElement.textContent = '\u2013';\n  }\n\n  function visualizationUrl(includeCallback){\n    const parameters =\n      new URLSearchParams();\n\n    parameters.set(\n      'sheet',\n      SHEET_NAME\n    );\n\n    parameters.set(\n      'headers',\n      '0'\n    );\n\n    parameters.set(\n      '_',\n      String(Date.now())\n    );\n\n    if(includeCallback){\n      parameters.set(\n        'tqx',\n        'out:json;responseHandler:aipnemaJournalReceive'\n      );\n    }else{\n      parameters.set(\n        'tqx',\n        'out:json'\n      );\n    }\n\n    return (\n      SHEET_BASE +\n      '?' +\n      parameters.toString()\n    );\n  }\n\n  function loadWithJsonp(){\n    return new Promise(function(resolve,reject){\n      const script = document.createElement('script');\n      let completed = false;\n\n      const timeout = window.setTimeout(function(){\n        if(completed) return;\n\n        completed = true;\n        script.remove();\n\n        reject(new Error(\n          'Waktu pemuatan spreadsheet telah habis.'\n        ));\n      },15000);\n\n      window.aipnemaJournalReceive = function(response){\n        if(completed) return;\n\n        completed = true;\n        window.clearTimeout(timeout);\n        script.remove();\n        resolve(response);\n      };\n\n      script.onerror = function(){\n        if(completed) return;\n\n        completed = true;\n        window.clearTimeout(timeout);\n        script.remove();\n\n        reject(new Error(\n          'Google Sheets tidak dapat dihubungi.'\n        ));\n      };\n\n      script.src = visualizationUrl(true);\n      document.head.appendChild(script);\n    });\n  }\n\n  async function loadSheet(){\n    try{\n      let responseObject;\n\n      try{\n        const response = await fetch(\n          visualizationUrl(false),\n          {cache:'no-store'}\n        );\n\n        if(!response.ok){\n          throw new Error(\n            'Permintaan spreadsheet gagal.'\n          );\n        }\n\n        responseObject =\n          parseVisualizationText(await response.text());\n      }catch(fetchError){\n        responseObject = await loadWithJsonp();\n      }\n\n      initializeData(parseResponse(responseObject));\n    }catch(error){\n      showError(error);\n    }\n  }\n\n  searchElement.addEventListener('input',render);\n  sintaElement.addEventListener('change',function(){\n    render();\n    renderSintaChart();\n  });\n  doajElement.addEventListener('change',render);\n  scopusElement.addEventListener('change',render);\n  sortElement.addEventListener('change',render);\n\n  resetElement.addEventListener('click',function(){\n    searchElement.value = '';\n    sintaElement.value = '';\n    doajElement.value = '';\n    scopusElement.value = '';\n    sortElement.value = 'ranking';\n    render();\n    renderSintaChart();\n  });\n\n  setHeaderOffset();\n  loadSheet();\n\n  window.addEventListener('load',setHeaderOffset);\n  window.addEventListener('resize',setHeaderOffset);\n})();\n<\/script>\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>Beranda \/ Jurnal Ilmiah Publikasi Ilmiah Institusi Anggota Jurnal Ilmiah AIPNEMA Direktori jurnal ilmiah yang dikelola oleh institusi anggota, dilengkapi peringkat Sinta serta informasi pengindeksan DOAJ dan Scopus. Klik cover atau nama jurnal untuk mengunjungi website jurnal dan membaca informasi publikasi secara langsung. \u2013 Jurnal terdata \u2013 Terakreditasi Sinta \u2013 Terindeks DOAJ \u2013 Terindeks Scopus&#8230;<\/p>","protected":false},"author":2,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"elementor_header_footer","meta":{"_kadence_starter_templates_imported_post":false,"_kad_post_transparent":"disable","_kad_post_title":"hide","_kad_post_layout":"fullwidth","_kad_post_sidebar_id":"","_kad_post_content_style":"unboxed","_kad_post_vertical_padding":"hide","_kad_post_feature":"hide","_kad_post_feature_position":"","_kad_post_header":false,"_kad_post_footer":false,"_kad_post_classname":"","footnotes":""},"class_list":["post-1901","page","type-page","status-publish","hentry"],"_links":{"self":[{"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/pages\/1901","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/users\/2"}],"replies":[{"embeddable":true,"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/comments?post=1901"}],"version-history":[{"count":19,"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/pages\/1901\/revisions"}],"predecessor-version":[{"id":2001,"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/pages\/1901\/revisions\/2001"}],"wp:attachment":[{"href":"https:\/\/aipnemaptma.org\/en\/wp-json\/wp\/v2\/media?parent=1901"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}