diff --git a/assets/style.css b/assets/style.css index cb0d654f0..2b919921c 100644 --- a/assets/style.css +++ b/assets/style.css @@ -1,17 +1,234 @@ +/* ======================================== + CSS Custom Properties - Light Mode (Default) + ======================================== */ +:root { + /* Primary Colors */ + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --text-primary: #333333; + --text-secondary: #666666; + --text-tertiary: #999999; + + /* Surfaces */ + --navbar-bg: #eeeeee; + --navbar-border: #cccccc; + --panel-bg: #f1f1f1; + --code-bg: #000000; + + /* Borders & Dividers */ + --border-light: #f1f1f1; + --border-medium: #cccccc; + --border-dark: #999999; + + /* Interactive Elements */ + --accent-primary: #0088cc; + --accent-dark: #0077b3; + --accent-light: #0081c2; + --hover-bg: #0088cc; + + /* UI Components */ + --input-bg: #ffffff; + --input-border: #cccccc; + --input-text: #333333; + --dropdown-bg: #ffffff; + --dropdown-border: #cccccc; + --dropdown-hover-bg: #0088cc; + --dropdown-hover-text: #cccccc; + + /* Semantic Colors */ + --link-color: #0088cc; + --fork-me-bg: #e3e3e3; + --fork-me-border: #c2c2c2; + --fork-me-text: #484848; + + /* Labels & Tags */ + --label-acf-bg: #069; + --label-acf-hover: #246; + --label-lucee-bg: #449caf; + --label-lucee-hover: #01798a; + --label-openbd-bg: #2fa5d7; + --label-openbd-hover: #1b6c8f; + --label-boxlang-bg: #04CD70; + --label-boxlang-hover: #08834C; + + /* Syntax */ + --syntax-highlight: #c7254e; + --code-text: #333333; + --code-inline-bg: #f5f5f5; + + /* Transitions */ + --transition-speed: 0.3s; +} + +/* ======================================== + Dark Mode (Respects prefers-color-scheme) + ======================================== */ +@media (prefers-color-scheme: dark) { + :root { + /* Primary Colors - VS Code Dark */ + --bg-primary: #1e1e1e; + --bg-secondary: #252526; + --text-primary: #d4d4d4; + --text-secondary: #a0a0a0; + --text-tertiary: #858585; + + /* Surfaces */ + --navbar-bg: #252526; + --navbar-border: #3e3e42; + --panel-bg: #2d2d30; + --code-bg: #1e1e1e; + + /* Borders & Dividers */ + --border-light: #3e3e42; + --border-medium: #3e3e42; + --border-dark: #5a5a5a; + + /* Interactive Elements */ + --accent-primary: #4fc1ff; + --accent-dark: #3fa7d6; + --accent-light: #5fcfff; + --hover-bg: #264f78; + + /* UI Components */ + --input-bg: #3c3c3c; + --input-border: #3e3e42; + --input-text: #d4d4d4; + --dropdown-bg: #2d2d30; + --dropdown-border: #3e3e42; + --dropdown-hover-bg: #264f78; + --dropdown-hover-text: #d4d4d4; + + /* Semantic Colors */ + --link-color: #4fc1ff; + --fork-me-bg: #3e3e42; + --fork-me-border: #555555; + --fork-me-text: #b0b0b0; + + /* Labels & Tags (adjusted for dark mode) */ + --label-acf-bg: #1a4d7a; + --label-acf-hover: #2d6ba3; + --label-lucee-bg: #2a5a66; + --label-lucee-hover: #3a7a85; + --label-openbd-bg: #2a5a8a; + --label-openbd-hover: #3a7aab; + --label-boxlang-bg: #1a5a3a; + --label-boxlang-hover: #2a7a50; + + /* Syntax */ + --syntax-highlight: #ff7f7f; + --code-text: #d4d4d4; + --code-inline-bg: #2b2b2b; + } +} + +/* ======================================== + Manual Theme Override via data-attribute + ======================================== */ +[data-theme="dark"] { + --bg-primary: #1e1e1e; + --bg-secondary: #252526; + --text-primary: #d4d4d4; + --text-secondary: #a0a0a0; + --text-tertiary: #858585; + --navbar-bg: #252526; + --navbar-border: #3e3e42; + --panel-bg: #2d2d30; + --code-bg: #1e1e1e; + --border-light: #3e3e42; + --border-medium: #3e3e42; + --border-dark: #5a5a5a; + --accent-primary: #4fc1ff; + --accent-dark: #3fa7d6; + --accent-light: #5fcfff; + --hover-bg: #264f78; + --input-bg: #3c3c3c; + --input-border: #3e3e42; + --input-text: #d4d4d4; + --dropdown-bg: #2d2d30; + --dropdown-border: #3e3e42; + --dropdown-hover-bg: #264f78; + --dropdown-hover-text: #d4d4d4; + --link-color: #4fc1ff; + --fork-me-bg: #3e3e42; + --fork-me-border: #555555; + --fork-me-text: #b0b0b0; + --label-acf-bg: #1a4d7a; + --label-acf-hover: #2d6ba3; + --label-lucee-bg: #2a5a66; + --label-lucee-hover: #3a7a85; + --label-openbd-bg: #2a5a8a; + --label-openbd-hover: #3a7aab; + --label-boxlang-bg: #1a5a3a; + --label-boxlang-hover: #2a7a50; + --syntax-highlight: #ff7f7f; + --code-text: #d4d4d4; +} + +[data-theme="light"] { + --bg-primary: #ffffff; + --bg-secondary: #f8f9fa; + --text-primary: #333333; + --text-secondary: #666666; + --text-tertiary: #999999; + --navbar-bg: #eeeeee; + --navbar-border: #cccccc; + --panel-bg: #f1f1f1; + --code-bg: #000000; + --border-light: #f1f1f1; + --border-medium: #cccccc; + --border-dark: #999999; + --accent-primary: #0088cc; + --accent-dark: #0077b3; + --accent-light: #0081c2; + --hover-bg: #0088cc; + --input-bg: #ffffff; + --input-border: #cccccc; + --input-text: #333333; + --dropdown-bg: #ffffff; + --dropdown-border: #cccccc; + --dropdown-hover-bg: #0088cc; + --dropdown-hover-text: #000000; + --link-color: #0088cc; + --fork-me-bg: #e3e3e3; + --fork-me-border: #c2c2c2; + --fork-me-text: #484848; + --label-acf-bg: #069; + --label-acf-hover: #246; + --label-lucee-bg: #449caf; + --label-lucee-hover: #01798a; + --label-openbd-bg: #2fa5d7; + --label-openbd-hover: #1b6c8f; + --label-boxlang-bg: #04CD70; + --label-boxlang-hover: #08834C; + --syntax-highlight: #c7254e; + --code-text: #333333; +} + +/* ======================================== + Smooth Transitions for Theme Switching + ======================================== */ +* { + transition: background-color var(--transition-speed) ease, + color var(--transition-speed) ease, + border-color var(--transition-speed) ease; +} + body { padding-top: 50px; padding-bottom: 20px; font-size: 16px; + background-color: var(--bg-primary); + color: var(--text-primary); } div.listing > h2 > a.btn { text-transform: none; } -nav.navbar { background-color:#eee; } -#docname,h4,.typewriter { font-family: Menlo,Monaco,Consolas,"Courier New",monospace; } -.param h4 { background-color: #f1f1f1; padding: 8px 8px; margin-bottom: 2px; } +nav.navbar { background-color: var(--navbar-bg); } +#docname,h4,.typewriter { font-family: Menlo,Monaco,Consolas,"Courier New",monospace; color: var(--text-primary) !important; } +.param h4 { background-color: var(--panel-bg); padding: 8px 8px; margin-bottom: 2px; color: var(--text-primary) !important; } .p-default { font-weight:normal; font-size: smaller; } -h2 { border-bottom:1px solid #f1f1f1; padding-bottom: 12px; } -h2 .item-name { font-weight:bold; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; } +h2 { border-bottom: 1px solid var(--border-light); padding-bottom: 12px; color: var(--text-primary) !important; } +h2 .item-name { font-weight:bold; font-family: Menlo,Monaco,Consolas,"Courier New",monospace; color: var(--text-primary) !important; } .p-name { font-weight:bold; font-size:larger;} .p-desc { padding: 8px; margin: 0 5%; } .listing a { margin-right: 15px; margin-bottom: 15px; font-size: larger } @@ -33,9 +250,9 @@ h2 .item-name { font-weight:bold; font-family: Menlo,Monaco,Consolas,"Courier Ne } #forkme::before { content: ""; - background-color: #e3e3e3; - border-top: 1.5px dashed #c2c2c2; - border-bottom: 1.5px dashed #c2c2c2; + background-color: var(--fork-me-bg); + border-top: 1.5px dashed var(--fork-me-border); + border-bottom: 1.5px dashed var(--fork-me-border); pointer-events: auto; display: block; position: absolute; @@ -48,7 +265,7 @@ h2 .item-name { font-weight:bold; font-family: Menlo,Monaco,Consolas,"Courier Ne } #forkme::after { content: "Fork me on GitHub"; - color: #484848; + color: var(--fork-me-text); text-decoration: none; text-align: center; text-indent: 0; @@ -75,19 +292,20 @@ h2 .item-name { font-weight:bold; font-family: Menlo,Monaco,Consolas,"Courier Ne #foundeo { margin-left: 100px; -} -#foundeo img { - width: 70px; height:20px; + position: absolute; + top: 15px; + right: 14px; + transition: all 2.5s ease-in-out; } -#foundeo { - transition: all 2.5s ease-in-out; +#foundeo img { + width: 70px; + height: 20px; } -#foundeo { - position: absolute; - top: 15px; - right: 14px; +html[data-theme="dark"] #foundeo img, +:root:not([data-theme]) #foundeo img { + filter: invert(1) brightness(1.2); } nav.navbar.navbar-mini { min-height: 40px; @@ -122,7 +340,7 @@ nav.navbar.navbar-mini .navbar-header { nav.navbar.navbar-mini .navbar-brand { padding: 0px 10px 0px 7px; height: 24px; - border-right: 1px solid #ccc; + border-right: 1px solid var(--border-medium); margin: 13px 0 0; } nav.navbar.navbar-mini .navbar-nav>li { @@ -134,17 +352,17 @@ nav.navbar.navbar-mini .navbar-nav>li { } .headinglink { - color: #333333; + color: var(--text-primary); position: relative; } .headinglink:hover { - color: #333333; + color: var(--text-primary); text-decoration: none; } .headinglink:hover::before { content: "\e144"; font-family: "Glyphicons Halflings"; - color: #333333; + color: var(--text-primary); width: 0; position: absolute; left: -20px; @@ -156,8 +374,8 @@ nav.navbar.navbar-mini .navbar-nav>li { min-width: 160px; margin-top: 2px; padding: 5px 0; - background-color: #fff; - border: 1px solid #ccc; + background-color: var(--dropdown-bg); + border: 1px solid var(--border-medium); border: 1px solid rgba(0,0,0,.2); *border-right-width: 2px; *border-bottom-width: 2px; @@ -178,14 +396,14 @@ nav.navbar.navbar-mini .navbar-nav>li { } .tt-suggestion:hover, .tt-cursor { - color: #ccc; + color: var(--dropdown-hover-text); cursor: pointer; - background-color: #0081c2; - background-image: -moz-linear-gradient(top, #0088cc, #0077b3); - background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0077b3)); - background-image: -webkit-linear-gradient(top, #0088cc, #0077b3); - background-image: -o-linear-gradient(top, #0088cc, #0077b3); - background-image: linear-gradient(to bottom, #0088cc, #0077b3); + background-color: var(--accent-light); + background-image: -moz-linear-gradient(top, var(--accent-primary), var(--accent-dark)); + background-image: -webkit-gradient(linear, 0 0, 0 100%, from(var(--accent-primary)), to(var(--accent-dark))); + background-image: -webkit-linear-gradient(top, var(--accent-primary), var(--accent-dark)); + background-image: -o-linear-gradient(top, var(--accent-primary), var(--accent-dark)); + background-image: linear-gradient(to bottom, var(--accent-primary), var(--accent-dark)); background-repeat: repeat-x; filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0077b3', GradientType=0) } @@ -193,7 +411,7 @@ nav.navbar.navbar-mini .navbar-nav>li { .twitter-typeahead .tt-hint { display: block; border: 1px solid transparent; - color:#ccc; + color: var(--text-secondary); } .breadcrumb li.pull-right:before { content: " "; } .breadcrumb li.divider:before { content: "|"; padding-right:0px; padding-left:8px; } @@ -202,42 +420,73 @@ footer { text-align: center; margin-top: 10px; font-size:smaller; } .navbar-brand { font-weight:bold; } .label-acf { - background-color: #069; + background-color: var(--label-acf-bg); } .label-acf:hover, .label-acf:focus { - background-color: #246; + background-color: var(--label-acf-hover); } .label-lucee { - background-color: #449caf; + background-color: var(--label-lucee-bg); } .label-lucee:hover, .label-lucee:focus { - background-color: #01798a; + background-color: var(--label-lucee-hover); } .label-openbd { - background-color: #2fa5d7; + background-color: var(--label-openbd-bg); } .label-openbd:hover, .label-openbd:focus { - background-color: #1b6c8f; + background-color: var(--label-openbd-hover); } .label-boxlang { - background-color: #04CD70; + background-color: var(--label-boxlang-bg); } .label-boxlang:hover, .label-boxlang:focus { - background-color: #08834C; + background-color: var(--label-boxlang-hover); } .syntax-highlight { - color: #c7254e; - font-weight:bold; + color: var(--syntax-highlight); + font-weight: bold; } .alert-warning a { color: #fff; } /* for use in page anchor - moves -100px to allow space for toolbar */ +/* Force headings color to theme */ +h1,h2,h3,h4,h5,h6 { + color: var(--text-primary) !important; +} + +/* Panel heading H4s */ +.panel-heading h4, +.panel .panel-heading h4 { + color: var(--text-primary) !important; +} + +/* Inline code styling: apply only in dark mode (or when data-theme=dark). + Exclude code inside
 so block code styling remains controlled by pre.prettyprint. */
+[data-theme="dark"] :not(pre) > code {
+  background-color: var(--code-inline-bg) !important;
+  color: var(--syntax-highlight) !important;
+  padding: 0 0.25em;
+  border-radius: 3px;
+  font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme]) :not(pre) > code {
+    background-color: var(--code-inline-bg) !important;
+    color: var(--syntax-highlight) !important;
+    padding: 0 0.25em;
+    border-radius: 3px;
+    font-family: Menlo,Monaco,Consolas,"Courier New",monospace;
+  }
+}
+
 .page-anchor {
   position:absolute;
   margin-top:-80px;
@@ -250,16 +499,80 @@ p.clearfix .page-anchor {
 
 iframe { border:0px; }
 
-.contributor { background-color: #f1f1f1; border-radius: 7px; margin: 5px 0; padding:15px 10px; }
+.contributor { background-color: var(--panel-bg); border-radius: 7px; margin: 5px 0; padding: 15px 10px; }
 .contributor img { width: 75px; height: 75px; display: block; margin: 0 auto; }
 
 pre.prettyprint {
   padding: 5px 8px !important;
-  border: 1px solid #CCC !important;
+  border: 1px solid var(--border-medium) !important;
+  background-color: var(--code-bg) !important;
+  color: var(--code-text) !important;
   font-size: 14px;
   line-height: 21px;
 }
 
+/* Light-mode override: use a black background for prettify code examples */
+html:not([data-theme]) pre.prettyprint,
+html[data-theme="light"] pre.prettyprint {
+  background-color: #000 !important;
+  border-color: #333 !important;
+}
+
+/* Dark-mode prettify blocks: remove inline code and span backgrounds inside the block */
+html[data-theme="dark"] pre.prettyprint code,
+:root:not([data-theme]) pre.prettyprint code {
+  background-color: transparent !important;
+  background-image: none !important;
+  background: transparent !important;
+}
+
+html[data-theme="dark"] pre.prettyprint span,
+html[data-theme="dark"] pre.prettyprint code span,
+:root:not([data-theme]) pre.prettyprint span,
+:root:not([data-theme]) pre.prettyprint code span {
+  background-color: transparent !important;
+  background-image: none !important;
+  background: transparent !important;
+}
+
+/* Bootstrap striped table override for dark mode */
+[data-theme="dark"] .table-striped>tbody>tr:nth-of-type(odd) {
+  background-color: var(--bg-secondary);
+}
+
+/* Bootstrap bordered table override for dark mode */
+[data-theme="dark"] .table-bordered {
+  border: 1px solid var(--border-medium);
+}
+
+[data-theme="dark"] .table-bordered>tbody>tr>td,
+[data-theme="dark"] .table-bordered>tbody>tr>th,
+[data-theme="dark"] .table-bordered>thead>tr>td,
+[data-theme="dark"] .table-bordered>thead>tr>th,
+[data-theme="dark"] .table-bordered>tfoot>tr>td,
+[data-theme="dark"] .table-bordered>tfoot>tr>th {
+  border: 1px solid var(--border-medium);
+}
+
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme]) .table-striped>tbody>tr:nth-of-type(odd) {
+    background-color: var(--bg-secondary);
+  }
+
+  :root:not([data-theme]) .table-bordered {
+    border: 1px solid var(--border-medium);
+  }
+
+  :root:not([data-theme]) .table-bordered>tbody>tr>td,
+  :root:not([data-theme]) .table-bordered>tbody>tr>th,
+  :root:not([data-theme]) .table-bordered>thead>tr>td,
+  :root:not([data-theme]) .table-bordered>thead>tr>th,
+  :root:not([data-theme]) .table-bordered>tfoot>tr>td,
+  :root:not([data-theme]) .table-bordered>tfoot>tr>th {
+    border: 1px solid var(--border-medium);
+  }
+}
+
 .alert-danger h4 { line-height: 1.5; }
 
 #search { margin-right:100px; }
