Skip to content

Commit 48690e4

Browse files
Copilotulysses4ever
andcommitted
Add dark mode support with toggle button
Co-authored-by: ulysses4ever <[email protected]>
1 parent a3185b0 commit 48690e4

5 files changed

Lines changed: 351 additions & 0 deletions

File tree

docs/assets/css/dark-mode.css

Lines changed: 195 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,195 @@
1+
/* Dark Mode Styles for Learn You a Haskell */
2+
3+
/* Dark mode is activated by adding 'dark-mode' class to body */
4+
body.dark-mode {
5+
background-color: #1a1a1a;
6+
color: #e0e0e0;
7+
}
8+
9+
body.dark-mode.introcontent {
10+
/* Add a semi-transparent dark overlay on top of bg.png */
11+
background-image: linear-gradient(rgba(26, 26, 26, 0.75), rgba(26, 26, 26, 0.75)), url(https://s3.amazonaws.com/lyah/bg.png);
12+
background-color: #1a1a1a;
13+
}
14+
15+
body.dark-mode .bgwrapper {
16+
background-color: #1a1a1a;
17+
}
18+
19+
body.dark-mode h1,
20+
body.dark-mode h2,
21+
body.dark-mode h3 {
22+
color: #e0e0e0;
23+
}
24+
25+
body.dark-mode a {
26+
color: #66b3ff;
27+
}
28+
29+
body.dark-mode a:visited {
30+
color: #9999ff;
31+
}
32+
33+
body.dark-mode a:hover {
34+
color: #ff6666;
35+
}
36+
37+
body.dark-mode :not(pre) > code:not(.label, .function, .type, .class, .law) {
38+
background-color: #2d2d2d;
39+
color: #e0e0e0;
40+
}
41+
42+
body.dark-mode pre > code {
43+
background-color: #0d0d0d;
44+
color: #90ee90;
45+
}
46+
47+
body.dark-mode .hintbox {
48+
background-color: #2d2d1a;
49+
background-image: none !important;
50+
color: #f0f0f0;
51+
border: 1px solid #5a5a3a;
52+
}
53+
54+
body.dark-mode .chapters > li > a {
55+
color: #e0e0e0;
56+
border-bottom: 1px solid #404040;
57+
}
58+
59+
body.dark-mode .chapters > li > a:visited {
60+
color: #b0b0b0;
61+
}
62+
63+
body.dark-mode .chapters > li > a:hover {
64+
color: #ff6666;
65+
}
66+
67+
body.dark-mode .chapters > li > ul > li > a {
68+
color: #e0e0e0;
69+
}
70+
71+
body.dark-mode .chapters > li > ul > li > a:visited {
72+
color: #b0b0b0;
73+
}
74+
75+
body.dark-mode .chapters > li > ul > li > a:hover {
76+
color: #ff6666;
77+
}
78+
79+
body.dark-mode .errata {
80+
background-color: #3d1a1a;
81+
color: #e0e0e0;
82+
}
83+
84+
/* Dark mode toggle button */
85+
.dark-mode-toggle {
86+
position: fixed;
87+
top: 40px;
88+
right: 20px;
89+
/* Using z-index 10000 to match the Ukraine banner and ensure visibility */
90+
z-index: 10000;
91+
background-color: #333;
92+
color: white;
93+
border: 2px solid #555;
94+
border-radius: 50%;
95+
width: 50px;
96+
height: 50px;
97+
cursor: pointer;
98+
font-size: 24px;
99+
display: flex;
100+
align-items: center;
101+
justify-content: center;
102+
transition: all 0.3s ease;
103+
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
104+
}
105+
106+
.dark-mode-toggle:hover {
107+
background-color: #444;
108+
transform: scale(1.1);
109+
}
110+
111+
body.dark-mode .dark-mode-toggle {
112+
background-color: #f0f0f0;
113+
color: #333;
114+
border-color: #ccc;
115+
}
116+
117+
body.dark-mode .dark-mode-toggle:hover {
118+
background-color: #e0e0e0;
119+
}
120+
121+
/* Adjust toggle button for mobile */
122+
@media screen and (max-width: 600px) {
123+
.dark-mode-toggle {
124+
width: 45px;
125+
height: 45px;
126+
font-size: 20px;
127+
top: 40px;
128+
right: 10px;
129+
}
130+
}
131+
132+
/* Dark mode for index page specific elements */
133+
body.dark-mode {
134+
background-color: #1a1a1a !important;
135+
}
136+
137+
body.dark-mode #newsplash {
138+
color: #e0e0e0;
139+
background-color: #1a1a1a !important;
140+
position: relative;
141+
overflow: hidden; /* Prevent content from spilling outside the container */
142+
}
143+
144+
/* Add a dark overlay using pseudo-element to dim background while keeping text readable */
145+
body.dark-mode #newsplash::before {
146+
content: "";
147+
position: absolute;
148+
top: 0;
149+
left: 0;
150+
right: 0;
151+
bottom: 0;
152+
background-color: rgba(26, 26, 26, 0.40);
153+
pointer-events: none;
154+
z-index: 0;
155+
}
156+
157+
/* Ensure content is above the overlay and constrain width to prevent overflow */
158+
/* The text div is absolutely positioned, so we need to constrain it with explicit left/right */
159+
/* Parent container is 880px wide, text should wrap within it with proper margins */
160+
body.dark-mode #newsplash > div[style*="position:absolute"] {
161+
position: absolute !important; /* Keep original positioning */
162+
z-index: 1;
163+
left: 0 !important;
164+
right: 20px !important; /* 20px margin on the right to prevent overflow */
165+
padding-left: 20px; /* Match the left padding to keep text centered */
166+
padding-right: 20px; /* Additional padding for safety */
167+
box-sizing: border-box;
168+
}
169+
170+
body.dark-mode .newsplash a.nostarchlink {
171+
color: #66b3ff;
172+
}
173+
174+
body.dark-mode .newsplash a.nostarchlink:hover {
175+
color: #ff6666;
176+
}
177+
178+
/* Make index page buttons slightly more visible in dark mode without hiding graphics */
179+
body.dark-mode #faq-button,
180+
body.dark-mode #book-button,
181+
body.dark-mode #read-button {
182+
/* Keep the buttons as clickable areas over the dimmed graphics */
183+
opacity: 1;
184+
}
185+
186+
/* Handle image backgrounds in dark mode */
187+
body.dark-mode img {
188+
opacity: 0.9;
189+
background-color: transparent;
190+
}
191+
192+
/* Keep images at full opacity when needed */
193+
body.dark-mode img.full-opacity {
194+
opacity: 1;
195+
}

