diff --git a/.github/javadoc/javadoc-footer.html b/.github/javadoc/javadoc-footer.html
new file mode 100644
index 000000000..487326dfc
--- /dev/null
+++ b/.github/javadoc/javadoc-footer.html
@@ -0,0 +1,44 @@
+
diff --git a/.github/javadoc/javadoc-header.html b/.github/javadoc/javadoc-header.html
new file mode 100644
index 000000000..782ae85db
--- /dev/null
+++ b/.github/javadoc/javadoc-header.html
@@ -0,0 +1,11 @@
+
diff --git a/.github/javadoc/javadoc-theme.css b/.github/javadoc/javadoc-theme.css
new file mode 100644
index 000000000..d7ceb8275
--- /dev/null
+++ b/.github/javadoc/javadoc-theme.css
@@ -0,0 +1,954 @@
+/* ============================================================
+ CustomNPC+ — Javadoc Theme (Javadoc 11)
+ Tokyo Night — Styled Edition
+ ============================================================ */
+
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700&family=JetBrains+Mono:wght@400;500;600&display=swap');
+
+:root {
+ /* ── Tokyo Night palette ── */
+ --bg: #1a1b26;
+ --bg2: #1f2335;
+ --bg3: #24283b;
+ --bg-elevated: #292e42;
+ --border: #3b4261;
+ --border2: #414868;
+ --text: #c0caf5;
+ --text-mid: #a9b1d6;
+ --text-dim: #565f89;
+ --green: #9ece6a;
+ --green-dim: rgba(158, 206, 106, 0.12);
+ --blue: #7aa2f7;
+ --blue-dim: rgba(122, 162, 247, 0.12);
+ --amber: #e0af68;
+ --amber-dim: rgba(224, 175, 104, 0.10);
+ --purple: #bb9af7;
+ --purple-dim: rgba(187, 154, 247, 0.10);
+ --cyan: #7dcfff;
+ --cyan-dim: rgba(125, 207, 255, 0.10);
+ --red: #f7768e;
+ --highlight-bg: rgba(122, 162, 247, 0.08);
+ --hover-bg: rgba(187, 154, 247, 0.08);
+ --nav-h: 44px;
+ --subnav-h: 36px;
+
+ /* ── Font stacks ── */
+ --font-ui: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
+ --font-mono: 'JetBrains Mono', 'Cascadia Code', 'Fira Code', monospace;
+}
+
+* { box-sizing: border-box !important; }
+
+html, body {
+ height: 100%;
+}
+
+body {
+ background: var(--bg) !important;
+ color: var(--text) !important;
+ font-family: var(--font-mono) !important;
+ font-size: 13px !important;
+ line-height: 1.6 !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ min-height: 100vh !important;
+ display: flex !important;
+ flex-direction: column !important;
+}
+
+.skipNav { display: none !important; }
+
+/* ══════════════════════════════════════════════
+ NAVIGATION
+ ══════════════════════════════════════════════ */
+
+.fixedNav {
+ position: fixed !important;
+ top: 0 !important; left: 0 !important; right: 0 !important;
+ z-index: 100 !important;
+}
+
+/* Top bar — gradient accent stripe on top */
+.topNav {
+ position: relative !important;
+ background: var(--bg2) !important;
+ border-bottom: 1px solid var(--border) !important;
+ height: var(--nav-h) !important;
+ margin: 0 !important; padding: 0 !important;
+ display: flex !important;
+ align-items: stretch !important;
+ overflow: visible !important;
+}
+
+/* Gradient accent line across the very top */
+.topNav::before {
+ content: '' !important;
+ position: absolute !important;
+ top: 0 !important; left: 0 !important; right: 0 !important;
+ height: 2px !important;
+ background: linear-gradient(90deg, var(--blue), var(--purple), var(--cyan)) !important;
+ z-index: 10 !important;
+}
+
+/* Nav list — leave room on right for brand */
+.topNav > ul.navList {
+ list-style: none !important;
+ margin: 0 !important;
+ padding: 2px 0 0 8px !important;
+ padding-right: 320px !important;
+ display: flex !important;
+ align-items: stretch !important;
+ flex-shrink: 0 !important;
+}
+
+.topNav > ul.navList > li {
+ display: flex !important;
+ align-items: center !important;
+ margin: 0 !important;
+ padding: 0 16px !important;
+ height: var(--nav-h) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ white-space: nowrap !important;
+ text-transform: none !important;
+ letter-spacing: 0.01em !important;
+ color: var(--text-dim) !important;
+ border-bottom: 2px solid transparent !important;
+ transition: color 0.2s !important;
+}
+
+.topNav > ul.navList > li > a {
+ display: flex !important;
+ align-items: center !important;
+ height: var(--nav-h) !important;
+ margin: 0 -16px !important;
+ padding: 0 16px !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ text-decoration: none !important;
+ text-transform: none !important;
+ letter-spacing: 0.01em !important;
+ white-space: nowrap !important;
+ border-bottom: 2px solid transparent !important;
+ transition: color 0.2s, border-color 0.2s !important;
+}
+
+.topNav > ul.navList > li > a:hover {
+ color: var(--text) !important;
+ border-bottom-color: var(--purple) !important;
+ background: transparent !important;
+ text-shadow: none !important;
+ text-decoration: none !important;
+}
+
+/* Active item — gradient underline */
+.topNav > ul.navList > li.navBarCell1Rev {
+ color: var(--text) !important;
+ border-image: linear-gradient(90deg, var(--blue), var(--purple)) 1 !important;
+ border-bottom-width: 2px !important;
+ border-bottom-style: solid !important;
+}
+
+/* ── Brand — pinned to far right of topNav ── */
+.topNav > .aboutLanguage {
+ position: absolute !important;
+ top: 0 !important; right: 0 !important;
+ height: var(--nav-h) !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+ padding: 0 16px !important;
+ background: var(--bg2) !important;
+ border-left: 1px solid var(--border) !important;
+ z-index: 5 !important;
+}
+
+/* Hide the duplicate in bottom nav */
+.bottomNav > .aboutLanguage,
+.bottomNav .aboutLanguage {
+ display: none !important;
+}
+
+#javadoc-header {
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+}
+
+.header-brand { text-decoration: none !important; }
+
+/* Brand title — gradient text */
+.header-title {
+ font-family: var(--font-ui) !important;
+ font-size: 14px !important;
+ font-weight: 700 !important;
+ background: linear-gradient(135deg, var(--blue), var(--purple)) !important;
+ -webkit-background-clip: text !important;
+ -webkit-text-fill-color: transparent !important;
+ background-clip: text !important;
+ line-height: 1.4 !important;
+ white-space: nowrap !important;
+}
+
+.header-title span {
+ display: block !important;
+ font-family: var(--font-mono) !important;
+ font-size: 9px !important;
+ font-weight: 400 !important;
+ background: none !important;
+ -webkit-text-fill-color: var(--text-dim) !important;
+ color: var(--text-dim) !important;
+ margin-top: 1px !important;
+ letter-spacing: 0.1em !important;
+ text-transform: uppercase !important;
+}
+
+.header-back {
+ display: inline-flex !important;
+ align-items: center !important;
+ text-decoration: none !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 11px !important;
+ font-weight: 500 !important;
+ padding: 5px 12px !important;
+ border: 1px solid var(--border2) !important;
+ border-radius: 6px !important;
+ white-space: nowrap !important;
+ transition: all 0.2s !important;
+}
+
+.header-back:hover {
+ color: var(--text) !important;
+ border-color: var(--purple) !important;
+ background: var(--purple-dim) !important;
+ text-decoration: none !important;
+ text-shadow: none !important;
+}
+
+/* ── Sub nav ── */
+.subNav {
+ background: var(--bg3) !important;
+ border-bottom: 1px solid var(--border) !important;
+ height: var(--subnav-h) !important;
+ margin: 0 !important;
+ padding: 0 14px !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+ overflow: hidden !important;
+}
+
+/* Search first */
+.subNav > ul.navListSearch {
+ order: -1 !important;
+}
+
+.subNav ul,
+.subNav ul.navList,
+.subNav ul.subNavList,
+.subNav ul.navListSearch {
+ list-style: none !important;
+ margin: 0 !important;
+ padding: 0 !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 0 !important;
+}
+
+.subNav li {
+ display: flex !important;
+ align-items: center !important;
+ font-family: var(--font-ui) !important;
+ font-size: 11px !important;
+ color: var(--text-dim) !important;
+ padding: 0 6px !important;
+ white-space: nowrap !important;
+ border: none !important;
+ height: auto !important;
+ text-transform: none !important;
+}
+
+.subNav a {
+ color: var(--blue) !important;
+ text-decoration: none !important;
+ font-size: 11px !important;
+ font-family: var(--font-ui) !important;
+ transition: color 0.15s !important;
+}
+
+.subNav a:hover { color: var(--purple) !important; text-shadow: none !important; }
+
+.subNav label {
+ font-family: var(--font-ui) !important;
+ font-size: 11px !important;
+ color: var(--text-dim) !important;
+ text-transform: none !important;
+ margin-right: 4px !important;
+}
+
+/* ══════════════════════════════════════════════
+ CONTENT
+ ══════════════════════════════════════════════ */
+
+main {
+ background: var(--bg) !important;
+ padding: 28px !important;
+ flex: 1 0 auto !important;
+}
+
+.contentContainer, div.contentContainer {
+ background: var(--bg) !important;
+ padding: 0 28px 0px !important;
+}
+
+.header {
+ padding: 0 28px 0 !important;
+ margin-bottom: 20px !important;
+}
+
+/* Subtitle under h1 on help/overview pages */
+.header .subTitle,
+div.subTitle {
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ line-height: 1.7 !important;
+ margin-top: 8px !important;
+}
+
+.packageLabelInType {
+ font-family: var(--font-mono) !important;
+ font-size: 11px !important;
+ color: var(--text-dim) !important;
+}
+
+/* ── Headings ── */
+h1, h2, h3, h4, h5, h6 {
+ font-family: var(--font-ui) !important;
+ font-weight: 600 !important;
+ line-height: 1.5 !important;
+ text-transform: none !important;
+ letter-spacing: -0.01em !important;
+}
+
+/* Page title — gradient accent bar */
+h1.title, h2.title {
+ font-size: 22px !important;
+ font-weight: 700 !important;
+ color: var(--text) !important;
+ border-bottom: none !important;
+ padding-bottom: 16px !important;
+ padding-left: 16px !important;
+ margin-bottom: 24px !important;
+ margin-top: 0 !important;
+ border-left: 4px solid transparent !important;
+ border-image: linear-gradient(180deg, var(--blue), var(--purple)) 1 !important;
+}
+
+/* Section h2 — colored panel */
+section > h2,
+li.blockList > section > h2 {
+ font-size: 14px !important;
+ color: var(--green) !important;
+ border-left: 3px solid var(--green) !important;
+ border-bottom: none !important;
+ padding: 10px 14px !important;
+ margin: 0 !important;
+ background: var(--green-dim) !important;
+}
+
+/* Generic h2 (table section headers like "Method Summary") */
+h2 {
+ font-size: 14px !important;
+ color: var(--amber) !important;
+ border-bottom: none !important;
+ border-left: 3px solid var(--amber) !important;
+ padding: 8px 12px !important;
+ margin-top: 28px !important;
+ margin-bottom: 12px !important;
+ background: var(--amber-dim) !important;
+}
+
+/* h3 — table section headers inside blockList */
+h3 {
+ font-size: 12px !important;
+ color: var(--text-mid) !important;
+ margin: 0 !important;
+ padding: 10px 14px !important;
+ background: var(--bg3) !important;
+ border-bottom: 1px solid var(--border) !important;
+}
+
+/* h4 — method detail names */
+h4 {
+ font-size: 13px !important;
+ font-weight: 600 !important;
+ color: var(--cyan) !important;
+ margin: 0 !important;
+ padding: 10px 14px !important;
+ background: var(--bg3) !important;
+ border-bottom: 1px solid var(--border) !important;
+ border-left: 3px solid var(--cyan) !important;
+}
+
+/* ── blockList sections — card-like containers ── */
+ul.blockList > li.blockList,
+ul.blockListLast > li.blockList {
+ border: 1px solid var(--border) !important;
+ border-radius: 6px !important;
+ margin-bottom: 14px !important;
+ padding: 0 !important;
+ background: var(--bg2) !important;
+ overflow: hidden !important;
+}
+
+/* Help page: section cards with padding around their content */
+ul.blockList > li.blockList > section {
+ padding: 0 !important;
+}
+
+ul.blockList > li.blockList > section > p,
+ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) {
+ padding: 14px 18px !important;
+ margin: 0 !important;
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ line-height: 1.7 !important;
+}
+
+/* Nested lists inside section content */
+ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) > li {
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ padding: 3px 0 !important;
+ list-style: none !important;
+}
+
+ul.blockList > li.blockList > section > ul:not(.blockList):not(.blockListLast) > li::before {
+ content: '\203A' !important;
+ color: var(--purple) !important;
+ margin-right: 8px !important;
+ font-weight: 700 !important;
+}
+
+/* ── Inherited methods block ── */
+ul.blockList > li.blockList > h3,
+ul.blockList > li.blockList > ul.blockList > li.blockList > h3 {
+ font-size: 11px !important;
+ color: var(--text-dim) !important;
+ padding: 8px 14px !important;
+ background: var(--bg3) !important;
+ border-bottom: 1px solid var(--border) !important;
+ margin: 0 !important;
+}
+
+/* The comma-separated code block of inherited method names */
+ul.blockList > li.blockList > code,
+li.blockList > code {
+ display: block !important;
+ padding: 10px 16px !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ background: var(--bg) !important;
+ border: none !important;
+ line-height: 1.8 !important;
+}
+
+/* ── Inheritance chain ── */
+ul.inheritance {
+ list-style: none !important;
+ padding: 0 !important;
+ margin: 0 0 16px !important;
+ font-size: 12px !important;
+ color: var(--text-dim) !important;
+}
+ul.inheritance li { padding-left: 16px !important; }
+ul.inheritance li:first-child { padding-left: 0 !important; }
+
+/* ── General content lists (help page) ── */
+main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) {
+ padding-left: 0 !important;
+ margin: 4px 0 4px 12px !important;
+ list-style: none !important;
+}
+main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) > li {
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ padding: 3px 0 !important;
+ line-height: 1.7 !important;
+}
+main ul:not(.blockList):not(.blockListLast):not(.navList):not(.subNavList):not(.navListSearch):not(.inheritance) > li::before {
+ content: '\203A' !important;
+ color: var(--purple) !important;
+ margin-right: 8px !important;
+ font-weight: 700 !important;
+}
+
+/* General paragraphs in content */
+main > div p, .contentContainer p {
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ line-height: 1.7 !important;
+ margin: 8px 0 !important;
+}
+
+/* Emphasized phrase */
+.emphasizedPhrase {
+ display: block !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ font-style: italic !important;
+ border-left: 2px solid var(--purple) !important;
+ padding: 8px 14px !important;
+ margin-top: 16px !important;
+ background: var(--purple-dim) !important;
+ border-radius: 0 4px 4px 0 !important;
+}
+
+/* Bottom footer subNav */
+footer {
+ /*margin-top: auto;*/
+}
+footer .subNav {
+ background: var(--bg3) !important;
+ border-top: 1px solid var(--border) !important;
+ border-bottom: none !important;
+ height: var(--subnav-h) !important;
+ padding: 0 14px !important;
+ display: flex !important;
+ align-items: center !important;
+ gap: 12px !important;
+ overflow: hidden !important;
+}
+
+/* ══════════════════════════════════════════════
+ TABLES
+ ══════════════════════════════════════════════ */
+
+table {
+ width: 100% !important;
+ border-collapse: collapse !important;
+ border: 1px solid var(--border) !important;
+ border-radius: 6px !important;
+ margin: 16px 0 !important;
+ font-size: 13px !important;
+ overflow: hidden !important;
+}
+
+caption {
+ background: var(--bg3) !important;
+ padding: 8px 14px !important;
+ text-align: left !important;
+ border-bottom: 1px solid var(--border2) !important;
+ caption-side: top !important;
+}
+
+/* Table tabs — pill style */
+.activeTableTab, .tableTab {
+ font-family: var(--font-ui) !important;
+ font-size: 11px !important;
+ font-weight: 500 !important;
+ text-transform: none !important;
+ padding: 4px 12px !important;
+ border: 1px solid var(--border2) !important;
+ border-radius: 20px !important;
+ display: inline-block !important;
+ margin-right: 6px !important;
+ cursor: pointer !important;
+ transition: all 0.2s !important;
+}
+
+.activeTableTab {
+ color: var(--text) !important;
+ border-color: var(--blue) !important;
+ background: var(--blue-dim) !important;
+}
+
+.tableTab { color: var(--text-dim) !important; background: var(--bg3) !important; }
+.tableTab a { color: var(--text-dim) !important; text-decoration: none !important; }
+.tableTab a:hover { color: var(--text) !important; text-shadow: none !important; }
+
+/* Column headers */
+th[scope="col"] {
+ background: var(--bg-elevated) !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 10px !important;
+ font-weight: 600 !important;
+ letter-spacing: 0.06em !important;
+ text-transform: uppercase !important;
+ padding: 10px 14px !important;
+ border-bottom: 2px solid var(--border2) !important;
+ text-align: left !important;
+}
+
+/* Data cells */
+th[scope="row"], td {
+ background: var(--bg2) !important;
+ color: var(--text) !important;
+ padding: 10px 14px !important;
+ border-bottom: 1px solid var(--border) !important;
+ vertical-align: top !important;
+ text-align: left !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ font-weight: 400 !important;
+ text-transform: none !important;
+ letter-spacing: 0 !important;
+ transition: background 0.15s !important;
+}
+
+/* Stronger alternating row contrast */
+tr.altColor > td, tr.altColor > th[scope="row"] { background: var(--bg) !important; }
+tr.rowColor > td, tr.rowColor > th[scope="row"] { background: var(--bg2) !important; }
+
+tr:hover > td, tr:hover > th[scope="row"] {
+ background: var(--purple-dim) !important;
+ border-bottom-color: rgba(187, 154, 247, 0.25) !important;
+}
+tr:hover > td a, tr:hover > th[scope="row"] a {
+ color: var(--purple) !important;
+}
+
+/* ── Links ── */
+a, a:visited { color: var(--blue) !important; text-decoration: none !important; transition: color 0.15s !important; }
+a:hover { color: var(--purple) !important; text-decoration: none !important; }
+.memberNameLink a { color: var(--cyan) !important; font-weight: 500 !important; }
+.memberNameLink a:hover { color: var(--purple) !important; }
+.externalLink { color: var(--blue) !important; }
+
+/* ── Description blocks ── */
+div.block {
+ color: var(--text-mid) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 13px !important;
+ line-height: 1.7 !important;
+ padding: 4px 0 !important;
+}
+ul.blockList > li.blockList > div.block{ padding: 12px 16px !important; margin: 0 !important; }
+
+/* ── Method detail ── */
+ul.blockList, ul.blockListLast { list-style: none !important; padding: 0 !important; margin: 0 !important; }
+ul.blockList > li.blockList, ul.blockListLast > li.blockList {
+ border: 1px solid var(--border) !important;
+ border-radius: 6px !important;
+ margin-bottom: 12px !important;
+ padding: 0 !important;
+ overflow: hidden !important;
+}
+
+/* Method/type signatures — accent left bar */
+pre.methodSignature, pre.typeSignature {
+ background: var(--bg2) !important;
+ border: none !important;
+ border-bottom: 1px solid var(--border) !important;
+ border-left: 3px solid var(--cyan) !important;
+ margin: 0 !important;
+ padding: 14px 18px !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ color: var(--text) !important;
+ overflow-x: auto !important;
+ white-space: pre-wrap !important;
+}
+
+pre {
+ background: var(--bg2) !important;
+ border: 1px solid var(--border) !important;
+ border-left: 3px solid var(--blue) !important;
+ border-radius: 6px !important;
+ padding: 14px 18px !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ color: var(--text) !important;
+ overflow-x: auto !important;
+ margin: 0 !important;
+ white-space: pre-wrap !important;
+}
+
+code {
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ color: var(--cyan) !important;
+ background: var(--bg3) !important;
+ padding: 2px 6px !important;
+ border-radius: 4px !important;
+}
+
+dl { padding: 12px 16px !important; margin: 0 !important; }
+dt { margin-bottom: 4px !important; }
+dd { margin: 0 0 8px 16px !important; color: var(--text-mid) !important; font-size: 12px !important; }
+
+.paramLabel, .returnLabel, .throwsLabel, .seeLabel, .simpleTagLabel {
+ font-family: var(--font-ui) !important;
+ font-size: 10px !important;
+ font-weight: 600 !important;
+ color: var(--purple) !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.06em !important;
+}
+
+.typeNameLabel { color: var(--cyan) !important; }
+
+.deprecationBlock {
+ background: var(--amber-dim) !important;
+ border-left: 3px solid var(--amber) !important;
+ border-radius: 0 6px 6px 0 !important;
+ padding: 10px 14px !important;
+ color: var(--amber) !important;
+ font-size: 12px !important;
+}
+
+/* ── Inputs ── */
+input[type="text"] {
+ background: var(--bg) !important;
+ border: 1px solid var(--border2) !important;
+ color: var(--text) !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ padding: 5px 12px !important;
+ outline: none !important;
+ border-radius: 6px !important;
+ transition: border-color 0.2s, box-shadow 0.2s !important;
+}
+input[type="text"]:focus {
+ border-color: var(--blue) !important;
+ box-shadow: 0 0 0 2px var(--blue-dim) !important;
+}
+input[type="reset"] {
+ background: var(--bg3) !important;
+ border: 1px solid var(--border2) !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 11px !important;
+ padding: 5px 12px !important;
+ cursor: pointer !important;
+ border-radius: 6px !important;
+ transition: all 0.2s !important;
+}
+input[type="reset"]:hover {
+ border-color: var(--text-dim) !important;
+ color: var(--text-mid) !important;
+}
+
+/* ══════════════════════════════════════════════
+ BOTTOM NAV
+ ══════════════════════════════════════════════ */
+
+.bottomNav {
+ background: var(--bg2) !important;
+ border-top: 1px solid var(--border) !important;
+ min-height: var(--nav-h) !important;
+ padding: 0 !important;
+ margin-top: 32px !important;
+ overflow: hidden !important;
+ position: relative !important;
+}
+
+/* Gradient accent line on bottom nav too */
+.bottomNav::after {
+ content: '' !important;
+ position: absolute !important;
+ bottom: 0 !important; left: 0 !important; right: 0 !important;
+ height: 2px !important;
+ background: linear-gradient(90deg, var(--blue), var(--purple), var(--cyan)) !important;
+}
+
+.bottomNav ul.navList[title="Navigation"] {
+ list-style: none !important;
+ margin: 0 !important;
+ padding: 0 0 0 8px !important;
+ display: flex !important;
+ align-items: stretch !important;
+ height: var(--nav-h) !important;
+}
+
+.bottomNav ul.navList[title="Navigation"] li {
+ display: flex !important;
+ align-items: center !important;
+ margin: 0 !important;
+ padding: 0 16px !important;
+ height: var(--nav-h) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ white-space: nowrap !important;
+ text-transform: none !important;
+ letter-spacing: 0.01em !important;
+ color: var(--text-dim) !important;
+ border-top: 2px solid transparent !important;
+ transition: color 0.2s !important;
+}
+
+.bottomNav ul.navList[title="Navigation"] li > a {
+ display: flex !important;
+ align-items: center !important;
+ height: var(--nav-h) !important;
+ margin: 0 -16px !important;
+ padding: 0 16px !important;
+ color: var(--text-dim) !important;
+ text-decoration: none !important;
+ font-family: var(--font-ui) !important;
+ font-size: 12px !important;
+ font-weight: 500 !important;
+ transition: color 0.2s !important;
+}
+
+.bottomNav ul.navList[title="Navigation"] li > a:hover {
+ color: var(--text) !important;
+ text-shadow: none !important;
+}
+
+.bottomNav ul.navList[title="Navigation"] li.navBarCell1Rev {
+ color: var(--text) !important;
+ border-image: linear-gradient(90deg, var(--blue), var(--purple)) 1 !important;
+ border-top-width: 2px !important;
+ border-top-style: solid !important;
+}
+
+.bottomNav .aboutLanguage {
+ display: none !important;
+}
+
+.legalCopy {
+ background: var(--bg2) !important;
+ color: var(--text-dim) !important;
+ font-size: 11px !important;
+ font-family: var(--font-ui) !important;
+ padding: 14px 18px !important;
+ text-align: center !important;
+ border-top: 1px solid var(--border) !important;
+}
+
+/* ══════════════════════════════════════════════
+ SEARCH RESULTS DROPDOWN (jQuery UI Autocomplete)
+ ══════════════════════════════════════════════ */
+
+.ui-autocomplete {
+ background: var(--bg2) !important;
+ border: 1px solid var(--border2) !important;
+ border-radius: 8px !important;
+ max-height: 400px !important;
+ overflow-y: auto !important;
+ overflow-x: hidden !important;
+ z-index: 9999 !important;
+ list-style: none !important;
+ padding: 4px 0 !important;
+ margin: 4px 0 0 !important;
+ box-shadow: 0 8px 32px rgba(0, 0, 0, 0.5), 0 0 0 1px rgba(122, 162, 247, 0.05) !important;
+}
+
+/* Category headers */
+.ui-autocomplete .ui-autocomplete-category {
+ background: var(--bg-elevated) !important;
+ color: var(--text-dim) !important;
+ font-family: var(--font-ui) !important;
+ font-size: 10px !important;
+ font-weight: 600 !important;
+ text-transform: uppercase !important;
+ letter-spacing: 0.06em !important;
+ padding: 6px 14px !important;
+ border-bottom: 1px solid var(--border) !important;
+ border-top: 1px solid var(--border) !important;
+ cursor: default !important;
+}
+
+/* Individual result items */
+.ui-autocomplete .ui-menu-item {
+ background: var(--bg2) !important;
+ margin: 0 !important;
+ list-style: none !important;
+}
+
+.ui-autocomplete .ui-menu-item > div,
+.ui-autocomplete .ui-menu-item-wrapper {
+ background: var(--bg2) !important;
+ color: var(--text) !important;
+ font-family: var(--font-mono) !important;
+ font-size: 12px !important;
+ padding: 7px 14px !important;
+ border: none !important;
+ border-left: 2px solid transparent !important;
+ cursor: pointer !important;
+ line-height: 1.5 !important;
+ transition: all 0.1s !important;
+}
+
+/* Hover / focus state */
+.ui-autocomplete .ui-menu-item > div.ui-state-active,
+.ui-autocomplete .ui-menu-item > div.ui-state-focus,
+.ui-autocomplete .ui-menu-item-wrapper.ui-state-active,
+.ui-autocomplete .ui-menu-item-wrapper.ui-state-focus,
+.ui-autocomplete .ui-state-active,
+.ui-autocomplete .ui-state-focus {
+ background: var(--purple-dim) !important;
+ color: var(--text) !important;
+ border: none !important;
+ border-left: 2px solid var(--purple) !important;
+ margin: 0 !important;
+}
+
+/* Highlighted matching text */
+.resultHighlight {
+ color: var(--cyan) !important;
+ font-weight: 600 !important;
+}
+
+/* Search tag result extras */
+.searchTagHolderResult {
+ color: var(--text-dim) !important;
+ font-size: 11px !important;
+}
+
+.searchTagDescResult {
+ color: var(--text-dim) !important;
+ font-size: 11px !important;
+ font-style: italic !important;
+}
+
+/* "No results found" */
+.ui-autocomplete .ui-menu-item:only-child > div {
+ color: var(--text-dim) !important;
+ font-style: italic !important;
+}
+
+/* Override jQuery UI defaults */
+.ui-widget {
+ font-family: var(--font-mono) !important;
+}
+
+.ui-widget-content {
+ background: var(--bg2) !important;
+ color: var(--text) !important;
+ border-color: var(--border2) !important;
+}
+
+.ui-menu .ui-menu-item {
+ padding: 0 !important;
+}
+
+/* Watermark style for search input placeholder */
+.watermark {
+ color: var(--text-dim) !important;
+}
+
+/* ══════════════════════════════════════════════
+ MISC
+ ══════════════════════════════════════════════ */
+
+::-webkit-scrollbar { width: 8px; height: 8px; }
+::-webkit-scrollbar-track { background: var(--bg); }
+::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 4px; }
+::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
+::selection { background: rgba(187, 154, 247, 0.25); color: var(--text); }
+hr { border: none !important; border-top: 1px solid var(--border) !important; margin: 24px 0 !important; }
diff --git a/.github/pages/index.html b/.github/pages/index.html
new file mode 100644
index 000000000..5e467c986
--- /dev/null
+++ b/.github/pages/index.html
@@ -0,0 +1,14 @@
+
+
+
+
+
+ Javadoc Index
+
+
+
+
+
+
+
+
diff --git a/.github/pages/package-lock.json b/.github/pages/package-lock.json
new file mode 100644
index 000000000..3e8db3276
--- /dev/null
+++ b/.github/pages/package-lock.json
@@ -0,0 +1,1679 @@
+{
+ "name": "javadoc-index",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "javadoc-index",
+ "version": "1.0.0",
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.2.1",
+ "vite": "^7.3.1"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz",
+ "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "js-tokens": "^4.0.0",
+ "picocolors": "^1.1.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz",
+ "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz",
+ "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-compilation-targets": "^7.28.6",
+ "@babel/helper-module-transforms": "^7.28.6",
+ "@babel/helpers": "^7.28.6",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/traverse": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/remapping": "^2.3.5",
+ "convert-source-map": "^2.0.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.3",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.29.1",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz",
+ "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.29.0",
+ "@babel/types": "^7.29.0",
+ "@jridgewell/gen-mapping": "^0.3.12",
+ "@jridgewell/trace-mapping": "^0.3.28",
+ "jsesc": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz",
+ "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/compat-data": "^7.28.6",
+ "@babel/helper-validator-option": "^7.27.1",
+ "browserslist": "^4.24.0",
+ "lru-cache": "^5.1.1",
+ "semver": "^6.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-globals": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz",
+ "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz",
+ "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/traverse": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz",
+ "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-module-imports": "^7.28.6",
+ "@babel/helper-validator-identifier": "^7.28.5",
+ "@babel/traverse": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-plugin-utils": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.28.6.tgz",
+ "integrity": "sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
+ "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.28.5",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
+ "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz",
+ "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.6.tgz",
+ "integrity": "sha512-xOBvwq86HHdB7WUDTfKfT/Vuxh7gElQ+Sfti2Cy6yIWNW05P8iUslOVcZ4/sKbE+/jQaukQAdz/gf3724kYdqw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.0.tgz",
+ "integrity": "sha512-IyDgFV5GeDUVX4YdF/3CPULtVGSXXMLh1xVIgdCgxApktqnQV0r7/8Nqthg+8YLGaAtdyIlo2qIdZrbCv4+7ww==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.29.0"
+ },
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-self": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-self/-/plugin-transform-react-jsx-self-7.27.1.tgz",
+ "integrity": "sha512-6UzkCs+ejGdZ5mFFC/OCUrv028ab2fp1znZmCZjAOBKiBK2jXD1O+BPSfX8X2qjJ75fZBMSnQn3Rq2mrBJK2mw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/plugin-transform-react-jsx-source": {
+ "version": "7.27.1",
+ "resolved": "https://registry.npmjs.org/@babel/plugin-transform-react-jsx-source/-/plugin-transform-react-jsx-source-7.27.1.tgz",
+ "integrity": "sha512-zbwoTsBruTeKB9hSq73ha66iFeJHuaFkUbwvqElnygoNbj/jHRsSeokowZFN3CZ64IvEqcmmkVe89OPXc7ldAw==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-plugin-utils": "^7.27.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0-0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.28.6",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz",
+ "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.28.6",
+ "@babel/parser": "^7.28.6",
+ "@babel/types": "^7.28.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz",
+ "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.29.0",
+ "@babel/generator": "^7.29.0",
+ "@babel/helper-globals": "^7.28.0",
+ "@babel/parser": "^7.29.0",
+ "@babel/template": "^7.28.6",
+ "@babel/types": "^7.29.0",
+ "debug": "^4.3.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.29.0",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz",
+ "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.27.1",
+ "@babel/helper-validator-identifier": "^7.28.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.3.tgz",
+ "integrity": "sha512-9fJMTNFTWZMh5qwrBItuziu834eOCUcEqymSH7pY+zoMVEZg3gcPuBNxH1EvfVYe9h0x/Ptw8KBzv7qxb7l8dg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.3.tgz",
+ "integrity": "sha512-i5D1hPY7GIQmXlXhs2w8AWHhenb00+GxjxRncS2ZM7YNVGNfaMxgzSGuO8o8SJzRc/oZwU2bcScvVERk03QhzA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.3.tgz",
+ "integrity": "sha512-YdghPYUmj/FX2SYKJ0OZxf+iaKgMsKHVPF1MAq/P8WirnSpCStzKJFjOjzsW0QQ7oIAiccHdcqjbHmJxRb/dmg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.3.tgz",
+ "integrity": "sha512-IN/0BNTkHtk8lkOM8JWAYFg4ORxBkZQf9zXiEOfERX/CzxW3Vg1ewAhU7QSWQpVIzTW+b8Xy+lGzdYXV6UZObQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.3.tgz",
+ "integrity": "sha512-Re491k7ByTVRy0t3EKWajdLIr0gz2kKKfzafkth4Q8A5n1xTHrkqZgLLjFEHVD+AXdUGgQMq+Godfq45mGpCKg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.3.tgz",
+ "integrity": "sha512-vHk/hA7/1AckjGzRqi6wbo+jaShzRowYip6rt6q7VYEDX4LEy1pZfDpdxCBnGtl+A5zq8iXDcyuxwtv3hNtHFg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-ipTYM2fjt3kQAYOvo6vcxJx3nBYAzPjgTCk7QEgZG8AUO3ydUhvelmhrbOheMnGOlaSFUoHXB6un+A7q4ygY9w==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.3.tgz",
+ "integrity": "sha512-dDk0X87T7mI6U3K9VjWtHOXqwAMJBNN2r7bejDsc+j03SEjtD9HrOl8gVFByeM0aJksoUuUVU9TBaZa2rgj0oA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.3.tgz",
+ "integrity": "sha512-s6nPv2QkSupJwLYyfS+gwdirm0ukyTFNl3KTgZEAiJDd+iHZcbTPPcWCcRYH+WlNbwChgH2QkE9NSlNrMT8Gfw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.3.tgz",
+ "integrity": "sha512-sZOuFz/xWnZ4KH3YfFrKCf1WyPZHakVzTiqji3WDc0BCl2kBwiJLCXpzLzUBLgmp4veFZdvN5ChW4Eq/8Fc2Fg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.3.tgz",
+ "integrity": "sha512-yGlQYjdxtLdh0a3jHjuwOrxQjOZYD/C9PfdbgJJF3TIZWnm/tMd/RcNiLngiu4iwcBAOezdnSLAwQDPqTmtTYg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.3.tgz",
+ "integrity": "sha512-WO60Sn8ly3gtzhyjATDgieJNet/KqsDlX5nRC5Y3oTFcS1l0KWba+SEa9Ja1GfDqSF1z6hif/SkpQJbL63cgOA==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.3.tgz",
+ "integrity": "sha512-APsymYA6sGcZ4pD6k+UxbDjOFSvPWyZhjaiPyl/f79xKxwTnrn5QUnXR5prvetuaSMsb4jgeHewIDCIWljrSxw==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.3.tgz",
+ "integrity": "sha512-eizBnTeBefojtDb9nSh4vvVQ3V9Qf9Df01PfawPcRzJH4gFSgrObw+LveUyDoKU3kxi5+9RJTCWlj4FjYXVPEA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.3.tgz",
+ "integrity": "sha512-3Emwh0r5wmfm3ssTWRQSyVhbOHvqegUDRd0WhmXKX2mkHJe1SFCMJhagUleMq+Uci34wLSipf8Lagt4LlpRFWQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.3.tgz",
+ "integrity": "sha512-pBHUx9LzXWBc7MFIEEL0yD/ZVtNgLytvx60gES28GcWMqil8ElCYR4kvbV2BDqsHOvVDRrOxGySBM9Fcv744hw==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.3.tgz",
+ "integrity": "sha512-Czi8yzXUWIQYAtL/2y6vogER8pvcsOsk5cpwL4Gk5nJqH5UZiVByIY8Eorm5R13gq+DQKYg0+JyQoytLQas4dA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-sDpk0RgmTCR/5HguIZa9n9u+HVKf40fbEUt+iTzSnCaGvY9kFP0YKBWZtJaraonFnqef5SlJ8/TiPAxzyS+UoA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-P14lFKJl/DdaE00LItAukUdZO5iqNH7+PjoBm+fLQjtxfcfFE20Xf5CrLsmZdq5LFFZzb5JMZ9grUwvtVYzjiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.3.tgz",
+ "integrity": "sha512-AIcMP77AvirGbRl/UZFTq5hjXK+2wC7qFRGoHSDrZ5v5b8DK/GYpXW3CPRL53NkvDqb9D+alBiC/dV0Fb7eJcw==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.3.tgz",
+ "integrity": "sha512-DnW2sRrBzA+YnE70LKqnM3P+z8vehfJWHXECbwBmH/CU51z6FiqTQTHFenPlHmo3a8UgpLyH3PT+87OViOh1AQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/openharmony-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.3.tgz",
+ "integrity": "sha512-NinAEgr/etERPTsZJ7aEZQvvg/A6IsZG/LgZy+81wON2huV7SrK3e63dU0XhyZP4RKGyTm7aOgmQk0bGp0fy2g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.3.tgz",
+ "integrity": "sha512-PanZ+nEz+eWoBJ8/f8HKxTTD172SKwdXebZ0ndd953gt1HRBbhMsaNqjTyYLGLPdoWHy4zLU7bDVJztF5f3BHA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.3.tgz",
+ "integrity": "sha512-B2t59lWWYrbRDw/tjiWOuzSsFh1Y/E95ofKz7rIVYSQkUYBjfSgf6oeYPNWHToFRr2zx52JKApIcAS/D5TUBnA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.3.tgz",
+ "integrity": "sha512-QLKSFeXNS8+tHW7tZpMtjlNb7HKau0QDpwm49u0vUp9y1WOF+PEzkU84y9GqYaAVW8aH8f3GcBck26jh54cX4Q==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.3.tgz",
+ "integrity": "sha512-4uJGhsxuptu3OcpVAzli+/gWusVGwZZHTlS63hh++ehExkVT8SgiEf7/uC/PclrPPkLhZqGgCTjd0VWLo6xMqA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.13",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz",
+ "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.0",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/remapping": {
+ "version": "2.3.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/remapping/-/remapping-2.3.5.tgz",
+ "integrity": "sha512-LI9u/+laYG4Ds1TDKSJW2YPrIlcVYOwi2fUC6xB43lueCjgxV4lffOCZCtYFiH6TNOX+tQKXx97T4IKHbhyHEQ==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.3.5",
+ "@jridgewell/trace-mapping": "^0.3.24"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz",
+ "integrity": "sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.5.5",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.5.5.tgz",
+ "integrity": "sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==",
+ "dev": true
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.31",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz",
+ "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==",
+ "dev": true,
+ "dependencies": {
+ "@jridgewell/resolve-uri": "^3.1.0",
+ "@jridgewell/sourcemap-codec": "^1.4.14"
+ }
+ },
+ "node_modules/@rolldown/pluginutils": {
+ "version": "1.0.0-beta.27",
+ "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz",
+ "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==",
+ "dev": true
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.59.0.tgz",
+ "integrity": "sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.59.0.tgz",
+ "integrity": "sha512-hZ+Zxj3SySm4A/DylsDKZAeVg0mvi++0PYVceVyX7hemkw7OreKdCvW2oQ3T1FMZvCaQXqOTHb8qmBShoqk69Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.59.0.tgz",
+ "integrity": "sha512-W2Psnbh1J8ZJw0xKAd8zdNgF9HRLkdWwwdWqubSVk0pUuQkoHnv7rx4GiF9rT4t5DIZGAsConRE3AxCdJ4m8rg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.59.0.tgz",
+ "integrity": "sha512-ZW2KkwlS4lwTv7ZVsYDiARfFCnSGhzYPdiOU4IM2fDbL+QGlyAbjgSFuqNRbSthybLbIJ915UtZBtmuLrQAT/w==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.59.0.tgz",
+ "integrity": "sha512-EsKaJ5ytAu9jI3lonzn3BgG8iRBjV4LxZexygcQbpiU0wU0ATxhNVEpXKfUa0pS05gTcSDMKpn3Sx+QB9RlTTA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.59.0.tgz",
+ "integrity": "sha512-d3DuZi2KzTMjImrxoHIAODUZYoUUMsuUiY4SRRcJy6NJoZ6iIqWnJu9IScV9jXysyGMVuW+KNzZvBLOcpdl3Vg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.59.0.tgz",
+ "integrity": "sha512-t4ONHboXi/3E0rT6OZl1pKbl2Vgxf9vJfWgmUoCEVQVxhW6Cw/c8I6hbbu7DAvgp82RKiH7TpLwxnJeKv2pbsw==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.59.0.tgz",
+ "integrity": "sha512-CikFT7aYPA2ufMD086cVORBYGHffBo4K8MQ4uPS/ZnY54GKj36i196u8U+aDVT2LX4eSMbyHtyOh7D7Zvk2VvA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.59.0.tgz",
+ "integrity": "sha512-jYgUGk5aLd1nUb1CtQ8E+t5JhLc9x5WdBKew9ZgAXg7DBk0ZHErLHdXM24rfX+bKrFe+Xp5YuJo54I5HFjGDAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.59.0.tgz",
+ "integrity": "sha512-peZRVEdnFWZ5Bh2KeumKG9ty7aCXzzEsHShOZEFiCQlDEepP1dpUl/SrUNXNg13UmZl+gzVDPsiCwnV1uI0RUA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.59.0.tgz",
+ "integrity": "sha512-gbUSW/97f7+r4gHy3Jlup8zDG190AuodsWnNiXErp9mT90iCy9NKKU0Xwx5k8VlRAIV2uU9CsMnEFg/xXaOfXg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.59.0.tgz",
+ "integrity": "sha512-yTRONe79E+o0FWFijasoTjtzG9EBedFXJMl888NBEDCDV9I2wGbFFfJQQe63OijbFCUZqxpHz1GzpbtSFikJ4Q==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.59.0.tgz",
+ "integrity": "sha512-sw1o3tfyk12k3OEpRddF68a1unZ5VCN7zoTNtSn2KndUE+ea3m3ROOKRCZxEpmT9nsGnogpFP9x6mnLTCaoLkA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.59.0.tgz",
+ "integrity": "sha512-+2kLtQ4xT3AiIxkzFVFXfsmlZiG5FXYW7ZyIIvGA7Bdeuh9Z0aN4hVyXS/G1E9bTP/vqszNIN/pUKCk/BTHsKA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.59.0.tgz",
+ "integrity": "sha512-NDYMpsXYJJaj+I7UdwIuHHNxXZ/b/N2hR15NyH3m2qAtb/hHPA4g4SuuvrdxetTdndfj9b1WOmy73kcPRoERUg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.59.0.tgz",
+ "integrity": "sha512-nLckB8WOqHIf1bhymk+oHxvM9D3tyPndZH8i8+35p/1YiVoVswPid2yLzgX7ZJP0KQvnkhM4H6QZ5m0LzbyIAg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.59.0.tgz",
+ "integrity": "sha512-oF87Ie3uAIvORFBpwnCvUzdeYUqi2wY6jRFWJAy1qus/udHFYIkplYRW+wo+GRUP4sKzYdmE1Y3+rY5Gc4ZO+w==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-3AHmtQq/ppNuUspKAlvA8HtLybkDflkMuLK4DPo77DfthRb71V84/c4MlWJXixZz4uruIH4uaa07IqoAkG64fg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.59.0.tgz",
+ "integrity": "sha512-2UdiwS/9cTAx7qIUZB/fWtToJwvt0Vbo0zmnYt7ED35KPg13Q0ym1g442THLC7VyI6JfYTP4PiSOWyoMdV2/xg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.59.0.tgz",
+ "integrity": "sha512-M3bLRAVk6GOwFlPTIxVBSYKUaqfLrn8l0psKinkCFxl4lQvOSz8ZrKDz2gxcBwHFpci0B6rttydI4IpS4IS/jQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.59.0.tgz",
+ "integrity": "sha512-tt9KBJqaqp5i5HUZzoafHZX8b5Q2Fe7UjYERADll83O4fGqJ49O1FsL6LpdzVFQcpwvnyd0i+K/VSwu/o/nWlA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.59.0.tgz",
+ "integrity": "sha512-V5B6mG7OrGTwnxaNUzZTDTjDS7F75PO1ae6MJYdiMu60sq0CqN5CVeVsbhPxalupvTX8gXVSU9gq+Rx1/hvu6A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.59.0.tgz",
+ "integrity": "sha512-UKFMHPuM9R0iBegwzKF4y0C4J9u8C6MEJgFuXTBerMk7EJ92GFVFYBfOZaSGLu6COf7FxpQNqhNS4c4icUPqxA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.59.0.tgz",
+ "integrity": "sha512-laBkYlSS1n2L8fSo1thDNGrCTQMmxjYY5G0WFWjFFYZkKPjsMBsgJfGf4TLxXrF6RyhI60L8TMOjBMvXiTcxeA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.59.0.tgz",
+ "integrity": "sha512-2HRCml6OztYXyJXAvdDXPKcawukWY2GpR5/nxKp4iBgiO3wcoEGkAaqctIbZcNB6KlUQBIqt8VYkNSj2397EfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/babel__core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz",
+ "integrity": "sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.20.7",
+ "@babel/types": "^7.20.7",
+ "@types/babel__generator": "*",
+ "@types/babel__template": "*",
+ "@types/babel__traverse": "*"
+ }
+ },
+ "node_modules/@types/babel__generator": {
+ "version": "7.27.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__generator/-/babel__generator-7.27.0.tgz",
+ "integrity": "sha512-ufFd2Xi92OAVPYsy+P4n7/U7e68fex0+Ee8gSG9KX7eo084CWiQ4sdxktvdl0bOPupXtVJPY19zk6EwWqUQ8lg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__template": {
+ "version": "7.4.4",
+ "resolved": "https://registry.npmjs.org/@types/babel__template/-/babel__template-7.4.4.tgz",
+ "integrity": "sha512-h/NUaSyG5EyxBIp8YRxo4RMe2/qQgvyowRwVMzhYhBCONbW8PUsg4lkFMrhgZhUe5z3L3MiLDuvyJ/CaPa2A8A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/parser": "^7.1.0",
+ "@babel/types": "^7.0.0"
+ }
+ },
+ "node_modules/@types/babel__traverse": {
+ "version": "7.28.0",
+ "resolved": "https://registry.npmjs.org/@types/babel__traverse/-/babel__traverse-7.28.0.tgz",
+ "integrity": "sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==",
+ "dev": true,
+ "dependencies": {
+ "@babel/types": "^7.28.2"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz",
+ "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-react": {
+ "version": "4.7.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz",
+ "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/core": "^7.28.0",
+ "@babel/plugin-transform-react-jsx-self": "^7.27.1",
+ "@babel/plugin-transform-react-jsx-source": "^7.27.1",
+ "@rolldown/pluginutils": "1.0.0-beta.27",
+ "@types/babel__core": "^7.20.5",
+ "react-refresh": "^0.17.0"
+ },
+ "engines": {
+ "node": "^14.18.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0"
+ }
+ },
+ "node_modules/baseline-browser-mapping": {
+ "version": "2.10.0",
+ "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.0.tgz",
+ "integrity": "sha512-lIyg0szRfYbiy67j9KN8IyeD7q7hcmqnJ1ddWmNt19ItGpNN64mnllmxUNFIOdOm6by97jlL6wfpTTJrmnjWAA==",
+ "dev": true,
+ "bin": {
+ "baseline-browser-mapping": "dist/cli.cjs"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/browserslist": {
+ "version": "4.28.1",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.1.tgz",
+ "integrity": "sha512-ZC5Bd0LgJXgwGqUknZY/vkUQ04r8NXnJZ3yYi4vDmSiZmC/pdSN0NbNRPxZpbtO4uAfDUAFffO8IZoM3Gj8IkA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "baseline-browser-mapping": "^2.9.0",
+ "caniuse-lite": "^1.0.30001759",
+ "electron-to-chromium": "^1.5.263",
+ "node-releases": "^2.0.27",
+ "update-browserslist-db": "^1.2.0"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001777",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001777.tgz",
+ "integrity": "sha512-tmN+fJxroPndC74efCdp12j+0rk0RHwV5Jwa1zWaFVyw2ZxAuPeG8ZgWC3Wz7uSjT3qMRQ5XHZ4COgQmsCMJAQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ]
+ },
+ "node_modules/convert-source-map": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz",
+ "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==",
+ "dev": true
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.5.307",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.307.tgz",
+ "integrity": "sha512-5z3uFKBWjiNR44nFcYdkcXjKMbg5KXNdciu7mhTPo9tB7NbqSNP2sSnGR+fqknZSCwKkBN+oxiiajWs4dT6ORg==",
+ "dev": true
+ },
+ "node_modules/esbuild": {
+ "version": "0.27.3",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.3.tgz",
+ "integrity": "sha512-8VwMnyGCONIs6cWue2IdpHxHnAjzxnw2Zr7MkVxB2vjmQ2ivqGFb4LEG3SMnv0Gb2F/G/2yA8zUaiL1gywDCCg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=18"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.27.3",
+ "@esbuild/android-arm": "0.27.3",
+ "@esbuild/android-arm64": "0.27.3",
+ "@esbuild/android-x64": "0.27.3",
+ "@esbuild/darwin-arm64": "0.27.3",
+ "@esbuild/darwin-x64": "0.27.3",
+ "@esbuild/freebsd-arm64": "0.27.3",
+ "@esbuild/freebsd-x64": "0.27.3",
+ "@esbuild/linux-arm": "0.27.3",
+ "@esbuild/linux-arm64": "0.27.3",
+ "@esbuild/linux-ia32": "0.27.3",
+ "@esbuild/linux-loong64": "0.27.3",
+ "@esbuild/linux-mips64el": "0.27.3",
+ "@esbuild/linux-ppc64": "0.27.3",
+ "@esbuild/linux-riscv64": "0.27.3",
+ "@esbuild/linux-s390x": "0.27.3",
+ "@esbuild/linux-x64": "0.27.3",
+ "@esbuild/netbsd-arm64": "0.27.3",
+ "@esbuild/netbsd-x64": "0.27.3",
+ "@esbuild/openbsd-arm64": "0.27.3",
+ "@esbuild/openbsd-x64": "0.27.3",
+ "@esbuild/openharmony-arm64": "0.27.3",
+ "@esbuild/sunos-x64": "0.27.3",
+ "@esbuild/win32-arm64": "0.27.3",
+ "@esbuild/win32-ia32": "0.27.3",
+ "@esbuild/win32-x64": "0.27.3"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz",
+ "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/fdir": {
+ "version": "6.5.0",
+ "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz",
+ "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "peerDependencies": {
+ "picomatch": "^3 || ^4"
+ },
+ "peerDependenciesMeta": {
+ "picomatch": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/jsesc": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz",
+ "integrity": "sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==",
+ "dev": true,
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/json5": {
+ "version": "2.2.3",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz",
+ "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==",
+ "dev": true,
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-5.1.1.tgz",
+ "integrity": "sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^3.0.2"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.11",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
+ "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.36",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.36.tgz",
+ "integrity": "sha512-TdC8FSgHz8Mwtw9g5L4gR/Sh9XhSP/0DEkQxfEFXOpiul5IiHgHan2VhYYb6agDSfp4KuvltmGApc8HMgUrIkA==",
+ "dev": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true
+ },
+ "node_modules/picomatch": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.3.tgz",
+ "integrity": "sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.5.8",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.8.tgz",
+ "integrity": "sha512-OW/rX8O/jXnm82Ey1k44pObPtdblfiuWnrd8X7GJ7emImCOstunGbXUpp7HdBrFQX6rJzn3sPT397Wp5aCwCHg==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.11",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.1"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/react": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz",
+ "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/react-dom": {
+ "version": "18.3.1",
+ "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-18.3.1.tgz",
+ "integrity": "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw==",
+ "dependencies": {
+ "loose-envify": "^1.1.0",
+ "scheduler": "^0.23.2"
+ },
+ "peerDependencies": {
+ "react": "^18.3.1"
+ }
+ },
+ "node_modules/react-refresh": {
+ "version": "0.17.0",
+ "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz",
+ "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.59.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.59.0.tgz",
+ "integrity": "sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "1.0.8"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.59.0",
+ "@rollup/rollup-android-arm64": "4.59.0",
+ "@rollup/rollup-darwin-arm64": "4.59.0",
+ "@rollup/rollup-darwin-x64": "4.59.0",
+ "@rollup/rollup-freebsd-arm64": "4.59.0",
+ "@rollup/rollup-freebsd-x64": "4.59.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.59.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.59.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.59.0",
+ "@rollup/rollup-linux-arm64-musl": "4.59.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.59.0",
+ "@rollup/rollup-linux-loong64-musl": "4.59.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.59.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.59.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.59.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.59.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-gnu": "4.59.0",
+ "@rollup/rollup-linux-x64-musl": "4.59.0",
+ "@rollup/rollup-openbsd-x64": "4.59.0",
+ "@rollup/rollup-openharmony-arm64": "4.59.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.59.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.59.0",
+ "@rollup/rollup-win32-x64-gnu": "4.59.0",
+ "@rollup/rollup-win32-x64-msvc": "4.59.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/scheduler": {
+ "version": "0.23.2",
+ "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.23.2.tgz",
+ "integrity": "sha512-UOShsPwz7NrMUqhR6t0hWjFduvOzbtv7toDH1/hIrfRNIDBnnBWd0CwJTGvTpngVlmwGCdP9/Zl/tVrDqcuYzQ==",
+ "dependencies": {
+ "loose-envify": "^1.1.0"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.1",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
+ "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
+ "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/tinyglobby": {
+ "version": "0.2.15",
+ "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz",
+ "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==",
+ "dev": true,
+ "dependencies": {
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/SuperchupuDev"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.2.3.tgz",
+ "integrity": "sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.2.0",
+ "picocolors": "^1.1.1"
+ },
+ "bin": {
+ "update-browserslist-db": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.1.tgz",
+ "integrity": "sha512-w+N7Hifpc3gRjZ63vYBXA56dvvRlNWRczTdmCBBa+CotUzAPf5b7YMdMR/8CQoeYE5LX3W4wj6RYTgonm1b9DA==",
+ "dev": true,
+ "dependencies": {
+ "esbuild": "^0.27.0",
+ "fdir": "^6.5.0",
+ "picomatch": "^4.0.3",
+ "postcss": "^8.5.6",
+ "rollup": "^4.43.0",
+ "tinyglobby": "^0.2.15"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^20.19.0 || >=22.12.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^20.19.0 || >=22.12.0",
+ "jiti": ">=1.21.0",
+ "less": "^4.0.0",
+ "lightningcss": "^1.21.0",
+ "sass": "^1.70.0",
+ "sass-embedded": "^1.70.0",
+ "stylus": ">=0.54.8",
+ "sugarss": "^5.0.0",
+ "terser": "^5.16.0",
+ "tsx": "^4.8.1",
+ "yaml": "^2.4.2"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "jiti": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ },
+ "tsx": {
+ "optional": true
+ },
+ "yaml": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/yallist": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz",
+ "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==",
+ "dev": true
+ }
+ }
+}
diff --git a/.github/pages/package.json b/.github/pages/package.json
new file mode 100644
index 000000000..c7ccaf335
--- /dev/null
+++ b/.github/pages/package.json
@@ -0,0 +1,19 @@
+{
+ "name": "javadoc-index",
+ "private": true,
+ "version": "1.0.0",
+ "type": "module",
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0"
+ },
+ "devDependencies": {
+ "@vitejs/plugin-react": "^4.2.1",
+ "vite": "^7.3.1"
+ }
+}
diff --git a/.github/pages/src/App.jsx b/.github/pages/src/App.jsx
new file mode 100644
index 000000000..b306112fa
--- /dev/null
+++ b/.github/pages/src/App.jsx
@@ -0,0 +1,64 @@
+import styles from './App.module.css'
+import { useDocs } from './useDocs'
+import TerminalHeader from './components/TerminalHeader'
+import Section from './components/Section'
+import { ReleaseCard, ExperimentalCard } from './components/Card'
+import { LoadingState, ErrorState, EmptyState } from './components/States'
+import Footer from './components/Footer'
+
+export default function App() {
+ const { data, loading, error } = useDocs()
+
+ const releases = data?.releases || []
+ const experimental = data?.experimental || []
+
+ return (
+
+
+
+ {/* ── Releases ── */}
+
+ {loading ? (
+
+ ) : error ? (
+
+ ) : releases.length === 0 ? (
+
+ ) : (
+
+ {releases.map((r, i) => (
+
+ ))}
+
+ )}
+
+
+ {/* ── Experimental ── */}
+
+ {loading ? (
+
+ ) : error ? null : experimental.length === 0 ? (
+
+ ) : (
+
+ {experimental.map((b, i) => (
+
+ ))}
+
+ )}
+
+
+ {!loading && !error && (
+
+ )}
+
+ )
+}
diff --git a/.github/pages/src/App.module.css b/.github/pages/src/App.module.css
new file mode 100644
index 000000000..395790dac
--- /dev/null
+++ b/.github/pages/src/App.module.css
@@ -0,0 +1,16 @@
+.root {
+ max-width: 1100px;
+ margin: 0 auto;
+ padding: 48px 32px 96px;
+}
+
+.grid {
+ display: grid;
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
+ gap: 10px;
+}
+
+@media (max-width: 600px) {
+ .root { padding: 24px 16px 64px; }
+ .grid { grid-template-columns: 1fr; }
+}
diff --git a/.github/pages/src/components/Card.jsx b/.github/pages/src/components/Card.jsx
new file mode 100644
index 000000000..73b1f7cf0
--- /dev/null
+++ b/.github/pages/src/components/Card.jsx
@@ -0,0 +1,97 @@
+import styles from './Card.module.css'
+import { REPO_NAME } from '../config'
+
+const BASE_PATH = `/${REPO_NAME}`
+
+function formatDate(iso) {
+ if (!iso) return null
+ try {
+ return new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit', month: 'short', year: 'numeric'
+ }).format(new Date(iso))
+ } catch (e) { return null }
+}
+
+export function ReleaseCard({ release, index }) {
+ const isLatest = release.isLatest
+ const date = formatDate(release.date)
+ const cardClass = isLatest
+ ? styles.card + ' ' + styles.latest
+ : styles.card + ' ' + styles.release
+
+ return (
+
+
+
+ {isLatest && (
+ ● latest
+ )}
+ release
+
+
→
+
+
+
+ v{release.version}
+
+
+ {date && (
+
+ date
+ {date}
+
+ )}
+
+ path
+ {release.path}/
+
+
+
+ )
+}
+
+export function ExperimentalCard({ branch, index }) {
+ const date = formatDate(branch.date)
+
+ return (
+
+
+
+ experimental
+
+ →
+
+
+
+ {branch.branch}
+
+
+
+ {date && (
+
+ date
+ {date}
+
+ )}
+ {branch.hash && (
+
+ sha
+ {branch.hash}
+
+ )}
+
+ path
+ {branch.path}/
+
+
+
+ )
+}
diff --git a/.github/pages/src/components/Card.module.css b/.github/pages/src/components/Card.module.css
new file mode 100644
index 000000000..b19f37f72
--- /dev/null
+++ b/.github/pages/src/components/Card.module.css
@@ -0,0 +1,143 @@
+.card {
+ display: block;
+ text-decoration: none;
+ background: var(--bg2);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 20px 22px;
+ transition: border-color 0.15s, background 0.15s, transform 0.15s;
+ position: relative;
+ overflow: hidden;
+ animation: fadeUp 0.35s ease both;
+}
+
+.card::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 2px;
+ opacity: 0;
+ transition: opacity 0.15s;
+}
+
+.release::before { background: var(--green); }
+.latest::before { background: linear-gradient(90deg, var(--green), var(--blue)); }
+.experimental::before { background: var(--amber); }
+
+.card:hover {
+ border-color: var(--border2);
+ background: var(--bg3);
+ transform: translateY(-2px);
+}
+
+.card:hover::before { opacity: 1; }
+
+/* ── Header ── */
+.header {
+ display: flex;
+ align-items: center;
+ justify-content: space-between;
+ margin-bottom: 14px;
+}
+
+.badges {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+}
+
+.badge {
+ font-size: 9px;
+ font-weight: 600;
+ letter-spacing: 0.12em;
+ text-transform: uppercase;
+ padding: 2px 8px;
+ border-radius: 2px;
+ border: 1px solid;
+}
+
+.badgeLatest {
+ color: var(--green);
+ border-color: var(--green-dim);
+ background: rgba(57, 211, 83, 0.08);
+ animation: pulseBorder 2s ease infinite;
+}
+
+.badgeRelease {
+ color: var(--text-dim);
+ border-color: var(--border2);
+ background: transparent;
+}
+
+.badgeExperimental {
+ color: var(--amber);
+ border-color: var(--amber-dim);
+ background: rgba(240, 160, 48, 0.08);
+}
+
+.arrow {
+ font-size: 14px;
+ color: var(--border2);
+ transition: color 0.15s, transform 0.15s;
+}
+
+.card:hover .arrow {
+ color: var(--text-mid);
+ transform: translateX(3px);
+}
+
+/* ── Version ── */
+.version {
+ font-size: 20px;
+ font-weight: 500;
+ color: var(--text);
+ margin-bottom: 4px;
+ letter-spacing: -0.01em;
+}
+
+.latest .version { color: var(--green); }
+.versionBranch { font-size: 16px; color: var(--amber); }
+
+/* ── Meta ── */
+.meta {
+ display: flex;
+ flex-direction: column;
+ gap: 3px;
+ margin-top: 12px;
+ padding-top: 12px;
+ border-top: 1px solid var(--border);
+}
+
+.metaRow {
+ display: flex;
+ align-items: center;
+ gap: 8px;
+ font-size: 10px;
+ color: var(--text-dim);
+}
+
+.metaKey {
+ min-width: 36px;
+ color: var(--text-dim);
+}
+
+.metaVal {
+ color: var(--text-mid);
+ font-family: 'IBM Plex Mono', monospace;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+}
+
+.hash { color: var(--blue); }
+
+/* ── Animations ── */
+@keyframes fadeUp {
+ from { opacity: 0; transform: translateY(10px); }
+ to { opacity: 1; transform: translateY(0); }
+}
+
+@keyframes pulseBorder {
+ 0%, 100% { border-color: var(--green-dim); }
+ 50% { border-color: var(--green); }
+}
diff --git a/.github/pages/src/components/Footer.jsx b/.github/pages/src/components/Footer.jsx
new file mode 100644
index 000000000..503f5e0f4
--- /dev/null
+++ b/.github/pages/src/components/Footer.jsx
@@ -0,0 +1,29 @@
+import styles from './Footer.module.css'
+import { REPO_OWNER, REPO_NAME } from '../useDocs'
+
+export default function Footer({ source }) {
+ const now = new Intl.DateTimeFormat('en-GB', {
+ day: '2-digit', month: 'short', year: 'numeric',
+ hour: '2-digit', minute: '2-digit', timeZoneName: 'short'
+ }).format(new Date())
+
+ return (
+
+ )
+}
diff --git a/.github/pages/src/components/Footer.module.css b/.github/pages/src/components/Footer.module.css
new file mode 100644
index 000000000..26424d3b6
--- /dev/null
+++ b/.github/pages/src/components/Footer.module.css
@@ -0,0 +1,36 @@
+.footer {
+ margin-top: 64px;
+ padding-top: 20px;
+ border-top: 1px solid var(--border);
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ flex-wrap: wrap;
+ gap: 8px;
+ font-size: 11px;
+ color: var(--text-dim);
+}
+
+.dot {
+ display: inline-block;
+ width: 6px;
+ height: 6px;
+ border-radius: 50%;
+ background: var(--green);
+ margin-right: 6px;
+ animation: blink 2s ease infinite;
+}
+
+.link {
+ color: var(--blue);
+ text-decoration: none;
+}
+
+.link:hover {
+ text-decoration: underline;
+}
+
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0.3; }
+}
diff --git a/.github/pages/src/components/Section.jsx b/.github/pages/src/components/Section.jsx
new file mode 100644
index 000000000..dc28e1dbd
--- /dev/null
+++ b/.github/pages/src/components/Section.jsx
@@ -0,0 +1,18 @@
+import styles from './Section.module.css'
+
+export default function Section({ label, count, variant, children }) {
+ return (
+
+
+
{label}
+
+ {count !== undefined && (
+
+ {count} {label.toLowerCase()}{count !== 1 ? 's' : ''}
+
+ )}
+
+ {children}
+
+ )
+}
diff --git a/.github/pages/src/components/Section.module.css b/.github/pages/src/components/Section.module.css
new file mode 100644
index 000000000..00f90a81a
--- /dev/null
+++ b/.github/pages/src/components/Section.module.css
@@ -0,0 +1,46 @@
+.section {
+ margin-top: 40px;
+ margin-bottom: 8px;
+}
+
+.header {
+ display: flex;
+ align-items: center;
+ gap: 12px;
+ margin-bottom: 16px;
+}
+
+.tag {
+ font-size: 10px;
+ font-weight: 600;
+ letter-spacing: 0.15em;
+ text-transform: uppercase;
+ padding: 3px 10px;
+ border-radius: 3px;
+ border: 1px solid;
+ white-space: nowrap;
+}
+
+.releases {
+ color: var(--green);
+ border-color: var(--green-dim);
+ background: rgba(57, 211, 83, 0.06);
+}
+
+.experimental {
+ color: var(--amber);
+ border-color: var(--amber-dim);
+ background: rgba(240, 160, 48, 0.06);
+}
+
+.line {
+ flex: 1;
+ height: 1px;
+ background: var(--border);
+}
+
+.count {
+ font-size: 11px;
+ color: var(--text-dim);
+ white-space: nowrap;
+}
diff --git a/.github/pages/src/components/States.jsx b/.github/pages/src/components/States.jsx
new file mode 100644
index 000000000..2827d401a
--- /dev/null
+++ b/.github/pages/src/components/States.jsx
@@ -0,0 +1,30 @@
+import styles from './States.module.css'
+
+export function LoadingState() {
+ return (
+
+
+
Fetching documentation index...
+
+ )
+}
+
+export function ErrorState({ message }) {
+ return (
+
+
⚠
+
{message}
+
+ Check that the gh-pages branch exists and is publicly accessible.
+
+
+ )
+}
+
+export function EmptyState({ label }) {
+ return (
+
+ )
+}
diff --git a/.github/pages/src/components/States.module.css b/.github/pages/src/components/States.module.css
new file mode 100644
index 000000000..319fc6e52
--- /dev/null
+++ b/.github/pages/src/components/States.module.css
@@ -0,0 +1,47 @@
+.box {
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 48px;
+ text-align: center;
+ background: var(--bg2);
+}
+
+.empty {
+ padding: 24px;
+}
+
+.spinner {
+ width: 22px;
+ height: 22px;
+ border: 2px solid var(--border2);
+ border-top-color: var(--green);
+ border-radius: 50%;
+ animation: spin 0.75s linear infinite;
+ margin: 0 auto 14px;
+}
+
+.label {
+ font-size: 12px;
+ color: var(--text-dim);
+}
+
+.errorIcon {
+ font-size: 22px;
+ margin-bottom: 10px;
+ color: var(--red);
+}
+
+.errorMsg {
+ font-size: 13px;
+ color: var(--red);
+ margin-bottom: 6px;
+}
+
+.errorSub {
+ font-size: 11px;
+ color: var(--text-dim);
+}
+
+@keyframes spin {
+ to { transform: rotate(360deg); }
+}
diff --git a/.github/pages/src/components/TerminalHeader.jsx b/.github/pages/src/components/TerminalHeader.jsx
new file mode 100644
index 000000000..e2efb2779
--- /dev/null
+++ b/.github/pages/src/components/TerminalHeader.jsx
@@ -0,0 +1,186 @@
+import { useState, useEffect } from 'react'
+import styles from './TerminalHeader.module.css'
+import { PROJECT_NAME, REPO_NAME, LINKS, AUTHORS } from '../config'
+
+function useGitHubProfile(username) {
+ const [profile, setProfile] = useState(null)
+ const [loading, setLoading] = useState(true)
+
+ useEffect(() => {
+ const cacheKey = 'gh-profile-' + username
+
+ // Check sessionStorage first — avoids hammering the API on every render
+ try {
+ const cached = sessionStorage.getItem(cacheKey)
+ if (cached) {
+ setProfile(JSON.parse(cached))
+ setLoading(false)
+ return
+ }
+ } catch (e) {}
+
+ fetch('https://api.github.com/users/' + username)
+ .then(r => {
+ if (!r.ok) throw new Error('HTTP ' + r.status)
+ return r.json()
+ })
+ .then(d => {
+ // Don't cache error responses like rate limit messages
+ if (!d.message) {
+ try { sessionStorage.setItem(cacheKey, JSON.stringify(d)) } catch (e) {}
+ }
+ setProfile(d)
+ setLoading(false)
+ })
+ .catch(() => setLoading(false))
+ }, [username])
+
+ return { profile, loading }
+}
+
+function AuthorTile({ username, active }) {
+ const { profile, loading } = useGitHubProfile(username)
+ const tileClass = styles.authorTile + (active ? ' ' + styles.authorTileActive : '')
+
+ if (loading) {
+ return (
+
+ )
+ }
+
+ if (!profile || profile.message) {
+ return (
+
+
+
+
{username}
+
@{username}
+
+
+
+ )
+ }
+
+ return (
+
+
+
+
{profile.name || profile.login}
+
@{profile.login}
+ {profile.bio
+ ?
{profile.bio}
+ :
+ }
+
+
+ )
+}
+
+function AuthorsTiles() {
+ const [active, setActive] = useState(0)
+
+ useEffect(() => {
+ if (AUTHORS.length <= 1) return
+ const t = setInterval(() => {
+ setActive(i => (i + 1) % AUTHORS.length)
+ }, 4000)
+ return () => clearInterval(t)
+ }, [])
+
+ return (
+
+
+ › Authors:
+
+
+ {AUTHORS.map((username, i) => (
+
+ ))}
+
+
+ )
+}
+
+export default function TerminalHeader() {
+ return (
+
+
+
+
+
+
javadoc-index — bash
+
+
+
+ {PROJECT_NAME}
+
+
+
+ $
+ browse-docs
+ {REPO_NAME}
+ --all-versions
+
+
+
+ › Fetching available documentation builds...
+
+
+ › Found project links:
+
+
+
+
+
+
+
+ $
+
+
+
+
+ )
+}
diff --git a/.github/pages/src/components/TerminalHeader.module.css b/.github/pages/src/components/TerminalHeader.module.css
new file mode 100644
index 000000000..84853abac
--- /dev/null
+++ b/.github/pages/src/components/TerminalHeader.module.css
@@ -0,0 +1,303 @@
+.wrap {
+ margin-bottom: 48px;
+}
+
+.bar {
+ display: flex;
+ align-items: center;
+ gap: 6px;
+ background: var(--bg3);
+ border: 1px solid var(--border2);
+ border-bottom: none;
+ border-radius: 8px 8px 0 0;
+ padding: 10px 16px;
+}
+
+.dot {
+ width: 12px;
+ height: 12px;
+ border-radius: 50%;
+}
+.red { background: #ff5f57; }
+.amber { background: #febc2e; }
+.green { background: #28c840; }
+
+.title {
+ margin-left: 8px;
+ font-size: 11px;
+ color: var(--text-dim);
+ letter-spacing: 0.05em;
+}
+
+.body {
+ background: var(--bg2);
+ border: 1px solid var(--border2);
+ border-radius: 0 0 8px 8px;
+ padding: 16px 36px 36px;
+}
+
+/* ── Heading ── */
+.heading {
+ font-family: 'IBM Plex Sans', sans-serif;
+ font-size: clamp(28px, 5vw, 52px);
+ font-weight: 300;
+ letter-spacing: -0.02em;
+ color: var(--text);
+ line-height: 1.05;
+ margin-bottom: 20px;
+}
+
+.heading strong {
+ font-weight: 600;
+ color: var(--green);
+}
+
+/* ── Prompt ── */
+.promptLine {
+ display: flex;
+ align-items: baseline;
+ gap: 10px;
+ margin-bottom: 6px;
+ font-size: 13px;
+ flex-wrap: wrap;
+}
+
+.prompt { color: var(--green); font-weight: 600; }
+.cmd { color: var(--text); }
+.arg { color: var(--blue); }
+.flag { color: var(--amber); }
+
+/* ── Output lines ── */
+.outputLine {
+ font-size: 12px;
+ color: var(--text-dim);
+ line-height: 1.8;
+ padding-left: 20px;
+}
+
+.chevron {
+ color: var(--text-mid);
+ margin-right: 4px;
+}
+
+/* ── Links ── */
+.links {
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+ padding-left: 36px;
+ margin-top: 4px;
+ margin-bottom: 12px;
+}
+
+.link {
+ display: inline-flex;
+ align-items: center;
+ gap: 10px;
+ text-decoration: none;
+ padding: 3px 0;
+ width: fit-content;
+ transition: opacity 0.15s;
+}
+
+.link:hover { opacity: 0.8; }
+
+.link:hover .linkLabel {
+ text-decoration: underline;
+ text-underline-offset: 3px;
+}
+
+.linkTag {
+ font-size: 9px;
+ font-weight: 600;
+ letter-spacing: 0.1em;
+ border: 1px solid;
+ border-radius: 2px;
+ padding: 1px 5px;
+ min-width: 28px;
+ text-align: center;
+ opacity: 0.9;
+ font-family: 'IBM Plex Mono', monospace;
+}
+
+.linkLabel {
+ font-size: 12px;
+ color: var(--text-mid);
+ font-family: 'IBM Plex Mono', monospace;
+}
+
+.linkArrow {
+ font-size: 11px;
+ opacity: 0.6;
+ transition: opacity 0.15s, transform 0.15s;
+}
+
+.link:hover .linkArrow {
+ opacity: 1;
+ transform: translate(2px, -2px);
+}
+
+/* ── Authors ── */
+.authors {
+ margin-bottom: 8px;
+}
+
+.authorRow {
+ display: flex;
+ flex-direction: row;
+ gap: 8px;
+ padding-left: 36px;
+ margin-top: 6px;
+ flex-wrap: wrap;
+}
+
+/* ── Author tile ── */
+.authorTile {
+ display: flex;
+ align-items: center;
+ gap: 10px;
+ background: var(--bg3);
+ border: 1px solid var(--border);
+ border-radius: 6px;
+ padding: 8px 12px;
+ text-decoration: none;
+ cursor: pointer;
+ /* width is dynamic — grows with content */
+ width: max-content;
+ min-width: 140px;
+ position: relative;
+ overflow: hidden;
+ transition: border-color 0.2s, background 0.2s, transform 0.2s;
+}
+
+.authorTile::before {
+ content: '';
+ position: absolute;
+ top: 0; left: 0; right: 0;
+ height: 2px;
+ background: var(--green);
+ opacity: 0;
+ transition: opacity 0.2s;
+}
+
+.authorTileActive::before,
+.authorTile:hover::before {
+ opacity: 1;
+}
+
+.authorTileActive {
+ border-color: var(--green-dim);
+ background: rgba(57, 211, 83, 0.04);
+}
+
+.authorTile:hover {
+ border-color: var(--border2);
+ background: #161b22;
+ transform: translateY(-1px);
+}
+
+/* ── Author content ── */
+.authorAvatar {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ border: 1px solid var(--border2);
+ flex-shrink: 0;
+}
+
+.authorAvatarSkeleton {
+ width: 32px;
+ height: 32px;
+ border-radius: 50%;
+ background: var(--border2);
+ flex-shrink: 0;
+ animation: shimmer 1.5s ease infinite;
+}
+
+.authorInfo {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+ min-width: 0;
+}
+
+.authorName {
+ font-size: 12px;
+ font-weight: 500;
+ color: var(--text);
+ font-family: 'IBM Plex Sans', sans-serif;
+ white-space: nowrap;
+}
+
+.authorLogin {
+ font-size: 10px;
+ color: var(--blue);
+ font-family: 'IBM Plex Mono', monospace;
+ white-space: nowrap;
+}
+
+/* Bio — always reserves vertical height, width animates open */
+.authorBio {
+ display: block;
+ font-size: 10px;
+ color: var(--text-dim);
+ font-family: 'IBM Plex Sans', sans-serif;
+ margin-top: 2px;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ /* always reserve vertical space */
+ height: 14px;
+ /* collapsed horizontally */
+ width: 0;
+ opacity: 0;
+ transition: opacity 0.25s ease, width 0.3s ease;
+}
+
+.authorTileActive .authorBio,
+.authorTile:hover .authorBio {
+ width: 200px;
+ opacity: 1;
+}
+
+/* Placeholder for loading/no-profile/no-bio states */
+.authorBioPlaceholder {
+ height: 14px;
+ margin-top: 2px;
+ width: 0; /* never takes horizontal space, never expands */
+}
+
+.authorSkeleton {
+ height: 9px;
+ border-radius: 4px;
+ background: var(--border2);
+ animation: shimmer 1.5s ease infinite;
+}
+
+/* ── Cursor ── */
+.cursor {
+ display: inline-block;
+ width: 8px;
+ height: 13px;
+ background: var(--green);
+ margin-left: 2px;
+ vertical-align: text-bottom;
+ animation: blink 1s step-end infinite;
+}
+
+/* ── Animations ── */
+@keyframes blink {
+ 0%, 100% { opacity: 1; }
+ 50% { opacity: 0; }
+}
+
+@keyframes shimmer {
+ 0%, 100% { opacity: 0.4; }
+ 50% { opacity: 0.8; }
+}
+
+@media (max-width: 600px) {
+ .body { padding: 20px; }
+ .links { padding-left: 20px; }
+ .authorRow { padding-left: 20px; }
+}
diff --git a/.github/pages/src/config.js b/.github/pages/src/config.js
new file mode 100644
index 000000000..ed7a4baf9
--- /dev/null
+++ b/.github/pages/src/config.js
@@ -0,0 +1,38 @@
+// ─── Project Configuration ───────────────────────────────────
+// This is the ONLY file that should differ between projects.
+// Everything else in the pages app is generic.
+// ─────────────────────────────────────────────────────────────
+
+export const PROJECT_NAME = 'CustomNPC+'
+export const REPO_OWNER = 'KAMKEEL'
+export const REPO_NAME = 'CustomNPC-Plus'
+
+export const LINKS = [
+ {
+ tag: 'GH',
+ label: 'github.com/KAMKEEL/CustomNPC-Plus',
+ href: 'https://github.com/KAMKEEL/CustomNPC-Plus',
+ color: '#e6edf3',
+ },
+ {
+ tag: 'DC',
+ label: 'discord.gg/94qmjEDxkp',
+ href: 'https://discord.gg/94qmjEDxkp',
+ color: '#5865f2',
+ },
+ {
+ tag: 'CF',
+ label: 'curseforge.com/minecraft/mc-mods/customnpc-plus',
+ href: 'https://www.curseforge.com/minecraft/mc-mods/customnpc-plus',
+ color: '#f16436',
+ },
+ {
+ tag: 'MR',
+ label: 'modrinth.com/mod/customnpc-plus',
+ href: 'https://modrinth.com/mod/customnpc-plus/',
+ color: '#1bd96a',
+ },
+]
+
+// GitHub usernames — profiles are fetched at runtime
+export const AUTHORS = ['kamkeel', 'c0508383', 'bigguy345', 'somehussar']
diff --git a/.github/pages/src/index.css b/.github/pages/src/index.css
new file mode 100644
index 000000000..fef4fe128
--- /dev/null
+++ b/.github/pages/src/index.css
@@ -0,0 +1,50 @@
+*, *::before, *::after { box-sizing: border-box; margin: 0; padding: 0; }
+
+:root {
+ --bg: #080b0f;
+ --bg2: #0d1117;
+ --bg3: #131920;
+ --border: #1e2936;
+ --border2: #253040;
+ --green: #39d353;
+ --green-dim: #1a6b2a;
+ --amber: #f0a030;
+ --amber-dim: #7a4e10;
+ --blue: #58a6ff;
+ --blue-dim: #1a3a6b;
+ --text: #cdd9e5;
+ --text-dim: #636e7b;
+ --text-mid: #8b949e;
+ --red: #f85149;
+}
+
+html, body {
+ height: 100%;
+ background: var(--bg);
+ color: var(--text);
+ font-family: 'IBM Plex Mono', monospace;
+ overflow-x: hidden;
+}
+
+/* Scanlines */
+body::after {
+ content: '';
+ position: fixed;
+ inset: 0;
+ background: repeating-linear-gradient(
+ 0deg,
+ transparent,
+ transparent 2px,
+ rgba(0,0,0,0.025) 2px,
+ rgba(0,0,0,0.025) 4px
+ );
+ pointer-events: none;
+ z-index: 9999;
+}
+
+a { color: inherit; text-decoration: none; }
+
+::-webkit-scrollbar { width: 6px; }
+::-webkit-scrollbar-track { background: var(--bg2); }
+::-webkit-scrollbar-thumb { background: var(--border2); border-radius: 3px; }
+::-webkit-scrollbar-thumb:hover { background: var(--text-dim); }
diff --git a/.github/pages/src/main.jsx b/.github/pages/src/main.jsx
new file mode 100644
index 000000000..22bbe14e1
--- /dev/null
+++ b/.github/pages/src/main.jsx
@@ -0,0 +1,13 @@
+import React from 'react'
+import ReactDOM from 'react-dom/client'
+import App from './App'
+import { PROJECT_NAME } from './config'
+import './index.css'
+
+document.title = `${PROJECT_NAME} — Javadoc Index`
+
+ReactDOM.createRoot(document.getElementById('root')).render(
+
+
+
+)
diff --git a/.github/pages/src/useDocs.js b/.github/pages/src/useDocs.js
new file mode 100644
index 000000000..1c8c5b803
--- /dev/null
+++ b/.github/pages/src/useDocs.js
@@ -0,0 +1,94 @@
+import { useState, useEffect } from 'react'
+import { REPO_OWNER, REPO_NAME } from './config'
+
+const BASE_PATH = `/${REPO_NAME}`
+const API_BASE = `https://api.github.com/repos/${REPO_OWNER}/${REPO_NAME}`
+
+// Cache bust on every page load — appends current timestamp to manifest URL
+// so the browser never serves a stale manifest.json
+async function fetchFromManifest() {
+ const bust = `?t=${Date.now()}`
+ const res = await fetch(`${BASE_PATH}/manifest.json${bust}`, {
+ cache: 'no-store', // tells browser not to cache this request at all
+ })
+ if (!res.ok) throw new Error('No manifest')
+ return res.json()
+}
+
+async function fetchFromGitHubAPI() {
+ const refRes = await fetch(`${API_BASE}/git/ref/heads/gh-pages`)
+ if (!refRes.ok) throw new Error('gh-pages branch not found')
+ const refData = await refRes.json()
+ const sha = refData.object.sha
+
+ const treeRes = await fetch(`${API_BASE}/git/trees/${sha}?recursive=1`)
+ if (!treeRes.ok) throw new Error('Could not fetch tree')
+ const treeData = await treeRes.json()
+
+ const commitsRes = await fetch(`${API_BASE}/commits?sha=gh-pages&per_page=100`)
+ const commits = commitsRes.ok ? await commitsRes.json() : []
+
+ const commitMap = {}
+ commits.forEach(c => {
+ const date = c.commit?.committer?.date || c.commit?.author?.date || ''
+ const hash = c.sha?.slice(0, 7) || ''
+ const msg = c.commit?.message || ''
+ const vMatch = msg.match(/releases\/([\d.]+)/)
+ if (vMatch) commitMap[`releases/${vMatch[1]}`] = { date, hash }
+ const eMatch = msg.match(/experimental\/([\w-]+)/)
+ if (eMatch) commitMap[`experimental/${eMatch[1]}`] = { date, hash }
+ })
+
+ const releaseDirs = new Set()
+ const experimentalDirs = new Set()
+
+ treeData.tree.forEach(item => {
+ if (item.type !== 'tree') return
+ const parts = item.path.split('/')
+ if (parts[0] === 'releases' && parts.length === 2) releaseDirs.add(parts[1])
+ if (parts[0] === 'experimental' && parts.length === 2) experimentalDirs.add(parts[1])
+ })
+
+ // Sort releases newest first, mark first as latest
+ const sortedReleases = [...releaseDirs]
+ .sort((a, b) => b.localeCompare(a, undefined, { numeric: true }))
+
+ const releases = sortedReleases.map((v, i) => {
+ const c = commitMap[`releases/${v}`] || {}
+ return {
+ id: v,
+ version: v,
+ path: `releases/${v}`,
+ date: c.date,
+ hash: c.hash,
+ isLatest: i === 0, // newest version is always latest
+ }
+ })
+
+ const experimental = [...experimentalDirs].sort().map(b => {
+ const c = commitMap[`experimental/${b}`] || {}
+ return { id: b, branch: b, path: `experimental/${b}`, date: c.date, hash: c.hash }
+ })
+
+ return { releases, experimental, source: 'api' }
+}
+
+export function useDocs() {
+ const [data, setData] = useState(null)
+ const [loading, setLoading] = useState(true)
+ const [error, setError] = useState(null)
+
+ useEffect(() => {
+ fetchFromManifest()
+ .then(d => { setData(d); setLoading(false) })
+ .catch(() =>
+ fetchFromGitHubAPI()
+ .then(d => { setData(d); setLoading(false) })
+ .catch(e => { setError(e.message); setLoading(false) })
+ )
+ }, [])
+
+ return { data, loading, error }
+}
+
+export { BASE_PATH, REPO_OWNER, REPO_NAME }
diff --git a/.github/pages/vite.config.js b/.github/pages/vite.config.js
new file mode 100644
index 000000000..1b00c454f
--- /dev/null
+++ b/.github/pages/vite.config.js
@@ -0,0 +1,12 @@
+import { defineConfig } from 'vite'
+import react from '@vitejs/plugin-react'
+import { REPO_NAME } from './src/config.js'
+
+export default defineConfig({
+ plugins: [react()],
+ base: `/${REPO_NAME}/`,
+ build: {
+ outDir: '../../.github/pages-dist',
+ emptyOutDir: true,
+ }
+})
diff --git a/.github/scripts/doc-config.sh b/.github/scripts/doc-config.sh
new file mode 100644
index 000000000..37f6a0d90
--- /dev/null
+++ b/.github/scripts/doc-config.sh
@@ -0,0 +1,18 @@
+#!/bin/bash
+# -----------------------------------------------
+# doc-config.sh — shared config for javadoc deploy
+#
+# Sourced by:
+# - resolve-doc-path.sh
+# - javadoc.yml (via sanity-check job)
+#
+# ADD WHITELISTED BRANCHES HERE AND NOWHERE ELSE.
+# main is always handled automatically.
+# -----------------------------------------------
+
+MAIN_BRANCH_NAME="master"
+
+EXPERIMENTAL_WHITELIST=(
+ "dev"
+ # "new-branch"
+)
diff --git a/.github/scripts/generate-index.sh b/.github/scripts/generate-index.sh
new file mode 100644
index 000000000..7651a800e
--- /dev/null
+++ b/.github/scripts/generate-index.sh
@@ -0,0 +1,107 @@
+#!/bin/bash
+set -e
+
+RELEASES=()
+EXPERIMENTAL=()
+
+# Collect releases
+if [ -d "./releases" ]; then
+ for dir in ./releases/*/; do
+ [ -d "$dir" ] || continue
+ name=$(basename "$dir")
+ RELEASES+=("$name")
+ done
+fi
+
+# Collect experimental branches
+if [ -d "./experimental" ]; then
+ for dir in ./experimental/*/; do
+ [ -d "$dir" ] || continue
+ name=$(basename "$dir")
+ EXPERIMENTAL+=("$name")
+ done
+fi
+
+# Sort releases in reverse semver order (newest first)
+if [ ${#RELEASES[@]} -gt 0 ]; then
+ IFS=$'\n' RELEASES=($(sort -rV <<< "${RELEASES[*]}")); unset IFS
+fi
+
+# ── Store source commit metadata for this deploy ───────────────
+# DEPLOY_PATH, SOURCE_HASH, SOURCE_DATE are passed in as env vars from the workflow
+if [ -n "$DEPLOY_PATH" ] && [ -n "$SOURCE_HASH" ]; then
+ mkdir -p ".doc-meta"
+ META_KEY="${DEPLOY_PATH//\//_}"
+ cat > ".doc-meta/${META_KEY}.json" << JSON
+{"hash":"${SOURCE_HASH}","date":"${SOURCE_DATE}"}
+JSON
+ echo "Stored source metadata for $DEPLOY_PATH → $SOURCE_HASH @ $SOURCE_DATE"
+fi
+
+# ── Helper: get source commit hash + date for a path ──────────
+get_commit_info() {
+ local path="$1"
+
+ # If this is the path we just deployed, use the injected source commit
+ if [ "$path" = "$DEPLOY_PATH" ] && [ -n "$SOURCE_HASH" ]; then
+ echo "${SOURCE_HASH} ${SOURCE_DATE}"
+ return
+ fi
+
+ # Try reading from stored metadata file (set by previous deploys)
+ local meta=".doc-meta/${path//\//_}.json"
+ if [ -f "$meta" ]; then
+ local hash date
+ hash=$(python3 -c "import json,sys; d=json.load(open('$meta')); print(d.get('hash',''))" 2>/dev/null || grep -o '"hash":"[^"]*"' "$meta" | cut -d'"' -f4)
+ date=$(python3 -c "import json,sys; d=json.load(open('$meta')); print(d.get('date',''))" 2>/dev/null || grep -o '"date":"[^"]*"' "$meta" | cut -d'"' -f4)
+ echo "${hash:-} ${date:-}"
+ return
+ fi
+
+ # Last resort: gh-pages git log (hash will be wrong but better than empty)
+ local hash date
+ hash=$(git log --oneline -1 -- "$path" 2>/dev/null | awk '{print $1}')
+ date=$(git log --format="%cI" -1 -- "$path" 2>/dev/null)
+ echo "${hash:-} ${date:-}"
+}
+
+# ── Build releases JSON ────────────────────────────────────────
+RELEASES_JSON="["
+
+for i in "${!RELEASES[@]}"; do
+ version="${RELEASES[$i]}"
+ read -r HASH DATE <<< "$(get_commit_info "releases/$version")"
+ if [ "$i" -eq 0 ]; then
+ IS_LATEST="true"
+ else
+ IS_LATEST="false"
+ fi
+ RELEASES_JSON+="{\"id\":\"${version}\",\"version\":\"${version}\",\"path\":\"releases/${version}\",\"date\":\"${DATE}\",\"isLatest\":${IS_LATEST}},"
+done
+
+RELEASES_JSON="${RELEASES_JSON%,}]"
+
+# ── Build experimental JSON ────────────────────────────────────
+EXPERIMENTAL_JSON="["
+
+for branch in "${EXPERIMENTAL[@]}"; do
+ read -r HASH DATE <<< "$(get_commit_info "experimental/$branch")"
+ EXPERIMENTAL_JSON+="{\"id\":\"${branch}\",\"branch\":\"${branch}\",\"path\":\"experimental/${branch}\",\"hash\":\"${HASH}\",\"date\":\"${DATE}\"},"
+done
+
+EXPERIMENTAL_JSON="${EXPERIMENTAL_JSON%,}]"
+
+# ── Write manifest.json ────────────────────────────────────────
+cat > manifest.json << JSON
+{
+ "releases": ${RELEASES_JSON},
+ "experimental": ${EXPERIMENTAL_JSON},
+ "generated": "$(date -u '+%Y-%m-%dT%H:%M:%SZ')",
+ "source": "manifest"
+}
+JSON
+
+echo "manifest.json generated:"
+cat manifest.json
+echo ""
+echo "Done."
diff --git a/.github/scripts/resolve-doc-path.sh b/.github/scripts/resolve-doc-path.sh
new file mode 100644
index 000000000..92ad7ac8c
--- /dev/null
+++ b/.github/scripts/resolve-doc-path.sh
@@ -0,0 +1,52 @@
+#!/bin/bash
+set -e
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
+source "$SCRIPT_DIR/doc-config.sh"
+
+BRANCH="${GITHUB_REF#refs/heads/}"
+SAFE_BRANCH="${BRANCH//\//-}"
+
+if [ "$BRANCH" = "$MAIN_BRANCH_NAME" ]; then
+ # Version resolution order:
+ # -> check if gradle.properties has a `modVersion` tag. If it does, just grab it.
+ # -> check if it has the `version` tag. Grab it if it does
+ # -> check build.gradle for the version tag.
+ if [ -f "gradle.properties" ] && grep -q "^modVersion=" gradle.properties; then
+ VERSION=$(grep "^modVersion=" gradle.properties | cut -d'=' -f2 | tr -d '[:space:]')
+ elif [ -f "gradle.properties" ] && grep -q "^version=" gradle.properties; then
+ VERSION=$(grep "^version=" gradle.properties | cut -d'=' -f2 | tr -d '[:space:]')
+ elif [ -f "build.gradle" ]; then
+ VERSION=$(grep "^version" build.gradle | head -1 | sed "s/version\s*=\s*['\"]//;s/['\"].*//;s/\s//g")
+ elif [ -f "build.gradle.kts" ]; then
+ VERSION=$(grep "^version" build.gradle.kts | head -1 | sed "s/version\s*=\s*\"//;s/\".*//;s/\s//g")
+ else
+ echo "Could not find version. Defaulting to 'unknown'."
+ VERSION="unknown"
+ fi
+
+ echo "Resolved version: $VERSION"
+
+ # Only deploy to releases/ — isLatest is handled by generate-index.sh
+ echo "path=releases/$VERSION" >> $GITHUB_OUTPUT
+ echo "extra_path=" >> $GITHUB_OUTPUT
+
+else
+ MATCHED=false
+ for ALLOWED in "${EXPERIMENTAL_WHITELIST[@]}"; do
+ if [ "$BRANCH" = "$ALLOWED" ]; then
+ MATCHED=true
+ break
+ fi
+ done
+
+ if [ "$MATCHED" = true ]; then
+ echo "path=experimental/$SAFE_BRANCH" >> $GITHUB_OUTPUT
+ echo "extra_path=" >> $GITHUB_OUTPUT
+ echo "Branch '$BRANCH' is whitelisted, deploying to experimental/$SAFE_BRANCH"
+ else
+ echo "path=" >> $GITHUB_OUTPUT
+ echo "extra_path=" >> $GITHUB_OUTPUT
+ echo "Branch '$BRANCH' is not whitelisted. Skipping deploy."
+ fi
+fi
diff --git a/.github/workflows/build-and-test.yml b/.github/workflows/build-and-test.yml
index 2d7779d62..c2fb186fe 100644
--- a/.github/workflows/build-and-test.yml
+++ b/.github/workflows/build-and-test.yml
@@ -24,8 +24,8 @@ jobs:
with:
fetch-depth: 0
- - name: Initialize CNPC+ API submodule
- run: git submodule update --init --recursive src/api
+ - name: Initialize submodules
+ run: git submodule update --init --recursive
- name: Checkout workflows repo
uses: actions/checkout@v4
diff --git a/.github/workflows/javadoc.yml b/.github/workflows/javadoc.yml
new file mode 100644
index 000000000..52954f202
--- /dev/null
+++ b/.github/workflows/javadoc.yml
@@ -0,0 +1,224 @@
+name: Generate and Deploy Javadoc
+
+on:
+ push:
+ branches:
+ - master
+ - '**'
+ delete:
+ workflow_dispatch:
+ inputs:
+ force_deploy:
+ description: 'Force redeploy Javadocs without a new commit'
+ required: false
+ default: 'true'
+
+jobs:
+ sanity-check:
+ runs-on: ubuntu-latest
+ outputs:
+ should_deploy: ${{ steps.deploy_check.outputs.should_deploy }}
+ steps:
+ - uses: actions/checkout@v4
+ - name: Check for deploy tag
+ id: deploy_check
+ run: |
+ source .github/scripts/doc-config.sh
+
+ BRANCH="${GITHUB_REF#refs/heads/}"
+
+ # Always deploy on manual trigger
+ if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
+ echo "should_deploy=true" >> $GITHUB_OUTPUT
+ echo "Reason: workflow_dispatch"
+ exit 0
+ fi
+
+ # Main branch: only deploy if commit message contains [deploy-docs]
+ if [ "$BRANCH" = "$MAIN_BRANCH_NAME" ]; then
+ if echo "${{ github.event.head_commit.message }}" | grep -q "\[deploy-docs\]"; then
+ echo "should_deploy=true" >> $GITHUB_OUTPUT
+ echo "Reason: master branch with [deploy-docs] tag"
+ else
+ echo "should_deploy=false" >> $GITHUB_OUTPUT
+ echo "Reason: master branch without [deploy-docs] tag, skipping"
+ fi
+ exit 0
+ fi
+
+ # Experimental branches: only deploy if whitelisted
+ for ALLOWED in "${EXPERIMENTAL_WHITELIST[@]}"; do
+ if [ "$BRANCH" = "$ALLOWED" ]; then
+ echo "should_deploy=true" >> $GITHUB_OUTPUT
+ echo "Reason: branch '$BRANCH' is whitelisted"
+ exit 0
+ fi
+ done
+
+ echo "should_deploy=false" >> $GITHUB_OUTPUT
+ echo "Reason: branch '$BRANCH' is not whitelisted, skipping"
+
+ build-and-deploy-javadoc:
+ needs: sanity-check
+ if: needs.sanity-check.outputs.should_deploy == 'true'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout source branch
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+
+ - name: Initialize submodules
+ run: git submodule update --init --recursive
+
+ - name: Setup JDK 8 (compile)
+ uses: actions/setup-java@v4
+ with:
+ java-version: '8'
+ distribution: 'temurin'
+
+ - name: Set up JDK 8 (Zulu)
+ uses: actions/setup-java@v4
+ with:
+ java-version: '8'
+ distribution: 'zulu'
+ - name: Setup JDK 11 (javadoc)
+ uses: actions/setup-java@v4
+ with:
+ java-version: '11'
+ distribution: 'temurin'
+
+ - name: Setup Node
+ uses: actions/setup-node@v4
+ with:
+ node-version: '20'
+
+ - name: Extract convention plugin version
+ id: convention
+ run: |
+ VERSION=$(grep "id 'com.gtnewhorizons.gtnhsettingsconvention'" settings.gradle settings.gradle.kts 2>/dev/null | grep -o "version '[0-9]\+\.[0-9]\+\.[0-9]\+'" | grep -o '[0-9]\+\.[0-9]\+\.[0-9]\+')
+ echo "version=$VERSION" >> $GITHUB_OUTPUT
+ echo "Detected convention version: $VERSION"
+
+ - name: Get source commit info
+ id: source_commit
+ run: |
+ echo "hash=$(git rev-parse --short HEAD)" >> $GITHUB_OUTPUT
+ echo "date=$(git log -1 --format='%cI')" >> $GITHUB_OUTPUT
+
+ - name: Cache Gradle wrapper
+ uses: actions/cache@v4
+ with:
+ path: ~/.gradle/wrapper
+ key: gradle-wrapper-${{ hashFiles('**/gradle-wrapper.properties') }}
+
+ - name: Cache Gradle dependencies
+ uses: actions/cache@v4
+ with:
+ path: |
+ ~/.gradle/caches
+ ~/.gradle/caches/modules-*
+ ~/.gradle/caches/jars-*
+ key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle*', '**/gradle-wrapper.properties', '**/gradle.properties') }}
+ restore-keys: |
+ ${{ runner.os }}-gradle-
+
+ - name: Cache RFG
+ uses: actions/cache@v4
+ with:
+ path: |
+ build/rfg
+ build/tmp/.cache/expanded
+ build/tmp/deobfuscateMergedJarToSrg
+ key: ${{ runner.os }}-rfg-convention${{ steps.convention.outputs.version }}
+ restore-keys: |
+ ${{ runner.os }}-rfg-
+
+ - name: Cache Node modules
+ uses: actions/cache@v4
+ with:
+ path: .github/pages/node_modules
+ key: ${{ runner.os }}-node-${{ hashFiles('.github/pages/package-lock.json') }}
+ restore-keys: |
+ ${{ runner.os }}-node-
+
+ - name: Generate Javadoc
+ run: ./gradlew javadoc
+
+ - name: Get deploy path
+ id: deploy_path
+ run: bash .github/scripts/resolve-doc-path.sh
+
+ - name: Deploy to gh-pages
+ if: steps.deploy_path.outputs.path != ''
+ uses: peaceiris/actions-gh-pages@v4
+ with:
+ github_token: ${{ secrets.GITHUB_TOKEN }}
+ publish_dir: ./build/docs/javadoc
+ destination_dir: ${{ steps.deploy_path.outputs.path }}
+ keep_files: true
+
+ # Give GitHub a moment to settle after the peaceiris push
+ - name: Wait for gh-pages to settle
+ run: sleep 10
+
+ - name: Checkout gh-pages
+ uses: actions/checkout@v4
+ with:
+ ref: gh-pages
+ path: gh-pages-index
+
+ - name: Generate manifest
+ run: |
+ cd gh-pages-index
+ SOURCE_HASH="${{ steps.source_commit.outputs.hash }}" \
+ SOURCE_DATE="${{ steps.source_commit.outputs.date }}" \
+ DEPLOY_PATH="${{ steps.deploy_path.outputs.path }}" \
+ bash ../.github/scripts/generate-index.sh
+
+ - name: Install page dependencies
+ run: npm ci --prefix .github/pages
+
+ - name: Build index page
+ run: |
+ mkdir -p .github/pages/public
+ cp gh-pages-index/manifest.json .github/pages/public/manifest.json
+ npm run build --prefix .github/pages
+
+ - name: Deploy index to gh-pages
+ run: |
+ cp -r .github/pages-dist/* gh-pages-index/
+ cd gh-pages-index
+ git config user.name "github-actions"
+ git config user.email "github-actions@github.com"
+ git add -A
+ git commit -m "Regenerate index and manifest [skip ci]" || echo "Unchanged"
+ git push
+
+ cleanup-on-delete:
+ if: github.event_name == 'delete' && github.event.ref_type == 'branch'
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout gh-pages
+ uses: actions/checkout@v4
+ with:
+ ref: 'gh-pages'
+
+ - name: Remove deleted branch docs
+ run: |
+ BRANCH="${{ github.event.ref }}"
+ SAFE="experimental/${BRANCH//\//-}"
+ if [ -d "./$SAFE" ]; then
+ rm -rf "./$SAFE"
+ META_KEY="${SAFE//\//_}"
+ rm -f ".doc-meta/${META_KEY}.json"
+ git config user.name "github-actions"
+ git config user.email "github-actions@github.com"
+ git add -A
+ git commit -m "Remove docs for deleted branch: $BRANCH"
+ git push
+ else
+ echo "No docs found for $SAFE, skipping."
+ fi
diff --git a/.gitignore b/.gitignore
index 4d121433c..5d29506b6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -45,4 +45,6 @@ src/main/resources/assets/customnpcs/api
df/
tmp/
src/test
+node_modules
+.github/pages-dist
diff --git a/build.gradle b/build.gradle
index 01a710526..a76186d6d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -6,7 +6,7 @@ plugins {
id "dts.typescript-generator"
}
-version = "1.11-prerelease3"
+version = "1.11"
group= "kamkeel.customnpc-plus"
archivesBaseName = "CustomNPC-Plus"
@@ -81,11 +81,32 @@ processResources.dependsOn generateTypeScriptDefinitions
sourcesJar.dependsOn generateTypeScriptDefinitions
-// Register @hookName as a valid Javadoc tag
-tasks.withType(Javadoc) {
+// Javadoc configuration
+javadoc {
+ options.encoding = 'UTF-8'
+ options.charSet = 'UTF-8'
+ options.docEncoding = 'UTF-8'
+
options.tags = [
'hookName:a:Hook Name:'
]
+
+ options.stylesheetFile = file('.github/javadoc/javadoc-theme.css')
+ options.header = file('.github/javadoc/javadoc-header.html').text.replaceAll('\\s+', ' ').trim()
+
+ options.addBooleanOption('-allow-script-in-comments', true)
+ options.footer = file('.github/javadoc/javadoc-footer.html').text.replaceAll('\\s+', ' ').trim()
+
+ // Only document the public API, not net.minecraft stubs
+ include 'noppes/npcs/api/**'
+
+ options.links(
+ 'https://docs.oracle.com/en/java/javase/11/docs/api/'
+ )
+
+ javadocTool = javaToolchains.javadocToolFor {
+ languageVersion = JavaLanguageVersion.of(11)
+ }
}
// Modify the existing 'build' task to depend on 'updateAPI'
diff --git a/src/api b/src/api
index efce1a99b..f91e17ab3 160000
--- a/src/api
+++ b/src/api
@@ -1 +1 @@
-Subproject commit efce1a99b75dc6931e406b77bf188115befb54e2
+Subproject commit f91e17ab3ef4bf168836f8619c60cf6806f6ab84
diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java
index c870f6a07..17316fcf2 100644
--- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java
+++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyBeam.java
@@ -63,9 +63,9 @@ public class RenderEnergyBeam extends RenderEnergy {
double renderOriginY = y - headOffsetY;
double renderOriginZ = z - headOffsetZ;
- // Get beam properties
- float beamWidth = beam.getBeamWidth();
- float headSize = beam.getHeadSize();
+ // Get beam properties (interpolated for smooth size transitions)
+ float beamWidth = beam.getInterpolatedBeamWidth(partialTicks);
+ float headSize = beam.getInterpolatedHeadSize(partialTicks);
int innerColor = beam.getInnerColor();
int outerColor = beam.getOuterColor();
float outerAlpha = beam.getOuterColorAlpha() * proximityAlpha;
@@ -105,7 +105,7 @@ public class RenderEnergyBeam extends RenderEnergy {
* Grows from 0 to headSize based on charge progress.
*/
private void renderChargingOrb(EntityAbilityBeam beam, double x, double y, double z, float partialTicks, float proximityAlpha) {
- float headSize = beam.getHeadSize();
+ float headSize = beam.getInterpolatedHeadSize(partialTicks);
float chargeProgress = beam.getInterpolatedChargeProgress(partialTicks);
float size = headSize * chargeProgress;
diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDisc.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDisc.java
index eb4d9f21c..93b90023e 100644
--- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDisc.java
+++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyDisc.java
@@ -31,8 +31,8 @@ public class RenderEnergyDisc extends RenderEnergy {
// Get interpolated values
float size = disc.getInterpolatedSize(partialTicks);
- float radius = disc.getDiscRadius() * size;
- float thickness = disc.getDiscThickness() * size;
+ float radius = disc.getInterpolatedDiscRadius(partialTicks) * size;
+ float thickness = disc.getInterpolatedDiscThickness(partialTicks) * size;
// Subtle pulsing effect
float pulseTime = entity.ticksExisted + partialTicks;
diff --git a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyLaser.java b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyLaser.java
index c2cfe716a..8b0c79b65 100644
--- a/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyLaser.java
+++ b/src/main/java/kamkeel/npcs/client/renderer/RenderEnergyLaser.java
@@ -43,7 +43,7 @@ public class RenderEnergyLaser extends RenderEnergy {
double endX = laser.getInterpolatedEndX(partialTicks);
double endY = laser.getInterpolatedEndY(partialTicks);
double endZ = laser.getInterpolatedEndZ(partialTicks);
- float width = laser.getLaserWidth();
+ float width = laser.getInterpolatedLaserWidth(partialTicks);
float alpha = 1.0f;
// Calculate render offset (entity position is used as reference)
@@ -156,7 +156,7 @@ public class RenderEnergyLaser extends RenderEnergy {
}
private void renderChargingOrb(EntityAbilityLaser laser, double x, double y, double z, float partialTicks, float proximityAlpha) {
- float headSize = laser.getLaserWidth() * 0.5f;
+ float headSize = laser.getInterpolatedLaserWidth(partialTicks) * 0.5f;
float chargeProgress = laser.getInterpolatedChargeProgress(partialTicks);
float size = headSize * chargeProgress;
diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java
index baeb7f81c..4e5e2e3cb 100644
--- a/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java
+++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityBeam.java
@@ -41,6 +41,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
// Beam shape properties
private float beamWidth = 0.3f;
private float headSize = 0.5f;
+ private float prevBeamWidth = 0.3f;
+ private float prevHeadSize = 0.5f;
// Trail - list of points showing beam path (RELATIVE to origin!)
private List trailPoints = new ArrayList<>();
@@ -240,10 +242,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
return;
}
- // Store previous head offset for interpolation
+ // Store previous values for interpolation
prevHeadOffsetX = headOffsetX;
prevHeadOffsetY = headOffsetY;
prevHeadOffsetZ = headOffsetZ;
+ prevBeamWidth = beamWidth;
+ prevHeadSize = headSize;
// Age trail points for fading effect
if (isFadingMode()) {
@@ -580,7 +584,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
return beamWidth;
}
+ public float getInterpolatedBeamWidth(float partialTicks) {
+ return prevBeamWidth + (beamWidth - prevBeamWidth) * partialTicks;
+ }
+
public void setBeamWidth(float beamWidth) {
+ this.prevBeamWidth = this.beamWidth;
this.beamWidth = beamWidth;
}
@@ -588,7 +597,12 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
return headSize;
}
+ public float getInterpolatedHeadSize(float partialTicks) {
+ return prevHeadSize + (headSize - prevHeadSize) * partialTicks;
+ }
+
public void setHeadSize(float headSize) {
+ this.prevHeadSize = this.headSize;
this.headSize = headSize;
}
@@ -747,6 +761,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
@Override
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
+ prevBeamWidth = beamWidth;
+ prevHeadSize = headSize;
beamWidth = nbt.getFloat("BeamWidth");
headSize = nbt.getFloat("HeadSize");
setAttachedToOwner(nbt.getBoolean("AttachedToOwner"));
@@ -758,6 +774,8 @@ public class EntityAbilityBeam extends EntityEnergyProjectile {
protected void readProjectileNBT(NBTTagCompound nbt) {
this.beamWidth = sanitize(nbt.hasKey("BeamWidth") ? nbt.getFloat("BeamWidth") : 0.3f, 0.3f, MAX_ENTITY_SIZE);
this.headSize = sanitize(nbt.hasKey("HeadSize") ? nbt.getFloat("HeadSize") : 0.5f, 0.5f, MAX_ENTITY_SIZE);
+ this.prevBeamWidth = this.beamWidth;
+ this.prevHeadSize = this.headSize;
this.headOffsetX = nbt.hasKey("HeadOffsetX") ? nbt.getDouble("HeadOffsetX") : 0;
this.headOffsetY = nbt.hasKey("HeadOffsetY") ? nbt.getDouble("HeadOffsetY") : 0;
this.headOffsetZ = nbt.hasKey("HeadOffsetZ") ? nbt.getDouble("HeadOffsetZ") : 0;
diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java
index 817357d9b..db5e2eb2c 100644
--- a/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java
+++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityDisc.java
@@ -33,6 +33,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
// Disc shape properties
private float discRadius = 1.0f; // Width of disc
private float discThickness = 0.2f; // Height of disc
+ private float prevDiscRadius = 1.0f;
+ private float prevDiscThickness = 0.2f;
// Boomerang owner-gone tracking
private int returnOwnerNullTicks = 0;
@@ -134,6 +136,10 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
@Override
protected void updateProjectile() {
+ // Store previous values for interpolation
+ prevDiscRadius = discRadius;
+ prevDiscThickness = discThickness;
+
// Handle charging state (windup phase)
if (isCharging()) {
updateCharging();
@@ -347,7 +353,12 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
return discRadius;
}
+ public float getInterpolatedDiscRadius(float partialTicks) {
+ return prevDiscRadius + (discRadius - prevDiscRadius) * partialTicks;
+ }
+
public void setDiscRadius(float radius) {
+ this.prevDiscRadius = this.discRadius;
this.discRadius = radius;
}
@@ -355,7 +366,12 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
return discThickness;
}
+ public float getInterpolatedDiscThickness(float partialTicks) {
+ return prevDiscThickness + (discThickness - prevDiscThickness) * partialTicks;
+ }
+
public void setDiscThickness(float thickness) {
+ this.prevDiscThickness = this.discThickness;
this.discThickness = thickness;
}
@@ -443,6 +459,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
@Override
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
+ prevDiscRadius = discRadius;
+ prevDiscThickness = discThickness;
discRadius = nbt.getFloat("DiscRadius");
discThickness = nbt.getFloat("DiscThickness");
vertical = nbt.getBoolean("Vertical");
@@ -456,6 +474,8 @@ public class EntityAbilityDisc extends EntityEnergyProjectile {
this.boomerangDelay = nbt.hasKey("BoomerangDelay") ? nbt.getInteger("BoomerangDelay") : 40;
this.discRadius = sanitize(nbt.hasKey("DiscRadius") ? nbt.getFloat("DiscRadius") : 1.0f, 1.0f, MAX_ENTITY_SIZE);
this.discThickness = sanitize(nbt.hasKey("DiscThickness") ? nbt.getFloat("DiscThickness") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
+ this.prevDiscRadius = this.discRadius;
+ this.prevDiscThickness = this.discThickness;
this.vertical = nbt.hasKey("Vertical") ? nbt.getBoolean("Vertical") : false;
this.returning = nbt.hasKey("Returning") && nbt.getBoolean("Returning");
// Charging state (common fields handled by base)
diff --git a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java
index 6e40ac999..f1eb9c617 100644
--- a/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java
+++ b/src/main/java/kamkeel/npcs/entity/EntityAbilityLaser.java
@@ -33,6 +33,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
// Laser-specific properties
private float laserWidth = 0.2f;
+ private float prevLaserWidth = 0.2f;
private float expansionSpeed = 2.0f; // Blocks per tick
private float maxLength = 32.0f; // Maximum beam reach in blocks
@@ -144,6 +145,9 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
protected void updateProjectile() {
+ // Store previous values for interpolation
+ prevLaserWidth = laserWidth;
+
if (isCharging()) {
updateCharging();
return;
@@ -703,7 +707,12 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
return laserWidth;
}
+ public float getInterpolatedLaserWidth(float partialTicks) {
+ return prevLaserWidth + (laserWidth - prevLaserWidth) * partialTicks;
+ }
+
public void setLaserWidth(float width) {
+ this.prevLaserWidth = this.laserWidth;
this.laserWidth = width;
}
@@ -879,6 +888,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
protected void applyProjectileClientSyncData(NBTTagCompound nbt) {
+ prevLaserWidth = laserWidth;
laserWidth = nbt.getFloat("LaserWidth");
expansionSpeed = nbt.getFloat("ExpansionSpeed");
maxLength = nbt.getFloat("MaxLength");
@@ -892,6 +902,7 @@ public class EntityAbilityLaser extends EntityEnergyProjectile {
@Override
protected void readProjectileNBT(NBTTagCompound nbt) {
this.laserWidth = sanitize(nbt.hasKey("LaserWidth") ? nbt.getFloat("LaserWidth") : 0.2f, 0.2f, MAX_ENTITY_SIZE);
+ this.prevLaserWidth = this.laserWidth;
this.expansionSpeed = sanitize(nbt.hasKey("ExpansionSpeed") ? nbt.getFloat("ExpansionSpeed") : 2.0f, 0.1f, MAX_ENTITY_SIZE);
this.maxLength = sanitize(nbt.hasKey("MaxLength") ? nbt.getFloat("MaxLength") : 32.0f, 1.0f, MAX_ENTITY_SIZE);
this.dirX = nbt.getDouble("DirX");
diff --git a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java
index 1527f644d..702c62c4d 100644
--- a/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java
+++ b/src/main/java/kamkeel/npcs/entity/EntityEnergyProjectile.java
@@ -348,7 +348,7 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
// Fire tick event (server-side, non-preview only)
if (!previewMode && !worldObj.isRemote) {
- EventHooks.onEnergyProjectileTick(this);
+ EventHooks.onEnergyProjectileTick(this, ticksExisted);
}
debugLogTick();
@@ -738,8 +738,8 @@ public abstract class EntityEnergyProjectile extends EntityEnergyAbility {
motionY = nbt.getDouble("MotionY");
motionZ = nbt.getDouble("MotionZ");
syncPositionStateToCurrent(true);
- // Size & visual
- setProjectileSize(nbt.getFloat("Size"));
+ // Size & visual (set target only - let the lerp in onUpdate smooth the transition)
+ this.size = nbt.getFloat("Size");
displayData.setRotationSpeed(nbt.getFloat("RotationSpeed"));
displayData.setInnerAlpha(nbt.getFloat("InnerAlpha"));
displayData.setOuterColorEnabled(nbt.getBoolean("OuterColorEnabled"));
diff --git a/src/main/java/noppes/npcs/CustomNpcs.java b/src/main/java/noppes/npcs/CustomNpcs.java
index cfd3ad7bb..5f020e507 100644
--- a/src/main/java/noppes/npcs/CustomNpcs.java
+++ b/src/main/java/noppes/npcs/CustomNpcs.java
@@ -124,7 +124,7 @@ import java.util.HashSet;
import java.util.Set;
import java.util.function.Consumer;
-@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11-prerelease3")
+@Mod(modid = "customnpcs", name = "CustomNPC+", version = "1.11")
public class CustomNpcs {
@SidedProxy(clientSide = "noppes.npcs.client.ClientProxy", serverSide = "noppes.npcs.CommonProxy")
diff --git a/src/main/java/noppes/npcs/EventHooks.java b/src/main/java/noppes/npcs/EventHooks.java
index b783f98e0..92475f8b3 100644
--- a/src/main/java/noppes/npcs/EventHooks.java
+++ b/src/main/java/noppes/npcs/EventHooks.java
@@ -482,9 +482,9 @@ public class EventHooks {
NpcAPI.EVENT_BUS.post(event);
}
- public static void onEnergyProjectileTick(EntityEnergyProjectile projectile) {
+ public static void onEnergyProjectileTick(EntityEnergyProjectile projectile, int tick) {
IEnergyProjectile wrapped = (IEnergyProjectile) NpcAPI.Instance().getIEntity(projectile);
- EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped);
+ EnergyProjectileEvent.UpdateEvent event = new EnergyProjectileEvent.UpdateEvent(wrapped, tick);
ScriptController.Instance.globalNpcScripts.callScript(EnumScriptType.ENERGY_PROJECTILE_TICK, event);
Entity owner = projectile.worldObj.getEntityByID(projectile.getOwnerEntityId());
diff --git a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java
index 0a175528a..6f95538c6 100644
--- a/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java
+++ b/src/main/java/noppes/npcs/client/gui/util/script/interpreter/ScriptDocument.java
@@ -88,7 +88,7 @@ public class ScriptDocument {
// Also matches ',' as a delimiter so that "int x, y, z;" is recognized
// (the first declarator "int x," is captured; continuations are scanned manually).
private static final Pattern FIELD_DECL_PATTERN = Pattern.compile(
- "\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
+ "\\b([A-Za-z_][a-zA-Z0-9_.<>,[ \\t\\n\\r]\\[\\]]*)[ \\t]+([a-zA-Z_][a-zA-Z0-9_]*)[ \\t]*(=|;|,)");
private static final Pattern NEW_TYPE_PATTERN = Pattern.compile("\\bnew\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?");
// Cast expression pattern: captures type name inside cast parentheses
@@ -785,8 +785,10 @@ public class ScriptDocument {
*/
private void parseScriptTypes() {
// ========== PASS 1: Discover all type declarations (flat list) ==========
+ // Regex only anchors on keyword + name — generic clauses are extracted manually
+ // below to correctly handle nested angle brackets like >.
Pattern typeDecl = Pattern.compile(
- "(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<([^>]*)>)?\\s*(?:\\(\\))?\\s*(?:extends\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*(?:implements\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*\\{");
+ "(?:(?:public|private|protected|static|final|abstract)\\s+)*(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)");
List rawDeclarations = new ArrayList<>();
Matcher m = typeDecl.matcher(text);
@@ -796,16 +798,58 @@ public class ScriptDocument {
String kindStr = m.group(1);
String typeName = m.group(2);
- String typeParamsClause = m.group(3);
- String extendsClause = m.group(4);
- String implementsClause = m.group(5);
- int bodyStart = text.indexOf('{', m.start());
- int bodyEnd = findMatchingBrace(bodyStart);
- if (bodyEnd < 0) {
- bodyEnd = text.length();
+ // Manually extract everything between the type name and the opening '{',
+ // handling nested angle brackets so > is captured whole.
+ int scanPos = m.end();
+ while (scanPos < text.length() && Character.isWhitespace(text.charAt(scanPos)))
+ scanPos++;
+
+ // Extract type params clause <...> if present (depth-aware)
+ String typeParamsClause = null;
+ if (scanPos < text.length() && text.charAt(scanPos) == '<') {
+ int depth = 1, i = scanPos + 1;
+ while (i < text.length() && depth > 0) {
+ char c = text.charAt(i++);
+ if (c == '<') depth++;
+ else if (c == '>') depth--;
+ }
+ if (depth == 0) {
+ typeParamsClause = text.substring(scanPos + 1, i - 1);
+ scanPos = i;
+ while (scanPos < text.length() && Character.isWhitespace(text.charAt(scanPos)))
+ scanPos++;
+ }
}
+ // Find the opening brace, extracting extends/implements from text between here and '{'
+ int bracePos = scanPos;
+ int angleDepth = 0;
+ while (bracePos < text.length()) {
+ char c = text.charAt(bracePos);
+ if (c == '<') angleDepth++;
+ else if (c == '>') angleDepth--;
+ else if (c == '{' && angleDepth == 0) break;
+ bracePos++;
+ }
+ if (bracePos >= text.length()) continue;
+
+ String betweenNameAndBrace = text.substring(scanPos, bracePos).trim();
+
+ // Parse extends/implements from that window
+ String extendsClause = null;
+ String implementsClause = null;
+ java.util.regex.Matcher extM = Pattern.compile(
+ "extends\\s+([A-Za-z_][a-zA-Z0-9_.]*(?:<[^{]*>)?)").matcher(betweenNameAndBrace);
+ if (extM.find()) extendsClause = extM.group(1).trim();
+ java.util.regex.Matcher implM = Pattern.compile(
+ "implements\\s+(.+)$").matcher(betweenNameAndBrace);
+ if (implM.find()) implementsClause = implM.group(1).trim();
+
+ int bodyStart = bracePos;
+ int bodyEnd = findMatchingBrace(bodyStart);
+ if (bodyEnd < 0) bodyEnd = text.length();
+
TypeInfo.Kind kind;
switch (kindStr) {
case "interface": kind = TypeInfo.Kind.INTERFACE; break;
@@ -813,7 +857,7 @@ public class ScriptDocument {
default: kind = TypeInfo.Kind.CLASS; break;
}
- String fullMatch = text.substring(m.start(), m.end());
+ String fullMatch = text.substring(m.start(), bodyStart + 1);
int modifiers = parseModifiers(fullMatch);
JSDocInfo jsDoc = jsDocParser.extractJSDocBefore(text, m.start());
@@ -870,6 +914,11 @@ public class ScriptDocument {
// Resolve extends clause (parent class inheritance)
if (raw.extendsClause != null && !raw.extendsClause.trim().isEmpty()) {
String parentName = raw.extendsClause.trim();
+ // Strip generic args (e.g., "NumberBox" -> "NumberBox") for type resolution
+ int genIdx = parentName.indexOf('<');
+ if (genIdx >= 0) {
+ parentName = parentName.substring(0, genIdx);
+ }
TypeInfo parentType = resolveType(parentName, raw.declOffset);
if (parentType == null) {
parentType = TypeInfo.unresolved(parentName, parentName);
@@ -884,6 +933,11 @@ public class ScriptDocument {
String trimmedName = ifaceName.trim();
if (trimmedName.isEmpty())
continue;
+ // Strip generic args (e.g., "Comparable" -> "Comparable")
+ int genIdx = trimmedName.indexOf('<');
+ if (genIdx >= 0) {
+ trimmedName = trimmedName.substring(0, genIdx);
+ }
TypeInfo ifaceType = resolveType(trimmedName, raw.declOffset);
if (ifaceType == null) {
ifaceType = TypeInfo.unresolved(trimmedName, trimmedName);
@@ -920,14 +974,16 @@ public class ScriptDocument {
String boundName = null;
if (extendsIdx >= 0) {
String afterExtends = trimmed.substring(extendsIdx + 7).trim();
- if (!afterExtends.isEmpty()) {
+ // Split on & to get the primary bound (first one); additional bounds are interfaces
+ String primaryBound = afterExtends.split("&")[0].trim();
+ if (!primaryBound.isEmpty()) {
int boundEndIdx = 0;
- while (boundEndIdx < afterExtends.length() &&
- (Character.isJavaIdentifierPart(afterExtends.charAt(boundEndIdx)) || afterExtends.charAt(boundEndIdx) == '.')) {
+ while (boundEndIdx < primaryBound.length() &&
+ (Character.isJavaIdentifierPart(primaryBound.charAt(boundEndIdx)) || primaryBound.charAt(boundEndIdx) == '.')) {
boundEndIdx++;
}
if (boundEndIdx > 0) {
- boundName = afterExtends.substring(0, boundEndIdx);
+ boundName = primaryBound.substring(0, boundEndIdx);
}
}
}
@@ -941,7 +997,7 @@ public class ScriptDocument {
}
scriptType.addDeclaredTypeParam(typeParam);
}
- }
+ }
private TypeInfo parameterizeWithClause(TypeInfo baseType, String typeArgsClause, int position) {
String[] argNames = typeArgsClause.split(",");
@@ -964,42 +1020,68 @@ public class ScriptDocument {
private void markTypeParamDeclarations(List marks, int afterNameEnd, ScriptTypeInfo typeInfo) {
int ltPos = text.indexOf('<', afterNameEnd);
if (ltPos < 0 || ltPos > afterNameEnd + 5) return;
- int gtPos = text.indexOf('>', ltPos);
- if (gtPos < 0) return;
- String clause = text.substring(ltPos + 1, gtPos);
- int offset = ltPos + 1;
- for (TypeParamInfo param : typeInfo.getDeclaredTypeParams()) {
- int paramIdx = clause.indexOf(param.getName(), offset - (ltPos + 1));
- if (paramIdx >= 0) {
- int paramStart = ltPos + 1 + paramIdx;
- int paramLen = param.getName().length();
- int paramEnd = paramStart + paramLen;
- marks.add(new ScriptLine.Mark(paramStart, paramEnd, TokenType.GENERIC_TYPE_PARAM, param));
+ int depth = 1, i = ltPos + 1;
+ while (i < text.length() && depth > 0) {
+ char c = text.charAt(i++);
+ if (c == '<') depth++;
+ else if (c == '>') depth--;
+ }
+ if (depth != 0) return;
+ int gtPos = i - 1;
- int searchFrom = paramIdx + param.getName().length();
- int extendsIdx = clause.indexOf("extends", searchFrom);
- if (extendsIdx >= 0) {
- int extendsStart = ltPos + 1 + extendsIdx;
- marks.add(new ScriptLine.Mark(extendsStart, extendsStart + 7, TokenType.KEYWORD));
+ markTypeParamsInClause(marks, ltPos + 1, gtPos - 1, typeInfo.getDeclaredTypeParams());
+ }
- TypeInfo boundType = param.getBoundTypeInfo();
- if (boundType != null) {
- String remaining = clause.substring(extendsIdx + 7).trim();
- if (!remaining.isEmpty()) {
- int boundEnd = remaining.indexOf(',');
- if (boundEnd < 0) boundEnd = remaining.indexOf('&');
- String boundName = boundEnd < 0 ? remaining : remaining.substring(0, boundEnd).trim();
- if (!boundName.isEmpty()) {
- int boundStart = extendsStart + 7;
- while (boundStart < gtPos && Character.isWhitespace(text.charAt(boundStart))) boundStart++;
- marks.add(new ScriptLine.Mark(boundStart, boundStart + boundName.length(), TokenType.getByType(boundType), boundType));
- }
- }
- }
+ private void markTypeParamsInClause(List marks, int clauseStart, int clauseEnd, List typeParams) {
+ if (clauseStart >= clauseEnd) return;
+
+ String clause = text.substring(clauseStart, clauseEnd);
+ Map paramMap = new HashMap<>();
+ for (TypeParamInfo param : typeParams) {
+ paramMap.put(param.getName(), param);
+ }
+
+ int i = 0;
+ while (i < clause.length()) {
+ char c = clause.charAt(i);
+
+ if (c == '<') {
+ int innerStart = clauseStart + i + 1;
+ int depth = 1;
+ int j = i + 1;
+ while (j < clause.length() && depth > 0) {
+ char innerC = clause.charAt(j);
+ if (innerC == '<') depth++;
+ else if (innerC == '>') depth--;
+ j++;
}
+ if (depth == 0) {
+ markTypeParamsInClause(marks, innerStart, clauseStart + j - 1, typeParams);
+ i = j;
+ } else {
+ i++;
+ }
+ } else if (c == '&') {
+ i++;
+ } else if (c == ',') {
+ i++;
+ } else if (Character.isJavaIdentifierStart(c)) {
+ int start = i;
+ while (i < clause.length() && Character.isJavaIdentifierPart(clause.charAt(i))) {
+ i++;
+ }
+ String name = clause.substring(start, i);
+ TypeParamInfo param = paramMap.get(name);
+ if (param != null) {
+ int absStart = clauseStart + start;
+ int absEnd = clauseStart + i;
+ marks.add(new ScriptLine.Mark(absStart, absEnd, TokenType.GENERIC_TYPE_PARAM, param));
+ }
+ } else {
+ i++;
}
- }
+ }
}
/**
* Parse fields and methods inside a script-defined type.
@@ -5038,8 +5120,8 @@ public class ScriptDocument {
// Extended pattern to capture extends and implements clauses
// Groups: 1=class|interface|enum, 2=TypeName, 3=extends clause, 4=implements clause
Pattern classWithInheritance = Pattern.compile(
- "\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<[^>]*>)?\\s*(?:\\(\\))?\\s*" +
- "(?:(extends)\\s+([A-Za-z_][a-zA-Z0-9_.]*))?\\s*" +
+ "\\b(class|interface|enum)\\s+([A-Za-z_][a-zA-Z0-9_]*)\\s*(?:<[^{]*>)?\\s*(?:\\(\\))?\\s*" +
+ "(?:(extends)\\s+([A-Za-z_][a-zA-Z0-9_.]*)(?:<[^{]*>)?)?\\s*" +
"(?:(implements)\\s+([A-Za-z_][a-zA-Z0-9_.,\\s]*))?\\s*(?=\\{)");
Matcher m = classWithInheritance.matcher(text);
@@ -5087,7 +5169,7 @@ public class ScriptDocument {
if (m.group(3) != null) {
//Extends key word
marks.add(new ScriptLine.Mark(m.start(3), m.end(3), TokenType.IMPORT_KEYWORD));
- markExtendsClause(marks, m.start(4), m.group(4));
+ markExtendsClause(marks, m.start(4), m.group(4), typeInfo);
}
// Mark implements clause
@@ -5126,36 +5208,50 @@ public class ScriptDocument {
* Mark the extends clause with proper coloring.
* The parent class is colored based on its resolved type.
*/
- private void markExtendsClause(List marks, int clauseStart, String parentName) {
+ private void markExtendsClause(List marks, int clauseStart, String parentName, ScriptTypeInfo typeInfo) {
String trimmedName = parentName.trim();
if (trimmedName.isEmpty())
return;
+ int actualStart = clauseStart;
+ while (actualStart < clauseStart + trimmedName.length() &&
+ Character.isWhitespace(text.charAt(actualStart))) {
+ actualStart++;
+ }
+
+ int baseNameEnd = actualStart;
+ while (baseNameEnd < text.length() && Character.isJavaIdentifierPart(text.charAt(baseNameEnd))) {
+ baseNameEnd++;
+ }
+ String baseName = text.substring(actualStart, baseNameEnd);
- // Resolve the parent type with position context for inner class names
TypeInfo parentType = resolveType(trimmedName, clauseStart);
TokenType tokenType;
if (parentType != null && parentType.isResolved()) {
- // Use the type's specific token type (CLASS, INTERFACE, ENUM)
tokenType = parentType.getTokenType();
} else {
- // Unresolved - mark as undefined
tokenType = TokenType.UNDEFINED_VAR;
if (parentType == null) {
parentType = TypeInfo.unresolved(trimmedName, trimmedName);
}
}
- // Find actual position in text (might have leading whitespace in the group)
- int actualStart = clauseStart;
- String fullClause = parentName;
- while (actualStart < clauseStart + fullClause.length() &&
- Character.isWhitespace(text.charAt(actualStart))) {
- actualStart++;
- }
+ marks.add(new ScriptLine.Mark(actualStart, baseNameEnd, tokenType, parentType));
- marks.add(new ScriptLine.Mark(actualStart, actualStart + trimmedName.length(), tokenType, parentType));
+ if (typeInfo != null && typeInfo.hasDeclaredTypeParams() && baseNameEnd < text.length() && text.charAt(baseNameEnd) == '<') {
+ int ltPos = baseNameEnd;
+ int depth = 1;
+ int i = ltPos + 1;
+ while (i < text.length() && depth > 0) {
+ char c = text.charAt(i++);
+ if (c == '<') depth++;
+ else if (c == '>') depth--;
+ }
+ if (depth == 0) {
+ markTypeParamsInClause(marks, ltPos + 1, i - 1, typeInfo.getDeclaredTypeParams());
+ }
+ }
}
/**
@@ -6059,7 +6155,7 @@ public class ScriptDocument {
while (actualStart < i && Character.isWhitespace(text.charAt(actualStart))) {
actualStart++;
}
- int actualEnd = i;
+ int actualEnd = i;
while (actualEnd > actualStart && Character.isWhitespace(text.charAt(actualEnd - 1))) {
actualEnd--;
}
diff --git a/src/main/java/noppes/npcs/scripted/event/EnergyProjectileEvent.java b/src/main/java/noppes/npcs/scripted/event/EnergyProjectileEvent.java
index 4a1fab5a2..024376103 100644
--- a/src/main/java/noppes/npcs/scripted/event/EnergyProjectileEvent.java
+++ b/src/main/java/noppes/npcs/scripted/event/EnergyProjectileEvent.java
@@ -49,10 +49,16 @@ public class EnergyProjectileEvent extends CustomNPCsEvent implements IEnergyPro
* energyProjectileTick
*/
public static class UpdateEvent extends EnergyProjectileEvent implements IEnergyProjectileEvent.UpdateEvent {
- public UpdateEvent(IEnergyProjectile projectile) {
+ private final int tick;
+
+ public UpdateEvent(IEnergyProjectile projectile, int tick) {
super(projectile);
+ this.tick = tick;
}
+ @Override
+ public int getTick() { return this.tick; }
+
@Override
public String getHookName() {
return EnumScriptType.ENERGY_PROJECTILE_TICK.function;