Skip to content

Commit d834bae

Browse files
johnsideserfclaude
andcommitted
Add dark mode toggle with light as default
Sun/moon toggle button in menu bar switches between light mIRC theme and dark irssi-style theme. Preference persists in localStorage. Dark mode uses desaturated IRC nick colors, dark beveled borders, and muted blue gradients on code block and table headers. JS injected via mdBook additional-js config. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 23a2cbc commit d834bae

File tree

3 files changed

+356
-0
lines changed

3 files changed

+356
-0
lines changed

docs/book.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ build-dir = "book"
1212
default-theme = "light"
1313
preferred-dark-theme = "light"
1414
additional-css = ["theme/css/irc-theme.css"]
15+
additional-js = ["theme/js/dark-toggle.js"]
1516
site-url = "/signal-tui/"
1617
git-repository-url = "https://github.com/johnsideserf/signal-tui"
1718
edit-url-template = "https://github.com/johnsideserf/signal-tui/edit/master/docs/{path}"

docs/theme/css/irc-theme.css

Lines changed: 305 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -527,11 +527,316 @@ body {
527527
}
528528
}
529529

530+
/* ═══════════════════════════════════════════════════════════
531+
* DARK MODE TOGGLE BUTTON
532+
* ═══════════════════════════════════════════════════════════ */
533+
.irc-dark-toggle {
534+
cursor: pointer;
535+
line-height: var(--menu-bar-height);
536+
padding: 0 8px;
537+
}
538+
539+
.irc-dark-toggle .toggle-icon {
540+
font-size: 1.2em;
541+
}
542+
543+
/* ═══════════════════════════════════════════════════════════
544+
* DARK MODE — irssi / terminal IRC aesthetic
545+
* Activated via data-irc-mode="dark" on <html>
546+
* ═══════════════════════════════════════════════════════════ */
547+
html[data-irc-mode="dark"] {
548+
--irc-bg: #1a1a1a;
549+
--irc-bg-alt: #222222;
550+
--irc-sidebar-bg: #141414;
551+
--irc-code-bg: #111111;
552+
--irc-panel-bg: #2a2a2a;
553+
554+
--irc-text: #cccccc;
555+
--irc-text-dim: #888888;
556+
--irc-text-light: #666666;
557+
558+
--irc-blue: #5599ff;
559+
--irc-green: #44bb44;
560+
--irc-red: #ff5555;
561+
--irc-brown: #cc9944;
562+
--irc-purple: #bb77dd;
563+
--irc-teal: #44bbbb;
564+
--irc-navy: #6688cc;
565+
--irc-magenta: #ee66cc;
566+
567+
--bevel-light: #3a3a3a;
568+
--bevel-dark: #0a0a0a;
569+
--bevel-shadow: #000000;
570+
--border-mid: #333333;
571+
572+
--link-blue: #6699ff;
573+
--link-visited: #aa88cc;
574+
}
575+
576+
/* override mdBook theme vars in dark mode */
577+
html[data-irc-mode="dark"] .coal,
578+
html[data-irc-mode="dark"] .light,
579+
html[data-irc-mode="dark"] .ayu,
580+
html[data-irc-mode="dark"] .navy,
581+
html[data-irc-mode="dark"] .rust,
582+
html[data-irc-mode="dark"]:not(.js) {
583+
--bg: var(--irc-bg);
584+
--fg: var(--irc-text);
585+
--sidebar-bg: var(--irc-sidebar-bg);
586+
--sidebar-fg: var(--irc-text-dim);
587+
--sidebar-active: var(--irc-blue);
588+
--sidebar-non-existant: var(--irc-text-light);
589+
--sidebar-spacer: var(--border-mid);
590+
--links: var(--link-blue);
591+
--inline-code-color: var(--irc-teal);
592+
--theme-popup-bg: var(--irc-bg);
593+
--theme-popup-border: var(--border-mid);
594+
--theme-hover: var(--irc-bg-alt);
595+
--icons: var(--irc-text-dim);
596+
--icons-hover: var(--irc-text);
597+
--scrollbar: var(--border-mid);
598+
--quote-bg: var(--irc-bg-alt);
599+
--quote-border: var(--border-mid);
600+
--table-border-color: var(--border-mid);
601+
--table-header-bg: var(--irc-panel-bg);
602+
--table-alternate-bg: #1f1f1f;
603+
--searchbar-border-color: var(--border-mid);
604+
--searchbar-bg: #111111;
605+
--searchbar-fg: var(--irc-text);
606+
--searchbar-shadow-color: #000;
607+
--searchresults-header-fg: var(--irc-text-dim);
608+
--searchresults-border-color: var(--border-mid);
609+
--searchresults-li-bg: var(--irc-bg-alt);
610+
--search-mark-bg: #555500;
611+
--warning-border: #ff8e00;
612+
--color-scheme: dark;
613+
--sidebar-header-border-color: var(--irc-blue);
614+
615+
--copy-button-filter: invert(55%);
616+
--copy-button-filter-hover: invert(75%);
617+
--overlay-bg: rgba(0, 0, 0, 0.5);
618+
}
619+
620+
/* ── dark: page background ─────────────────────────────── */
621+
html[data-irc-mode="dark"] {
622+
background-color: var(--irc-bg) !important;
623+
}
624+
625+
html[data-irc-mode="dark"] body {
626+
color: var(--irc-text);
627+
}
628+
629+
/* ── dark: menu bar ────────────────────────────────────── */
630+
html[data-irc-mode="dark"] #mdbook-menu-bar,
631+
html[data-irc-mode="dark"] #mdbook-menu-bar.bordered {
632+
background-color: var(--irc-panel-bg) !important;
633+
border-bottom-color: var(--bevel-dark) !important;
634+
border-top-color: var(--bevel-light) !important;
635+
}
636+
637+
html[data-irc-mode="dark"] .menu-bar h1.menu-title {
638+
color: var(--irc-navy) !important;
639+
}
640+
641+
html[data-irc-mode="dark"] .menu-bar .icon-button {
642+
color: var(--irc-text-dim);
643+
}
644+
645+
html[data-irc-mode="dark"] .menu-bar .icon-button:hover {
646+
color: var(--irc-text);
647+
}
648+
649+
/* ── dark: sidebar ─────────────────────────────────────── */
650+
html[data-irc-mode="dark"] .sidebar {
651+
background-color: var(--irc-sidebar-bg) !important;
652+
border-right-color: var(--bevel-dark);
653+
}
654+
655+
html[data-irc-mode="dark"] .sidebar .sidebar-scrollbox {
656+
background-color: var(--irc-sidebar-bg) !important;
657+
}
658+
659+
html[data-irc-mode="dark"] .sidebar .chapter li.part-title {
660+
color: var(--irc-navy);
661+
background-color: var(--irc-panel-bg);
662+
border-color: var(--border-mid);
663+
}
664+
665+
html[data-irc-mode="dark"] .sidebar .chapter a {
666+
color: var(--irc-text-dim);
667+
}
668+
669+
html[data-irc-mode="dark"] .sidebar .chapter a:hover {
670+
color: var(--irc-blue);
671+
background-color: var(--irc-bg-alt);
672+
}
673+
674+
html[data-irc-mode="dark"] .sidebar .chapter li.chapter-item.expanded > a,
675+
html[data-irc-mode="dark"] .sidebar .chapter a.active {
676+
color: #ffffff;
677+
background-color: var(--irc-blue);
678+
}
679+
680+
/* ── dark: headings ────────────────────────────────────── */
681+
html[data-irc-mode="dark"] .content h1 {
682+
color: var(--irc-navy);
683+
border-bottom-color: var(--irc-navy);
684+
}
685+
686+
html[data-irc-mode="dark"] .content h2 {
687+
color: var(--irc-green);
688+
border-bottom-color: var(--border-mid);
689+
}
690+
691+
html[data-irc-mode="dark"] .content h3 {
692+
color: var(--irc-brown);
693+
}
694+
695+
html[data-irc-mode="dark"] .content h4 {
696+
color: var(--irc-purple);
697+
}
698+
699+
/* ── dark: code ────────────────────────────────────────── */
700+
html[data-irc-mode="dark"] .content code {
701+
color: var(--irc-teal);
702+
background-color: var(--irc-code-bg);
703+
border-color: var(--border-mid);
704+
}
705+
706+
html[data-irc-mode="dark"] .content pre {
707+
background-color: #0d0d0d !important;
708+
border-color: var(--bevel-dark) var(--bevel-light) var(--bevel-light) var(--bevel-dark);
709+
}
710+
711+
html[data-irc-mode="dark"] .content pre::before {
712+
background: linear-gradient(90deg, #2a2a5a, #3a3a7a);
713+
}
714+
715+
html[data-irc-mode="dark"] .content pre > code {
716+
color: var(--irc-text);
717+
}
718+
719+
/* ── dark: tables ──────────────────────────────────────── */
720+
html[data-irc-mode="dark"] .content table {
721+
border-color: var(--bevel-dark) var(--bevel-light) var(--bevel-light) var(--bevel-dark);
722+
}
723+
724+
html[data-irc-mode="dark"] .content table th {
725+
background: linear-gradient(90deg, #2a2a5a, #3a3a7a);
726+
border-color: var(--bevel-dark);
727+
}
728+
729+
html[data-irc-mode="dark"] .content table td {
730+
border-color: var(--border-mid);
731+
}
732+
733+
html[data-irc-mode="dark"] .content table tr:nth-child(even) {
734+
background-color: #1f1f1f;
735+
}
736+
737+
html[data-irc-mode="dark"] .content table tr:hover {
738+
background-color: #252540;
739+
}
740+
741+
/* ── dark: links ───────────────────────────────────────── */
742+
html[data-irc-mode="dark"] .content a {
743+
color: var(--link-blue);
744+
}
745+
746+
html[data-irc-mode="dark"] .content a:visited {
747+
color: var(--link-visited);
748+
}
749+
750+
html[data-irc-mode="dark"] .content a:hover {
751+
color: var(--irc-red);
752+
}
753+
754+
/* ── dark: blockquotes ─────────────────────────────────── */
755+
html[data-irc-mode="dark"] .content blockquote {
756+
background-color: var(--irc-bg-alt);
757+
border-left-color: var(--irc-green);
758+
color: var(--irc-text-dim);
759+
}
760+
761+
/* ── dark: strong ──────────────────────────────────────── */
762+
html[data-irc-mode="dark"] .content strong {
763+
color: var(--irc-text);
764+
}
765+
766+
/* ── dark: search ──────────────────────────────────────── */
767+
html[data-irc-mode="dark"] #searchbar {
768+
background-color: #111111 !important;
769+
border-color: var(--bevel-dark) var(--bevel-light) var(--bevel-light) var(--bevel-dark) !important;
770+
color: var(--irc-text) !important;
771+
}
772+
773+
/* ── dark: hr ──────────────────────────────────────────── */
774+
html[data-irc-mode="dark"] .content hr {
775+
border-top-color: var(--bevel-dark);
776+
border-bottom-color: var(--bevel-light);
777+
}
778+
779+
/* ── dark: help popup ──────────────────────────────────── */
780+
html[data-irc-mode="dark"] #mdbook-help-popup {
781+
background-color: var(--irc-bg) !important;
782+
border-color: var(--bevel-light) var(--bevel-dark) var(--bevel-dark) var(--bevel-light) !important;
783+
}
784+
785+
html[data-irc-mode="dark"] #mdbook-help-popup h2 {
786+
color: var(--irc-navy);
787+
}
788+
789+
html[data-irc-mode="dark"] #mdbook-help-popup kbd {
790+
background-color: var(--irc-panel-bg);
791+
border-color: var(--bevel-light) var(--bevel-dark) var(--bevel-dark) var(--bevel-light);
792+
color: var(--irc-text);
793+
}
794+
795+
/* ── dark: warnings ────────────────────────────────────── */
796+
html[data-irc-mode="dark"] .content .warning {
797+
background-color: #2a1515;
798+
}
799+
800+
/* ── dark: selection ───────────────────────────────────── */
801+
html[data-irc-mode="dark"] ::selection {
802+
background-color: var(--irc-blue);
803+
color: #ffffff;
804+
}
805+
806+
/* ── dark: scrollbar ───────────────────────────────────── */
807+
html[data-irc-mode="dark"] ::-webkit-scrollbar-track {
808+
background: #111111;
809+
border-color: var(--border-mid);
810+
}
811+
812+
html[data-irc-mode="dark"] ::-webkit-scrollbar-thumb {
813+
background: var(--irc-panel-bg);
814+
border-color: var(--bevel-light) var(--bevel-dark) var(--bevel-dark) var(--bevel-light);
815+
}
816+
817+
html[data-irc-mode="dark"] ::-webkit-scrollbar-thumb:hover {
818+
background: #3a3a3a;
819+
}
820+
821+
html[data-irc-mode="dark"] ::-webkit-scrollbar-button {
822+
background: var(--irc-panel-bg);
823+
border-color: var(--bevel-light) var(--bevel-dark) var(--bevel-dark) var(--bevel-light);
824+
}
825+
826+
/* ── dark: status bar ──────────────────────────────────── */
827+
html[data-irc-mode="dark"] #mdbook-content.content main {
828+
border-bottom-color: var(--bevel-dark);
829+
}
830+
530831
/* ═══════════════════════════════════════════════════════════
531832
* PRINT — strip decorative elements
532833
* ═══════════════════════════════════════════════════════════ */
533834
@media print {
534835
.content pre::before {
535836
display: none !important;
536837
}
838+
839+
.irc-dark-toggle {
840+
display: none !important;
841+
}
537842
}

