1
0
mirror of https://github.com/cotes2020/jekyll-theme-chirpy.git synced 2026-06-21 23:38:39 +00:00

feat(theme): persist user theme preferences (#2756)

- Migrate theme persistence from `sessionStorage` to `localStorage`
- Rename theme HTML attribute to `data-bs-theme` for Bootstrap compatibility
- Trim and compile CSS according to the chosen theme mode
This commit is contained in:
Cotes Chung
2026-06-17 23:20:12 +08:00
committed by GitHub
parent ceb2a41463
commit 7496dd41fa
51 changed files with 541 additions and 153 deletions
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: يتوفر اصدار جديد للمحتوى.
update: تحديث
theme:
light: فاتح
dark: داكن
system: النظام
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Налична е нова версия на съдържанието.
update: Обнови
theme:
light: Светла
dark: Тъмна
system: Системна
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Hi ha una nova versió de contingut disponible.
update: Actualitzar
theme:
light: Clar
dark: Fosc
system: Sistema
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Je k dispozici nová verze obsahu.
update: Aktualizace
theme:
light: Světlý
dark: Tmavý
system: Systém
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: En ny version af indholdet er fundet!
update: Opdater
theme:
light: Lys
dark: Mørk
system: System
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: Eine neue Version ist verfügbar.
update: Neue Version
theme:
light: Hell
dark: Dunkel
system: System
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: ޔޫ ވާރޝަން ހުރިހާ.
update: އޮޕްޑޭޓް
theme:
light: އަލި
dark: އަނދިރި
system: ސިސްޓަމް
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Υπάρχει διαθέσιμη μια νέα έκδοση του περιεχομένου.
update: Ενημέρωση
theme:
light: Φωτεινό
dark: Σκοτεινό
system: Σύστημα
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: A new version of content is available.
update: Update
theme:
light: Light
dark: Dark
system: System
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Hay una nueva versión de contenido disponible.
update: Actualizar
theme:
light: Claro
dark: Oscuro
system: Sistema
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: نسخه جدیدی از محتوا موجود است.
update: به‌روزرسانی
theme:
light: روشن
dark: تیره
system: سیستم
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: Uusi versio sisällöstä on saatavilla.
update: Päivitä
theme:
light: Vaalea
dark: Tumma
system: Järjestelmä
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Une nouvelle version du contenu est disponible.
update: Mise à jour
theme:
light: Clair
dark: Sombre
system: Système
# ----- Posts related labels -----
post:
+5
View File
@@ -50,6 +50,11 @@ notification:
update_found: Elérhető a tartalom új verziója.
update: Frissítés
theme:
light: Világos
dark: Sötét
system: Rendszer
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Versi konten baru tersedia.
update: Perbarui
theme:
light: Terang
dark: Gelap
system: Sistem
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: Nuova versione del contenuto disponibile.
update: Aggiornamento
theme:
light: Chiaro
dark: Scuro
system: Sistema
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: 新しいバージョンが利用可能です。
update: 更新
theme:
light: ライト
dark: ダーク
system: システム
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: 새 버전의 콘텐츠를 사용할 수 있습니다.
update: 업데이트
theme:
light: 라이트
dark: 다크
system: 시스템
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: وەشانێکی نوێی ناوەڕۆک بەردەستە.
update: نوێکردنەوە
theme:
light: ڕووناک
dark: تاریک
system: سیستەم
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: အကြောင်းအရာဗားရှင်းအသစ်ကို ရနိုင်ပါပြီ။
update: အပ်ဒိတ်
theme:
light: အလင်း
dark: အမှောင်
system: စနစ်
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Nieuwe versie van inhoud beschikbaar.
update: Update
theme:
light: Licht
dark: Donker
system: Systeem
# ----- Posts related labels -----
post:
written_by: Door
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: نوې نسخه شتون لري.
update: تازه
theme:
light: روښانه
dark: تیاره
system: سیستم
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Uma nova versão do conteúdo está disponível.
update: atualização
theme:
light: Claro
dark: Escuro
system: Sistema
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: Доступна новая версия контента.
update: Обновить
theme:
light: Светлая
dark: Темная
system: Системная
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Novejša različica vsebine je na voljo. #A new version of content is available.
update: Posodobi #Update
theme:
light: Svetla
dark: Temna
system: Sistemska
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Det finns en ny version av innehållet.
update: Uppdatera sidan
theme:
light: Ljust
dark: Mörkt
system: System
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: มีเวอร์ชันใหม่ของเนื้อหา
update: อัปเดต
theme:
light: สว่าง
dark: มืด
system: ระบบ
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: İçeriğin yeni bir sürümü mevcut.
update: Güncelle
theme:
light: Açık
dark: Koyu
system: Sistem
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: Доступна нова версія вмісту.
update: Оновлення
theme:
light: Світла
dark: Темна
system: Системна
# ----- Posts related labels -----
post:
+5
View File
@@ -49,6 +49,11 @@ notification:
update_found: نیا مواد دستیاب ہے۔
update: اپ ڈیٹ
theme:
light: روشن
dark: تاریک
system: سسٹم
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: Đã có phiên bản mới của nội dung.
update: Cập nhật
theme:
light: Sáng
dark: Tối
system: Hệ thống
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: 发现新版本的内容。
update: 更新
theme:
light: 浅色
dark: 深色
system: 跟随系统
# ----- Posts related labels -----
post:
+5
View File
@@ -48,6 +48,11 @@ notification:
update_found: 發現新版本更新。
update: 更新
theme:
light: 淺色
dark: 深色
system: 跟隨系統
# ----- Posts related labels -----
post:
+3
View File
@@ -19,6 +19,9 @@ webfonts: https://fonts.googleapis.com/css2?family=Lato:wght@300;400&family=Sour
# Libraries
bootstrap:
css: https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css
toc:
css: https://cdn.jsdelivr.net/npm/tocbot@4/dist/tocbot.min.css
js: https://cdn.jsdelivr.net/npm/tocbot@4/dist/tocbot.min.js
+2 -2
View File
@@ -19,7 +19,7 @@
{%- comment -%} Auto switch theme {%- endcomment -%}
function reloadDisqus(event) {
if (event.source === window && event.data && event.data.id === Theme.ID) {
if (event.source === window && event.data && event.data.id === Theme.eventId) {
{%- comment -%} Disqus hasn't been loaded {%- endcomment -%}
if (typeof DISQUS === 'undefined') {
return;
@@ -33,7 +33,7 @@
addDisqus();
if (Theme.switchable) {
if (Theme.isToggleable) {
addEventListener('message', reloadDisqus);
}
+5 -4
View File
@@ -1,8 +1,8 @@
<!-- https://giscus.app/ -->
<script>
(function () {
const themeMapper = Theme.getThemeMapper('light', 'dark_dimmed');
const initTheme = themeMapper[Theme.visualState];
const themeMap = Theme.newThemeMap('light', 'dark_dimmed');
const initTheme = themeMap[Theme.resolvedTheme];
let lang = '{{ site.comments.giscus.lang | default: lang }}';
{%- comment -%} https://github.com/giscus/giscus/tree/main/locales {%- endcomment -%}
@@ -37,8 +37,9 @@
$footer.insertAdjacentElement("beforebegin", giscusNode);
addEventListener('message', (event) => {
if (event.source === window && event.data && event.data.id === Theme.ID) {
const newTheme = themeMapper[Theme.visualState];
if (event.source === window && event.data && event.data.id === Theme.eventId) {
const newTheme = themeMap[Theme.resolvedTheme];
const message = {
setConfig: {
theme: newTheme
+4 -4
View File
@@ -2,8 +2,8 @@
<script>
(function () {
const origin = 'https://utteranc.es';
const themeMapper = Theme.getThemeMapper('github-light', 'github-dark');
const initTheme = themeMapper[Theme.visualState];
const themeMap = Theme.newThemeMap('github-light', 'github-dark');
const initTheme = themeMap[Theme.resolvedTheme];
let script = document.createElement('script');
script.src = 'https://utteranc.es/client.js';
@@ -22,8 +22,8 @@
{%- comment -%}
Credit to <https://github.com/utterance/utterances/issues/170#issuecomment-594036347>
{%- endcomment -%}
if (event.source === window && event.data && event.data.id === Theme.ID) {
newTheme = themeMapper[Theme.visualState];
if (event.source === window && event.data && event.data.id === Theme.eventId) {
newTheme = themeMap[Theme.resolvedTheme];
const message = {
type: 'set-theme',
+1 -1
View File
@@ -81,7 +81,7 @@
<!-- Bootstrap -->
{% unless jekyll.environment == 'production' %}
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap@5/dist/css/bootstrap.min.css">
<link rel="stylesheet" href="{{ site.data.origin.cors.bootstrap.css }}">
{% endunless %}
<!-- Theme style -->
+45 -3
View File
@@ -41,10 +41,52 @@
</nav>
<div class="sidebar-bottom d-flex flex-wrap align-items-center w-100">
{% unless site.theme_mode %}
<button type="button" class="btn btn-link nav-link" aria-label="Switch Mode" id="mode-toggle">
<i class="fas fa-adjust"></i>
{% unless site.theme_mode == 'light' or site.theme_mode == 'dark' %}
{%- capture icon_system -%}
<i class="fa-solid fa-display" data-theme-mode="system"></i>
{%- endcapture -%}
{%- capture icon_light -%}
<i class="fa-regular fa-sun" data-theme-mode="light"></i>
{%- endcapture -%}
{%- capture icon_dark -%}
<i class="fa-regular fa-moon" data-theme-mode="dark"></i>
{%- endcapture -%}
<div class="btn-group dropup">
<button
type="button"
class="btn btn-link nav-link"
aria-label="Switch Mode"
id="mode-toggle"
data-bs-toggle="dropdown"
>
{{- icon_light -}}
{{- icon_dark -}}
{{- icon_system -}}
</button>
<ul class="dropdown-menu rounded-3 mb-1 p-1">
<li>
<button class="dropdown-item d-flex align-items-center" type="button" data-theme-mode="light">
{{- icon_light -}}
{{- site.data.locales[lang].theme.light -}}
</button>
</li>
<li>
<button class="dropdown-item d-flex align-items-center" type="button" data-theme-mode="dark">
{{- icon_dark -}}
{{- site.data.locales[lang].theme.dark -}}
</button>
</li>
<li>
<button class="dropdown-item d-flex align-items-center" type="button" data-theme-mode="system">
{{- icon_system -}}
{{- site.data.locales[lang].theme.system -}}
</button>
</li>
</ul>
</div>
{% if site.data.contact.size > 0 %}
<span class="icon-border"></span>
+7 -3
View File
@@ -32,17 +32,21 @@ export function imgPopup() {
document.querySelector('.popup.dark') === null
);
if (Theme.visualState === Theme.DARK) {
if (Theme.isDark) {
selector = darkImages;
}
let current = GLightbox({ selector: `${selector}` });
if (hasDualImages && Theme.switchable) {
if (hasDualImages && Theme.isToggleable) {
let reverse = null;
window.addEventListener('message', (event) => {
if (event.source === window && event.data && event.data.id === Theme.ID) {
if (
event.source === window &&
event.data &&
event.data.id === Theme.eventId
) {
[current, reverse] = swapImages(current, reverse);
}
});
+9 -5
View File
@@ -3,10 +3,14 @@
*/
const MERMAID = 'mermaid';
const themeMapper = Theme.getThemeMapper('default', 'dark');
const themeMap = Theme.newThemeMap('default', 'dark');
function refreshTheme(event) {
if (event.source === window && event.data && event.data.id === Theme.ID) {
if (
event.source === window &&
event.data &&
event.data.id === Theme.eventId
) {
// Re-render the SVG <https://github.com/mermaid-js/mermaid/issues/311#issuecomment-332557344>
const mermaidList = document.getElementsByClassName(MERMAID);
@@ -16,7 +20,7 @@ function refreshTheme(event) {
elem.removeAttribute('data-processed');
});
const newTheme = themeMapper[Theme.visualState];
const newTheme = themeMap[Theme.resolvedTheme];
mermaid.initialize({ theme: newTheme });
mermaid.init(null, `.${MERMAID}`);
@@ -43,7 +47,7 @@ export function loadMermaid() {
return;
}
const initTheme = themeMapper[Theme.visualState];
const initTheme = themeMap[Theme.resolvedTheme];
let mermaidConf = {
theme: initTheme
@@ -54,7 +58,7 @@ export function loadMermaid() {
mermaid.initialize(mermaidConf);
if (Theme.switchable) {
if (Theme.isToggleable) {
window.addEventListener('message', refreshTheme);
}
}
+36 -5
View File
@@ -1,15 +1,46 @@
/**
* Add listener for theme mode toggle
* Sets up the mode toggle dropdown, allowing users to switch between light, dark, and system themes.
*
* Dependencies:
* - Theme (${JS_ROOT}/theme.js)
*/
const $toggle = document.getElementById('mode-toggle');
import 'bootstrap/js/src/dropdown.js';
const ACTIVE_CLASS = 'active';
const dropdown = document.querySelector('#mode-toggle + .dropdown-menu');
const activeMode = Theme.isSystemTheme
? Theme.Mode.SYSTEM
: Theme.resolvedTheme;
export function modeWatcher() {
if (!$toggle) {
if (!Theme.isToggleable) {
return;
}
$toggle.addEventListener('click', () => {
Theme.flip();
dropdown.querySelectorAll('.dropdown-item').forEach((option) => {
const mode = option.dataset.themeMode;
if (mode === activeMode) {
option.classList.add(ACTIVE_CLASS);
return;
}
});
dropdown.addEventListener('click', (event) => {
const current = event.target.closest('.dropdown-item');
if (!current) {
return;
}
const lastActive = dropdown.querySelector(`.${ACTIVE_CLASS}`);
if (lastActive === current) {
return;
}
lastActive.classList.remove(ACTIVE_CLASS);
current.classList.add(ACTIVE_CLASS);
Theme.update(current.dataset.themeMode);
});
}
+105 -87
View File
@@ -1,135 +1,153 @@
/**
* Theme management class
* A utility class that manages the site's theme mode.
*
* To reduce flickering during page load, this script should be loaded synchronously.
* Concepts:
* - Mode: dark, light, or system. The latter follows the operating system's preference.
* - Theme: The actual theme applied to the DOM, either dark or light. Determined by the mode or system preference.
*/
class Theme {
static #modeKey = 'mode';
static #modeAttr = 'data-mode';
static #darkMedia = window.matchMedia('(prefers-color-scheme: dark)');
static switchable = !document.documentElement.hasAttribute(this.#modeAttr);
/** @type {string} LocalStorage key for the selected theme mode. */
static #storageKey = 'theme';
static get DARK() {
return 'dark';
static Mode = Object.freeze({
DARK: 'dark',
LIGHT: 'light',
SYSTEM: 'system'
});
static #root = document.documentElement;
/** @type {MediaQueryList} System dark-mode preference query. */
static #mediaDark = window.matchMedia('(prefers-color-scheme: dark)');
/** @returns {string|null} The theme currently set on the DOM. */
static get #domTheme() {
return this.#root.dataset.bsTheme || null;
}
static get LIGHT() {
return 'light';
/** @returns {string|null} The theme stored on the client. */
static get #storedTheme() {
return localStorage.getItem(this.#storageKey);
}
/** @returns {string} The theme preferred by the operating system. */
static get #systemTheme() {
return this.#prefersDark ? this.Mode.DARK : this.Mode.LIGHT;
}
/** @returns {boolean} Whether the operating system prefers dark mode. */
static get #prefersDark() {
return this.#mediaDark.matches;
}
/**
* @returns {string} Theme mode identifier
*/
static get ID() {
return 'theme-mode';
}
/**
* Gets the current visual state of the theme.
* Applies a theme and optionally persists it as a user preference.
*
* @returns {string} The current visual state, either the mode if it exists,
* or the system dark mode state ('dark' or 'light').
* @param {'light'|'dark'} theme
* @param {{ persist?: boolean, domPersist?: boolean }} [options]
* - `persist`: Whether the theme is persisted in localStorage.
* - `domPersist`: Whether the theme is persisted in data attributes on the DOM.
*/
static get visualState() {
if (this.#hasMode) {
return this.#mode;
} else {
return this.#sysDark ? this.DARK : this.LIGHT;
static #apply(theme, { persist = false, domPersist = false } = {}) {
this.#root.dataset.bsTheme = theme;
if (persist) {
localStorage.setItem(this.#storageKey, theme);
}
if (domPersist || persist) {
this.#root.toggleAttribute('data-theme-persisted', true);
}
}
static get #mode() {
return (
sessionStorage.getItem(this.#modeKey) ||
document.documentElement.getAttribute(this.#modeAttr)
);
/** Removes the stored user preference. */
static #clearStorage() {
localStorage.removeItem(this.#storageKey);
this.#root.toggleAttribute('data-theme-persisted', false);
}
static get #isDarkMode() {
return this.#mode === this.DARK;
/** Broadcasts a theme change event to dependent modules. */
static #notify() {
window.postMessage({ id: this.eventId }, '*');
}
static get #hasMode() {
return this.#mode !== null;
/** @type {boolean} Whether the current page allows theme toggling. */
static isToggleable = this.#domTheme === null;
static eventId = 'theme-updated';
/** @returns {string} Resolved theme, falling back to the system preference. */
static get resolvedTheme() {
return this.#storedTheme || this.#systemTheme;
}
static get #sysDark() {
return this.#darkMedia.matches;
/** @returns {boolean} Whether the theme is determined by the system preference. */
static get isSystemTheme() {
return this.#storedTheme === null;
}
/** @returns {boolean} Whether the resolved theme is dark. */
static get isDark() {
return this.resolvedTheme === this.Mode.DARK;
}
/**
* Maps theme modes to provided values
* @param {string} light Value for light mode
* @param {string} dark Value for dark mode
* @returns {Object} Mapped values
* Creates a mode-indexed value map.
*
* @template T
* @param {T} light Value for light mode.
* @param {T} dark Value for dark mode.
* @returns {{ light: T, dark: T }}
*/
static getThemeMapper(light, dark) {
static newThemeMap(light, dark) {
return {
[this.LIGHT]: light,
[this.DARK]: dark
[this.Mode.LIGHT]: light,
[this.Mode.DARK]: dark
};
}
/**
* Initializes the theme based on system preferences or stored mode
*/
/** Initializes the theme from the stored value or system preference. */
static init() {
if (!this.switchable) {
if (!this.isToggleable) {
this.#clearStorage();
return;
}
this.#darkMedia.addEventListener('change', () => {
const lastMode = this.#mode;
this.#clearMode();
const storedTheme = this.#storedTheme;
if (lastMode !== this.visualState) {
this.#notify();
if (storedTheme) {
this.#apply(storedTheme, { domPersist: true });
} else {
this.#apply(this.#systemTheme);
}
this.#mediaDark.addEventListener('change', () => {
if (this.#storedTheme) {
return;
}
this.#apply(this.#systemTheme);
this.#notify();
});
if (!this.#hasMode) {
return;
}
if (this.#isDarkMode) {
this.#setDark();
} else {
this.#setLight();
}
}
/**
* Flips the current theme mode
* Updates the theme by the specified mode.
*
* @param {'light'|'dark'|'system'} mode
*/
static flip() {
if (this.#hasMode) {
this.#clearMode();
} else {
this.#sysDark ? this.#setLight() : this.#setDark();
}
static update(mode) {
const newTheme = mode === this.Mode.SYSTEM ? this.#systemTheme : mode;
if (newTheme !== this.resolvedTheme) {
this.#notify();
}
static #setDark() {
document.documentElement.setAttribute(this.#modeAttr, this.DARK);
sessionStorage.setItem(this.#modeKey, this.DARK);
}
this.#apply(newTheme, { persist: mode !== this.Mode.SYSTEM });
static #setLight() {
document.documentElement.setAttribute(this.#modeAttr, this.LIGHT);
sessionStorage.setItem(this.#modeKey, this.LIGHT);
if (mode === this.Mode.SYSTEM) {
this.#clearStorage();
}
static #clearMode() {
document.documentElement.removeAttribute(this.#modeAttr);
sessionStorage.removeItem(this.#modeKey);
}
/**
* Notifies other plugins that the theme mode has changed
*/
static #notify() {
window.postMessage({ id: this.ID }, '*');
}
}
+6 -5
View File
@@ -8,12 +8,13 @@ layout: compress
{% include lang.html %}
{% if site.theme_mode %}
{% capture prefer_mode %}data-mode="{{ site.theme_mode }}"{% endcapture %}
{% endif %}
<!-- `site.alt_lang` can specify a language different from the UI -->
<html lang="{{ page.lang | default: site.alt_lang | default: site.lang }}" {{ prefer_mode }}>
<html
lang="{{ page.lang | default: site.alt_lang | default: site.lang }}"
{%- if site.theme_mode == 'light' or site.theme_mode == 'dark' -%}
data-bs-theme="{{ site.theme_mode }}"
{%- endif -%}
>
{% include head.html lang=lang %}
<body>
+6
View File
@@ -1,3 +1,9 @@
@mixin color-scheme($mode) {
@media (prefers-color-scheme: #{$mode}) {
@content;
}
}
@mixin text-ellipsis {
overflow: hidden;
text-overflow: ellipsis;
+6
View File
@@ -28,3 +28,9 @@ $code-icon-width: 1.75rem !default;
$font-family-base: 'Source Sans Pro', 'Microsoft Yahei', sans-serif !default;
$font-family-heading: Lato, 'Microsoft Yahei', sans-serif !default;
/* Theme mode settings */
$theme-attr: 'data-bs-theme'; /* the attribute used to indicate the resolved theme */
$theme-options: light, dark;
$theme: null !default; /* set by Jekyll site configuration */
+25 -18
View File
@@ -1,3 +1,4 @@
@use 'sass:list';
@use '../abstracts/variables' as v;
@use '../abstracts/breakpoints' as bp;
@use '../abstracts/mixins' as mx;
@@ -5,32 +6,36 @@
@use '../themes/light';
@use '../themes/dark';
:root {
font-size: 16px;
$enable-dual: not list.index(v.$theme-options, v.$theme);
$enable-light: v.$theme == light or $enable-dual;
$enable-dark: v.$theme == dark or $enable-dual;
@if $enable-light {
:root[#{v.$theme-attr}='light'] {
@include light.styles;
}
}
html {
@media (prefers-color-scheme: light) {
&:not([data-mode]),
&[data-mode='light'] {
@if $enable-dark {
:root[#{v.$theme-attr}='dark'] {
@include dark.styles;
}
}
@if $enable-dual {
:root:not([#{v.$theme-attr}]) {
@include mx.color-scheme(light) {
@include light.styles;
}
&[data-mode='dark'] {
@include mx.color-scheme(dark) {
@include dark.styles;
}
}
}
@media (prefers-color-scheme: dark) {
&:not([data-mode]),
&[data-mode='dark'] {
@include dark.styles;
}
&[data-mode='light'] {
@include light.styles;
}
}
:root {
font-size: 16px;
@include bp.lg {
overflow-y: scroll;
@@ -369,7 +374,9 @@ main {
box-shadow: none;
border-color: var(--input-focus-border-color) !important;
background: center !important;
transition: background-color 0.15s ease-in-out, border-color 0.15s ease-in-out;
transition:
background-color 0.15s ease-in-out,
border-color 0.15s ease-in-out;
}
.left {
+94 -8
View File
@@ -17,7 +17,6 @@ $sidebar-display: 'sidebar-display'; /* the attribute for sidebar display */
overflow-y: auto;
width: v.$sidebar-width;
background: var(--sidebar-bg);
border-right: 1px solid var(--sidebar-border-color);
/* Hide scrollbar for IE, Edge and Firefox */
-ms-overflow-style: none; /* IE and Edge */
@@ -105,6 +104,8 @@ $sidebar-display: 'sidebar-display'; /* the attribute for sidebar display */
letter-spacing: 0.25px;
margin-top: 1.25rem;
margin-bottom: 0.5rem;
width: -webkit-fit-content;
width: -moz-fit-content;
width: fit-content;
color: var(--site-title-color);
}
@@ -207,7 +208,7 @@ $sidebar-display: 'sidebar-display'; /* the attribute for sidebar display */
}
}
a {
> a {
@extend %button;
@extend %sidebar-link-hover;
@extend %clickable-transition;
@@ -227,13 +228,102 @@ $sidebar-display: 'sidebar-display'; /* the attribute for sidebar display */
#mode-toggle {
@extend %button;
@extend %sidebar-links;
@extend %sidebar-link-hover;
@extend %clickable-transition;
> i {
display: none;
@at-root :root[data-bs-theme='light'][data-theme-persisted]
&[data-theme-mode='light'] {
display: block;
}
@at-root :root[data-bs-theme='dark'][data-theme-persisted]
&[data-theme-mode='dark'] {
display: block;
}
@at-root :root:not([data-theme-persisted]) &[data-theme-mode='system'] {
display: block;
}
}
@-webkit-keyframes menu-pop {
from {
opacity: 0;
translate: 0 0.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
@keyframes menu-pop {
from {
opacity: 0;
translate: 0 0.5rem;
}
to {
opacity: 1;
translate: 0 0;
}
}
+ .dropdown-menu {
background-color: var(--menu-bg);
border-color: var(--menu-border-color);
box-shadow: var(--menu-shadow-color) 0 1px 4px;
border-radius: 0.75rem !important;
&.show {
display: flex;
flex-direction: column;
gap: 0.25rem;
left: -0.25rem !important;
-webkit-animation: menu-pop 0.2s ease-out;
animation: menu-pop 0.2s ease-out;
}
.dropdown-item {
border-radius: 0.5rem;
color: var(--sidebar-muted-color);
font-size: 90%;
&.active {
font-weight: 600;
color: var(--menu-active-color);
&::after {
content: '\f00c';
font: var(--fa-font-solid);
font-size: 0.75rem;
color: var(--sidebar-btn-color);
margin-left: auto;
padding-left: 1rem;
}
}
&:active,
&:hover,
&.active {
background-color: var(--menu-highlight-bg);
}
> i {
color: var(--sidebar-btn-color);
margin-right: 0.5rem;
}
}
}
}
.icon-border {
@extend %no-cursor;
@include mx.ml-mr(calc((v.$sb-btn-gap - $btn-border-width) / 2));
@include mx.ml-mr(0.6rem);
background-color: var(--sidebar-btn-color);
content: '';
@@ -241,10 +331,6 @@ $sidebar-display: 'sidebar-display'; /* the attribute for sidebar display */
height: $btn-border-width;
border-radius: 50%;
margin-bottom: $btn-mb;
@include bp.xxxl {
@include mx.ml-mr(calc((v.$sb-btn-gap-lg - $btn-border-width) / 2));
}
}
} /* .sidebar-bottom */
} /* #sidebar */
+7 -2
View File
@@ -30,6 +30,11 @@
rgb(58 55 55 / 40%) 50%,
rgb(255 255 255 / 0%) 100%
);
--menu-bg: rgb(30 30 30);
--menu-border-color: rgb(77 77 77 / 60%);
--menu-shadow-color: rgb(4 4 4 / 42%);
--menu-active-color: rgb(240 248 255 / 63%);
--menu-highlight-bg: rgb(90 91 92 / 12%);
/* Sidebar */
--site-title-color: #717070;
@@ -67,8 +72,8 @@
--btn-share-hover-color: #bfc1ca;
--card-bg: #1e1e1e;
--card-hover-bg: #464d51;
--card-shadow: rgb(21 21 21 / 72%) 0 6px 18px 0,
rgb(137 135 135 / 24%) 0 0 0 1px;
--card-shadow:
rgb(21 21 21 / 72%) 0 6px 18px 0, rgb(137 135 135 / 24%) 0 0 0 1px;
--kbd-wrap-color: #6a6a6a;
--kbd-text-color: #d3d3d3;
--kbd-bg-color: #242424;
+7 -2
View File
@@ -27,6 +27,11 @@
rgb(232 230 230 / 100%) 50%,
rgb(250 250 250 / 0%) 100%
);
--menu-bg: white;
--menu-border-color: white;
--menu-shadow-color: rgb(0 0 0 / 16%);
--menu-active-color: rgb(91 91 91);
--menu-highlight-bg: rgb(243 244 245 / 50%);
/* Sidebar */
--site-title-color: rgb(113 113 113);
@@ -59,8 +64,8 @@
--btn-share-hover-color: #0d6efd;
--card-bg: white;
--card-hover-bg: #e2e2e2;
--card-shadow: rgb(104 104 104 / 5%) 0 2px 6px 0,
rgb(211 209 209 / 15%) 0 0 0 1px;
--card-shadow:
rgb(104 104 104 / 5%) 0 2px 6px 0, rgb(211 209 209 / 15%) 0 0 0 1px;
--footnote-target-bg: lightcyan;
--tb-odd-bg: #fbfcfd;
--tb-border-color: #eaeaea;
+4
View File
@@ -1,6 +1,10 @@
---
---
@use 'abstracts/variables' with (
$theme: '{{ site.theme_mode }}'
);
/* prettier-ignore */
@use 'main
{%- if jekyll.environment == 'production' -%}