diff --git a/.gitignore b/.gitignore
index 0691a5444..c12003316 100644
--- a/.gitignore
+++ b/.gitignore
@@ -5,3 +5,4 @@ site
static/styles/vendor.css
static/styles/app.css
static/styles/fonts.css
+static/styles/noscript.css
diff --git a/src/lib.rs b/src/lib.rs
index 6823f1193..38e0cfe05 100644
--- a/src/lib.rs
+++ b/src/lib.rs
@@ -88,6 +88,7 @@ impl<'a> Generator<'a> {
self.render_blog(blog)?;
}
self.compile_sass("app")?;
+ self.compile_sass("noscript")?;
self.compile_sass("fonts")?;
self.concat_vendor_css(vec!["skeleton", "tachyons"])?;
self.copy_static_files()?;
diff --git a/src/styles/app.scss b/src/styles/app.scss
index bd59032be..2d53046af 100644
--- a/src/styles/app.scss
+++ b/src/styles/app.scss
@@ -3,14 +3,66 @@
$body-font: 'Fira Sans', Helvetica, Arial, sans-serif;
$header-font: 'Alfa Slab One', serif;
-$gray: #2a3439;
-$red: #a72145;
-$green: #0b7261;
-$purple: #2e2459;
-$yellow: #ffc832;
+// Switching theme will only work if JavaScript is enabled as well
+
+// Default light theme
+:root, :root:not([data-theme]) {
+ --gray: #2a3439;
+ --red: #a72145;
+ --yellow: #ffc832;
+ --code-color: black;
+ --code-bg-color: rgba(42, 52, 57, 0.05);
+ --code-border-color: rgba(42, 52, 57, 0.25);
+ --blockquote-color: black;
+ --blockquote-bg-color: rgb(247, 249, 249);
+ --blockquote-left-border-color: rgb(195, 205, 210);
+ --body-background-color: white;
+ --body-foreground-color: white;
+ --body-color: rgb(34,34,34);
+ --div-brand-a-color: black;
+ --white-elem-color: black;
+ --white-a: #2a3439;
+ --nav-links-a: #2a3439;
+ --publish-date-author: #2a3439;
+ --section-header-h2-color: black;
+ --theme-icon: #43484d;
+ --theme-popup-border: #43484d;
+ --theme-popup-bg: white;
+ --theme-hover: #cacaca;
+ --theme-choice-color: black;
+ --rust-logo-filter: initial;
+}
+
+// Dark theme
+:root[data-theme='dark'] {
+ --gray: #2a3439;
+ --red: #a72145;
+ --yellow: #ffc832;
+ --code-color: white;
+ --code-bg-color: rgba(213, 203, 198, 0.05);
+ --code-border-color: rgba(213, 203, 198, 0.25);
+ --blockquote-color: rgb(195, 205, 210);
+ --blockquote-bg-color: rgba(213, 203, 198, 0.05);
+ --blockquote-left-border-color: rgb(195, 205, 210);
+ --body-background-color: #181a1b;
+ --body-foreground-color: #e8e6e3;
+ --body-color: white;
+ --div-brand-a-color: white;
+ --white-elem-color: white;
+ --white-elem-a: #d5cbc6;
+ --nav-links-a: #d5cbc6;
+ --publish-date-author: #d5cbc6;
+ --section-header-h2-color: white;
+ --theme-icon: #43484d;
+ --theme-popup-border: #43484d;
+ --theme-popup-bg: #141617;
+ --theme-hover: #474c51;
+ --theme-choice-color: #d5cbc6;
+ --rust-logo-filter: drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);
+}
html {
- font-size: 62.5%
+ font-size: 62.5%;
}
@media screen and (min-width: 30em) {
@@ -21,7 +73,8 @@ html {
body {
font-family: $body-font;
- background-color: white;
+ background-color: var(--body-background-color);
+ color: var(--body-color);
/* Ensure the footer is always at the bottom of the screen */
min-height: 100vh;
@@ -35,8 +88,8 @@ body {
blockquote {
font-style: italic;
position: relative;
- background-color: lighten($gray, 78%);
- border-left: 8px solid lighten($gray, 60%);
+ background-color: var(--blockquote-bg-color);
+ border-left: 8px solid var(--blockquote-left-border-color);
border-radius: 5px;
margin: 0;
margin-bottom: 2rem;
@@ -80,6 +133,7 @@ section {
margin: 0;
padding: 0;
letter-spacing: 1px;
+ color: var(--section-header-h2-color);
}
}
}
@@ -110,7 +164,8 @@ ul.nav, ul.nav li {
}
.nav a {
- color: $gray;
+ color: var(--gray);
+ color: var(--nav-links-a);
}
div.brand {
@@ -123,7 +178,7 @@ div.brand {
margin-top: $v-top;
& a {
- color: black;
+ color: var(--div-brand-a-color);
text-decoration: none;
}
@@ -139,30 +194,30 @@ div.brand {
}
.white {
- color: black;
+ color: var(--white-elem-color);
.highlight {
- background-color: $yellow;
+ background-color: var(--yellow);
}
a {
- color: $gray;
+ color: var(--white-elem-a);
text-decoration: underline;
}
.button.button-secondary {
- background-color: $yellow;
- border: 1px solid $yellow;
- color: $gray;
+ background-color: var(--yellow);
+ color: var(--gray);
+ border: 1px solid var(--yellow);
text-decoration: none;
display: block;
overflow: hidden;
text-overflow: ellipsis;
&:hover, &:focus {
- border-color: $gray;
+ border-color: var(--gray);
}
}
code {
- color: black;
- background-color: rgba($gray, 0.05);
- border: 1px solid rgba($gray, 0.25);
+ color: var(--code-color);
+ background-color: var(--code-bg-color);
+ border: 1px solid var(--code-border-color);
}
a.anchor::before {
@@ -173,7 +228,7 @@ div.brand {
margin-left: -1em;
text-decoration: none;
opacity: 0.7;
- color: $gray;
+ color: var(--gray);
font-weight: normal;
}
:hover > a.anchor::before {
@@ -194,18 +249,18 @@ ul {
}
.posts {
- background-color: $gray;
- color: white;
+ background-color: var(--gray);
+ color: var(--body-foreground-color);
.highlight {
- background-color: $red;
+ background-color: var(--red);
}
a {
- color: white;
+ color: var(--body-foreground-color);
text-decoration: underline;
}
.button.button-secondary {
- background-color: $red;
- border: 1px solid $red;
+ background-color: var(--red);
+ border: 1px solid var(--red);
color: white;
text-decoration: none;
display: block;
@@ -230,7 +285,7 @@ table.post-list a:hover {
}
.publish-date-author {
- color: $gray;
+ color: var(--publish-date-author);
margin: -60px 0 60px 0;
}
@@ -245,11 +300,11 @@ footer {
}
a {
- color: $yellow;
+ color: var(--yellow);
text-decoration: none;
&:hover {
- color: $yellow;
+ color: var(--yellow);
text-decoration: underline;
}
}
@@ -324,3 +379,48 @@ header h1 {
}
}
+// Theme switch popup
+// theme selector visible only if JavaScript is available
+
+.theme-icon {
+ display: none;
+ line-height: 2em;
+ border: 2px solid var(--theme-icon);
+ border-radius: 5px;
+ padding: 0px 5px;
+ color: var(--theme-choice-color);
+ &:hover {
+ color: var(--theme-choice-color);
+ }
+}
+
+#theme-choice {
+ display: none;
+ border: 2px solid var(--theme-popup-border);
+ border-radius: 5px;
+ color: var(--theme-choice-color);
+ background: var(--theme-popup-bg);
+ position: absolute;
+ list-style: none;
+ font-weight: 400;
+ padding: 0px;
+ // counterbalance vendored skeleton.css
+ margin: 0.2em -0.5em;
+}
+
+.theme-item {
+ padding: 0px 5px;
+}
+
+#theme-choice.showThemeDropdown {
+ display: block;
+ z-index: 1;
+}
+
+li.theme-item:hover {
+ background-color: var(--theme-hover);
+}
+
+.rust-logo {
+ filter: var(--rust-logo-filter);
+}
diff --git a/src/styles/noscript.scss b/src/styles/noscript.scss
new file mode 100644
index 000000000..94a877e0c
--- /dev/null
+++ b/src/styles/noscript.scss
@@ -0,0 +1,55 @@
+// This stylesheet is used when the user agent has no JavaScript capabilities (or has it disabled)
+// Sets dark theme according to user agent preferences
+
+// Default light theme
+:root, :root:not([data-theme]) {
+ --gray: #2a3439;
+ --red: #a72145;
+ --yellow: #ffc832;
+ --code-color: black;
+ --code-bg-color: rgba(42, 52, 57, 0.05);
+ --code-border-color: rgba(42, 52, 57, 0.25);
+ --blockquote-color: black;
+ --blockquote-bg-color: rgb(247, 249, 249);
+ --blockquote-left-border-color: rgb(195, 205, 210);
+ --body-background-color: white;
+ --body-foreground-color: white;
+ --body-color: rgb(34,34,34);
+ --div-brand-a-color: black;
+ --white-elem-color: black;
+ --white-a: #2a3439;
+ --nav-links-a: #2a3439;
+ --publish-date-author: #2a3439;
+ --section-header-h2-color: black;
+ --rust-logo-filter: initial;
+}
+
+// Dark theme (probed from user prefs)
+@media (prefers-color-scheme: dark) {
+ :root, :root:not([data-theme]) {
+ --gray: #2a3439;
+ --red: #a72145;
+ --yellow: #ffc832;
+ --code-color: white;
+ --code-bg-color: rgba(213, 203, 198, 0.05);
+ --code-border-color: rgba(213, 203, 198, 0.25);
+ --blockquote-color: rgb(195, 205, 210);
+ --blockquote-bg-color: rgba(213, 203, 198, 0.05);
+ --blockquote-left-border-color: rgb(195, 205, 210);
+ --body-background-color: #181a1b;
+ --body-foreground-color: #e8e6e3;
+ --body-color: white;
+ --div-brand-a-color: white;
+ --white-elem-color: white;
+ --white-elem-a: #d5cbc6;
+ --nav-links-a: #d5cbc6;
+ --publish-date-author: #d5cbc6;
+ --section-header-h2-color: white;
+ --rust-logo-filter: drop-shadow(1px 0 0px #fff) drop-shadow(0 1px 0 #fff) drop-shadow(-1px 0 0 #fff) drop-shadow(0 -1px 0 #fff);
+ }
+}
+
+// Don't show the theme selector button when JavaScript is disabled
+#theme-icon {
+ display: none !important;
+}
diff --git a/static/scripts/theme-switch.js b/static/scripts/theme-switch.js
new file mode 100644
index 000000000..804e72b58
--- /dev/null
+++ b/static/scripts/theme-switch.js
@@ -0,0 +1,63 @@
+"use strict";
+
+function changeThemeTo(val) {
+ document.documentElement.setAttribute("data-theme", val);
+ // save theme prefs in the browser
+ if (storageAvailable("localStorage")) {
+ localStorage.setItem("blog-rust-lang-org-theme", val);
+ }
+ // the theme dropdown will close itself when returning to the dropdown() caller
+}
+
+function dropdown () {
+ document.getElementById("theme-choice").classList.toggle("showThemeDropdown");
+}
+
+// courtesy MDN
+function storageAvailable(type) {
+ let storage;
+ try {
+ storage = window[type];
+ const x = "__storage_test__";
+ storage.setItem(x, x);
+ storage.removeItem(x);
+ return true;
+ } catch (e) {
+ return (
+ e instanceof DOMException &&
+ e.name === "QuotaExceededError" &&
+ // acknowledge QuotaExceededError only if there's something already stored
+ storage &&
+ storage.length !== 0
+ );
+ }
+}
+
+function handleBlur(event) {
+ const parent = document.getElementById("theme-choice");
+ if (!parent.contains(document.activeElement) &&
+ !parent.contains(event.relatedTarget) &&
+ parent.classList.contains("showThemeDropdown")
+ ) {
+ console.debug('Closing the dropdown');
+ parent.classList.remove("showThemeDropdown");
+ }
+}
+
+// close the theme dropdown if clicking somewhere else
+document.querySelector('.theme-icon').onblur = handleBlur;
+
+// Check for saved user preference on load, else check and save user agent prefs
+let savedTheme = null;
+if (storageAvailable("localStorage")) {
+ savedTheme = localStorage.getItem("blog-rust-lang-org-theme");
+}
+if (savedTheme) {
+ document.documentElement.setAttribute("data-theme", savedTheme);
+} else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
+ document.documentElement.setAttribute("data-theme", "dark");
+ localStorage.setItem("blog-rust-lang-org-theme", "dark");
+}
+
+// show the theme selector only if JavaScript is enabled/available
+document.querySelector('.theme-icon').style.display = 'block';
diff --git a/templates/footer.hbs b/templates/footer.hbs
index 30899b465..b089d6b1b 100644
--- a/templates/footer.hbs
+++ b/templates/footer.hbs
@@ -44,3 +44,4 @@
+
diff --git a/templates/headers.hbs b/templates/headers.hbs
index 3e19aa182..fb4b824ae 100644
--- a/templates/headers.hbs
+++ b/templates/headers.hbs
@@ -19,6 +19,11 @@
+
+
+
diff --git a/templates/nav.hbs b/templates/nav.hbs
index d63976122..36d1da5ed 100644
--- a/templates/nav.hbs
+++ b/templates/nav.hbs
@@ -1,7 +1,7 @@