docs/theme/js/dark-toggle.js

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
// Dark mode toggle for signal-tui docs
2+
// Injects a sun/moon toggle into the menu bar and persists preference.
3+
(function () {
4+
'use strict';
5+
6+
var STORAGE_KEY = 'irc-dark-mode';
7+
8+
function isDark() {
9+
try {
10+
return localStorage.getItem(STORAGE_KEY) === 'true';
11+
} catch (e) {
12+
return false;
13+
}
14+
}
15+
16+
function applyMode(dark) {
17+
document.documentElement.setAttribute('data-irc-mode', dark ? 'dark' : 'light');
18+
try {
19+
localStorage.setItem(STORAGE_KEY, dark ? 'true' : 'false');
20+
} catch (e) { /* ignore */ }
21+
}
22+
23+
// Apply immediately (before paint) to prevent flash
24+
applyMode(isDark());
25+
26+
document.addEventListener('DOMContentLoaded', function () {
27+
var btn = document.createElement('button');
28+
btn.className = 'icon-button irc-dark-toggle';
29+
btn.type = 'button';
30+
btn.title = 'Toggle dark mode';
31+
btn.setAttribute('aria-label', 'Toggle dark mode');
32+
btn.innerHTML = isDark()
33+
? '<span class="toggle-icon">&#9788;</span>' // sun
34+
: '<span class="toggle-icon">&#9790;</span>'; // moon
35+
36+
btn.addEventListener('click', function () {
37+
var dark = !isDark();
38+
applyMode(dark);
39+
btn.innerHTML = dark
40+
? '<span class="toggle-icon">&#9788;</span>'
41+
: '<span class="toggle-icon">&#9790;</span>';
42+
});
43+
44+
// Insert into the left-buttons area, after the sidebar toggle
45+
var leftButtons = document.querySelector('.left-buttons');
46+
if (leftButtons) {
47+
leftButtons.appendChild(btn);
48+
}
49+
});
50+
})();

0 commit comments

Comments
 (0)