This is an automated email from the ASF dual-hosted git repository.

paulk pushed a commit to branch asf-site
in repository https://gitbox.apache.org/repos/asf/groovy-website.git


The following commit(s) were added to refs/heads/asf-site by this push:
     new 3904836  changes to look and feel
3904836 is described below

commit 3904836834841b8b9c37cf84e2c0ab4856672dfb
Author: Paul King <[email protected]>
AuthorDate: Tue Apr 14 18:15:16 2026 +1000

    changes to look and feel
---
 site/src/site/assets/css/modern-design.css | 879 +++++++++++++++++++++--------
 site/src/site/assets/js/theme-switcher.js  | 122 ++++
 site/src/site/includes/topmenu.groovy      |   5 +
 site/src/site/layouts/page.groovy          |   2 +-
 site/src/site/pages/blog.groovy            |   2 +-
 site/src/site/pages/docpage.groovy         |   2 +-
 site/src/site/pages/release-notes.groovy   |   2 +-
 site/src/site/pages/wiki.groovy            |   2 +-
 8 files changed, 771 insertions(+), 245 deletions(-)

diff --git a/site/src/site/assets/css/modern-design.css 
b/site/src/site/assets/css/modern-design.css
index 664a453..a683477 100644
--- a/site/src/site/assets/css/modern-design.css
+++ b/site/src/site/assets/css/modern-design.css
@@ -18,281 +18,603 @@
  */
 
 /*
- * Modern Design System for Apache Groovy
- * Features: HSL Variables, Glassmorphism, Premium Typography, Micro-animations
+ * Theme System for Apache Groovy
+ * Supports: light, dark, and system (OS preference) modes
+ * Based on HSL color tokens for consistent theming
  */
 
+/* ===== Light Mode (default) ===== */
 :root {
-    /* Core HSL Tokens */
-    --primary-h: 198;
-    /* Groovy Blue */
-    --primary-s: 47%;
-    --primary-l: 49%;
-
+    --groovy-h: 198;
     --accent-h: 20;
-    /* Groovy Orange/Red */
     --accent-s: 100%;
     --accent-l: 43%;
 
-    --bg-dark: hsl(var(--primary-h), 20%, 10%);
-    --bg-card: hsla(var(--primary-h), 20%, 15%, 0.7);
-    --text-main: hsl(var(--primary-h), 10%, 90%);
-    --text-muted: hsl(var(--primary-h), 10%, 70%);
+    --bg-body: #fff;
+    --bg-alt: #f2f2f2;
+    --bg-card: #fff;
+    --bg-code: #f5f5f5;
+    --bg-navbar: #286b86;
+    --bg-band: #4298b8;
+    --bg-sidebar-menu: #4298b8;
+    --bg-sidebar-hover: rgba(0, 0, 0, 0.2);
+    --bg-footer: #f2f2f2;
+    --bg-they-use: #db4800;
+
+    --text-main: #343437;
+    --text-muted: #6f6f6f;
+    --text-heading: #343437;
+    --text-navbar: #c0d3db;
+    --text-navbar-brand: #fff;
+    --text-footer: #aaa;
+    --text-footer-body: #222;
+
+    --link-color: #db4800;
+    --link-hover: #db4800;
+    --accent-color: #db4800;
+    --heading-accent: #245f78;
+
+    --border-color: #eee;
+    --border-light: #e7e7e7;
+    --divider: #eee;
+
+    --nav-active-bg: #f2f2f2;
+    --nav-hover-bg: #db4800;
+    --nav-hover-text: #fff;
+
+    --table-header-bg: transparent;
+    --table-border: #eee;
+    --table-hover-bg: #f9f9f9;
+
+    --admonition-border: #ddd;
+    --admonition-text: #6f6f6f;
+    --admonition-title: #db4800;
+
+    --code-bg: #f2f2f2;
+    --code-border: transparent;
+
+    --shadow-card: inset 0 0 0 1px rgba(0, 0, 0, 0.1);
+    --glass-border: rgba(0, 0, 0, 0.1);
+
+    --conum-bg: #222;
+    --conum-text: #fff;
+
+    --toc-bg: transparent;
+}
 
-    --glass-bg: hsla(var(--primary-h), 20%, 5%, 0.9);
-    --glass-border: hsla(0, 0%, 100%, 0.1);
+/* ===== Dark Mode ===== */
+[data-theme="dark"] {
+    --bg-body: hsl(198, 20%, 12%);
+    --bg-alt: hsl(198, 20%, 16%);
+    --bg-card: hsl(198, 15%, 18%);
+    --bg-code: hsl(198, 15%, 10%);
+    --bg-navbar: hsl(198, 30%, 14%);
+    --bg-band: hsl(198, 30%, 22%);
+    --bg-sidebar-menu: hsl(198, 30%, 18%);
+    --bg-sidebar-hover: rgba(255, 255, 255, 0.1);
+    --bg-footer: hsl(198, 20%, 14%);
+    --bg-they-use: hsl(20, 80%, 18%);
+
+    --text-main: hsl(198, 10%, 88%);
+    --text-muted: hsl(198, 10%, 65%);
+    --text-heading: hsl(198, 10%, 92%);
+    --text-navbar: hsl(198, 20%, 75%);
+    --text-navbar-brand: #fff;
+    --text-footer: hsl(198, 10%, 55%);
+    --text-footer-body: hsl(198, 10%, 78%);
+
+    --link-color: hsl(20, 90%, 60%);
+    --link-hover: hsl(20, 95%, 70%);
+    --accent-color: hsl(20, 90%, 55%);
+    --heading-accent: hsl(198, 50%, 65%);
+
+    --border-color: hsl(198, 15%, 22%);
+    --border-light: hsl(198, 15%, 20%);
+    --divider: hsl(198, 15%, 22%);
+
+    --nav-active-bg: hsl(198, 20%, 20%);
+    --nav-hover-bg: hsl(20, 80%, 40%);
+    --nav-hover-text: #fff;
+
+    --table-header-bg: hsl(198, 20%, 18%);
+    --table-border: hsl(198, 15%, 22%);
+    --table-hover-bg: hsl(198, 15%, 20%);
+
+    --admonition-border: hsl(198, 15%, 25%);
+    --admonition-text: hsl(198, 10%, 72%);
+    --admonition-title: hsl(20, 90%, 60%);
+
+    --code-bg: hsl(198, 15%, 10%);
+    --code-border: hsl(198, 15%, 22%);
+
+    --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.3);
+    --glass-border: hsla(0, 0%, 100%, 0.08);
+
+    --conum-bg: hsl(198, 20%, 30%);
+    --conum-text: #fff;
+
+    --toc-bg: hsl(198, 15%, 15%);
+}
 
