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 @@