diff --git a/gatsby-config.js b/gatsby-config.js
index abeeb8d7..43ca80ef 100644
--- a/gatsby-config.js
+++ b/gatsby-config.js
@@ -38,7 +38,8 @@ module.exports = {
withWebp: true,
linkImagesToOriginal: true,
showCaptions: true,
- backgroundColor: '#e7eef4'
+ backgroundColor: 'transparent',
+ disableBgImageOnAlpha: true
}
},
{
@@ -208,6 +209,14 @@ module.exports = {
exclude: ['/page/*', '/tags/*']
}
},
+ {
+ resolve: 'gatsby-plugin-use-dark-mode',
+ options: {
+ classNameDark: 'dark',
+ classNameLight: 'light',
+ minify: true
+ }
+ },
'gatsby-plugin-webpack-size',
'gatsby-plugin-react-helmet',
'gatsby-plugin-catch-links',
diff --git a/package.json b/package.json
index bb1e0ed9..be0f346c 100644
--- a/package.json
+++ b/package.json
@@ -28,6 +28,7 @@
"not op_mini all"
],
"dependencies": {
+ "classnames": "^2.2.6",
"dms2dec": "^1.1.0",
"fast-exif": "^1.0.1",
"fraction.js": "^4.0.12",
@@ -46,6 +47,7 @@
"gatsby-plugin-sitemap": "^2.2.16",
"gatsby-plugin-svgr": "^2.0.2",
"gatsby-plugin-typescript": "^2.1.11",
+ "gatsby-plugin-use-dark-mode": "^1.1.2",
"gatsby-plugin-webpack-size": "^1.0.0",
"gatsby-redirect-from": "^0.2.1",
"gatsby-remark-autolink-headers": "^2.1.13",
@@ -76,6 +78,7 @@
"remark": "^11.0.1",
"remark-react": "^6.0.0",
"slugify": "^1.3.4",
+ "use-dark-mode": "^2.3.1",
"web3": "^1.2.1"
},
"devDependencies": {
@@ -85,6 +88,7 @@
"@svgr/webpack": "^4.3.3",
"@testing-library/jest-dom": "^4.1.0",
"@testing-library/react": "^9.2.0",
+ "@types/classnames": "^2.2.9",
"@types/jest": "^24.0.18",
"@types/node": "^12.7.8",
"@types/react": "^16.9.4",
diff --git a/src/components/Layout.module.scss b/src/components/Layout.module.scss
index 7d1a9aa2..ada64bc4 100644
--- a/src/components/Layout.module.scss
+++ b/src/components/Layout.module.scss
@@ -22,22 +22,31 @@
/////////////////////////////////////
.document {
- @include transition;
-
width: 100%;
padding-top: ($spacer * 2);
- background-color: $page-background-color;
+ background-color: $body-background-color;
border-top: 1px solid rgba(255, 255, 255, 0.7);
border-bottom: 1px solid rgba(255, 255, 255, 0.7);
padding-bottom: $spacer * 2;
box-shadow: 0 1px 4px rgba($brand-main, 0.1),
0 -1px 4px rgba($brand-main, 0.2);
transform: translate3d(0, 0, 0);
+ transition: 0.4s $easing;
+ transition-property: transform, background;
:global(.has-menu-open) & {
transform: translate3d(0, ($spacer * 3), 0);
}
+ :global(.dark) & {
+ background-color: $body-background-color--dark;
+ color: $text-color--dark;
+ border-top-color: darken($brand-grey, 15%);
+ border-bottom-color: darken($brand-grey, 15%);
+ box-shadow: 0 1px 4px darken($brand-main, 10%),
+ 0 -1px 4px darken($brand-main, 15%);
+ }
+
@media (min-width: $screen-sm) and (min-height: 500px) {
margin-top: $spacer * 2.65;
margin-bottom: $spacer * 19; // height of footer
diff --git a/src/components/Post/PostActions.module.scss b/src/components/Post/PostActions.module.scss
index 007c1210..0ba10de8 100644
--- a/src/components/Post/PostActions.module.scss
+++ b/src/components/Post/PostActions.module.scss
@@ -13,6 +13,10 @@
flex-wrap: wrap;
justify-content: space-between;
+ :global(.dark) & {
+ background: darken($body-background-color--dark, 2%);
+ }
+
@media (min-width: $screen-md) {
margin-left: -100%;
margin-right: -18%;
diff --git a/src/components/Post/PostImage.module.scss b/src/components/Post/PostImage.module.scss
index 061c624c..da0a36e3 100644
--- a/src/components/Post/PostImage.module.scss
+++ b/src/components/Post/PostImage.module.scss
@@ -15,7 +15,7 @@
top: 10%;
padding: $spacer / 3 $spacer;
background: rgba($link-color, 0.85);
- color: #fff;
+ color: #fff !important;
text-shadow: 0 1px 0 #000;
left: 0;
opacity: 0;
diff --git a/src/components/Post/PostTitle.module.scss b/src/components/Post/PostTitle.module.scss
index bc970961..e8eb1642 100644
--- a/src/components/Post/PostTitle.module.scss
+++ b/src/components/Post/PostTitle.module.scss
@@ -9,6 +9,10 @@
color: $color-headings;
margin-top: 0;
margin-bottom: $spacer;
+
+ :global(.dark) & {
+ color: $color-headings--dark;
+ }
}
.hentry__title__link {
diff --git a/src/components/Search/SearchResults.module.scss b/src/components/Search/SearchResults.module.scss
index fca406b1..7c0c1160 100644
--- a/src/components/Search/SearchResults.module.scss
+++ b/src/components/Search/SearchResults.module.scss
@@ -15,6 +15,10 @@
-webkit-overflow-scrolling: touch;
height: 91vh;
+ :global(.dark) & {
+ background: rgba($body-background-color--dark, 0.95);
+ }
+
ul {
@include breakoutviewport;
diff --git a/src/components/Search/index.tsx b/src/components/Search/index.tsx
index f3a4be52..a4f90171 100644
--- a/src/components/Search/index.tsx
+++ b/src/components/Search/index.tsx
@@ -38,7 +38,7 @@ export default function Search({ lng }: { lng: string }) {
{searchOpen && (
<>
-
+
= 2 ? '@2x' : ''
+const isDarkMode =
+ typeof window !== 'undefined' && document.body.classList.contains('dark')
+
const mapbox = (mapboxId: string, accessToken: string) => (
x: string,
y: string,
@@ -50,7 +53,7 @@ export default function ExifMap({
zoom={zoom}
height={160}
attribution={false}
- provider={providers['light']}
+ provider={isDarkMode ? providers['dark'] : providers['light']}
metaWheelZoom={true}
metaWheelZoomWarning={'META+wheel to zoom'}
>
diff --git a/src/components/atoms/Modal.module.scss b/src/components/atoms/Modal.module.scss
index 3bf22e3b..49bd3945 100644
--- a/src/components/atoms/Modal.module.scss
+++ b/src/components/atoms/Modal.module.scss
@@ -14,6 +14,10 @@
animation: fadein 0.3s;
padding: $spacer;
+ :global(.dark) & {
+ background: rgba($body-background-color--dark, 0.95);
+ }
+
@media (min-width: $screen-sm) {
display: flex;
align-items: flex-start;
@@ -24,7 +28,7 @@
.modal__content {
outline: 0;
- background: transparent;
+ background: $body-background-color;
position: relative;
border-radius: $border-radius;
border: 1px solid rgba($brand-grey-light, 0.4);
@@ -32,6 +36,11 @@
padding: 0 $spacer / 2 $spacer / 2;
max-width: 100%;
+ :global(.dark) & {
+ background: $body-background-color--dark;
+ box-shadow: 0 5px 30px rgba(darken($brand-main, 20%), 0.5);
+ }
+
@media (min-width: $screen-md) {
max-width: $screen-sm;
padding: 0 $spacer $spacer;
diff --git a/src/components/molecules/Featured.module.scss b/src/components/molecules/Featured.module.scss
index 5b601ee2..342fc162 100644
--- a/src/components/molecules/Featured.module.scss
+++ b/src/components/molecules/Featured.module.scss
@@ -35,7 +35,7 @@
text-align: right;
padding: $spacer / 3;
background: rgba($link-color, 0.85);
- color: #fff;
+ color: #fff !important;
text-shadow: 0 1px 0 #000;
left: 0;
opacity: 0;
diff --git a/src/components/molecules/Menu.module.scss b/src/components/molecules/Menu.module.scss
index 44bd9f56..ecbc11f1 100644
--- a/src/components/molecules/Menu.module.scss
+++ b/src/components/molecules/Menu.module.scss
@@ -43,5 +43,9 @@
padding: $padding-base-horizontal;
display: block;
text-align: center;
+
+ :global(.dark) & {
+ text-shadow: 0 -1px 0 rgba(#000, 0.5);
+ }
}
}
diff --git a/src/components/molecules/Menu.tsx b/src/components/molecules/Menu.tsx
index 201a7a85..77c7b154 100644
--- a/src/components/molecules/Menu.tsx
+++ b/src/components/molecules/Menu.tsx
@@ -24,7 +24,7 @@ export default function Menu() {
return (
<>
-
+
diff --git a/src/components/molecules/ModalThanks.module.scss b/src/components/molecules/ModalThanks.module.scss
index 67570bb6..5deb94d0 100644
--- a/src/components/molecules/ModalThanks.module.scss
+++ b/src/components/molecules/ModalThanks.module.scss
@@ -13,6 +13,10 @@
margin-bottom: $spacer / 2;
color: $brand-grey;
text-transform: capitalize;
+
+ :global(.dark) & {
+ color: $brand-grey-light;
+ }
}
header {
@@ -20,6 +24,7 @@
text-align: center;
margin-bottom: $spacer;
+ // stylelint-disable-next-line no-descending-specificity
h4 {
font-size: $font-size-large;
margin-top: 0;
@@ -28,6 +33,10 @@
p {
color: $brand-grey-light;
+
+ :global(.dark) & {
+ color: $brand-grey;
+ }
}
}
}
diff --git a/src/components/molecules/ThemeSwitch.module.scss b/src/components/molecules/ThemeSwitch.module.scss
new file mode 100644
index 00000000..4735aaa9
--- /dev/null
+++ b/src/components/molecules/ThemeSwitch.module.scss
@@ -0,0 +1,84 @@
+@import 'variables';
+@import 'mixins';
+
+.themeSwitch {
+ position: relative;
+ display: inline-block;
+ margin-right: $spacer;
+ opacity: 0.75;
+ bottom: -0.1rem;
+
+ svg {
+ width: 0.8rem;
+ height: 0.8rem;
+ margin-top: -0.05rem;
+ fill: $brand-grey-light;
+
+ &:last-child {
+ margin-top: -0.1rem;
+ width: 0.7rem;
+ height: 0.7rem;
+ }
+ }
+}
+
+.checkboxContainer {
+ display: flex;
+ align-items: center;
+}
+
+$knob-size: 8px;
+$knob-space: 1px;
+
+.checkboxFake {
+ display: block;
+ position: relative;
+ width: ($knob-size + ($knob-space * 2)) * 2;
+ height: $knob-size + ($knob-space * 4);
+ border: 1px solid $brand-grey-light;
+ border-radius: 15rem;
+ margin-left: $spacer / 3;
+ margin-right: $spacer / 3;
+
+ &::after {
+ content: '';
+ position: absolute;
+ top: $knob-space;
+ left: $knob-space;
+ width: $knob-size;
+ height: $knob-size;
+ background-color: $brand-grey-light;
+ border-radius: 15rem;
+ transition: transform 0.2s $easing;
+ transform: translate3d(0, 0, 0);
+ }
+}
+
+.checkbox {
+ position: relative;
+ cursor: pointer;
+
+ [type='checkbox'],
+ .label {
+ width: 1px;
+ height: 1px;
+ border: 0;
+ clip: rect(0 0 0 0);
+ margin: -1px;
+ overflow: hidden;
+ padding: 0;
+ position: absolute;
+ }
+
+ [type='checkbox'] {
+ &:checked {
+ + .checkboxContainer {
+ .checkboxFake {
+ &::after {
+ transform: translate3d(100%, 0, 0);
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/components/molecules/ThemeSwitch.test.tsx b/src/components/molecules/ThemeSwitch.test.tsx
new file mode 100644
index 00000000..9f27cdbb
--- /dev/null
+++ b/src/components/molecules/ThemeSwitch.test.tsx
@@ -0,0 +1,24 @@
+import React from 'react'
+import { render, fireEvent, cleanup } from '@testing-library/react'
+import ThemeSwitch from './ThemeSwitch'
+
+describe('ThemeSwitch', () => {
+ afterEach(cleanup)
+
+ it('renders correctly', () => {
+ const { container } = render()
+ const switchContainer = container.querySelector('aside')
+ expect(switchContainer).toBeInTheDocument()
+ })
+
+ it('checkbox can be changed', () => {
+ const { container } = render()
+
+ const toggle = container.querySelector('input')
+ const label = container.querySelector('label')
+ expect(toggle.checked).toBeFalsy()
+ fireEvent.click(label)
+ fireEvent.change(toggle, { target: { checked: true } })
+ expect(toggle.checked).toBeTruthy()
+ })
+})
diff --git a/src/components/molecules/ThemeSwitch.tsx b/src/components/molecules/ThemeSwitch.tsx
new file mode 100644
index 00000000..cbe769b5
--- /dev/null
+++ b/src/components/molecules/ThemeSwitch.tsx
@@ -0,0 +1,55 @@
+import React from 'react'
+import useDarkMode from 'use-dark-mode'
+
+import { ReactComponent as Day } from '../../images/day.svg'
+import { ReactComponent as Night } from '../../images/night.svg'
+import styles from './ThemeSwitch.module.scss'
+
+const ThemeToggle = () => (
+
+
+
+
+
+)
+
+const ThemeToggleInput = ({
+ isDark,
+ toggleDark
+}: {
+ isDark: boolean
+ toggleDark(): void
+}) => (
+
+)
+
+export default function ThemeSwitch() {
+ const darkMode = useDarkMode(false, {
+ classNameDark: 'dark',
+ classNameLight: 'light'
+ })
+
+ return (
+
+ )
+}
diff --git a/src/components/organisms/Header.module.scss b/src/components/organisms/Header.module.scss
index cbd69b9d..7bb79f01 100644
--- a/src/components/organisms/Header.module.scss
+++ b/src/components/organisms/Header.module.scss
@@ -15,7 +15,7 @@
}
}
-.header__content {
+.headerContent {
@include breakoutviewport;
position: relative;
@@ -31,56 +31,28 @@
// Logo
/////////////////////////////////////
-.logo {
- display: block;
- background-image: url('../../images/logo.png');
- background-repeat: no-repeat;
- background-position: left top;
- width: 47px;
- height: 31px;
-
- @media (min-width: $screen-sm) {
- width: 183px;
- }
-
- @media (-webkit-min-device-pixel-ratio: 1.25), (min-resolution: 120dpi) {
- background-image: url('../../images/logo@2x.png');
- background-size: 183px 62px;
- }
-
- @media (-webkit-min-device-pixel-ratio: 3), (min-resolution: 344dpi) {
- background-image: url('../../images/logo@3x.png');
- background-size: 183px 62px;
- }
-}
-
.title {
margin-top: $spacer / 5;
margin-bottom: 0;
- // display toned down logo
- // by default
- @extend .logo;
-}
-.header__logo {
- @include hide-text;
- // repeat logo
- // but display hover version
- @extend .logo;
-
- background-position: left bottom;
-
- // hide by default
- opacity: 0;
- // show smooooothly on hover
- &:hover {
- opacity: 1;
- transform: none;
+ svg {
+ fill: $brand-grey-light;
+ width: 160px;
+ height: 30px;
+ margin: 0;
+ transition: 0.2s $easing;
}
- &:active {
- top: 0;
- box-shadow: none;
+ a {
+ display: block;
+ @include hide-text;
+
+ &:hover,
+ &:focus {
+ svg {
+ fill: $brand-cyan;
+ }
+ }
}
}
diff --git a/src/components/organisms/Header.tsx b/src/components/organisms/Header.tsx
index d04a61f7..375870da 100644
--- a/src/components/organisms/Header.tsx
+++ b/src/components/organisms/Header.tsx
@@ -3,6 +3,8 @@ import { Link } from 'gatsby'
import Container from '../atoms/Container'
import Search from '../Search'
import Menu from '../molecules/Menu'
+import ThemeSwitch from '../molecules/ThemeSwitch'
+import { ReactComponent as Logo } from '../../images/logo.svg'
import styles from './Header.module.scss'
@@ -10,14 +12,15 @@ export default function Header() {
return (
-
+
-
- kremalicious
+
+ kremalicious
diff --git a/src/images/day.svg b/src/images/day.svg
new file mode 100644
index 00000000..b48ffbb3
--- /dev/null
+++ b/src/images/day.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/images/logo.png b/src/images/logo.png
deleted file mode 100644
index ab0ef47f..00000000
Binary files a/src/images/logo.png and /dev/null differ
diff --git a/src/images/logo.svg b/src/images/logo.svg
new file mode 100644
index 00000000..4c82100e
--- /dev/null
+++ b/src/images/logo.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/images/logo@2x.png b/src/images/logo@2x.png
deleted file mode 100644
index 1459db5d..00000000
Binary files a/src/images/logo@2x.png and /dev/null differ
diff --git a/src/images/logo@3x.png b/src/images/logo@3x.png
deleted file mode 100644
index 6f6fb4ef..00000000
Binary files a/src/images/logo@3x.png and /dev/null differ
diff --git a/src/images/night.svg b/src/images/night.svg
new file mode 100644
index 00000000..c2ee7c9d
--- /dev/null
+++ b/src/images/night.svg
@@ -0,0 +1,3 @@
+
diff --git a/src/styles/_mixins.scss b/src/styles/_mixins.scss
index 31f58a5b..2583af2d 100644
--- a/src/styles/_mixins.scss
+++ b/src/styles/_mixins.scss
@@ -97,6 +97,14 @@
width: 100%;
border-bottom: 1px dashed #fff;
}
+
+ :global(.dark) & {
+ border-bottom-color: darken($brand-main, 25%);
+
+ &::before {
+ border-bottom-color: darken($brand-grey, 12%);
+ }
+ }
}
@mixin divider-top() {
@@ -114,6 +122,14 @@
width: 100%;
border-bottom: 1px dashed #fff;
}
+
+ :global(.dark) & {
+ border-top-color: darken($brand-main, 25%);
+
+ &::after {
+ border-bottom-color: darken($brand-grey, 12%);
+ }
+ }
}
// Heading band
@@ -124,6 +140,10 @@
background: rgba(255, 255, 255, 0.5);
padding: ($spacer/2) $spacer ($spacer/2) 100%;
margin-left: -100%;
+
+ :global(.dark) & {
+ background: darken($body-background-color--dark, 2%);
+ }
}
// Layout breakout
@@ -224,6 +244,11 @@
border-radius: $border-radius;
box-shadow: 0 1px 3px rgba($brand-grey, 0.2);
+ :global(.dark) & {
+ box-shadow: 0 3px 5px rgba(darken($brand-main, 20%), 0.15),
+ 0 5px 16px rgba(darken($brand-main, 20%), 0.15);
+ }
+
@media (min-width: $screen-sm) {
border: 2px solid transparent;
}
diff --git a/src/styles/_variables.scss b/src/styles/_variables.scss
index 6b3e6dd1..fdb55f64 100644
--- a/src/styles/_variables.scss
+++ b/src/styles/_variables.scss
@@ -23,15 +23,13 @@ $alert-error: #f2dede;
$body-background-color: $brand-light;
$body-background-color--dark: darken($brand-grey, 22%);
-$page-background-color: $brand-light;
-
// Text Colors
/////////////////////////////////////
$text-color: $brand-grey;
$text-color-light: $brand-grey-light;
-$text-color--dark: lighten($brand-grey-light, 5%);
+$text-color--dark: lighten($brand-grey-light, 10%);
$text-color-light--dark: lighten($brand-grey-light, 5%);
$link-color: $brand-cyan;
diff --git a/src/styles/global.scss b/src/styles/global.scss
index 1586524c..4f1787ae 100644
--- a/src/styles/global.scss
+++ b/src/styles/global.scss
@@ -13,7 +13,6 @@ html,
body {
margin: 0;
padding: 0;
- background: $body-background-color;
}
html {
@@ -32,7 +31,8 @@ body {
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
min-height: 100vh;
- transition: background 0.6s $easing;
+ transition: background 0.4s $easing;
+ background: $body-background-color;
// handling long text, like URLs
overflow-wrap: break-word;
@@ -82,6 +82,26 @@ button {
}
}
+// Links
+/////////////////////////////////////
+
+a {
+ color: $link-color;
+ text-decoration: none;
+ transition: 0.2s ease-out;
+
+ &:hover,
+ &:focus {
+ outline: 0;
+ color: $link-color-hover;
+ }
+
+ &:active {
+ transition: none;
+ color: $link-color-active;
+ }
+}
+
// Headings
/////////////////////////////////////
@@ -153,38 +173,19 @@ h5,
h6 {
font-family: $font-family-headings;
line-height: $line-height-headings;
- color: $color-headings;
font-weight: $font-weight-headings;
letter-spacing: -0.02em;
- .dark & {
- color: $color-headings--dark;
- }
-}
-
-// Links
-/////////////////////////////////////
-
-a {
- color: $link-color;
- text-decoration: none;
- transition: 0.2s ease-out;
-
- h1 &,
- h2 &,
- h3 & {
+ // stylelint-disable no-descending-specificity
+ &,
+ a {
color: $color-headings;
}
+ // stylelint-enable no-descending-specificity
- &:hover,
- &:focus {
- outline: 0;
- color: $link-color-hover;
- }
-
- &:active {
- transition: none;
- color: $link-color-active;
+ .dark &,
+ .dark & a {
+ color: $color-headings--dark;
}
}
@@ -215,6 +216,10 @@ figcaption {
font-style: italic;
text-align: center;
margin-top: -($spacer / $line-height);
+
+ .dark & {
+ color: $brand-grey-light;
+ }
}
// Lists