-    --shadow-soft: 0 8px 32px 0 rgba(0, 0, 0, 0.37);
-    --transition-standard: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
+/* System preference: apply dark when OS prefers it and no explicit theme is 
set */
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) {
+        --bg-body: hsl(198, 20%, 12%);
+        --bg-alt: hsl(198, 20%, 16%);
+        --bg-card: hsl(198, 15%, 18%);
+        --bg-code: hsl(198, 15%, 10%);
+        --bg-navbar: hsl(198, 30%, 14%);
+        --bg-band: hsl(198, 30%, 22%);
+        --bg-sidebar-menu: hsl(198, 30%, 18%);
+        --bg-sidebar-hover: rgba(255, 255, 255, 0.1);
+        --bg-footer: hsl(198, 20%, 14%);
+        --bg-they-use: hsl(20, 80%, 18%);
+
+        --text-main: hsl(198, 10%, 88%);
+        --text-muted: hsl(198, 10%, 65%);
+        --text-heading: hsl(198, 10%, 92%);
+        --text-navbar: hsl(198, 20%, 75%);
+        --text-navbar-brand: #fff;
+        --text-footer: hsl(198, 10%, 55%);
+        --text-footer-body: hsl(198, 10%, 78%);
+
+        --link-color: hsl(20, 90%, 60%);
+        --link-hover: hsl(20, 95%, 70%);
+        --accent-color: hsl(20, 90%, 55%);
+        --heading-accent: hsl(198, 50%, 65%);
+
+        --border-color: hsl(198, 15%, 22%);
+        --border-light: hsl(198, 15%, 20%);
+        --divider: hsl(198, 15%, 22%);
+
+        --nav-active-bg: hsl(198, 20%, 20%);
+        --nav-hover-bg: hsl(20, 80%, 40%);
+        --nav-hover-text: #fff;
+
+        --table-header-bg: hsl(198, 20%, 18%);
+        --table-border: hsl(198, 15%, 22%);
+        --table-hover-bg: hsl(198, 15%, 20%);
+
+        --admonition-border: hsl(198, 15%, 25%);
+        --admonition-text: hsl(198, 10%, 72%);
+        --admonition-title: hsl(20, 90%, 60%);
+
+        --code-bg: hsl(198, 15%, 10%);
+        --code-border: hsl(198, 15%, 22%);
+
+        --shadow-card: 0 2px 8px rgba(0, 0, 0, 0.3);
+        --glass-border: hsla(0, 0%, 100%, 0.08);
+
+        --conum-bg: hsl(198, 20%, 30%);
+        --conum-text: #fff;
+
+        --toc-bg: hsl(198, 15%, 15%);
+    }
 }
 
-/* Base Modernization */
+/* ===== Apply theme variables ===== */
+
 body {
-    background-color: var(--bg-dark) !important;
-    color: var(--text-main) !important;
-    transition: var(--transition-standard);
-    font-size: 16px !important;
-    letter-spacing: 0.01em;
-}
-
-/* Aggressive Dark Mode Nuclear Overrides */
-html,
-body,
-#st-container,
-.st-pusher,
-.st-content,
-.st-content-inner,
-#content,
-#footer,
-.sidebarblock,
-.main-content,
-.navbar,
-.navbar-default,
-.navbar-static-top,
-.navbar-collapse,
-.container,
-.container-fluid,
-.well,
-.panel,
-.panel-default,
-.panel-heading {
-    background-color: var(--bg-dark) !important;
-    background: var(--bg-dark) !important;
-    color: var(--text-main) !important;
-    border-color: var(--glass-border) !important;
-}
-
-/* Ensure all links inside main content possess good contrast */
-#content a,
-.st-content a {
-    color: hsl(var(--primary-h), 80%, 70%) !important;
-}
-
-#content a:hover,
-.st-content a:hover {
-    color: white !important;
-    text-decoration: underline !important;
+    background-color: var(--bg-body);
+    color: var(--text-main);
+    transition: background-color 0.3s ease, color 0.3s ease;
 }
 
-#footer {
-    border-top: 1px solid var(--glass-border);
+/* Navbar */
+.navbar-default, .navbar-static-top {
+    background-color: var(--bg-navbar);
+}
+
+.navbar-default a {
+    color: var(--text-navbar);
 }
 