docs/assets/js/dark-mode.js

Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
// Dark Mode Toggle Functionality for Learn You a Haskell
2+
3+
(function() {
4+
'use strict';
5+
6+
// Check for saved theme preference or default to light mode
7+
const currentTheme = localStorage.getItem('theme') || 'light';
8+
9+
// Apply the theme immediately to prevent flash
10+
if (currentTheme === 'dark') {
11+
document.body.classList.add('dark-mode');
12+
}
13+
14+
// Create and add the toggle button
15+
function createToggleButton() {
16+
const toggleButton = document.createElement('button');
17+
toggleButton.className = 'dark-mode-toggle';
18+
toggleButton.setAttribute('aria-label', 'Toggle dark mode');
19+
toggleButton.setAttribute('title', 'Toggle dark mode');
20+
21+
// Set initial icon based on current theme
22+
updateButtonIcon(toggleButton);
23+
24+
// Add click handler
25+
toggleButton.addEventListener('click', function() {
26+
document.body.classList.toggle('dark-mode');
27+
28+
// Save preference
29+
if (document.body.classList.contains('dark-mode')) {
30+
localStorage.setItem('theme', 'dark');
31+
} else {
32+
localStorage.setItem('theme', 'light');
33+
}
34+
35+
// Update button icon
36+
updateButtonIcon(toggleButton);
37+
});
38+
39+
document.body.appendChild(toggleButton);
40+
}
41+
42+
function updateButtonIcon(button) {
43+
if (document.body.classList.contains('dark-mode')) {
44+
button.textContent = '☀️';
45+
button.setAttribute('title', 'Switch to light mode');
46+
button.setAttribute('aria-label', 'Switch to light mode');
47+
} else {
48+
button.textContent = '🌙';
49+
button.setAttribute('title', 'Switch to dark mode');
50+
button.setAttribute('aria-label', 'Switch to dark mode');
51+
}
52+
}
53+
54+
// Wait for DOM to be ready
55+
if (document.readyState === 'loading') {
56+
document.addEventListener('DOMContentLoaded', createToggleButton);
57+
} else {
58+
createToggleButton();
59+
}
60+
})();