@@ -296,3 +609,210 @@ nav.navbar ul.dropdown-menu {
   max-height: 80vh;
   overflow-y: auto;
 }
+
+/* ========================================
+   Theme Toggle Button Styling
+   ======================================== */
+.theme-toggle {
+  position: absolute;
+  top: 15px;
+  right: 110px;
+  cursor: pointer;
+  z-index: 1000;
+  user-select: none;
+}
+
+.theme-toggle svg {
+  width: 20px;
+  height: 20px;
+  stroke: var(--text-primary);
+  fill: none;
+  stroke-width: 2;
+  stroke-linecap: round;
+  stroke-linejoin: round;
+  transition: opacity var(--transition-speed) ease, transform var(--transition-speed) ease;
+}
+
+.theme-toggle svg:hover {
+  opacity: 0.8;
+  transform: scale(1.1);
+}
+
+.theme-toggle-menu {
+  position: fixed;
+  z-index: 2000;
+  min-width: 200px;
+  background-color: var(--bg-primary);
+  border: 1px solid var(--border-medium);
+  border-radius: 5px;
+  box-shadow: 0 8px 16px rgba(0, 0, 0, 0.15);
+  overflow: hidden;
+}
+
+.theme-toggle-menu button {
+  width: 100%;
+  padding: 10px 14px;
+  border: none;
+  background: transparent;
+  color: var(--text-primary);
+  text-align: left;
+  font: inherit;
+  cursor: pointer;
+}
+
+.theme-toggle-menu button:hover,
+.theme-toggle-menu button:focus {
+  background-color: rgba(0, 0, 0, 0.05);
+}
+
+/* By default show moon, hide sun (icon indicates the mode that will be activated when clicked)
+   - Light mode (default): show moon (click => activate dark)
+   - Dark mode: show sun (click => activate light) */
+#svg-sun {
+  display: none;
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+#svg-moon {
+  display: block;
+  position: absolute;
+  top: 0;
+  right: 0;
+}
+
+/* When dark mode is active, show sun and hide moon */
+[data-theme="dark"] #svg-sun {
+  display: block;
+}
+
+[data-theme="dark"] #svg-moon {
+  display: none;
+}
+
+/* OS dark mode preference (only affects when data-theme not set) */
+@media (prefers-color-scheme: dark) {
+  :root:not([data-theme]) #svg-sun {
+    display: block;
+  }
+
+  :root:not([data-theme]) #svg-moon {
+    display: none;
+  }
+}
+
+/* ========================================
+   Component overrides for dark mode
+   Ensure header search, jumbotron, breadcrumb and panels use variables
+   ======================================== */
+/* Header search input */
+#search .form-control,
+.navbar-form .form-control {
+  background-color: var(--input-bg);
+  border: 1px solid var(--input-border);
+  color: var(--input-text);
+}
+
+/* Jumbotron */
+.jumbotron,
+.jumbotron#cfbreak {
+  background-color: var(--bg-secondary) !important;
+  color: var(--text-primary) !important;
+  border: 1px solid var(--border-light) !important;
+}
+
+/* Breadcrumb container */
+.breadcrumb {
+  background-color: var(--panel-bg) !important;
+  color: var(--text-secondary) !important;
+  border: 1px solid var(--border-medium) !important;
+  border-radius: 3px;
+  padding: 6px 12px;
+}
+
+/* Panels */
+.panel,
+.panel-default,
+.panel .panel-body,
+.panel-collapse {
+  background-color: var(--panel-bg) !important;
+  color: var(--text-primary) !important;
+  border-color: var(--border-medium) !important;
+}
+
+.panel-default > .panel-heading {
+  background-color: var(--panel-bg) !important;
+  color: var(--text-primary) !important;
+  border-bottom: 1px solid var(--border-medium) !important;
+}
+
+/* Ensure dropdowns and other surfaces inherit dropdown variables */
+.dropdown-menu,
+.navbar .dropdown-menu {
+  background-color: var(--dropdown-bg);
+  border-color: var(--dropdown-border);
+  color: var(--text-primary);
+}
+
+/* Use theme variables for hover/focus on nav and dropdown items so hover colors follow the active theme */
+.navbar-nav > li > a:hover,
+.navbar-nav > li > a:focus,
+.navbar .dropdown-menu > li > a:hover,
+.navbar .dropdown-menu > li > a:focus,
+.dropdown-menu > li > a:hover,
+.dropdown-menu > li > a:focus {
+  color: var(--dropdown-hover-text) !important;
+}
+
+/* Ensure open/active dropdown anchors use hover colors too */
+.navbar-nav > .open > a,
+.navbar-nav > .open > a:hover,
+.navbar-nav > .open > a:focus {
+  color: var(--dropdown-hover-text) !important;
+}
+
+/* Navbar border & surfaces */
+.navbar,
+.navbar-default,
+.navbar-fixed-top {
+  border-color: var(--navbar-border) !important;
+  background-color: var(--navbar-bg) !important;
+}
+
+/* Search and form inputs (header, jumbotron, footer) */
+.navbar-form .form-control,
+.jumbotron .form-control,
+.newsletter .form-control,
+footer .form-control,
+.container .form-control {
+  background-color: var(--input-bg) !important;
+  border: 1px solid var(--input-border) !important;
+  color: var(--input-text) !important;
+}
+
+/* Footer button (Get It) */
+.btn-secondary,
+.btn.btn-secondary {
+  background-color: var(--accent-primary) !important;
+  border-color: var(--accent-dark) !important;
+  color: #fff !important;
+}
+.btn-secondary:hover,
+.btn.btn-secondary:hover {
+  background-color: var(--accent-dark) !important;
+}
+
+/* Copy code / code block buttons */
+.prettyprint .btn,
+pre.prettyprint + .btn,
+.code-toolbar .btn,
+.copy-btn,
+.copy-code,
+.btn-copy,
+.example-btn,
+.btn-default {
+  background-color: var(--panel-bg) !important;
+  border-color: var(--border-medium) !important;
+  color: var(--text-primary) !important;
+}
\ No newline at end of file
diff --git a/assets/theme-switcher.js b/assets/theme-switcher.js
new file mode 100644
index 000000000..21a1ec09e
--- /dev/null
+++ b/assets/theme-switcher.js
@@ -0,0 +1,183 @@
+/**
+ * CFDocs Theme Switcher - Handles light/dark mode switching with localStorage persistence and OS preference detection
+ */
+
+(function() {
+  'use strict';
+
+  const LIGHT_THEME = 'light';
+  const DARK_THEME = 'dark';
+  const THEME_STORAGE_KEY = 'cfdocs-theme';
+  const CONTEXT_MENU_ID = 'theme-toggle-reset-menu';
+  const HTML_ELEMENT = document.documentElement;
+
+  /**
+   * Get the user's preferred theme
+   * Priority: localStorage > OS preference > default to light
+   */
+  function getPreferredTheme() {
+    // Check localStorage first
+    const storedTheme = localStorage.getItem(THEME_STORAGE_KEY);
+    if (storedTheme === LIGHT_THEME || storedTheme === DARK_THEME) {
+      return storedTheme;
+    }
+
+    // Check OS preference
+    if (window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches) {
+      return DARK_THEME;
+    }
+
+    // Default to light theme
+    return LIGHT_THEME;
+  }
+
+  /**
+   * Apply theme by setting data-theme attribute on HTML element
+   */
+  function applyTheme(theme,useLocalStorage = true) {
+    if (theme === DARK_THEME || theme === LIGHT_THEME) {
+      HTML_ELEMENT.setAttribute('data-theme', theme);
+      if (useLocalStorage == true) {
+        localStorage.setItem(THEME_STORAGE_KEY, theme);
+      }
+      updateToggleButton(theme);
+    }
+  }
+
+  /**
+   * Toggle between light and dark themes
+   */
+  function toggleTheme() {
+    const currentTheme = HTML_ELEMENT.getAttribute('data-theme') || getPreferredTheme();
+    const newTheme = currentTheme === LIGHT_THEME ? DARK_THEME : LIGHT_THEME;
+    applyTheme(newTheme);
+  }
+
+  /**
+   * Update the toggle button visual state (if needed for additional UI feedback)
+   */
+  function updateToggleButton(theme) {
+    // The CSS handles the icon visibility through opacity
+    // This function is here for future extensibility
+    const toggleButton = document.getElementById('theme-toggle');
+    if (toggleButton) {
+      toggleButton.setAttribute('data-current-theme', theme);
+    }
+  }
+
+  function buildContextMenu() {
+    let menu = document.getElementById(CONTEXT_MENU_ID);
+    if (menu) {
+      return menu;
+    }
+
+    menu = document.createElement('div');
+    menu.id = CONTEXT_MENU_ID;
+    menu.className = 'theme-toggle-menu';
+    menu.innerHTML = '';
+    menu.style.display = 'none';
+
+    const button = menu.querySelector('button');
+    button.addEventListener('click', function() {
+      localStorage.removeItem(THEME_STORAGE_KEY);
+      applyTheme(getPreferredTheme(), false);
+      hideContextMenu();
+    });
+
+    menu.addEventListener('contextmenu', function(event) {
+      event.preventDefault();
+    });
+
+    document.body.appendChild(menu);
+    return menu;
+  }
+
+  function showContextMenu(x, y) {
+    const menu = buildContextMenu();
+    menu.style.display = 'block';
+    menu.style.visibility = 'hidden';
+    menu.style.left = '0px';
+    menu.style.top = '0px';
+
+    const menuWidth = menu.offsetWidth;
+    const menuHeight = menu.offsetHeight;
+    const maxLeft = Math.max(window.innerWidth - menuWidth - 10, 10);
+    const maxTop = Math.max(window.innerHeight - menuHeight - 10, 10);
+
+    menu.style.left = `${Math.min(x, maxLeft)}px`;
+    menu.style.top = `${Math.min(y, maxTop)}px`;
+    menu.style.visibility = 'visible';
+  }
+
+  function hideContextMenu() {
+    const menu = document.getElementById(CONTEXT_MENU_ID);
+    if (menu) {
+      menu.style.display = 'none';
+    }
+  }
+
+  /**
+   * Initialize theme switcher
+   */
+  function init() {
+    // Apply initial theme
+    const initialTheme = getPreferredTheme();
+    applyTheme(initialTheme,false); // Don't update localStorage on initial load since we're just applying the preferred theme
+
+    // Add click handler to theme toggle button
+    const toggleButton = document.getElementById('theme-toggle');
+    if (toggleButton) {
+      toggleButton.addEventListener('click', toggleTheme);
+      toggleButton.addEventListener('keydown', function(e) {
+        if (e.key === 'Enter' || e.key === ' ') {
+          e.preventDefault();
+          toggleTheme();
+        }
+      });
+      toggleButton.addEventListener('contextmenu', function(e) {
+        e.preventDefault();
+        e.stopPropagation();
+        showContextMenu(e.clientX, e.clientY);
+      });
+
+      document.addEventListener('click', function(e) {
+        const menu = document.getElementById(CONTEXT_MENU_ID);
+        if (menu && !menu.contains(e.target) && !toggleButton.contains(e.target)) {
+          hideContextMenu();
+        }
+      });
+
+      document.addEventListener('keydown', function(e) {
+        if (e.key === 'Escape') {
+          hideContextMenu();
+        }
+      });
+
+      // Make toggle button keyboard accessible
+      toggleButton.setAttribute('role', 'button');
+      toggleButton.setAttribute('tabindex', '0');
+      toggleButton.setAttribute('aria-label', 'Toggle dark/light mode');
+    }
+
+    // Listen for OS theme changes (only if localStorage preference not set)
+    if (window.matchMedia) {
+      window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', function(e) {
+        // Only apply OS change if user hasn't set a preference in localStorage
+        if (!localStorage.getItem(THEME_STORAGE_KEY)) {
+          const newTheme = e.matches ? DARK_THEME : LIGHT_THEME;
+          applyTheme(newTheme,false); // Don't update localStorage since this is an OS change
+        }
+      });
+    }
+  }
+
+  // Initialize when DOM is ready
+  if (document.readyState === 'loading') {
+    document.addEventListener('DOMContentLoaded', init);
+  } else {
+    init();
+  }
+
+  // Expose toggle function globally for testing/debugging
+  window.toggleTheme = toggleTheme;
+})();
diff --git a/views/layout.cfm b/views/layout.cfm
index 786547687..4dd97eb3c 100644
--- a/views/layout.cfm
+++ b/views/layout.cfm
@@ -15,6 +15,7 @@
 	
 	
 	
+	
 	
 		
 		
@@ -115,6 +116,23 @@
 					
 				
 				
+				
+				
+ + + + + + + + + + + + + + +