-/* Glassmorphism Navbar */
-.navbar-default {
-    background: var(--glass-bg) !important;
-    backdrop-filter: blur(12px) !important;
-    -webkit-backdrop-filter: blur(12px) !important;
-    border-bottom: 1px solid var(--glass-border) !important;
-    box-shadow: var(--shadow-soft);
-    transition: var(--transition-standard);
+a.navbar-brand {
+    color: var(--text-navbar-brand);
+}
+
+/* Main content area */
+#content {
+    background: var(--bg-body);
+}
+
+.st-content {
+    background: var(--bg-body);
+}
+
+/* Text and links */
+body, html {
+    color: var(--text-main);
+}
+
+a {
+    color: var(--link-color);
 }
 
-.navbar-brand {
-    font-weight: 700 !important;
-    letter-spacing: -0.02em;
-    color: white !important;
-    text-shadow: 0 0 10px hsla(var(--primary-h), 100%, 50%, 0.3);
-    padding: 15px 10px !important;
+a:hover {
+    color: var(--link-hover);
+}
+
+h5 {
+    color: var(--accent-color);
+}
+
+h6 {
+    color: var(--accent-color);
+}
+
+h7 {
+    color: var(--heading-accent);
+}
+
+h8 {
+    color: var(--heading-accent);
+}
+
+/* Hero band */
+.band, #band {
+    background-color: var(--bg-band);
+}
+
+/* "They use Groovy" section */
+#they-use-groovy {
+    background-color: var(--bg-they-use);
+}
+
+/* Footer */
+#footer {
+    background: var(--bg-footer);
+    color: var(--text-footer);
 }
 
-.navbar-nav>li>a {
-    color: var(--text-muted) !important;
-    transition: var(--transition-standard);
-    border-radius: 8px;
-    margin: 0 2px;
-    padding: 10px 8px !important;
-    font-size: 14px !important;
+#footer .colset-3-footer {
+    color: var(--text-footer-body);
 }
 
-.navbar-nav>li>a:hover,
-.navbar-nav>li>a:focus,
-.navbar-nav>li>a:focus-visible {
-    background-color: hsla(var(--accent-h), var(--accent-s), var(--accent-l), 
0.2) !important;
-    color: white !important;
-    transform: translateY(-1px);
-    outline: 2px solid hsla(var(--accent-h), var(--accent-s), var(--accent-l), 
0.8);
-    outline-offset: 2px;
+#footer .colset-3-footer .col-1 ul li a,
+#footer .colset-3-footer .col-2 ul li a,
+#footer .colset-3-footer .col-3 ul li a {
+    color: var(--text-footer-body);
 }
 
-/* Sidebar & Navigation Modernization */
+#footer .second a {
+    color: var(--accent-color);
+}
+
+/* Sidebar navigation */
 ul.nav-sidebar {
-    border: 1px solid var(--glass-border) !important;
-    background-color: var(--bg-card) !important;
-    border-radius: 12px;
-    padding: 10px 0 !important;
+    border-color: var(--border-color);
 }
 
 ul.nav-sidebar li a {
-    color: var(--text-muted) !important;
-    transition: var(--transition-standard);
-    padding: 8px 15px !important;
+    color: var(--text-main);
 }
 
