From a2b4e8b17926e100c92d99479782a44752c89553 Mon Sep 17 00:00:00 2001 From: epcgrs Date: Wed, 27 Aug 2025 13:35:01 -0300 Subject: [PATCH] Adding Darkmode functionality with animations and icons --- Gemfile | 1 + Gemfile.lock | 2 ++ _includes/head.html | 20 ++++++++++- _includes/header.html | 7 ++++ assets/css/_footer.scss | 2 +- assets/css/_global.scss | 36 ++++++++++++++++++-- assets/css/_header.scss | 44 +++++++++++++++++++++++- assets/img/icons.svg | 18 ++++++++++ assets/img/moon.svg | 1 + assets/img/sun.svg | 1 + assets/js/script.js | 75 ++++++++++++++++++++++++++++++++++++++++- 11 files changed, 200 insertions(+), 7 deletions(-) create mode 100644 assets/img/icons.svg create mode 100644 assets/img/moon.svg create mode 100644 assets/img/sun.svg diff --git a/Gemfile b/Gemfile index 0e441f53..8c61f51b 100644 --- a/Gemfile +++ b/Gemfile @@ -9,3 +9,4 @@ gem "jekyll-sitemap" gem "jekyll-feed" gem "jekyll-seo-tag" gem "kramdown-parser-gfm" +gem "webrick", "~> 1.8" diff --git a/Gemfile.lock b/Gemfile.lock index 52f3a848..9647b71f 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -63,6 +63,7 @@ GEM terminal-table (2.0.0) unicode-display_width (~> 1.1, >= 1.1.1) unicode-display_width (1.7.0) + webrick (1.9.1) PLATFORMS ruby @@ -73,6 +74,7 @@ DEPENDENCIES jekyll-seo-tag jekyll-sitemap kramdown-parser-gfm + webrick (~> 1.8) BUNDLED WITH 2.1.4 diff --git a/_includes/head.html b/_includes/head.html index 83646a80..0376172a 100755 --- a/_includes/head.html +++ b/_includes/head.html @@ -14,7 +14,25 @@ - + + + + + + + + + + + + + + + + + + + {% feed_meta %} diff --git a/_includes/header.html b/_includes/header.html index 34288ec3..ee927a54 100755 --- a/_includes/header.html +++ b/_includes/header.html @@ -14,6 +14,13 @@ {% endif %} {% endfor %} +
+ +
============================================================================================================================================================== diff --git a/assets/css/_footer.scss b/assets/css/_footer.scss index 2ab06e16..dba60c02 100644 --- a/assets/css/_footer.scss +++ b/assets/css/_footer.scss @@ -13,7 +13,7 @@ justify-content: space-between; a { - color: #333; + color: var(--text, #fff); opacity: 0.4; &:hover { diff --git a/assets/css/_global.scss b/assets/css/_global.scss index 023021ed..4865cf3f 100644 --- a/assets/css/_global.scss +++ b/assets/css/_global.scss @@ -1,9 +1,39 @@ +:root { + --bg: #{$background-color}; + --text: #{$font-color}; + --primary: #{$primary-color}; +} + +@media (prefers-color-scheme: dark) { + :root { + --bg: #111; + --text: #eaeaea; + --primary: #ffb84d; + } +} + +*, *::before, *::after { + transition: color .4s ease, background-color .4s ease, border-color .4s ease, stroke .4s ease, fill .4s ease; +} + +html.dark { + --bg: #0f0f0f; + --text: #e9e9e9; + --primary: #ffb84d; +} + +html.light { + --bg: #ffffff; + --text: #222222; + --primary: #{$primary-color}; +} + html, body { - font-family: $body-font; + font-family: #{$body-font}, monospace; font-size: 16px; line-height: 1.5; - background-color: $background-color; - color: $font-color; + background-color: var(--bg, #{$background-color}); + color: var(--text, #{$font-color}); @media (max-width: $large-screen) { font-size: 14px; diff --git a/assets/css/_header.scss b/assets/css/_header.scss index 9e496370..e4b7e759 100644 --- a/assets/css/_header.scss +++ b/assets/css/_header.scss @@ -31,7 +31,7 @@ } a { - color: #333; + color: var(--text, #fff); opacity: 0.4; margin-left: 1.5rem; @@ -41,4 +41,46 @@ } } } + + &-toggle { + margin-left: 1rem; + + button { + background: transparent; + border: none; + cursor: pointer; + font-size: 1.4rem; + color: var(--text, #333); + display: inline-flex; + align-items: center; + padding: 0.25rem 0.5rem; + transition: color .18s ease, opacity .18s ease; + + &:hover, + &:focus { + opacity: 0.9; + outline: none; + } + + .theme-icon { + display: inline-flex; + align-items: center; + justify-content: center; + + svg { + width: 20px; + height: 20px; + color: var(--text, #333); + stroke: currentColor; + fill: none; + transition: transform .4s cubic-bezier(.2,.9,.2,1), opacity .3s ease; + transform-origin: 50% 50%; + } + + &.anim svg { + transform: rotate(22deg) scale(1.12); + } + } + } + } } diff --git a/assets/img/icons.svg b/assets/img/icons.svg new file mode 100644 index 00000000..b1455db9 --- /dev/null +++ b/assets/img/icons.svg @@ -0,0 +1,18 @@ + + + + + + + + + + + + + + + + + + diff --git a/assets/img/moon.svg b/assets/img/moon.svg new file mode 100644 index 00000000..07b397d1 --- /dev/null +++ b/assets/img/moon.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/img/sun.svg b/assets/img/sun.svg new file mode 100644 index 00000000..65c97c7c --- /dev/null +++ b/assets/img/sun.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/js/script.js b/assets/js/script.js index fbd54899..94fcbb05 100755 --- a/assets/js/script.js +++ b/assets/js/script.js @@ -1 +1,74 @@ -// Place JS here \ No newline at end of file +;(function () { + const STORAGE_KEY = 'poabitdevs-theme'; + const darkClass = 'dark'; + const btn = document.getElementById('theme-toggle'); + + if (!btn) return; + + function systemPrefersDark() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches; + } + + function applyTheme(isDark, explicit = false) { + const root = document.documentElement; + if (explicit) { + if (isDark) { + root.classList.add('dark'); + root.classList.remove('light'); + } else { + root.classList.add('light'); + root.classList.remove('dark'); + } + } else { + if (isDark) root.classList.add(darkClass); + else root.classList.remove(darkClass); + } + + const use = btn.querySelector('.theme-icon use'); + if (use) { + try { + use.setAttribute('href', '#' + (isDark ? 'moon' : 'sun')); + } catch (e) { + use.setAttributeNS('http://www.w3.org/1999/xlink', 'xlink:href', '#' + (isDark ? 'moon' : 'sun')); + } + } + + btn.setAttribute('aria-pressed', String(isDark)); + } + + function loadTheme() { + const stored = localStorage.getItem(STORAGE_KEY); + if (stored === 'dark') return { isDark: true, explicit: true }; + if (stored === 'light') return { isDark: false, explicit: true }; + return { isDark: systemPrefersDark(), explicit: false }; + } + + const initial = loadTheme(); + applyTheme(initial.isDark, initial.explicit); + + btn.addEventListener('click', () => { + const isDark = document.documentElement.classList.toggle(darkClass); + if (isDark) { + document.documentElement.classList.remove('light'); + document.documentElement.classList.add('dark'); + } else { + document.documentElement.classList.remove('dark'); + document.documentElement.classList.add('light'); + } + localStorage.setItem(STORAGE_KEY, isDark ? 'dark' : 'light'); + applyTheme(isDark); + + const icon = btn.querySelector('.theme-icon'); + if (icon) { + icon.classList.add('anim'); + setTimeout(() => icon.classList.remove('anim'), 320); + } + }); + + if (window.matchMedia) { + const mq = window.matchMedia('(prefers-color-scheme: dark)'); + mq.addEventListener && mq.addEventListener('change', (e) => { + if (!localStorage.getItem(STORAGE_KEY)) applyTheme(e.matches); + }); + } +})(); \ No newline at end of file