docs/index.html

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
<link rel="stylesheet" href="assets/css/style.css">
1818
<!-- "Donato to Ukraine" top of the page banner -->
1919
<link rel="stylesheet" href="assets/css/ukraine-banner.css">
20+
<!-- Dark Mode Support -->
21+
<link rel="stylesheet" href="assets/css/dark-mode.css">
2022
<link rel="preload" as="image" href="assets/images/newsplash-new-short.webp">
2123
<link rel="preload" as="image" href="assets/images/newsplash-new-long.webp">
2224

@@ -106,6 +108,8 @@ <h1 style="display: none;">A guide to Haskell programming language</h1>
106108
<script async defer src="https://buttons.github.io/buttons.js"></script>
107109
<!-- custom JS -->
108110
<script src="assets/js/toggleShow.js" async></script>
111+
<!-- Dark Mode Toggle -->
112+
<script src="assets/js/dark-mode.js"></script>
109113
<!-- redirect Russia (for now) -->
110114
<!-- <script src="https://redirectrussia.org/v1.js" data-hide-domain="hide" async integrity="sha384-K4/XEYup4kNv/qt2ucIwIH2wLT9I+z3s17CHQNMBB2/E8/Kw2VYsXQKB/7kylubA" crossorigin="anonymous"></script> -->
111115
</body>

markdown/config/template.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
<base href="">
99
<link rel="stylesheet" href="assets/css/reset.css">
1010
<link rel="stylesheet" href="assets/css/style.css">
11+
<link rel="stylesheet" href="assets/css/dark-mode.css">
1112
<link rel="shortcut icon" href="assets/images/favicon.png" type="image/png">
1213
$if(prev_filename)$
1314
<link rel="prev" href="${prev_filename}.html">
@@ -73,6 +74,7 @@
7374
<script type="text/javascript" src="sh/Scripts/shCore.js"></script>
7475
<script type="text/javascript" src="sh/Scripts/shBrushHaskell.js"></script>
7576
<script type="text/javascript" src="sh/Scripts/shBrushPlain.js"></script>
77+
<script src="assets/js/dark-mode.js"></script>
7678
<script type="text/javascript">
7779
dp.SyntaxHighlighter.ClipboardSwf = '/sh/Scripts/clipboard.swf';
7880
dp.SyntaxHighlighter.HighlightAll('code', false, false, false, 1, false);

