Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
a864862
fix(javadoc): resolve broken header and tags on search.html
steffen-wilke Sep 2, 2026
385fd81
feat(javadoc): add backlink to main documentation in header and footer
steffen-wilke Sep 2, 2026
b8b0f6c
perf(javadoc): pre-render 2-level header at build time to eliminate D…
steffen-wilke Sep 2, 2026
f796502
style(javadoc): remove deprecated and help items from navigation
steffen-wilke Sep 2, 2026
9e9d780
fix(javadoc): eliminate excessive header vertical space and restore s…
steffen-wilke Sep 2, 2026
ae66260
fix(javadoc): ensure page search input is styled and pre-rendered
steffen-wilke Sep 2, 2026
6221b73
fix(javadoc): raise search autocomplete dropdown z-index above header
steffen-wilke Sep 2, 2026
340b82f
fix(javadoc): use icon.png instead of logo.png for header brand icon
steffen-wilke Sep 2, 2026
b9193ff
style(javadoc): add margin around sticky index navigation bar
steffen-wilke Sep 2, 2026
5b39a9f
fix(javadoc): skip redundant Object inheritance and restore hierarchy…
steffen-wilke Sep 2, 2026
1754092
fix(javadoc): pre-render TOC filter capsule and add resilient filter-…
steffen-wilke Sep 2, 2026
5b108d0
fix(javadoc): enable table tab filtering by respecting inline display…
steffen-wilke Sep 2, 2026
865722a
fix(javadoc): preserve navbar-toggle-button to prevent script.js Type…
steffen-wilke Sep 2, 2026
28da923
style(javadoc): apply vibrant gradient highlight badge to API brand tag
steffen-wilke Sep 2, 2026
78507e4
perf(javadoc): inject theme script and font preloads at top of head t…
steffen-wilke Sep 2, 2026
990b7a7
perf(javadoc): inline critical font sizing and use display=optional t…
steffen-wilke Sep 2, 2026
e2fa5ed
perf(javadoc): eliminate external font network requests to ensure con…
steffen-wilke Sep 2, 2026
1939047
perf(javadoc): remove runtime loadGoogleFonts to prevent stylesheet i…
steffen-wilke Sep 2, 2026
12fe48c
perf(javadoc): strip unused dejavu.css import from stylesheet.css
steffen-wilke Sep 2, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
165 changes: 155 additions & 10 deletions buildSrc/src/main/groovy/javadoc-defaults.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ tasks.withType(Javadoc).configureEach {
options.header = """<a href="${engineUrl}" target="_blank" class="nav-brand-link"><b>${engineName} 🎮</b> API</a>"""
options.bottom = """<p class="legal-copy"><small>Copyright &#169; ${copyrightText}. Released under the <a href="${mitUrl}" target="_blank">${mitName} License</a>.</small></p>"""

options.noDeprecatedList = true
options.noHelp = true

if (themeCss.exists()) {
options.addStringOption("-add-stylesheet", themeCss.absolutePath)
}
Expand All @@ -49,6 +52,14 @@ tasks.withType(Javadoc).configureEach {

// Generation-time HTML adjustments (Favicons, Fonts, SEO/GEO metadata, Bottom Tags, and Footer)
if (destinationDir && destinationDir.exists()) {
// Optimize stylesheet.css by stripping unused @import url('fonts/dejavu.css');
def stylesheetFile = new File(destinationDir, 'resource-files/stylesheet.css')
if (stylesheetFile.exists()) {
def css = stylesheetFile.getText('UTF-8')
css = css.replace("@import url('fonts/dejavu.css');", "/* dejavu.css omitted */")
stylesheetFile.setText(css, 'UTF-8')
}

destinationDir.eachFileRecurse(groovy.io.FileType.FILES) { file ->
if (file.name.endsWith(".html")) {
enhanceJavadocHtml(file, destinationDir)
Expand All @@ -65,12 +76,31 @@ void enhanceJavadocHtml(File file, File rootDir) {
def html = file.getText("UTF-8")
if (html.contains("<!-- liti-enhanced -->")) return

// 1. Favicons & Fonts in <head>
def headAdditions = """
// 1. Early Head Injections (Theme script & Critical font variables at the very top of <head> before any CSS/scripts)
def earlyHead = """<head>
<!-- liti-enhanced -->
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Fira+Code:wght@400;500;600&family=Montserrat:wght@400;500;600;700&family=Roboto:ital,wght@0,300;0,400;0,500;0,700;1,400&display=swap" rel="stylesheet">
<script>(function(){try{var t=localStorage.getItem('liti-theme')||(window.matchMedia&&window.matchMedia('(prefers-color-scheme: light)').matches?'light':'dark');document.documentElement.setAttribute('data-theme',t);}catch(e){}})();</script>
<style>
:root {
--body-font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
--code-font-family: "JetBrains Mono", ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--body-font-size: 16px;
--block-font-size: 16px;
--code-font-size: 14px;
--nav-font-size: 16.8px;
--block-line-height: 1.6;
--code-line-height: 1.55;
}
html, body {
font-family: var(--body-font-family);
font-size: var(--body-font-size);
line-height: var(--block-line-height);
}
</style>"""
html = html.replaceFirst("<head>", earlyHead)

// 2. Metadata & Favicons in <head>
def headAdditions = """
<link rel="icon" type="image/png" sizes="32x32" href="${r}assets/favicon-32x32.png">
<link rel="icon" type="image/png" sizes="16x16" href="${r}assets/favicon-16x16.png">
<link rel="shortcut icon" href="${r}assets/favicon.ico">
Expand Down Expand Up @@ -102,6 +132,119 @@ void enhanceJavadocHtml(File file, File rootDir) {
html = html.replace("</head>", headAdditions + "\n</head>")
}

// 2. Generation-Time 2-Level Header (Eliminates DOM delay on large pages like index-all.html)
def navMatcher = (html =~ /(?s)(<ul id="navbar-top-firstrow"[^>]*>.*?<\/ul>)/)
if (navMatcher) {
def origNavList = navMatcher[0][1]
origNavList = origNavList.replaceAll(/(?s)<li>\s*<a[^>]*href="[^"]*(deprecated-list|help-doc)\.html[^"]*"[^>]*>.*?<\/li>/, '')
origNavList = origNavList.replaceAll(/(?s)<li class="nav-bar-cell1-rev">\s*(Deprecated|Help)\s*<\/li>/, '')
def backlink = '<li class="nav-item-backlink"><a href="https://docs.litiengine.com/" class="nav-link-docs" title="Back to LITIENGINE Documentation"><svg class="docs-back-icon" viewBox="0 0 24 24" width="14" height="14" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round"><path d="M19 12H5M12 19l-7-7 7-7"/></svg><span>Docs</span></a></li>'
def enhancedNavList = origNavList.replaceFirst(/(<ul[^>]*>)/, "\$1\n${backlink}")

def level1Html = """
<div class="liti-header-level1">
<div class="liti-header-container">
<a class="liti-brand-link" href="https://docs.litiengine.com/">
<img class="liti-brand-logo" src="${r}assets/icon.png" alt="LITIENGINE Logo" width="28.79" height="28.79">
<span class="liti-brand-text"><strong>LITIENGINE Docs</strong> <span class="api-tag">API</span></span>
</a>
<div class="liti-header-level1-right">
<button type="button" class="liti-theme-toggle" title="Toggle dark / light mode" aria-label="Toggle theme">
<svg viewBox="0 0 24 24" width="18" height="18" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"></path></svg>
</button>
<div class="nav-list-search">
<span class="search-icon-wrapper"><svg class="search-icon-svg" viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg></span>
<input type="text" id="search-input" placeholder="Search" autocomplete="off">
<kbd class="search-kbd">Ctrl+K</kbd>
</div>
<a href="https://github.com/gurkenlabs/litiengine" target="_blank" rel="noopener noreferrer" class="liti-github-link">
<svg class="github-icon" viewBox="0 0 24 24" width="19" height="19" fill="currentColor"><path d="M12 0C5.37 0 0 5.37 0 12c0 5.31 3.435 9.795 8.205 11.385.6.105.825-.255.825-.57 0-.285-.015-1.23-.015-2.235-3.015.555-3.795-.735-4.035-1.41-.135-.345-.72-1.41-1.23-1.695-.42-.225-1.02-.78-.015-.795.945-.015 1.62.87 1.845 1.23 1.08 1.815 2.805 1.305 3.495.99.105-.78.42-1.305.765-1.605-2.67-.3-5.46-1.335-5.46-5.925 0-1.305.465-2.385 1.23-3.225-.12-.3-.54-1.53.12-3.18 0 0 1.005-.315 3.3 1.23.96-.27 1.98-.405 3-.405s2.04.135 3 .405c2.295-1.56 3.3-1.23 3.3-1.23.66 1.65.24 2.88.12 3.18.765.84 1.23 1.905 1.23 3.225 0 4.605-2.805 5.625-5.475 5.925.435.375.81 1.095.81 2.22 0 1.605-.015 2.895-.015 3.3 0 .315.225.69.825.57A12.02 12.02 0 0024 12c0-6.63-5.37-12-12-12z"/></svg> <span>gurkenlabs/litiengine</span>
</a>
</div>
</div>
</div>"""

def level2Html = """
<div class="liti-header-level2">
<div class="liti-header-container">
${enhancedNavList}
<a href="https://opencollective.com/litiengine" target="_blank" rel="noopener noreferrer" class="md-tabs__link--sponsor">
<svg class="sponsor-heart-icon" viewBox="0 0 24 24" width="16" height="16" fill="currentColor"><path d="M12 21.35l-1.45-1.32C5.4 15.36 2 12.28 2 8.5 2 5.42 4.42 3 7.5 3c1.74 0 3.41.81 4.5 2.09C13.09 3.81 14.76 3 16.5 3 19.58 3 22 5.42 22 8.5c0 3.78-3.4 6.86-8.55 11.54L12 21.35z"/></svg> <span>Sponsor</span>
</a>
</div>
</div>"""

def hiddenToggleBtn = '<button id="navbar-toggle-button" aria-controls="navbar-top" aria-expanded="false" aria-label="Toggle navigation links" style="display:none;"></button>'
def newTopNav = """<div class="top-nav" id="navbar-top">${hiddenToggleBtn}${level1Html}${level2Html}\n</div>"""
// Remove the old nav-list-search from sub-nav first
html = html.replaceFirst(/(?s)<div class="nav-list-search">.*?<\/div>/, '')
// Replace the entire top-nav block cleanly up to sub-nav
html = html.replaceFirst(/(?s)<div class="top-nav" id="navbar-top">.*?<\/div>(?=\s*<div class="sub-nav">)/, newTopNav)

// Remove empty sub-nav on pages without breadcrumbs (overview, tree, index, search, etc.)
html = html.replaceAll(/(?s)<div class="sub-nav">\s*<div class="nav-content">\s*<ol class="sub-nav-list">\s*<\/ol>\s*<\/div>\s*<\/div>/, '')

// Remove redundant single-level "java.lang.Object" inheritance tree on class declaration pages
html = html.replaceAll(/(?s)<div class="inheritance" title="Inheritance Tree">\s*<a href="[^"]*?Object\.html"[^>]*?>java\.lang\.Object<\/a>\s*<div class="inheritance">([^<]+)<\/div>\s*<\/div>/, '')

// Remove redundant "extends Object" from type-signature if class only extends Object
html = html.replaceAll(/(?s)<span class="extends-implements">\s*extends\s*<a href="[^"]*?Object\.html"[^>]*?>Object<\/a>\s*<\/span>/, '')

// Unwrap java.lang.Object root node in hierarchy trees
if (file.name.endsWith('-tree.html')) {
html = html.replaceAll(/(?s)<li class="circle">java\.lang\.<a href="[^"]*?Object\.html"[^>]*?>Object<\/a>\s*<ul>(.*?)<\/ul>\s*<\/li>/, '$1')
}

// Statically pre-render sticky alphabet bar on index-all.html
if (file.name == 'index-all.html') {
def jumpMatcher = (html =~ /(?s)<div class="header">\s*<h1>Index<\/h1>\s*<\/div>\s*(.*?)(?=<h2 class="title" id="I:A">)/)
if (jumpMatcher) {
def rawJump = jumpMatcher[0][1]
def letterLinks = []
def letM = (rawJump =~ /<a href="#I:[A-Z]">([A-Z])<\/a>/)
while (letM.find()) {
letterLinks.add(letM.group(0))
}
def secondaryLinks = []
def secM = (rawJump =~ /<a href="[^"]*?(allclasses-index|allpackages-index|constant-values|serialized-form)\.html">(.*?)<\/a>/)
while (secM.find()) {
secondaryLinks.add(secM.group(0))
}

def stickyBarHtml = """<div class="index-sticky-bar">
<div class="index-letters-container">
${letterLinks.join('\n ')}
</div>
<div class="index-secondary-container">
${secondaryLinks.join('\n ')}
</div>
</div>\n"""

html = html.replace(jumpMatcher[0][0], """<div class="header"><h1>Index</h1></div>\n${stickyBarHtml}""")
html = html.replaceAll(/(?s)<\/dl>\s*<a href="#I:A">A<\/a>.*?<\/main>/, '</dl>\n</main>')
}
}

// Statically pre-render search box wrapper on search.html
if (file.name == 'search.html') {
def searchInputPattern = /(?s)(<input type="text" id="page-search-input"[^>]*>)\s*(<input type="reset" id="page-search-reset"[^>]*>)?/
def searchIconSvg = '<span class="page-search-icon"><svg class="search-icon-svg" viewBox="0 0 24 24" width="16" height="16" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg></span>'
html = html.replaceFirst(searchInputPattern) { full, inp, rst ->
def resetStr = rst ?: ''
"""<div class="page-search-wrapper">${searchIconSvg}${inp}${resetStr}</div>"""
}
}

// Statically enhance TOC filter input on pages with a table of contents
def tocFilterPattern = /(?s)<div class="toc-header">\s*(?:Contents&nbsp;)?(<input type="text" class="filter-input"[^>]*>)\s*(<input type="reset" class="reset-filter"[^>]*>)?\s*<\/div>/
def tocSearchSvg = '<span class="search-icon-wrapper"><svg class="search-icon-svg" viewBox="0 0 24 24" width="14" height="14" stroke="currentColor" stroke-width="2" fill="none" stroke-linecap="round" stroke-linejoin="round"><circle cx="11" cy="11" r="8"></circle><line x1="21" y1="21" x2="16.65" y2="16.65"></line></svg></span>'
html = html.replaceAll(tocFilterPattern) { full, inp, rst ->
def cleanInp = inp.replace('placeholder="Filter contents (type .)"', 'placeholder="Filter"')
def resetStr = rst ?: ''
"""<div class="toc-header"><div class="toc-filter-wrapper">${tocSearchSvg}${cleanInp}${resetStr}</div></div>"""
}
}

// 2. Build Contextual Tags at generation time
def tags = [] as Set
def pathLower = file.absolutePath.toLowerCase().replace('\\', '/')
Expand Down Expand Up @@ -134,10 +277,12 @@ void enhanceJavadocHtml(File file, File rootDir) {
if (cleanHeading.endsWith("Listener")) tags.add("listener")
if (cleanHeading.endsWith("Event")) tags.add("event")
if (cleanHeading.endsWith("Controller")) tags.add("controller")
if (html.contains("deprecation-block") || html.contains("deprecated-label")) tags.add("deprecated")

if (tags.size() < 4) tags.add("api")
if (tags.size() < 5) tags.add("java")
if (file.name == 'search.html' || file.name == 'help-doc.html') {
tags.clear()
} else {
if (tags.size() < 4) tags.add("api")
if (tags.size() < 5) tags.add("java")
}

// 2. Insert Bottom Tags inside <main>
if (tags && html.contains("</main>")) {
Expand Down Expand Up @@ -165,7 +310,7 @@ void enhanceJavadocHtml(File file, File rootDir) {
</a>
</div>
<div class="md-copyright">
<span>Copyright &copy; 2026 GURKENLABS &middot; Released under the <a href="https://mit-license.org/" target="_blank" rel="noopener noreferrer">MIT License</a></span>
<span><a href="https://docs.litiengine.com/" target="_blank" rel="noopener noreferrer">Documentation</a> &middot; Copyright &copy; 2026 GURKENLABS &middot; Released under the <a href="https://mit-license.org/" target="_blank" rel="noopener noreferrer">MIT License</a></span>
</div>
</footer>
"""
Expand Down
Loading
Loading