-ul.nav-sidebar li a:hover,
-ul.nav-sidebar li.active a {
-    color: white !important;
-    background-color: hsla(var(--primary-h), 50%, 30%, 0.5) !important;
+ul.nav-sidebar li.active a:hover,
+ul.nav-sidebar li a:hover {
+    background-color: var(--nav-hover-bg);
+    color: var(--nav-hover-text);
 }
 
 ul.nav-sidebar li.active a {
-    border-left: 4px solid hsl(var(--accent-h), var(--accent-s), 
var(--accent-l)) !important;
-}
-
-/* Table Modernization for Download & Docs */
-.table,
-.download-table {
-    border-collapse: separate !important;
-    border-spacing: 0 !important;
-    width: 100% !important;
-    margin: 20px 0 !important;
-    border: 1px solid var(--glass-border) !important;
-    border-radius: 12px !important;
-    overflow: hidden !important;
-}
-
-.table th,
-.table td,
-.download-table th,
-.download-table td {
-    background-color: transparent !important;
-    border-top: 1px solid var(--glass-border) !important;
-    padding: 12px 15px !important;
-    color: var(--text-main) !important;
-}
-
-.table thead th,
-.download-table thead th {
-    background-color: hsla(var(--primary-h), 20%, 20%, 0.5) !important;
-    border-top: none !important;
-    font-weight: 600 !important;
-}
-
-.table tbody tr:hover,
-.download-table tbody tr:hover {
-    background-color: hsla(var(--primary-h), 20%, 20%, 0.3) !important;
-}
-
-/* Premium Hero Section */
-.band {
-    background-color: hsl(var(--primary-h), 30%, 20%) !important;
-    background-image:
-        url(../img/groovy-logo-white.svg),
-        linear-gradient(135deg, hsla(var(--primary-h), 60%, 40%, 0.5) 0%, 
hsla(var(--primary-h), 30%, 20%, 0.7) 100%) !important;
-    background-position: 50% 30%, center center !important;
-    background-repeat: no-repeat !important;
-    background-size: auto, cover !important;
-    position: relative;
-    overflow: hidden;
-}
-
-.band::after {
-    content: '';
-    position: absolute;
-    top: 0;
-    left: 0;
-    right: 0;
-    bottom: 0;
-    background: radial-gradient(circle at 70% 30%, hsla(var(--accent-h), 100%, 
50%, 0.15) 0%, transparent 50%);
-    pointer-events: none;
-}
-
-/* Buttons */
-#big-download-button,
-.btn-primary {
-    background-color: hsl(var(--accent-h), var(--accent-s), var(--accent-l)) 
!important;
-    border: none !important;
-    box-shadow: 0 4px 14px 0 hsla(var(--accent-h), var(--accent-s), 
var(--accent-l), 0.39);
-    transition: var(--transition-standard) !important;
-}
-
-#big-download-button:hover,
-.btn-primary:hover,
-#big-download-button:focus,
-.btn-primary:focus {
-    transform: translateY(-2px);
-    box-shadow: 0 6px 20px rgba(0, 0, 0, 0.23);
-    background-color: hsl(var(--accent-h), var(--accent-s), 50%) !important;
-    outline: 2px solid white;
-    outline-offset: 2px;
-}
-
-/* Cards & Content Blocks */
-article .content {
-    background: var(--bg-card) !important;
-    backdrop-filter: blur(8px);
-    border: 1px solid var(--glass-border);
-    border-radius: 16px !important;
-    padding: 24px !important;
-    transition: var(--transition-standard);
-    box-shadow: var(--shadow-soft);
-}
-
-article:hover .content {
-    transform: translateY(-5px);
-    border-color: hsla(var(--primary-h), 100%, 50%, 0.3);
-}
-
-/* Typography Polish */
-h1,
-h2,
-h3 {
-    letter-spacing: -0.03em !important;
-    line-height: 1.2 !important;
+    background-color: var(--nav-active-bg);
 }
 
-a {
-    transition: var(--transition-standard);
+/* Content sections */
+#content .row > h1 {
+    color: var(--text-heading);
+}
+
+#content hr.row, #content hr.divider {
+    border-top-color: var(--divider);
+}
+
+#content .colset-2-its > p {
+    color: var(--text-main);
+}
+
+#content .colset-2-its article > h1 {
+    color: var(--text-heading);
+}
+
+#content .colset-3-article article {
+    box-shadow: var(--shadow-card);
+}
+
+#content .colset-3-article article h1 a {
+    color: var(--text-heading);
+}
+
+#content .colset-3-article article h1 a:hover {
+    color: hsl(198, 50%, 55%);
 }
 
+/* Code blocks */
 pre {
-    background-color: hsla(0, 0%, 5%, 0.5) !important;
-    border: 1px solid var(--glass-border) !important;
-    border-radius: 12px !important;
-    padding: 1.5em !important;
+    background: var(--code-bg);
+    border-color: var(--code-border);
+    color: var(--text-main);
 }
 
-/* Homepage Specific Overrides */
-html body #content .colset-2-its>h1,
-html body #content .colset-2-its>p,
-html body #content .colset-2-its article>h1,
-html body #content .colset-2-its article p {
-    color: var(--text-main) !important;
+#content.page-1 .row article pre {
+    background: var(--code-bg);
 }
 