markdown/generated_md/chapters.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
# Learn You a Haskell for Great Good!
2+
3+
1. [Introduction](introduction.html)
4+
* [About this tutorial](introduction.html#about-this-tutorial)
5+
* [So what's Haskell?](introduction.html#so-whats-haskell)
6+
* [What you need to dive in](introduction.html#what-you-need)
7+
2. [Starting Out](starting-out.html)
8+
* [Ready, set, go!](starting-out.html#ready-set-go)
9+
* [Baby's first functions](starting-out.html#babys-first-functions)
10+
* [An intro to lists](starting-out.html#an-intro-to-lists)
11+
* [Texas ranges](starting-out.html#texas-ranges)
12+
* [I'm a list comprehension](starting-out.html#im-a-list-comprehension)
13+
* [Tuples](starting-out.html#tuples)
14+
3. [Types and Typeclasses](types-and-typeclasses.html)
15+
* [Believe the type](types-and-typeclasses.html#believe-the-type)
16+
* [Type variables](types-and-typeclasses.html#type-variables)
17+
* [Typeclasses 101](types-and-typeclasses.html#typeclasses-101)
18+
4. [Syntax in Functions](syntax-in-functions.html)
19+
* [Pattern matching](syntax-in-functions.html#pattern-matching)
20+
* [Guards, guards!](syntax-in-functions.html#guards-guards)
21+
* [Where!?](syntax-in-functions.html#where)
22+
* [Let it be](syntax-in-functions.html#let-it-be)
23+
* [Case expressions](syntax-in-functions.html#case-expressions)
24+
5. [Recursion](recursion.html)
25+
* [Hello recursion!](recursion.html#hello-recursion)
26+
* [Maximum awesome](recursion.html#maximum-awesome)
27+
* [A few more recursive functions](recursion.html#a-few-more-recursive-functions)
28+
* [Quick, sort!](recursion.html#quick-sort)
29+
* [Thinking recursively](recursion.html#thinking-recursively)
30+
6. [Higher Order Functions](higher-order-functions.html)
31+
* [Curried functions](higher-order-functions.html#curried-functions)
32+
* [Some higher-orderism is in order](higher-order-functions.html#higher-orderism)
33+
* [Maps and filters](higher-order-functions.html#maps-and-filters)
34+
* [Lambdas](higher-order-functions.html#lambdas)
35+
* [Only folds and horses](higher-order-functions.html#folds)
36+
* [Function application with $](higher-order-functions.html#function-application)
37+
* [Function composition](higher-order-functions.html#composition)
38+
7. [Modules](modules.html)
39+
* [Loading modules](modules.html#loading-modules)
40+
* [Data.List](modules.html#data-list)
41+
* [Data.Char](modules.html#data-char)
42+
* [Data.Map](modules.html#data-map)
43+
* [Data.Set](modules.html#data-set)
44+
* [Making our own modules](modules.html#making-our-own-modules)
45+
8. [Making Our Own Types and Typeclasses](making-our-own-types-and-typeclasses.html)
46+
* [Algebraic data types intro](making-our-own-types-and-typeclasses.html#algebraic-data-types)
47+
* [Record syntax](making-our-own-types-and-typeclasses.html#record-syntax)
48+
* [Type parameters](making-our-own-types-and-typeclasses.html#type-parameters)
49+
* [Derived instances](making-our-own-types-and-typeclasses.html#derived-instances)
50+
* [Type synonyms](making-our-own-types-and-typeclasses.html#type-synonyms)
51+
* [Recursive data structures](making-our-own-types-and-typeclasses.html#recursive-data-structures)
52+
* [Typeclasses 102](making-our-own-types-and-typeclasses.html#typeclasses-102)
53+
* [A yes-no typeclass](making-our-own-types-and-typeclasses.html#a-yes-no-typeclass)
54+
* [The Functor typeclass](making-our-own-types-and-typeclasses.html#the-functor-typeclass)
55+
* [Kinds and some type-foo](making-our-own-types-and-typeclasses.html#kinds-and-some-type-foo)
56+
9. [Input and Output](input-and-output.html)
57+
* [Hello, world!](input-and-output.html#hello-world)
58+
* [Files and streams](input-and-output.html#files-and-streams)
59+
* [Command line arguments](input-and-output.html#command-line-arguments)
60+
* [Randomness](input-and-output.html#randomness)
61+
* [Bytestrings](input-and-output.html#bytestrings)
62+
* [Exceptions](input-and-output.html#exceptions)
63+
10. [Functionally Solving Problems](functionally-solving-problems.html)
64+
* [Reverse Polish notation calculator](functionally-solving-problems.html#reverse-polish-notation-calculator)
65+
* [Heathrow to London](functionally-solving-problems.html#heathrow-to-london)
66+
11. [Functors, Applicative Functors and Monoids](functors-applicative-functors-and-monoids.html)
67+
* [Functors redux](functors-applicative-functors-and-monoids.html#functors-redux)
68+
* [Applicative functors](functors-applicative-functors-and-monoids.html#applicative-functors)
69+
* [The newtype keyword](functors-applicative-functors-and-monoids.html#the-newtype-keyword)
70+
* [Monoids](functors-applicative-functors-and-monoids.html#monoids)
71+
12. [A Fistful of Monads](a-fistful-of-monads.html)
72+
* [Getting our feet wet with Maybe](a-fistful-of-monads.html#getting-our-feet-wet-with-maybe)
73+
* [The Monad type class](a-fistful-of-monads.html#the-monad-type-class)
74+
* [Walk the line](a-fistful-of-monads.html#walk-the-line)
75+
* [do notation](a-fistful-of-monads.html#do-notation)
76+
* [The list monad](a-fistful-of-monads.html#the-list-monad)
77+
* [Monad laws](a-fistful-of-monads.html#monad-laws)
78+
13. [For a Few Monads More](for-a-few-monads-more.html)
79+
* [Writer? I hardly know her!](for-a-few-monads-more.html#writer)
80+
* [Reader? Ugh, not this joke again.](for-a-few-monads-more.html#reader)
81+
* [Tasteful stateful computations](for-a-few-monads-more.html#state)
82+
* [Error error on the wall](for-a-few-monads-more.html#error)
83+
* [Some useful monadic functions](for-a-few-monads-more.html#useful-monadic-functions)
84+
* [Making monads](for-a-few-monads-more.html#making-monads)
85+
14. [Zippers](zippers.html)
86+
* [Taking a walk](zippers.html#taking-a-walk)
87+
* [A trail of breadcrumbs](zippers.html#a-trail-of-breadcrumbs)
88+
* [Focusing on lists](zippers.html#focusing-on-lists)
89+
* [A very simple file system](zippers.html#a-very-simple-file-system)
90+
* [Watch your step](zippers.html#watch-your-step)

0 commit comments

Comments
 (0)