This is an automated email from the ASF dual-hosted git repository.
lprimak pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/shiro-site.git
The following commit(s) were added to refs/heads/main by this push:
new 410eb2593 Add dark mode support to documentation website (#277)
410eb2593 is described below
commit 410eb2593fb7c68ef2e87fa25167727a877a9fbe
Author: Ganesh Patil <[email protected]>
AuthorDate: Sat Jan 31 22:33:47 2026 +0530
Add dark mode support to documentation website (#277)
* Add troubleshooting and FAQ documentation page - Closes #267
* Fix: Spell out RIA abbreviation on first use for accessibility
* Add dark mode support to documentation site
* Update src/site/assets/css/base.css
Co-authored-by: Copilot <[email protected]>
* Fix localStorage error handling and add button type attribute
* Fix dark mode contrast for blog/announcements text
* Fix code sample contrast in dark mode
- Add Highlight.js syntax token colors for dark mode
- Style keywords (purple), strings (green), comments (gray), numbers
(orange)
- Add class names (yellow), attributes (blue), tags (cyan) styling
- Fix Asciidoctor listing blocks for dark mode
Addresses review feedback about code samples having bad contrast.
* Improve dark mode code syntax highlighting with high-contrast Dracula
theme colors
* made better contrast for notes
---------
Co-authored-by: Copilot <[email protected]>
Co-authored-by: lprimak <[email protected]>
---
src/site/assets/css/base.css | 436 +++++++++++++++++++++++++++++++++++++++++-
src/site/assets/js/theme.js | 39 ++++
src/site/templates/header.ftl | 3 +
src/site/templates/menu.ftl | 6 +
4 files changed, 477 insertions(+), 7 deletions(-)
diff --git a/src/site/assets/css/base.css b/src/site/assets/css/base.css
index 1c93c31fd..8f9a4ffb7 100644
--- a/src/site/assets/css/base.css
+++ b/src/site/assets/css/base.css
@@ -1,10 +1,44 @@
/* Sticky footer styles
-------------------------------------------------- */
+/* Theme color variables */
+:root {
+ --bg-color: #ffffff;
+ --bg-secondary: #f8f9fa;
+ --text-color: #212529;
+ --text-muted: #6c757d;
+ --link-color: #0d6efd;
+ --link-hover: #0a58ca;
+ --border-color: #dee2e6;
+ --code-bg: #f5f5f5;
+ --code-text: #1f2937;
+ --navbar-bg: #f8f9fa;
+ --footer-bg: #e8e8e8;
+ --card-bg: #f2f2f2;
+}
+
+[data-theme="dark"] {
+ --bg-color: #0f172a;
+ --bg-secondary: #1e293b;
+ --text-color: #e2e8f0;
+ --text-muted: #94a3b8;
+ --link-color: #60a5fa;
+ --link-hover: #93c5fd;
+ --border-color: #334155;
+ --code-bg: #1e293b;
+ --code-text: #e2e8f0;
+ --navbar-bg: #1e293b;
+ --footer-bg: #1e293b;
+ --card-bg: #1e293b;
+}
+
html,
body {
height: 100%;
/* The html and body elements cannot have any padding or margin. */
+ background-color: var(--bg-color);
+ color: var(--text-color);
+ transition: background-color 0.3s ease, color 0.3s ease;
}
/* Wrapper for page content to push down footer */
@@ -21,7 +55,8 @@ body {
#custom-footer {
min-height: 60px;
- background-color: #e8e8e8;
+ background-color: var(--footer-bg);
+ transition: background-color 0.3s ease;
}
/* Lastly, apply responsive CSS fixes as necessary */
@@ -38,10 +73,16 @@ body {
/* -------------------------------------------------- */
a:link {
text-decoration: none;
+ color: var(--link-color);
}
a:visited {
text-decoration: none;
+ color: var(--link-color);
+}
+
+a:hover {
+ color: var(--link-hover);
}
/* Custom page CSS
@@ -154,22 +195,22 @@ a:visited {
/** ======================================================= **/
.authentication {
- background:url(../images/authentication.png) no-repeat #f2f2f2 right 10px
top 5px;
+ background:url(../images/authentication.png) no-repeat var(--card-bg) right
10px top 5px;
}
.authorization {
- background:url(../images/authorization.png) no-repeat #f2f2f2 right 10px top
5px;
+ background:url(../images/authorization.png) no-repeat var(--card-bg) right
10px top 5px;
}
.cryptography {
- background:url(../images/crypt.png) no-repeat #f2f2f2 right 10px top 5px;
+ background:url(../images/crypt.png) no-repeat var(--card-bg) right 10px top
5px;
}
.session-management {
- background:url(../images/session.png) no-repeat #f2f2f2 right 10px top 5px;
+ background:url(../images/session.png) no-repeat var(--card-bg) right 10px
top 5px;
}
.web-integration {
- background:url(../images/web-integration.png) no-repeat #f2f2f2 right 10px
top 5px;
+ background:url(../images/web-integration.png) no-repeat var(--card-bg) right
10px top 5px;
}
.integrations {
- background:url(../images/integration.png) no-repeat #f2f2f2 right 10px top
5px;
+ background:url(../images/integration.png) no-repeat var(--card-bg) right
10px top 5px;
}
h2.panel-title {
@@ -213,3 +254,384 @@ div.related-content {
.related-content .read-more {
font-size: 11px;
}
+
+/* Dark mode overrides for Bootstrap components */
+[data-theme="dark"] .navbar-light {
+ background-color: var(--navbar-bg) !important;
+}
+
+[data-theme="dark"] .navbar-light .navbar-nav .nav-link {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .navbar-light .navbar-nav .nav-link:hover {
+ color: var(--link-color);
+}
+
+[data-theme="dark"] .dropdown-menu {
+ background-color: var(--bg-secondary);
+ border-color: var(--border-color);
+}
+
+[data-theme="dark"] .dropdown-item {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .dropdown-item:hover {
+ background-color: var(--border-color);
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .dropdown-divider {
+ border-color: var(--border-color);
+}
+
+[data-theme="dark"] .bg-light {
+ background-color: var(--navbar-bg) !important;
+}
+
+[data-theme="dark"] .border-top {
+ border-color: var(--border-color) !important;
+}
+
+[data-theme="dark"] .text-muted {
+ color: var(--text-muted) !important;
+}
+
+[data-theme="dark"] .shadow-sm {
+ box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.4) !important;
+}
+
+/* Code blocks dark mode */
+[data-theme="dark"] pre,
+[data-theme="dark"] code {
+ background-color: var(--code-bg);
+ color: var(--code-text);
+}
+
+[data-theme="dark"] .hljs {
+ background: var(--code-bg);
+ color: var(--code-text);
+}
+
+/* Highlight.js syntax token colors for dark mode - High contrast */
+[data-theme="dark"] .hljs-keyword,
+[data-theme="dark"] .hljs-selector-tag,
+[data-theme="dark"] .hljs-section {
+ color: #ff79c6;
+ font-weight: 500;
+}
+
+[data-theme="dark"] .hljs-string,
+[data-theme="dark"] .hljs-symbol,
+[data-theme="dark"] .hljs-bullet,
+[data-theme="dark"] .hljs-addition {
+ color: #50fa7b;
+}
+
+[data-theme="dark"] .hljs-title,
+[data-theme="dark"] .hljs-name {
+ color: #8be9fd;
+ font-weight: 500;
+}
+
+[data-theme="dark"] .hljs-type,
+[data-theme="dark"] .hljs-attribute,
+[data-theme="dark"] .hljs-variable,
+[data-theme="dark"] .hljs-template-tag,
+[data-theme="dark"] .hljs-template-variable {
+ color: #ffb86c;
+}
+
+[data-theme="dark"] .hljs-comment,
+[data-theme="dark"] .hljs-quote,
+[data-theme="dark"] .hljs-deletion,
+[data-theme="dark"] .hljs-meta {
+ color: #6272a4;
+ font-style: italic;
+}
+
+[data-theme="dark"] .hljs-number,
+[data-theme="dark"] .hljs-regexp,
+[data-theme="dark"] .hljs-literal,
+[data-theme="dark"] .hljs-link {
+ color: #bd93f9;
+}
+
+[data-theme="dark"] .hljs-class .hljs-title,
+[data-theme="dark"] .hljs-title.class_,
+[data-theme="dark"] .hljs-built_in {
+ color: #f1fa8c;
+}
+
+[data-theme="dark"] .hljs-attr {
+ color: #50fa7b;
+}
+
+[data-theme="dark"] .hljs-selector-class,
+[data-theme="dark"] .hljs-selector-attr,
+[data-theme="dark"] .hljs-selector-pseudo {
+ color: #8be9fd;
+}
+
+[data-theme="dark"] .hljs-tag {
+ color: #ff79c6;
+}
+
+[data-theme="dark"] .hljs-doctag,
+[data-theme="dark"] .hljs-strong {
+ font-weight: bold;
+}
+
+[data-theme="dark"] .hljs-emphasis {
+ font-style: italic;
+}
+
+/* XML/HTML specific - brighter tag colors */
+[data-theme="dark"] .language-xml .hljs-tag,
+[data-theme="dark"] .language-html .hljs-tag {
+ color: #ff79c6;
+}
+
+[data-theme="dark"] .language-xml .hljs-name,
+[data-theme="dark"] .language-html .hljs-name {
+ color: #8be9fd;
+}
+
+[data-theme="dark"] .language-xml .hljs-attr,
+[data-theme="dark"] .language-html .hljs-attr {
+ color: #50fa7b;
+}
+
+[data-theme="dark"] .language-xml .hljs-string,
+[data-theme="dark"] .language-html .hljs-string {
+ color: #f1fa8c;
+}
+
+/* Asciidoctor listing block code in dark mode */
+[data-theme="dark"] .listingblock pre.highlight code,
+[data-theme="dark"] .listingblock pre.highlight,
+[data-theme="dark"] .literalblock pre,
+[data-theme="dark"] pre.content {
+ background-color: var(--code-bg);
+ color: var(--code-text);
+}
+
+/* Theme toggle button */
+#theme-toggle {
+ background: none;
+ border: 1px solid var(--border-color);
+ border-radius: 4px;
+ padding: 4px 8px;
+ cursor: pointer;
+ font-size: 16px;
+ margin-left: 10px;
+ transition: background-color 0.3s ease;
+}
+
+#theme-toggle:focus,
+#theme-toggle:focus-visible {
+ outline: 2px solid var(--link-color);
+ outline-offset: 2px;
+ background-color: var(--bg-secondary);
+}
+#theme-toggle:hover {
+ background-color: var(--bg-secondary);
+}
+
+[data-theme="dark"] #theme-toggle {
+ border-color: var(--border-color);
+}
+
+/* Asciidoctor dark mode overrides */
+[data-theme="dark"] .subheader,
+[data-theme="dark"] #content #toctitle,
+[data-theme="dark"] .admonitionblock td.content > .title,
+[data-theme="dark"] .exampleblock > .title,
+[data-theme="dark"] .imageblock > .title,
+[data-theme="dark"] .listingblock > .title,
+[data-theme="dark"] .literalblock > .title,
+[data-theme="dark"] .paragraph > .title,
+[data-theme="dark"] .tableblock > .title,
+[data-theme="dark"] .dlist > .title,
+[data-theme="dark"] .olist > .title,
+[data-theme="dark"] .ulist > .title {
+ color: #f59e0b;
+}
+
+[data-theme="dark"] table {
+ background: var(--bg-secondary);
+ border-color: var(--border-color);
+}
+
+[data-theme="dark"] table thead tr th,
+[data-theme="dark"] table thead tr td,
+[data-theme="dark"] table tr th,
+[data-theme="dark"] table tr td {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] table tr.even,
+[data-theme="dark"] table tr.alt,
+[data-theme="dark"] table tr:nth-of-type(even) {
+ background: var(--bg-color);
+}
+
+[data-theme="dark"] *:not(pre) > code {
+ background-color: var(--code-bg);
+ border-color: var(--border-color);
+ color: var(--code-text);
+}
+
+[data-theme="dark"] blockquote {
+ border-left-color: var(--border-color);
+}
+
+[data-theme="dark"] blockquote,
+[data-theme="dark"] blockquote p,
+[data-theme="dark"] blockquote cite {
+ color: var(--text-muted);
+}
+
+[data-theme="dark"] abbr,
+[data-theme="dark"] acronym {
+ color: var(--text-color);
+ border-bottom-color: var(--border-color);
+}
+
+[data-theme="dark"] kbd:not(.keyseq) {
+ color: var(--text-color);
+ background-color: var(--bg-secondary);
+ border-color: var(--border-color);
+ box-shadow: 0 1px 0 rgba(255, 255, 255, 0.1), 0 0 0 2px var(--bg-color)
inset;
+}
+
+[data-theme="dark"] .admonitionblock > table {
+ background: var(--bg-secondary);
+}
+
+[data-theme="dark"] .admonitionblock > table td.content {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .admonitionblock td.icon .icon-note:before {
+ color: var(--link-color);
+}
+
+[data-theme="dark"] .listingblock pre {
+ background: var(--code-bg);
+}
+
+/* Blog/News/Announcements dark mode fixes */
+[data-theme="dark"] .card {
+ background-color: var(--bg-secondary);
+ border-color: var(--border-color);
+}
+
+[data-theme="dark"] .card-body {
+ background-color: var(--bg-secondary);
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .card-body p,
+[data-theme="dark"] .card-body small,
+[data-theme="dark"] .card-body h4,
+[data-theme="dark"] .card-body a {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .card-body a:hover {
+ color: var(--link-hover);
+}
+
+[data-theme="dark"] .card-body small,
+[data-theme="dark"] small {
+ color: var(--text-muted);
+}
+
+[data-theme="dark"] .card-header {
+ border-bottom-color: var(--border-color);
+}
+
+[data-theme="dark"] .news-title,
+[data-theme="dark"] .popular-guides {
+ color: var(--link-color);
+}
+
+[data-theme="dark"] .news-title:hover,
+[data-theme="dark"] .popular-guides:hover {
+ color: var(--link-hover);
+}
+
+/* Page header and content */
+[data-theme="dark"] .page-header,
+[data-theme="dark"] .page-header h1 {
+ color: var(--text-color);
+}
+
+/* Blog post text styling */
+[data-theme="dark"] em,
+[data-theme="dark"] time {
+ color: var(--text-muted);
+}
+
+[data-theme="dark"] p {
+ color: var(--text-color);
+}
+
+/* Pagination dark mode */
+[data-theme="dark"] .pagination .page-link {
+ background-color: var(--bg-secondary);
+ border-color: var(--border-color);
+ color: var(--link-color);
+}
+
+[data-theme="dark"] .pagination .page-item.disabled .page-link {
+ background-color: var(--bg-color);
+ color: var(--text-muted);
+}
+
+[data-theme="dark"] .pagination .page-link:hover {
+ background-color: var(--border-color);
+ color: var(--link-hover);
+}
+
+/* Panel headings */
+[data-theme="dark"] .panel-heading {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] .panel-title {
+ color: var(--text-color);
+}
+
+/* Hero section / intro box */
+[data-theme="dark"] .rounded-3.bg-light {
+ background-color: var(--bg-secondary) !important;
+ color: var(--text-color);
+}
+
+/* Archive page */
+[data-theme="dark"] h4 {
+ color: var(--text-color);
+}
+
+[data-theme="dark"] ul li {
+ color: var(--text-color);
+}
+
+/* Download button - ensure visibility */
+[data-theme="dark"] .btn-success {
+ color: #ffffff;
+}
+
+/* Border styling for cards */
+[data-theme="dark"] .border-primary {
+ border-color: var(--link-color) !important;
+}
+
+/* Horizontal rule */
+[data-theme="dark"] hr {
+ border-color: var(--border-color);
+ opacity: 0.5;
+}
diff --git a/src/site/assets/js/theme.js b/src/site/assets/js/theme.js
new file mode 100644
index 000000000..bc8735aee
--- /dev/null
+++ b/src/site/assets/js/theme.js
@@ -0,0 +1,39 @@
+(function () {
+ var storedTheme;
+ try {
+ storedTheme = localStorage.getItem("theme");
+ } catch (e) {
+ storedTheme = null;
+ }
+
+ var theme = storedTheme || "light";
+ document.documentElement.setAttribute("data-theme", theme);
+
+ function updateIcon(currentTheme) {
+ var icon = document.getElementById("theme-icon");
+ if (icon) {
+ icon.textContent = currentTheme === "dark" ? "☀️" : "🌙";
+ }
+ }
+
+ document.addEventListener("DOMContentLoaded", function () {
+ var toggle = document.getElementById("theme-toggle");
+ var currentTheme = document.documentElement.getAttribute("data-theme");
+
+ updateIcon(currentTheme);
+
+ if (toggle) {
+ toggle.addEventListener("click", function () {
+ var current = document.documentElement.getAttribute("data-theme");
+ var next = current === "dark" ? "light" : "dark";
+ document.documentElement.setAttribute("data-theme", next);
+ try {
+ localStorage.setItem("theme", next);
+ } catch (e) {
+ // localStorage not available
+ }
+ updateIcon(next);
+ });
+ }
+ });
+})();
diff --git a/src/site/templates/header.ftl b/src/site/templates/header.ftl
index 7e78715ef..079bed060 100644
--- a/src/site/templates/header.ftl
+++ b/src/site/templates/header.ftl
@@ -138,6 +138,9 @@
<link href="<#if
(content.rootpath)??>${content.rootpath}<#else></#if>highlight.js-11.11.1/styles/default.min.css"
rel="stylesheet">
<link href="<#if
(content.rootpath)??>${content.rootpath}<#else></#if>css/gh-pages/gh-fork-ribbon.css"
rel="stylesheet"/>
+ <!-- Theme toggle script (loaded early to prevent flash) -->
+ <script src="<#if
(content.rootpath)??>${content.rootpath}<#else></#if>js/theme.js"></script>
+
<!-- Fav and touch icons -->
<!--<link rel="apple-touch-icon-precomposed" sizes="144x144"
href="../assets/ico/apple-touch-icon-144-precomposed.png">
<link rel="apple-touch-icon-precomposed" sizes="114x114"
href="../assets/ico/apple-touch-icon-114-precomposed.png">
diff --git a/src/site/templates/menu.ftl b/src/site/templates/menu.ftl
index 345194907..cf36e81cc 100644
--- a/src/site/templates/menu.ftl
+++ b/src/site/templates/menu.ftl
@@ -90,6 +90,12 @@
<li><a class="dropdown-item"
href="https://www.apache.org/security/">Security</a></li>
</ul>
</li>
+ <!-- Theme toggle -->
+ <li class="nav-item d-flex align-items-center">
+ <button id="theme-toggle" type="button" aria-label="Toggle dark
mode" title="Toggle dark mode">
+ <span id="theme-icon">🌙</span>
+ </button>
+ </li>
</ul>
<#--
<form class="d-flex">