-#they-use-groovy {
-    background-color: hsla(var(--accent-h), 100%, 10%, 0.8) !important;
-    background-image: linear-gradient(135deg, hsla(var(--accent-h), 100%, 30%, 
0.3) 0%, transparent 100%) !important;
-    border-top: 1px solid var(--glass-border) !important;
-    border-bottom: 1px solid var(--glass-border) !important;
-    padding: 30px 0 !important;
+/* Tables */
+.table tbody tr td {
+    border-top-color: var(--table-border);
+}
+
+.table thead tr th {
+    background-color: var(--table-header-bg);
+    color: var(--text-heading);
+}
+
+.table tbody tr:hover {
+    background-color: var(--table-hover-bg);
+}
+
+.download-table td,
+.download-table th {
+    color: var(--text-main);
+}
+
+/* Doc embed */
+.doc-embed {
+    border-color: var(--border-light);
+}
+
+/* Big download button */
+#big-download-button {
+    background-color: var(--accent-color);
+    border-color: var(--accent-color);
+}
+
+/* Sidebar push menu */
+.st-menu {
+    background: var(--bg-sidebar-menu);
+}
+
+.st-menu ul li a:hover {
+    background: var(--bg-sidebar-hover);
+}
+
+/* ===== AsciiDoc / Documentation theming ===== */
+
+/* Table of contents */
+#content #toc {
+    background-color: var(--toc-bg);
+    border-color: var(--border-color);
+}
+
+#content #toc a {
+    color: var(--link-color);
+}
+
+#content #toctitle {
+    color: var(--text-heading);
+}
+
+/* Admonition blocks */
+.admonitionblock td.content > .title {
+    color: var(--admonition-title);
+}
+
+.admonitionblock > table td.content {
+    border-left-color: var(--admonition-border);
+    color: var(--admonition-text);
+}
+
+/* Sidebar blocks in docs */
+.sidebarblock {
+    background-color: var(--bg-alt);
+    border-color: var(--border-color);
+}
+
+/* Callout numbers */
+.conum {
+    background-color: var(--conum-bg);
+    color: var(--conum-text);
+}
+
+/* Definition lists */
+.hdlist > table,
+.colist > table {
+    background: none;
+}
+
+/* Listing/literal blocks */
+.listingblock pre,
+.literalblock pre {
+    background: var(--code-bg);
+    color: var(--text-main);
+}
+
+/* Inline code */
+code {
+    color: var(--text-main);
+}
+
+p code,
+td code,
+li code,
+dd code,
+dt code,
+h1 code, h2 code, h3 code, h4 code, h5 code, h6 code {
+    background-color: var(--code-bg);
+    padding: 1px 4px;
+    border-radius: 3px;
+    border: 1px solid var(--code-border);
+}
+
+/* ===== Google Code Prettify - dark mode overrides ===== */
+[data-theme="dark"] .pln { color: #c9d1d9; }
+[data-theme="dark"] .str { color: #a5d6ff; }
+[data-theme="dark"] .kwd { color: #ff7b72; }
+[data-theme="dark"] .com { color: #8b949e; }
+[data-theme="dark"] .typ { color: #d2a8ff; }
+[data-theme="dark"] .lit { color: #79c0ff; }
+[data-theme="dark"] .pun,
+[data-theme="dark"] .opn,
+[data-theme="dark"] .clo { color: #c9d1d9; }
+[data-theme="dark"] .tag { color: #7ee787; }
+[data-theme="dark"] .atn { color: #d2a8ff; }
+[data-theme="dark"] .atv { color: #a5d6ff; }
+[data-theme="dark"] .dec,
+[data-theme="dark"] .var { color: #ffa657; }
+[data-theme="dark"] .fun { color: #d2a8ff; }
+[data-theme="dark"] pre.prettyprint { border-color: var(--code-border); }
+[data-theme="dark"] li.L1,
+[data-theme="dark"] li.L3,
+[data-theme="dark"] li.L5,
+[data-theme="dark"] li.L7,
+[data-theme="dark"] li.L9 { background: hsl(198, 15%, 14%); }
+
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) .pln { color: #c9d1d9; }
+    :root:not([data-theme="light"]) .str { color: #a5d6ff; }
+    :root:not([data-theme="light"]) .kwd { color: #ff7b72; }
+    :root:not([data-theme="light"]) .com { color: #8b949e; }
+    :root:not([data-theme="light"]) .typ { color: #d2a8ff; }
+    :root:not([data-theme="light"]) .lit { color: #79c0ff; }
+    :root:not([data-theme="light"]) .pun,
+    :root:not([data-theme="light"]) .opn,
+    :root:not([data-theme="light"]) .clo { color: #c9d1d9; }
+    :root:not([data-theme="light"]) .tag { color: #7ee787; }
+    :root:not([data-theme="light"]) .atn { color: #d2a8ff; }
+    :root:not([data-theme="light"]) .atv { color: #a5d6ff; }
+    :root:not([data-theme="light"]) .dec,
+    :root:not([data-theme="light"]) .var { color: #ffa657; }
+    :root:not([data-theme="light"]) .fun { color: #d2a8ff; }
+    :root:not([data-theme="light"]) pre.prettyprint { border-color: 
var(--code-border); }
+    :root:not([data-theme="light"]) li.L1,
+    :root:not([data-theme="light"]) li.L3,
+    :root:not([data-theme="light"]) li.L5,
+    :root:not([data-theme="light"]) li.L7,
+    :root:not([data-theme="light"]) li.L9 { background: hsl(198, 15%, 14%); }
+}
+
+/* AsciiDoc color classes - dark mode adjustments */
+[data-theme="dark"] .aqua { color: #00e5e5; }
+[data-theme="dark"] .blue { color: #5b8def; }
+[data-theme="dark"] .fuchsia { color: #e55ce5; }
+[data-theme="dark"] .gray { color: #999; }
+[data-theme="dark"] .green { color: #4caf50; }
+[data-theme="dark"] .navy { color: #7b9ed8; }
+[data-theme="dark"] .olive { color: #b8b84d; }
+[data-theme="dark"] .purple { color: #b366b3; }
+[data-theme="dark"] .red { color: #e55c5c; }
+[data-theme="dark"] .silver { color: #bbb; }
+[data-theme="dark"] .teal { color: #4db8b8; }
+[data-theme="dark"] .white { color: #e0e0e0; }
+[data-theme="dark"] .maroon { color: #cc6666; }
+[data-theme="dark"] .lime { color: #66e566; }
+[data-theme="dark"] .yellow { color: #e5e566; }
+[data-theme="dark"] .gold { color: #e5c84d; }
+[data-theme="dark"] .black { color: #aaa; }
+
+[data-theme="dark"] .aqua-background { background: #005959; }
+[data-theme="dark"] .blue-background { background: #1a3a6b; }
+[data-theme="dark"] .fuchsia-background { background: #5e1a5e; }
+[data-theme="dark"] .gray-background { background: #3d3d3d; }
+[data-theme="dark"] .green-background { background: #1a4d1a; }
+[data-theme="dark"] .navy-background { background: #0d1a33; }
+[data-theme="dark"] .olive-background { background: #3d3d0d; }
+[data-theme="dark"] .purple-background { background: #331a33; }
+[data-theme="dark"] .red-background { background: #6b1a1a; }
+[data-theme="dark"] .silver-background { background: #4d4d4d; }
+[data-theme="dark"] .teal-background { background: #0d3333; }
+[data-theme="dark"] .white-background { background: #2a2a2a; }
+[data-theme="dark"] .black-background { background: #111; }
+[data-theme="dark"] .yellow-background { background: #3d3d0d; }
+[data-theme="dark"] .lime-background { background: #1a4d1a; }
+[data-theme="dark"] .maroon-background { background: #4d1a1a; }
+
+/* System preference dark mode color overrides */
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) .aqua { color: #00e5e5; }
+    :root:not([data-theme="light"]) .blue { color: #5b8def; }
+    :root:not([data-theme="light"]) .fuchsia { color: #e55ce5; }
+    :root:not([data-theme="light"]) .gray { color: #999; }
+    :root:not([data-theme="light"]) .green { color: #4caf50; }
+    :root:not([data-theme="light"]) .navy { color: #7b9ed8; }
+    :root:not([data-theme="light"]) .olive { color: #b8b84d; }
+    :root:not([data-theme="light"]) .purple { color: #b366b3; }
+    :root:not([data-theme="light"]) .red { color: #e55c5c; }
+    :root:not([data-theme="light"]) .silver { color: #bbb; }
+    :root:not([data-theme="light"]) .teal { color: #4db8b8; }
+    :root:not([data-theme="light"]) .white { color: #e0e0e0; }
+    :root:not([data-theme="light"]) .maroon { color: #cc6666; }
+    :root:not([data-theme="light"]) .lime { color: #66e566; }
+    :root:not([data-theme="light"]) .yellow { color: #e5e566; }
+    :root:not([data-theme="light"]) .gold { color: #e5c84d; }
+    :root:not([data-theme="light"]) .black { color: #aaa; }
+
+    :root:not([data-theme="light"]) .aqua-background { background: #005959; }
+    :root:not([data-theme="light"]) .blue-background { background: #1a3a6b; }
+    :root:not([data-theme="light"]) .fuchsia-background { background: #5e1a5e; 
}
+    :root:not([data-theme="light"]) .gray-background { background: #3d3d3d; }
+    :root:not([data-theme="light"]) .green-background { background: #1a4d1a; }
+    :root:not([data-theme="light"]) .navy-background { background: #0d1a33; }
+    :root:not([data-theme="light"]) .olive-background { background: #3d3d0d; }
+    :root:not([data-theme="light"]) .purple-background { background: #331a33; }
+    :root:not([data-theme="light"]) .red-background { background: #6b1a1a; }
+    :root:not([data-theme="light"]) .silver-background { background: #4d4d4d; }
+    :root:not([data-theme="light"]) .teal-background { background: #0d3333; }
+    :root:not([data-theme="light"]) .white-background { background: #2a2a2a; }
+    :root:not([data-theme="light"]) .black-background { background: #111; }
+    :root:not([data-theme="light"]) .yellow-background { background: #3d3d0d; }
+    :root:not([data-theme="light"]) .lime-background { background: #1a4d1a; }
+    :root:not([data-theme="light"]) .maroon-background { background: #4d1a1a; }
+}
+
+/* ===== Glassmorphism navbar enhancement (dark mode) ===== */
+[data-theme="dark"] .navbar-default {
+    backdrop-filter: blur(12px);
+    -webkit-backdrop-filter: blur(12px);
+    border-bottom: 1px solid var(--glass-border);
+    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+}
+
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) .navbar-default {
+        backdrop-filter: blur(12px);
+        -webkit-backdrop-filter: blur(12px);
+        border-bottom: 1px solid var(--glass-border);
+        box-shadow: 0 4px 16px rgba(0, 0, 0, 0.2);
+    }
+}
+
+/* ===== Theme Switcher Button ===== */
+.theme-switcher {
+    display: inline-flex;
+    align-items: center;
+    padding: 6px 10px;
+    cursor: pointer;
+    background: transparent;
+    border: 1px solid rgba(255, 255, 255, 0.2);
+    border-radius: 6px;
+    color: var(--text-navbar);
+    font-size: 14px;
+    line-height: 1;
+    transition: background-color 0.2s ease, border-color 0.2s ease;
+    vertical-align: middle;
+    margin-top: 13px;
 }
 
-#they-use-groovy .item {
-    color: var(--text-main) !important;
+.theme-switcher:hover {
+    background-color: rgba(255, 255, 255, 0.1);
+    border-color: rgba(255, 255, 255, 0.4);
+    color: #fff;
 }
 
-/* Protect Icon Fonts */
+.theme-switcher .theme-icon {
+    font-size: 16px;
+    width: 18px;
+    text-align: center;
+}
+
+/* Protect icon fonts - use correct Font Awesome 7 families */
 .fa,
 .fa-classic,
 .fa-solid,
@@ -300,24 +622,101 @@ html body #content .colset-2-its article p {
 .fa-brands,
 [class^="fa-"],
 [class*=" fa-"] {
-    font-family: 'Font Awesome 6 Free', 'Font Awesome 6 Brands', 
'FontAwesome', sans-serif !important;
-    font-style: normal !important;
-    background: transparent !important;
-    /* Ensure no background overrides icons */
+    font-family: var(--fa-family, 'Font Awesome 7 Free'), serif;
+    font-style: normal;
 }
 
-/* Accessibility: Reduced Motion */
-@media (prefers-reduced-motion: reduce) {
+/* ===== Dark mode image adjustments ===== */
+[data-theme="dark"] img:not([src*=".svg"]) {
+    opacity: 0.92;
+}
 
-    *,
-    ::before,
-    ::after {
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) img:not([src*=".svg"]) {
+        opacity: 0.92;
+    }
+}
+
+/* ===== Accessibility: Reduced Motion ===== */
+@media (prefers-reduced-motion: reduce) {
+    *, ::before, ::after {
         animation-delay: -1ms !important;
         animation-duration: 1ms !important;
         animation-iteration-count: 1 !important;
-        background-attachment: initial !important;
         scroll-behavior: auto !important;
         transition-duration: 0s !important;
         transition-delay: 0s !important;
     }
-}
\ No newline at end of file
+}
+
+/* ===== Dark mode form inputs ===== */
+[data-theme="dark"] input,
+[data-theme="dark"] select,
+[data-theme="dark"] textarea {
+    background-color: var(--bg-alt);
+    color: var(--text-main);
+    border-color: var(--border-color);
+}
+
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) input,
+    :root:not([data-theme="light"]) select,
+    :root:not([data-theme="light"]) textarea {
+        background-color: var(--bg-alt);
+        color: var(--text-main);
+        border-color: var(--border-color);
+    }
+}
+
+/* ===== Dark mode Bootstrap overrides ===== */
+[data-theme="dark"] .well,
+[data-theme="dark"] .panel,
+[data-theme="dark"] .panel-default,
+[data-theme="dark"] .panel-heading {
+    background-color: var(--bg-card);
+    border-color: var(--border-color);
+    color: var(--text-main);
+}
+
+[data-theme="dark"] .panel-default > .panel-heading {
+    background-color: var(--bg-alt);
+    color: var(--text-heading);
+}
+
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) .well,
+    :root:not([data-theme="light"]) .panel,
+    :root:not([data-theme="light"]) .panel-default,
+    :root:not([data-theme="light"]) .panel-heading {
+        background-color: var(--bg-card);
+        border-color: var(--border-color);
+        color: var(--text-main);
+    }
+
+    :root:not([data-theme="light"]) .panel-default > .panel-heading {
+        background-color: var(--bg-alt);
+        color: var(--text-heading);
+    }
+}
+
+/* ===== Presentations/Courses dark mode ===== */
+[data-theme="dark"] .presentations .speaker,
+[data-theme="dark"] .courses .instructor {
+    color: var(--heading-accent);
+}
+
+@media (prefers-color-scheme: dark) {
+    :root:not([data-theme="light"]) .presentations .speaker,
+    :root:not([data-theme="light"]) .courses .instructor {
+        color: var(--heading-accent);
+    }
+}
+
+/* ===== Anchor links ===== */
+.anchor-link:before {
+    color: var(--text-muted);
+}
+
+.anchor-link:hover:before {
+    color: var(--accent-color);
+}
diff --git a/site/src/site/assets/js/theme-switcher.js 
b/site/src/site/assets/js/theme-switcher.js
new file mode 100644
index 0000000..23eb840
--- /dev/null
+++ b/site/src/site/assets/js/theme-switcher.js
@@ -0,0 +1,122 @@
+/*
+ *  Licensed to the Apache Software Foundation (ASF) under one
+ *  or more contributor license agreements.  See the NOTICE file
+ *  distributed with this work for additional information
+ *  regarding copyright ownership.  The ASF licenses this file
+ *  to you under the Apache License, Version 2.0 (the
+ *  "License"); you may not use this file except in compliance
+ *  with the License.  You may obtain a copy of the License at
+ *
+ *    http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing,
+ *  software distributed under the License is distributed on an
+ *  "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ *  KIND, either express or implied.  See the License for the
+ *  specific language governing permissions and limitations
+ *  under the License.
+ */
+
+(function () {
+    'use strict';
+
+    // Modes cycle: system -> light -> dark -> system
+    var MODES = ['system', 'light', 'dark'];
+    var STORAGE_KEY = 'groovy-theme';
+
+    // Unicode icons for each mode (no external dependencies)
+    var ICONS = {
+        system: '\uD83D\uDCBB',  // laptop
+        light:  '\u2600\uFE0F',  // sun
+        dark:   '\uD83C\uDF19'   // moon
+    };
+
+    var TITLES = {
+        system: 'Theme: System preference',
+        light:  'Theme: Light',
+        dark:   'Theme: Dark'
+    };
+
+    function getStoredMode() {
+        try {
+            return localStorage.getItem(STORAGE_KEY);
+        } catch (e) {
+            return null;
+        }
+    }
+
+    function storeMode(mode) {
+        try {
+            if (mode === 'system') {
+                localStorage.removeItem(STORAGE_KEY);
+            } else {
+                localStorage.setItem(STORAGE_KEY, mode);
+            }
+        } catch (e) {
+            // localStorage not available
+        }
+    }
+
+    function getCurrentMode() {
+        var stored = getStoredMode();
+        if (stored === 'light' || stored === 'dark') return stored;
+        return 'system';
+    }
+
+    function applyMode(mode) {
+        var html = document.documentElement;
+        if (mode === 'light' || mode === 'dark') {
+            html.setAttribute('data-theme', mode);
+        } else {
+            // System mode: remove attribute so CSS media query takes over
+            html.removeAttribute('data-theme');
+        }
+    }
+
+    function updateButton(btn, mode) {
+        var icon = btn.querySelector('.theme-icon');
+        if (icon) icon.textContent = ICONS[mode];
+        btn.setAttribute('title', TITLES[mode]);
+        btn.setAttribute('aria-label', TITLES[mode]);
+    }
+
+    function nextMode(current) {
+        var idx = MODES.indexOf(current);
+        return MODES[(idx + 1) % MODES.length];
+    }
+
+    // Apply theme immediately (before DOM ready) to prevent flash
+    var initialMode = getCurrentMode();
+    applyMode(initialMode);
+
+    // Set up button once DOM is ready
+    function init() {
+        var btn = document.getElementById('theme-switcher');
+        if (!btn) return;
+
+        var mode = getCurrentMode();
+        updateButton(btn, mode);
+
+        btn.addEventListener('click', function () {
+            mode = nextMode(mode);
+            applyMode(mode);
+            storeMode(mode);
+            updateButton(btn, mode);
+        });
+
+        // Listen for OS theme changes (relevant when in system mode)
+        if (window.matchMedia) {
+            window.matchMedia('(prefers-color-scheme: 
dark)').addEventListener('change', function () {
+                if (getCurrentMode() === 'system') {
+                    applyMode('system');
+                }
+            });
+        }
+    }
+
+    if (document.readyState === 'loading') {
+        document.addEventListener('DOMContentLoaded', init);
+    } else {
+        init();
+    }
+})();
diff --git a/site/src/site/includes/topmenu.groovy 
b/site/src/site/includes/topmenu.groovy
index 057e175..855166c 100644
--- a/site/src/site/includes/topmenu.groovy
+++ b/site/src/site/includes/topmenu.groovy
@@ -26,6 +26,11 @@ div(class: 'navbar navbar-default navbar-static-top', role: 
'navigation') {
                         i(class: 'fa-classic fa-solid fa-magnifying-glass') {}
                     }
                 }
+                li {
+                    button(id: 'theme-switcher', class: 'theme-switcher', 
type: 'button', title: 'Toggle theme', 'aria-label': 'Toggle theme') {
+                        span(class: 'theme-icon', '')
+                    }
+                }
             }
         }
     }
diff --git a/site/src/site/layouts/page.groovy 
b/site/src/site/layouts/page.groovy
index d7663a2..f42c95f 100644
--- a/site/src/site/layouts/page.groovy
+++ b/site/src/site/layouts/page.groovy
@@ -76,7 +76,7 @@ body {
     }
 
     def scripts = extraScripts ?: []
-    ['vendor/jquery-1.10.2.min.js', 'vendor/classie.js', 
'vendor/bootstrap.js', 'vendor/sidebarEffects.js', 
'vendor/modernizr-2.6.2.min.js','plugins.js', *scripts].each {
+    ['vendor/jquery-1.10.2.min.js', 'vendor/classie.js', 
'vendor/bootstrap.js', 'vendor/sidebarEffects.js', 
'vendor/modernizr-2.6.2.min.js','plugins.js', 'theme-switcher.js', 
*scripts].each {
         yieldUnescaped "<script 
src='${it.startsWith('http')?it:relative('js/'+it)}' defer></script>"
     }
 
diff --git a/site/src/site/pages/blog.groovy b/site/src/site/pages/blog.groovy
index eb95877..d66eaa1 100644
--- a/site/src/site/pages/blog.groovy
+++ b/site/src/site/pages/blog.groovy
@@ -19,7 +19,7 @@ if (doc.attributes.description) {
 
 layout 'layouts/main.groovy', true,
         pageTitle: "The Apache Groovy programming language - Blogs - $title",
-        extraStyles: [relative('css/prettify.min.css')],
+        extraStyles: ['prettify.min.css'],
         extraMeta: metas,
         extraFooter: contents {
             script(src:relative('js/vendor/prettify.min.js')) { }
diff --git a/site/src/site/pages/docpage.groovy 
b/site/src/site/pages/docpage.groovy
index 16fcba7..8d8146c 100644
--- a/site/src/site/pages/docpage.groovy
+++ b/site/src/site/pages/docpage.groovy
@@ -9,7 +9,7 @@
  */
 layout 'layouts/main.groovy', true,
         pageTitle: "The Apache Groovy programming language - $title",
-        extraStyles: ['docstyle.css',relative('css/prettify.min.css')],
+        extraStyles: ['docstyle.css','prettify.min.css'],
         extraFooter: contents {
             script(src: relative('js/vendor/prettify.min.js')) {}
             script { yieldUnescaped 
"document.addEventListener('DOMContentLoaded',prettyPrint)" }
diff --git a/site/src/site/pages/release-notes.groovy 
b/site/src/site/pages/release-notes.groovy
index b8635f7..e9d9e91 100644
--- a/site/src/site/pages/release-notes.groovy
+++ b/site/src/site/pages/release-notes.groovy
@@ -7,7 +7,7 @@ modelTypes = {
 
 layout 'layouts/main.groovy', true,
         pageTitle: "The Apache Groovy programming language - Groovy 
$groovyVersion release notes",
-        extraStyles: [relative('css/prettify.min.css')],
+        extraStyles: ['prettify.min.css'],
         extraFooter: contents {
             script(src:relative('js/vendor/prettify.min.js')) { }
             script { yieldUnescaped 
"document.addEventListener('DOMContentLoaded',prettyPrint)" }
diff --git a/site/src/site/pages/wiki.groovy b/site/src/site/pages/wiki.groovy
index 06110c5..62e1626 100644
--- a/site/src/site/pages/wiki.groovy
+++ b/site/src/site/pages/wiki.groovy
@@ -11,7 +11,7 @@ title = doc.structuredDoctitle.main
 
 layout 'layouts/main.groovy', true,
         pageTitle: "The Apache Groovy programming language - Developer docs - 
$title",
-        extraStyles: [relative('css/prettify.min.css')],
+        extraStyles: ['prettify.min.css'],
         extraFooter: contents {
             script(src:relative('js/vendor/prettify.min.js')) { }
             script { yieldUnescaped 
"document.addEventListener('DOMContentLoaded',prettyPrint)" }


Reply via email to