' %}
+ {% assign content = content | replace: '', '' %}
+{% endif %}
diff --git a/_includes/notification.html b/_includes/notification.html
new file mode 100644
index 0000000..80049b0
--- /dev/null
+++ b/_includes/notification.html
@@ -0,0 +1,24 @@
+
diff --git a/_includes/origin-type.html b/_includes/origin-type.html
new file mode 100644
index 0000000..7f72012
--- /dev/null
+++ b/_includes/origin-type.html
@@ -0,0 +1,13 @@
+{% comment %} Site static assets origin type {% endcomment %}
+
+{% assign type = 'cors' %}
+
+{% if site.assets.self_host.enabled %}
+ {% if site.assets.self_host.env %}
+ {% if site.assets.self_host.env == jekyll.environment %}
+ {% assign type = 'basic' %}
+ {% endif %}
+ {% else %}
+ {% assign type = 'basic' %}
+ {% endif %}
+{% endif %}
diff --git a/_includes/pageviews/goatcounter.html b/_includes/pageviews/goatcounter.html
new file mode 100644
index 0000000..e62fd69
--- /dev/null
+++ b/_includes/pageviews/goatcounter.html
@@ -0,0 +1,19 @@
+
+
diff --git a/_includes/post-description.html b/_includes/post-description.html
new file mode 100644
index 0000000..6c40036
--- /dev/null
+++ b/_includes/post-description.html
@@ -0,0 +1,16 @@
+{%- comment -%}
+ Get post description or generate it from the post content.
+{%- endcomment -%}
+
+{%- assign max_length = include.max_length | default: 200 -%}
+
+{%- capture description -%}
+{%- if post.description -%}
+ {{- post.description -}}
+{%- else -%}
+ {%- include no-linenos.html content=post.content -%}
+ {{- content | markdownify | strip_html -}}
+{%- endif -%}
+{%- endcapture -%}
+
+{{- description | strip | truncate: max_length | escape -}}
diff --git a/_includes/post-nav.html b/_includes/post-nav.html
new file mode 100644
index 0000000..736bec3
--- /dev/null
+++ b/_includes/post-nav.html
@@ -0,0 +1,34 @@
+
+
+
diff --git a/_includes/post-paginator.html b/_includes/post-paginator.html
new file mode 100644
index 0000000..c74e978
--- /dev/null
+++ b/_includes/post-paginator.html
@@ -0,0 +1,91 @@
+
+
+
+
diff --git a/_includes/post-sharing.html b/_includes/post-sharing.html
new file mode 100644
index 0000000..d894199
--- /dev/null
+++ b/_includes/post-sharing.html
@@ -0,0 +1,52 @@
+
+
+
+ {{ site.data.locales[include.lang].post.share }}
+
+ {% capture title %}{{ page.title }} - {{ site.title }}{% endcapture %}
+ {% assign title = title | uri_escape %}
+ {% assign url = page.url | absolute_url | url_encode %}
+
+ {% for share in site.data.share.platforms -%}
+ {%- capture tooltip -%}
+ data-bs-toggle="tooltip" data-bs-placement="top" title="{{ share.type }}" aria-label="{{ share.type }}"
+ {%- endcapture -%}
+
+ {% if share.type == 'Mastodon' %}
+
+
+
+ {% continue %}
+ {% endif %}
+
+ {% assign link = share.link | replace: 'TITLE', title | replace: 'URL', url %}
+
+
+
+
+ {% endfor %}
+
+
+
+
diff --git a/_includes/read-time.html b/_includes/read-time.html
new file mode 100644
index 0000000..9952410
--- /dev/null
+++ b/_includes/read-time.html
@@ -0,0 +1,37 @@
+
+
+{% assign words = include.content | strip_html | number_of_words: 'auto' %}
+
+
+
+{% assign wpm = 180 %}
+{% assign min_time = 1 %}
+
+{% assign read_time = words | divided_by: wpm %}
+
+{% unless read_time > 0 %}
+ {% assign read_time = min_time %}
+{% endunless %}
+
+{% capture read_prompt %}
+ {{- site.data.locales[include.lang].post.read_time.prompt -}}
+{% endcapture %}
+
+
+
+
+ {{- read_time -}}
+ {{ ' ' }}
+ {{- site.data.locales[include.lang].post.read_time.unit -}}
+
+ {%- if include.prompt -%}
+ {%- assign _prompt_words = read_prompt | number_of_words: 'auto' -%}
+ {%- unless _prompt_words > 1 -%}{{ ' ' }}{%- endunless -%}
+ {{ read_prompt }}
+ {%- endif -%}
+
diff --git a/_includes/refactor-content.html b/_includes/refactor-content.html
new file mode 100644
index 0000000..8d298cd
--- /dev/null
+++ b/_includes/refactor-content.html
@@ -0,0 +1,255 @@
+
+
+{% assign _content = include.content %}
+
+
+
+{% if _content contains ''
+ | replace: '', ''
+ | replace: '
', ''
+ %}
+{% endif %}
+
+
+
+{% if _content contains '' %}
+ {% assign _content = _content
+ | replace: '', ''
+ %}
+{% endif %}
+
+
+
+{% if _content contains '',
+ ''
+ | replace: '',
+ ''
+ %}
+{% endif %}
+
+
+
+{% assign IMG_TAG = ' ' | first %}
+ {% assign _right = _img_snippet | remove: _left %}
+
+ {% unless _left contains 'src=' %}
+ {% continue %}
+ {% endunless %}
+
+ {% assign _left = _left | remove: ' /' | replace: ' w=', ' width=' | replace: ' h=', ' height=' %}
+ {% assign _attrs = _left | split: '" ' %}
+
+ {% assign _src = null %}
+ {% assign _lqip = null %}
+ {% assign _class = null %}
+
+ {% for _attr in _attrs %}
+ {% unless _attr contains '=' %}
+ {% continue %}
+ {% endunless %}
+
+ {% assign _pair = _attr | split: '="' %}
+ {% capture _key %}{{ _pair | first }}{% endcapture %}
+ {% capture _value %}{{ _pair | last | remove: '"' }}{% endcapture %}
+
+ {% case _key %}
+ {% when 'src' %}
+ {% assign _src = _value %}
+ {% when 'lqip' %}
+ {% assign _lqip = _value %}
+ {% when 'class' %}
+ {% assign _class = _value %}
+ {% endcase %}
+ {% endfor %}
+
+
+ {% if _class %}
+ {% capture _old_class %}class="{{ _class }}"{% endcapture %}
+ {% assign _left = _left | remove: _old_class %}
+ {% endif %}
+
+ {% assign _final_src = null %}
+ {% assign _lazyload = true %}
+
+ {%- capture _img_url -%}
+ {% include media-url.html src=_src subpath=page.media_subpath %}
+ {%- endcapture -%}
+
+ {% assign _path_prefix = _img_url | remove: _src %}
+
+ {% unless _src contains '//' %}
+ {% assign _final_src = _path_prefix | append: _src %}
+ {% assign _src_alt = 'src="' | append: _path_prefix %}
+ {% assign _left = _left | replace: 'src="', _src_alt %}
+ {% endunless %}
+
+ {% if _lqip %}
+ {% assign _lazyload = false %}
+ {% assign _class = _class | append: ' blur' %}
+
+ {% unless _lqip contains 'data:' %}
+ {% assign _lqip_alt = 'lqip="' | append: _path_prefix %}
+ {% assign _left = _left | replace: 'lqip="', _lqip_alt %}
+ {% endunless %}
+
+
+ {% assign _left = _left | replace: 'src=', 'data-src=' | replace: ' lqip=', ' data-lqip="true" src=' %}
+
+ {% else %}
+ {% assign _class = _class | append: ' shimmer' %}
+ {% endif %}
+
+
+ {% if _lazyload %}
+ {% assign _left = _left | append: ' loading="lazy"' %}
+ {% endif %}
+
+ {% if page.layout == 'home' %}
+
+ {% assign _wrapper_start = '' %}
+
+ {% assign _img_content = _img_content | append: _wrapper_start %}
+ {% assign _right = _right | prepend: '> ` is wrapped by `` -->
+ {% assign _parent = _right | slice: 1, 4 %}
+
+ {% if _parent == '' %}
+
+ {% assign _size = _img_content | size | minus: 1 %}
+ {% capture _class %}
+ class="img-link{% unless _lqip %} shimmer{% endunless %}"
+ {% endcapture %}
+ {% assign _img_content = _img_content | slice: 0, _size | append: _class | append: '>' %}
+
+ {% else %}
+
+ {% assign _wrapper_start = _final_src
+ | default: _src
+ | prepend: '
+ {% assign _img_content = _img_content | append: IMG_TAG | append: _left | append: _right %}
+ {% endfor %}
+
+ {% if _img_content %}
+ {% assign _content = _img_content %}
+ {% endif %}
+{% endif %}
+
+
+
+{% if _content contains '' %}
+ {% assign _code_spippets = _content | split: '' %}
+ {% assign _new_content = '' %}
+
+ {% for _snippet in _code_spippets %}
+ {% if forloop.last %}
+ {% assign _new_content = _new_content | append: _snippet %}
+
+ {% else %}
+ {% assign _left = _snippet | split: '><' | last %}
+
+ {% if _left contains 'file="' %}
+ {% assign _label_text = _left | split: 'file="' | last | split: '"' | first %}
+ {% assign _label_icon = 'far fa-file-code fa-fw' %}
+ {% else %}
+ {% assign _lang = _left | split: 'language-' | last | split: ' ' | first %}
+ {% capture _label_text %}{% include language-alias.html language=_lang %}{% endcapture %}
+ {% assign _label_icon = 'fas fa-code fa-fw small' %}
+ {% endif %}
+
+ {% capture _label %}
+
+ {% endcapture %}
+
+ {% assign _new_content = _new_content
+ | append: _snippet
+ | append: ''
+ | append: ''
+ %}
+ {% endif %}
+ {% endfor %}
+
+ {% assign _content = _new_content %}
+{% endif %}
+
+
+
+{% assign heading_levels = '2,3,4,5' | split: ',' %}
+{% assign _heading_content = _content %}
+
+{% for level in heading_levels %}
+ {% assign mark_start = ''
+ %}
+
+ {% assign left = snippet | split: mark_end | first %}
+ {% assign right = snippet | slice: left.size, snippet.size %}
+ {% assign left = left | replace_first: '">', '">' | append: '' %}
+
+ {% assign _new_content = _new_content | append: mark_start | append: left | append: anchor | append: right %}
+ {% endfor %}
+
+ {% assign _heading_content = _new_content %}
+ {% endif %}
+{% endfor %}
+
+{% assign _content = _heading_content %}
+
+
+{{ _content }}
diff --git a/_includes/related-posts.html b/_includes/related-posts.html
new file mode 100644
index 0000000..37a295b
--- /dev/null
+++ b/_includes/related-posts.html
@@ -0,0 +1,94 @@
+
+
+
+{% assign TOTAL_SIZE = 3 %}
+
+
+{% assign TAG_SCORE = 1 %}
+
+
+{% assign CATEGORY_SCORE = 0.5 %}
+
+{% assign SEPARATOR = ':' %}
+
+{% assign match_posts = '' | split: '' %}
+
+{% for category in page.categories %}
+ {% assign match_posts = match_posts | push: site.categories[category] | uniq %}
+{% endfor %}
+
+{% for tag in page.tags %}
+ {% assign match_posts = match_posts | push: site.tags[tag] | uniq %}
+{% endfor %}
+
+{% assign match_posts = match_posts | reverse %}
+{% assign last_index = match_posts.size | minus: 1 %}
+{% assign score_list = '' | split: '' %}
+
+{% for i in (0..last_index) %}
+ {% assign post = match_posts[i] %}
+
+ {% if post.url == page.url %}
+ {% continue %}
+ {% endif %}
+
+ {% assign score = 0 %}
+
+ {% for tag in post.tags %}
+ {% if page.tags contains tag %}
+ {% assign score = score | plus: TAG_SCORE %}
+ {% endif %}
+ {% endfor %}
+
+ {% for category in post.categories %}
+ {% if page.categories contains category %}
+ {% assign score = score | plus: CATEGORY_SCORE %}
+ {% endif %}
+ {% endfor %}
+
+ {% if score > 0 %}
+ {% capture score_item %}{{ score }}{{ SEPARATOR }}{{ i }}{% endcapture %}
+ {% assign score_list = score_list | push: score_item %}
+ {% endif %}
+{% endfor %}
+
+{% assign index_list = '' | split: '' %}
+
+{% if score_list.size > 0 %}
+ {% assign score_list = score_list | sort | reverse %}
+ {% for entry in score_list limit: TOTAL_SIZE %}
+ {% assign index = entry | split: SEPARATOR | last %}
+ {% assign index_list = index_list | push: index %}
+ {% endfor %}
+{% endif %}
+
+{% assign relate_posts = '' | split: '' %}
+
+{% for index in index_list %}
+ {% assign i = index | to_integer %}
+ {% assign relate_posts = relate_posts | push: match_posts[i] %}
+{% endfor %}
+
+{% if relate_posts.size > 0 %}
+
+
+{% endif %}
diff --git a/_includes/search-loader.html b/_includes/search-loader.html
new file mode 100644
index 0000000..2582580
--- /dev/null
+++ b/_includes/search-loader.html
@@ -0,0 +1,47 @@
+
+
+{% capture result_elem %}
+
+
+ {snippet}
+
+{% endcapture %}
+
+{% capture not_found %}{{ site.data.locales[include.lang].search.no_results }} {% endcapture %}
+
+
diff --git a/_includes/search-results.html b/_includes/search-results.html
new file mode 100644
index 0000000..00a3182
--- /dev/null
+++ b/_includes/search-results.html
@@ -0,0 +1,10 @@
+
+
+
+
+
+ {% include_cached trending-tags.html %}
+
+
+
+
diff --git a/_includes/sidebar.html b/_includes/sidebar.html
new file mode 100644
index 0000000..4f0bb8c
--- /dev/null
+++ b/_includes/sidebar.html
@@ -0,0 +1,99 @@
+
+
+
+
diff --git a/_includes/toc.html b/_includes/toc.html
new file mode 100644
index 0000000..15eb934
--- /dev/null
+++ b/_includes/toc.html
@@ -0,0 +1,13 @@
+{% assign enable_toc = false %}
+{% if site.toc and page.toc %}
+ {% if page.content contains '
+ {{- site.data.locales[include.lang].panel.toc -}}
+
+
+{% endif %}
diff --git a/_includes/topbar.html b/_includes/topbar.html
new file mode 100644
index 0000000..fd68d1f
--- /dev/null
+++ b/_includes/topbar.html
@@ -0,0 +1,77 @@
+
+
+
diff --git a/_includes/trending-tags.html b/_includes/trending-tags.html
new file mode 100644
index 0000000..57369f0
--- /dev/null
+++ b/_includes/trending-tags.html
@@ -0,0 +1,46 @@
+
+
+{% assign MAX = 10 %}
+
+{% assign size_list = '' | split: '' %}
+{% assign tag_list = '' | split: '' %}
+
+{% for tag in site.tags %}
+ {% assign size = tag | last | size %}
+ {% assign size_list = size_list | push: size %}
+
+ {% assign tag_str = tag | first | append: '::' | append: size %}
+ {% assign tag_list = tag_list | push: tag_str %}
+{% endfor %}
+
+{% assign size_list = size_list | sort | reverse %}
+
+{% assign tag_list = tag_list | sort_natural %}
+
+{% assign trending_tags = '' | split: '' %}
+
+{% for size in size_list limit: MAX %}
+ {% for tag_str in tag_list %}
+ {% assign tag = tag_str | split: '::' %}
+ {% assign tag_name = tag | first %}
+ {% assign tag_size = tag | last | plus: 0 %}
+ {% if tag_size == size %}
+ {% unless trending_tags contains tag_name %}
+ {% assign trending_tags = trending_tags | push: tag_name %}
+ {% break %}
+ {% endunless %}
+ {% endif %}
+ {% endfor %}
+{% endfor %}
+
+{% if trending_tags.size > 0 %}
+
+ {{- site.data.locales[include.lang].panel.trending_tags -}}
+
+ {% for tag_name in trending_tags %}
+ {% assign url = tag_name | slugify | url_encode | prepend: '/tags/' | append: '/' %}
+ {{ tag_name }}
+ {% endfor %}
+
+
+{% endif %}
diff --git a/_includes/update-list.html b/_includes/update-list.html
new file mode 100644
index 0000000..93684c3
--- /dev/null
+++ b/_includes/update-list.html
@@ -0,0 +1,40 @@
+
+
+{% assign MAX_SIZE = 5 %}
+
+{% assign all_list = '' | split: '' %}
+
+{% for post in site.posts %}
+ {% assign datetime = post.last_modified_at | default: post.date %}
+
+ {% capture elem %}
+ {{- datetime | date: "%Y%m%d%H%M%S" -}}::{{- forloop.index0 -}}
+ {% endcapture %}
+
+ {% assign all_list = all_list | push: elem %}
+{% endfor %}
+
+{% assign all_list = all_list | sort | reverse %}
+
+{% assign update_list = '' | split: '' %}
+
+{% for entry in all_list limit: MAX_SIZE %}
+ {% assign update_list = update_list | push: entry %}
+{% endfor %}
+
+{% if update_list.size > 0 %}
+
+ {{- site.data.locales[include.lang].panel.lastmod -}}
+
+ {% for item in update_list %}
+ {% assign index = item | split: '::' | last | plus: 0 %}
+ {% assign post = site.posts[index] %}
+ {% assign url = post.url | relative_url %}
+ -
+ {{ post.title }}
+
+ {% endfor %}
+
+
+
+{% endif %}
diff --git a/_javascript/categories.js b/_javascript/categories.js
new file mode 100644
index 0000000..15d8251
--- /dev/null
+++ b/_javascript/categories.js
@@ -0,0 +1,7 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+import { categoryCollapse } from './modules/plugins';
+
+basic();
+initSidebar();
+initTopbar();
+categoryCollapse();
diff --git a/_javascript/commons.js b/_javascript/commons.js
new file mode 100644
index 0000000..6a17fb9
--- /dev/null
+++ b/_javascript/commons.js
@@ -0,0 +1,5 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+
+initSidebar();
+initTopbar();
+basic();
diff --git a/_javascript/home.js b/_javascript/home.js
new file mode 100644
index 0000000..ef22cb9
--- /dev/null
+++ b/_javascript/home.js
@@ -0,0 +1,8 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+import { initLocaleDatetime, loadImg } from './modules/plugins';
+
+loadImg();
+initLocaleDatetime();
+initSidebar();
+initTopbar();
+basic();
diff --git a/_javascript/misc.js b/_javascript/misc.js
new file mode 100644
index 0000000..52b4043
--- /dev/null
+++ b/_javascript/misc.js
@@ -0,0 +1,7 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+import { initLocaleDatetime } from './modules/plugins';
+
+initSidebar();
+initTopbar();
+initLocaleDatetime();
+basic();
diff --git a/_javascript/modules/components/back-to-top.js b/_javascript/modules/components/back-to-top.js
new file mode 100644
index 0000000..40d9cd1
--- /dev/null
+++ b/_javascript/modules/components/back-to-top.js
@@ -0,0 +1,19 @@
+/**
+ * Reference: https://bootsnipp.com/snippets/featured/link-to-top-page
+ */
+
+export function back2top() {
+ const btn = document.getElementById('back-to-top');
+
+ window.addEventListener('scroll', () => {
+ if (window.scrollY > 50) {
+ btn.classList.add('show');
+ } else {
+ btn.classList.remove('show');
+ }
+ });
+
+ btn.addEventListener('click', () => {
+ window.scrollTo({ top: 0 });
+ });
+}
diff --git a/_javascript/modules/components/category-collapse.js b/_javascript/modules/components/category-collapse.js
new file mode 100644
index 0000000..0c53cb4
--- /dev/null
+++ b/_javascript/modules/components/category-collapse.js
@@ -0,0 +1,36 @@
+/**
+ * Tab 'Categories' expand/close effect.
+ */
+
+import 'bootstrap/js/src/collapse.js';
+
+const childPrefix = 'l_';
+const parentPrefix = 'h_';
+const children = document.getElementsByClassName('collapse');
+
+export function categoryCollapse() {
+ [...children].forEach((elem) => {
+ const id = parentPrefix + elem.id.substring(childPrefix.length);
+ const parent = document.getElementById(id);
+
+ // collapse sub-categories
+ elem.addEventListener('hide.bs.collapse', () => {
+ if (parent) {
+ parent.querySelector('.far.fa-folder-open').className =
+ 'far fa-folder fa-fw';
+ parent.querySelector('.fas.fa-angle-down').classList.add('rotate');
+ parent.classList.remove('hide-border-bottom');
+ }
+ });
+
+ // expand sub-categories
+ elem.addEventListener('show.bs.collapse', () => {
+ if (parent) {
+ parent.querySelector('.far.fa-folder').className =
+ 'far fa-folder-open fa-fw';
+ parent.querySelector('.fas.fa-angle-down').classList.remove('rotate');
+ parent.classList.add('hide-border-bottom');
+ }
+ });
+ });
+}
diff --git a/_javascript/modules/components/clipboard.js b/_javascript/modules/components/clipboard.js
new file mode 100644
index 0000000..9566e9d
--- /dev/null
+++ b/_javascript/modules/components/clipboard.js
@@ -0,0 +1,143 @@
+/**
+ * Clipboard functions
+ *
+ * Dependencies:
+ * clipboard.js (https://github.com/zenorocha/clipboard.js)
+ */
+
+import Tooltip from 'bootstrap/js/src/tooltip';
+
+const clipboardSelector = '.code-header>button';
+
+const ICON_DEFAULT = 'far fa-clipboard';
+const ICON_SUCCESS = 'fas fa-check';
+
+const ATTR_TIMEOUT = 'timeout';
+const ATTR_TITLE_SUCCEED = 'data-title-succeed';
+const ATTR_TITLE_ORIGIN = 'data-bs-original-title';
+const TIMEOUT = 2000; // in milliseconds
+
+function isLocked(node) {
+ if (node.hasAttribute(ATTR_TIMEOUT)) {
+ let timeout = node.getAttribute(ATTR_TIMEOUT);
+ if (Number(timeout) > Date.now()) {
+ return true;
+ }
+ }
+
+ return false;
+}
+
+function lock(node) {
+ node.setAttribute(ATTR_TIMEOUT, Date.now() + TIMEOUT);
+}
+
+function unlock(node) {
+ node.removeAttribute(ATTR_TIMEOUT);
+}
+
+function showTooltip(btn) {
+ const succeedTitle = btn.getAttribute(ATTR_TITLE_SUCCEED);
+ btn.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
+ Tooltip.getInstance(btn).show();
+}
+
+function hideTooltip(btn) {
+ Tooltip.getInstance(btn).hide();
+ btn.removeAttribute(ATTR_TITLE_ORIGIN);
+}
+
+function setSuccessIcon(btn) {
+ const icon = btn.children[0];
+ icon.setAttribute('class', ICON_SUCCESS);
+}
+
+function resumeIcon(btn) {
+ const icon = btn.children[0];
+ icon.setAttribute('class', ICON_DEFAULT);
+}
+
+function setCodeClipboard() {
+ const clipboardList = document.querySelectorAll(clipboardSelector);
+
+ if (clipboardList.length === 0) {
+ return;
+ }
+
+ // Initial the clipboard.js object
+ const clipboard = new ClipboardJS(clipboardSelector, {
+ target: (trigger) => {
+ const codeBlock = trigger.parentNode.nextElementSibling;
+ return codeBlock.querySelector('code .rouge-code');
+ }
+ });
+
+ [...clipboardList].map(
+ (elem) =>
+ new Tooltip(elem, {
+ placement: 'left'
+ })
+ );
+
+ clipboard.on('success', (e) => {
+ const trigger = e.trigger;
+
+ e.clearSelection();
+
+ if (isLocked(trigger)) {
+ return;
+ }
+
+ setSuccessIcon(trigger);
+ showTooltip(trigger);
+ lock(trigger);
+
+ setTimeout(() => {
+ hideTooltip(trigger);
+ resumeIcon(trigger);
+ unlock(trigger);
+ }, TIMEOUT);
+ });
+}
+
+function setLinkClipboard() {
+ const btnCopyLink = document.getElementById('copy-link');
+
+ if (btnCopyLink === null) {
+ return;
+ }
+
+ btnCopyLink.addEventListener('click', (e) => {
+ const target = e.target;
+
+ if (isLocked(target)) {
+ return;
+ }
+
+ // Copy URL to clipboard
+ navigator.clipboard.writeText(window.location.href).then(() => {
+ const defaultTitle = target.getAttribute(ATTR_TITLE_ORIGIN);
+ const succeedTitle = target.getAttribute(ATTR_TITLE_SUCCEED);
+
+ // Switch tooltip title
+ target.setAttribute(ATTR_TITLE_ORIGIN, succeedTitle);
+ Tooltip.getInstance(target).show();
+
+ lock(target);
+
+ setTimeout(() => {
+ target.setAttribute(ATTR_TITLE_ORIGIN, defaultTitle);
+ unlock(target);
+ }, TIMEOUT);
+ });
+ });
+
+ btnCopyLink.addEventListener('mouseleave', (e) => {
+ Tooltip.getInstance(e.target).hide();
+ });
+}
+
+export function initClipboard() {
+ setCodeClipboard();
+ setLinkClipboard();
+}
diff --git a/_javascript/modules/components/img-loading.js b/_javascript/modules/components/img-loading.js
new file mode 100644
index 0000000..989d9e6
--- /dev/null
+++ b/_javascript/modules/components/img-loading.js
@@ -0,0 +1,67 @@
+/**
+ * Setting up image lazy loading and LQIP switching
+ */
+
+const ATTR_DATA_SRC = 'data-src';
+const ATTR_DATA_LQIP = 'data-lqip';
+
+const cover = {
+ SHIMMER: 'shimmer',
+ BLUR: 'blur'
+};
+
+function removeCover(clzss) {
+ this.parentElement.classList.remove(clzss);
+}
+
+function handleImage() {
+ if (!this.complete) {
+ return;
+ }
+
+ if (this.hasAttribute(ATTR_DATA_LQIP)) {
+ removeCover.call(this, cover.BLUR);
+ } else {
+ removeCover.call(this, cover.SHIMMER);
+ }
+}
+
+/**
+ * Switches the LQIP with the real image URL.
+ */
+function switchLQIP() {
+ const src = this.getAttribute(ATTR_DATA_SRC);
+ this.setAttribute('src', encodeURI(src));
+ this.removeAttribute(ATTR_DATA_SRC);
+}
+
+export function loadImg() {
+ const images = document.querySelectorAll('article img');
+
+ if (images.length === 0) {
+ return;
+ }
+
+ images.forEach((img) => {
+ img.addEventListener('load', handleImage);
+ });
+
+ // Images loaded from the browser cache do not trigger the 'load' event
+ document.querySelectorAll('article img[loading="lazy"]').forEach((img) => {
+ if (img.complete) {
+ removeCover.call(img, cover.SHIMMER);
+ }
+ });
+
+ // LQIPs set by the data URI or WebP will not trigger the 'load' event,
+ // so manually convert the URI to the URL of a high-resolution image.
+ const lqips = document.querySelectorAll(
+ `article img[${ATTR_DATA_LQIP}="true"]`
+ );
+
+ if (lqips.length) {
+ lqips.forEach((lqip) => {
+ switchLQIP.call(lqip);
+ });
+ }
+}
diff --git a/_javascript/modules/components/img-popup.js b/_javascript/modules/components/img-popup.js
new file mode 100644
index 0000000..b542a2e
--- /dev/null
+++ b/_javascript/modules/components/img-popup.js
@@ -0,0 +1,48 @@
+/**
+ * Set up image popup
+ *
+ * Dependencies: https://github.com/biati-digital/glightbox
+ */
+
+const html = document.documentElement;
+const lightImages = '.popup:not(.dark)';
+const darkImages = '.popup:not(.light)';
+let selector = lightImages;
+
+if (
+ (html.hasAttribute('data-mode') &&
+ html.getAttribute('data-mode') === 'dark') ||
+ (!html.hasAttribute('data-mode') &&
+ window.matchMedia('(prefers-color-scheme: dark)').matches)
+) {
+ selector = darkImages;
+}
+
+let lightbox = GLightbox({ selector: `${selector}` });
+
+function updateImages(event) {
+ if (
+ event.source === window &&
+ event.data &&
+ event.data.direction === ModeToggle.ID
+ ) {
+ if (selector === lightImages) {
+ selector = darkImages;
+ } else {
+ selector = lightImages;
+ }
+ }
+
+ lightbox.destroy();
+ lightbox = GLightbox({ selector: `${selector}` });
+}
+
+export function imgPopup() {
+ if (document.querySelector(`${selector}`) === null) {
+ return;
+ }
+
+ if (document.getElementById('mode-toggle')) {
+ window.addEventListener('message', updateImages);
+ }
+}
diff --git a/_javascript/modules/components/locale-datetime.js b/_javascript/modules/components/locale-datetime.js
new file mode 100644
index 0000000..eb75626
--- /dev/null
+++ b/_javascript/modules/components/locale-datetime.js
@@ -0,0 +1,53 @@
+/**
+ * Update month/day to locale datetime
+ *
+ * Requirement:
+ */
+
+/* A tool for locale datetime */
+class LocaleHelper {
+ static get attrTimestamp() {
+ return 'data-ts';
+ }
+
+ static get attrDateFormat() {
+ return 'data-df';
+ }
+
+ static get locale() {
+ return document.documentElement.getAttribute('lang').substring(0, 2);
+ }
+
+ static getTimestamp(elem) {
+ return Number(elem.getAttribute(this.attrTimestamp)); // unix timestamp
+ }
+
+ static getDateFormat(elem) {
+ return elem.getAttribute(this.attrDateFormat);
+ }
+}
+
+export function initLocaleDatetime() {
+ dayjs.locale(LocaleHelper.locale);
+ dayjs.extend(window.dayjs_plugin_localizedFormat);
+
+ document
+ .querySelectorAll(`[${LocaleHelper.attrTimestamp}]`)
+ .forEach((elem) => {
+ const date = dayjs.unix(LocaleHelper.getTimestamp(elem));
+ const text = date.format(LocaleHelper.getDateFormat(elem));
+ elem.textContent = text;
+ elem.removeAttribute(LocaleHelper.attrTimestamp);
+ elem.removeAttribute(LocaleHelper.attrDateFormat);
+
+ // setup tooltips
+ if (
+ elem.hasAttribute('data-bs-toggle') &&
+ elem.getAttribute('data-bs-toggle') === 'tooltip'
+ ) {
+ // see: https://day.js.org/docs/en/display/format#list-of-localized-formats
+ const tooltipText = date.format('llll');
+ elem.setAttribute('data-bs-title', tooltipText);
+ }
+ });
+}
diff --git a/_javascript/modules/components/mode-watcher.js b/_javascript/modules/components/mode-watcher.js
new file mode 100644
index 0000000..9eecd09
--- /dev/null
+++ b/_javascript/modules/components/mode-watcher.js
@@ -0,0 +1,14 @@
+/**
+ * Add listener for theme mode toggle
+ */
+const toggle = document.getElementById('mode-toggle');
+
+export function modeWatcher() {
+ if (!toggle) {
+ return;
+ }
+
+ toggle.addEventListener('click', () => {
+ modeToggle.flipMode();
+ });
+}
diff --git a/_javascript/modules/components/search-display.js b/_javascript/modules/components/search-display.js
new file mode 100644
index 0000000..40059ac
--- /dev/null
+++ b/_javascript/modules/components/search-display.js
@@ -0,0 +1,110 @@
+/**
+ * This script make #search-result-wrapper switch to unload or shown automatically.
+ */
+
+const btnSbTrigger = document.getElementById('sidebar-trigger');
+const btnSearchTrigger = document.getElementById('search-trigger');
+const btnCancel = document.getElementById('search-cancel');
+const content = document.querySelectorAll('#main-wrapper>.container>.row');
+const topbarTitle = document.getElementById('topbar-title');
+const search = document.getElementById('search');
+const resultWrapper = document.getElementById('search-result-wrapper');
+const results = document.getElementById('search-results');
+const input = document.getElementById('search-input');
+const hints = document.getElementById('search-hints');
+
+// CSS class names
+const LOADED = 'd-block';
+const UNLOADED = 'd-none';
+const FOCUS = 'input-focus';
+const FLEX = 'd-flex';
+
+/* Actions in mobile screens (Sidebar hidden) */
+class MobileSearchBar {
+ static on() {
+ btnSbTrigger.classList.add(UNLOADED);
+ topbarTitle.classList.add(UNLOADED);
+ btnSearchTrigger.classList.add(UNLOADED);
+ search.classList.add(FLEX);
+ btnCancel.classList.add(LOADED);
+ }
+
+ static off() {
+ btnCancel.classList.remove(LOADED);
+ search.classList.remove(FLEX);
+ btnSbTrigger.classList.remove(UNLOADED);
+ topbarTitle.classList.remove(UNLOADED);
+ btnSearchTrigger.classList.remove(UNLOADED);
+ }
+}
+
+class ResultSwitch {
+ static resultVisible = false;
+
+ static on() {
+ if (!this.resultVisible) {
+ resultWrapper.classList.remove(UNLOADED);
+ content.forEach((el) => {
+ el.classList.add(UNLOADED);
+ });
+ this.resultVisible = true;
+ }
+ }
+
+ static off() {
+ if (this.resultVisible) {
+ results.innerHTML = '';
+
+ if (hints.classList.contains(UNLOADED)) {
+ hints.classList.remove(UNLOADED);
+ }
+
+ resultWrapper.classList.add(UNLOADED);
+ content.forEach((el) => {
+ el.classList.remove(UNLOADED);
+ });
+ input.textContent = '';
+ this.resultVisible = false;
+ }
+ }
+}
+
+function isMobileView() {
+ return btnCancel.classList.contains(LOADED);
+}
+
+export function displaySearch() {
+ btnSearchTrigger.addEventListener('click', () => {
+ MobileSearchBar.on();
+ ResultSwitch.on();
+ input.focus();
+ });
+
+ btnCancel.addEventListener('click', () => {
+ MobileSearchBar.off();
+ ResultSwitch.off();
+ });
+
+ input.addEventListener('focus', () => {
+ search.classList.add(FOCUS);
+ });
+
+ input.addEventListener('focusout', () => {
+ search.classList.remove(FOCUS);
+ });
+
+ input.addEventListener('input', () => {
+ if (input.value === '') {
+ if (isMobileView()) {
+ hints.classList.remove(UNLOADED);
+ } else {
+ ResultSwitch.off();
+ }
+ } else {
+ ResultSwitch.on();
+ if (isMobileView()) {
+ hints.classList.add(UNLOADED);
+ }
+ }
+ });
+}
diff --git a/_javascript/modules/components/sidebar.js b/_javascript/modules/components/sidebar.js
new file mode 100644
index 0000000..6b562d8
--- /dev/null
+++ b/_javascript/modules/components/sidebar.js
@@ -0,0 +1,27 @@
+/**
+ * Expand or close the sidebar in mobile screens.
+ */
+
+const ATTR_DISPLAY = 'sidebar-display';
+
+class SidebarUtil {
+ static isExpanded = false;
+
+ static toggle() {
+ if (SidebarUtil.isExpanded === false) {
+ document.body.setAttribute(ATTR_DISPLAY, '');
+ } else {
+ document.body.removeAttribute(ATTR_DISPLAY);
+ }
+
+ SidebarUtil.isExpanded = !SidebarUtil.isExpanded;
+ }
+}
+
+export function sidebarExpand() {
+ document
+ .getElementById('sidebar-trigger')
+ .addEventListener('click', SidebarUtil.toggle);
+
+ document.getElementById('mask').addEventListener('click', SidebarUtil.toggle);
+}
diff --git a/_javascript/modules/components/toc.js b/_javascript/modules/components/toc.js
new file mode 100644
index 0000000..56ce26f
--- /dev/null
+++ b/_javascript/modules/components/toc.js
@@ -0,0 +1,15 @@
+export function toc() {
+ if (document.querySelector('main h2, main h3')) {
+ // see: https://github.com/tscanlin/tocbot#usage
+ tocbot.init({
+ tocSelector: '#toc',
+ contentSelector: '.content',
+ ignoreSelector: '[data-toc-skip]',
+ headingSelector: 'h2, h3, h4',
+ orderedList: false,
+ scrollSmooth: false
+ });
+
+ document.getElementById('toc-wrapper').classList.remove('d-none');
+ }
+}
diff --git a/_javascript/modules/components/tooltip-loader.js b/_javascript/modules/components/tooltip-loader.js
new file mode 100644
index 0000000..c36c879
--- /dev/null
+++ b/_javascript/modules/components/tooltip-loader.js
@@ -0,0 +1,11 @@
+import Tooltip from 'bootstrap/js/src/tooltip';
+
+export function loadTooptip() {
+ const tooltipTriggerList = document.querySelectorAll(
+ '[data-bs-toggle="tooltip"]'
+ );
+
+ [...tooltipTriggerList].map(
+ (tooltipTriggerEl) => new Tooltip(tooltipTriggerEl)
+ );
+}
diff --git a/_javascript/modules/layouts.js b/_javascript/modules/layouts.js
new file mode 100644
index 0000000..28f7962
--- /dev/null
+++ b/_javascript/modules/layouts.js
@@ -0,0 +1,3 @@
+export { basic } from './layouts/basic';
+export { initSidebar } from './layouts/sidebar';
+export { initTopbar } from './layouts/topbar';
diff --git a/_javascript/modules/layouts/basic.js b/_javascript/modules/layouts/basic.js
new file mode 100644
index 0000000..fb36a8b
--- /dev/null
+++ b/_javascript/modules/layouts/basic.js
@@ -0,0 +1,7 @@
+import { back2top } from '../components/back-to-top';
+import { loadTooptip } from '../components/tooltip-loader';
+
+export function basic() {
+ back2top();
+ loadTooptip();
+}
diff --git a/_javascript/modules/layouts/sidebar.js b/_javascript/modules/layouts/sidebar.js
new file mode 100644
index 0000000..8795693
--- /dev/null
+++ b/_javascript/modules/layouts/sidebar.js
@@ -0,0 +1,7 @@
+import { modeWatcher } from '../components/mode-watcher';
+import { sidebarExpand } from '../components/sidebar';
+
+export function initSidebar() {
+ modeWatcher();
+ sidebarExpand();
+}
diff --git a/_javascript/modules/layouts/topbar.js b/_javascript/modules/layouts/topbar.js
new file mode 100644
index 0000000..cfcd0ed
--- /dev/null
+++ b/_javascript/modules/layouts/topbar.js
@@ -0,0 +1,5 @@
+import { displaySearch } from '../components/search-display';
+
+export function initTopbar() {
+ displaySearch();
+}
diff --git a/_javascript/modules/plugins.js b/_javascript/modules/plugins.js
new file mode 100644
index 0000000..fb892e2
--- /dev/null
+++ b/_javascript/modules/plugins.js
@@ -0,0 +1,6 @@
+export { categoryCollapse } from './components/category-collapse';
+export { initClipboard } from './components/clipboard';
+export { loadImg } from './components/img-loading';
+export { imgPopup } from './components/img-popup';
+export { initLocaleDatetime } from './components/locale-datetime';
+export { toc } from './components/toc';
diff --git a/_javascript/page.js b/_javascript/page.js
new file mode 100644
index 0000000..76e8ce9
--- /dev/null
+++ b/_javascript/page.js
@@ -0,0 +1,9 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+import { loadImg, imgPopup, initClipboard } from './modules/plugins';
+
+loadImg();
+imgPopup();
+initSidebar();
+initTopbar();
+initClipboard();
+basic();
diff --git a/_javascript/post.js b/_javascript/post.js
new file mode 100644
index 0000000..9340f05
--- /dev/null
+++ b/_javascript/post.js
@@ -0,0 +1,17 @@
+import { basic, initSidebar, initTopbar } from './modules/layouts';
+import {
+ loadImg,
+ imgPopup,
+ initLocaleDatetime,
+ initClipboard,
+ toc
+} from './modules/plugins';
+
+loadImg();
+toc();
+imgPopup();
+initSidebar();
+initLocaleDatetime();
+initClipboard();
+initTopbar();
+basic();
diff --git a/_javascript/pwa/app.js b/_javascript/pwa/app.js
new file mode 100644
index 0000000..c71036a
--- /dev/null
+++ b/_javascript/pwa/app.js
@@ -0,0 +1,51 @@
+import { pwa, baseurl } from '../../_config.yml';
+import Toast from 'bootstrap/js/src/toast';
+
+if ('serviceWorker' in navigator) {
+ if (pwa.enabled) {
+ const swUrl = `${baseurl}/sw.min.js`;
+ const notification = document.getElementById('notification');
+ const btnRefresh = notification.querySelector('.toast-body>button');
+ const popupWindow = Toast.getOrCreateInstance(notification);
+
+ navigator.serviceWorker.register(swUrl).then((registration) => {
+ // In case the user ignores the notification
+ if (registration.waiting) {
+ popupWindow.show();
+ }
+
+ registration.addEventListener('updatefound', () => {
+ registration.installing.addEventListener('statechange', () => {
+ if (registration.waiting) {
+ if (navigator.serviceWorker.controller) {
+ popupWindow.show();
+ }
+ }
+ });
+ });
+
+ btnRefresh.addEventListener('click', () => {
+ if (registration.waiting) {
+ registration.waiting.postMessage('SKIP_WAITING');
+ }
+ popupWindow.hide();
+ });
+ });
+
+ let refreshing = false;
+
+ // Detect controller change and refresh all the opened tabs
+ navigator.serviceWorker.addEventListener('controllerchange', () => {
+ if (!refreshing) {
+ window.location.reload();
+ refreshing = true;
+ }
+ });
+ } else {
+ navigator.serviceWorker.getRegistrations().then(function (registrations) {
+ for (let registration of registrations) {
+ registration.unregister();
+ }
+ });
+ }
+}
diff --git a/_javascript/pwa/sw.js b/_javascript/pwa/sw.js
new file mode 100644
index 0000000..94b64bf
--- /dev/null
+++ b/_javascript/pwa/sw.js
@@ -0,0 +1,94 @@
+import { baseurl } from '../../_config.yml';
+
+importScripts(`${baseurl}/assets/js/data/swconf.js`);
+
+const purge = swconf.purge;
+const interceptor = swconf.interceptor;
+
+function verifyUrl(url) {
+ const requestUrl = new URL(url);
+ const requestPath = requestUrl.pathname;
+
+ if (!requestUrl.protocol.startsWith('http')) {
+ return false;
+ }
+
+ for (const prefix of interceptor.urlPrefixes) {
+ if (requestUrl.href.startsWith(prefix)) {
+ return false;
+ }
+ }
+
+ for (const path of interceptor.paths) {
+ if (requestPath.startsWith(path)) {
+ return false;
+ }
+ }
+ return true;
+}
+
+self.addEventListener('install', (event) => {
+ if (purge) {
+ return;
+ }
+
+ event.waitUntil(
+ caches.open(swconf.cacheName).then((cache) => {
+ return cache.addAll(swconf.resources);
+ })
+ );
+});
+
+self.addEventListener('activate', (event) => {
+ event.waitUntil(
+ caches.keys().then((keyList) => {
+ return Promise.all(
+ keyList.map((key) => {
+ if (purge) {
+ return caches.delete(key);
+ } else {
+ if (key !== swconf.cacheName) {
+ return caches.delete(key);
+ }
+ }
+ })
+ );
+ })
+ );
+});
+
+self.addEventListener('message', (event) => {
+ if (event.data === 'SKIP_WAITING') {
+ self.skipWaiting();
+ }
+});
+
+self.addEventListener('fetch', (event) => {
+ if (event.request.headers.has('range')) {
+ return;
+ }
+
+ event.respondWith(
+ caches.match(event.request).then((response) => {
+ if (response) {
+ return response;
+ }
+
+ return fetch(event.request).then((response) => {
+ const url = event.request.url;
+
+ if (purge || event.request.method !== 'GET' || !verifyUrl(url)) {
+ return response;
+ }
+
+ // See:
+ let responseToCache = response.clone();
+
+ caches.open(swconf.cacheName).then((cache) => {
+ cache.put(event.request, responseToCache);
+ });
+ return response;
+ });
+ })
+ );
+});
diff --git a/_layouts/compress.html b/_layouts/compress.html
index bb34487..2779e92 100644
--- a/_layouts/compress.html
+++ b/_layouts/compress.html
@@ -1,10 +1,10 @@
---
# Jekyll layout that compresses HTML
-# v3.1.0
+# v3.2.0
# http://jch.penibelst.de/
# © 2014–2015 Anatol Broder
# MIT License
---
{% capture _LINE_FEED %}
-{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment or site.compress_html.ignore.envs == "all" %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains " " %}{% endif %}{% unless _pre_before contains " " and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " ;; ;" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} Step | Bytes | raw | {{ content | size }}{% if _profile_endings %} | endings | {{ _profile_endings }}{% endif %}{% if _profile_startings %} | startings | {{ _profile_startings }}{% endif %}{% if _profile_comments %} | comments | {{ _profile_comments }}{% endif %}{% if _profile_collapse %} | collapse | {{ _profile_collapse }}{% endif %}{% if _profile_clippings %} | clippings | {{ _profile_clippings }}{% endif %} | {% endif %}{% endif %}
+{% endcapture %}{% if site.compress_html.ignore.envs contains jekyll.environment or site.compress_html.ignore.envs == "all" or page.compress_html == false %}{{ content }}{% else %}{% capture _content %}{{ content }}{% endcapture %}{% assign _profile = site.compress_html.profile %}{% if site.compress_html.endings == "all" %}{% assign _endings = "html head body li dt dd optgroup option colgroup caption thead tbody tfoot tr td th" | split: " " %}{% else %}{% assign _endings = site.compress_html.endings %}{% endif %}{% for _element in _endings %}{% capture _end %}{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _end %}{% endfor %}{% if _profile and _endings %}{% assign _profile_endings = _content | size | plus: 1 %}{% endif %}{% for _element in site.compress_html.startings %}{% capture _start %}<{{ _element }}>{% endcapture %}{% assign _content = _content | remove: _start %}{% endfor %}{% if _profile and site.compress_html.startings %}{% assign _profile_startings = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.comments == "all" %}{% assign _comments = "" | split: " " %}{% else %}{% assign _comments = site.compress_html.comments %}{% endif %}{% if _comments.size == 2 %}{% capture _comment_befores %}.{{ _content }}{% endcapture %}{% assign _comment_befores = _comment_befores | split: _comments.first %}{% for _comment_before in _comment_befores %}{% if forloop.first %}{% continue %}{% endif %}{% capture _comment_outside %}{% if _carry %}{{ _comments.first }}{% endif %}{{ _comment_before }}{% endcapture %}{% capture _comment %}{% unless _carry %}{{ _comments.first }}{% endunless %}{{ _comment_outside | split: _comments.last | first }}{% if _comment_outside contains _comments.last %}{{ _comments.last }}{% assign _carry = false %}{% else %}{% assign _carry = true %}{% endif %}{% endcapture %}{% assign _content = _content | remove_first: _comment %}{% endfor %}{% if _profile %}{% assign _profile_comments = _content | size | plus: 1 %}{% endif %}{% endif %}{% assign _pre_befores = _content | split: "" %}{% assign _pres_after = "" %}{% if _pres.size != 0 %}{% if site.compress_html.blanklines %}{% assign _lines = _pres.last | split: _LINE_FEED %}{% capture _pres_after %}{% for _line in _lines %}{% assign _trimmed = _line | split: " " | join: " " %}{% if _trimmed != empty or forloop.last %}{% unless forloop.first %}{{ _LINE_FEED }}{% endunless %}{{ _line }}{% endif %}{% endfor %}{% endcapture %}{% else %}{% assign _pres_after = _pres.last | split: " " | join: " " %}{% endif %}{% endif %}{% capture _content %}{{ _content }}{% if _pre_before contains " " %}{% endif %}{% unless _pre_before contains " " and _pres.size == 1 %}{{ _pres_after }}{% endunless %}{% endcapture %}{% endfor %}{% if _profile %}{% assign _profile_collapse = _content | size | plus: 1 %}{% endif %}{% if site.compress_html.clippings == "all" %}{% assign _clippings = "html head title base link meta style body article section nav aside h1 h2 h3 h4 h5 h6 hgroup header footer address p hr blockquote ol ul li dl dt dd figure figcaption main div table caption colgroup col tbody thead tfoot tr td th" | split: " " %}{% else %}{% assign _clippings = site.compress_html.clippings %}{% endif %}{% for _element in _clippings %}{% assign _edges = " ;; ;" | replace: "e", _element | split: ";" %}{% assign _content = _content | replace: _edges[0], _edges[1] | replace: _edges[2], _edges[3] | replace: _edges[4], _edges[5] %}{% endfor %}{% if _profile and _clippings %}{% assign _profile_clippings = _content | size | plus: 1 %}{% endif %}{{ _content }}{% if _profile %} Step | Bytes | raw | {{ content | size }}{% if _profile_endings %} | endings | {{ _profile_endings }}{% endif %}{% if _profile_startings %} | startings | {{ _profile_startings }}{% endif %}{% if _profile_comments %} | comments | {{ _profile_comments }}{% endif %}{% if _profile_collapse %} | collapse | {{ _profile_collapse }}{% endif %}{% if _profile_clippings %} | clippings | {{ _profile_clippings }}{% endif %} | {% endif %}{% endif %}
diff --git a/_layouts/default.html b/_layouts/default.html
index 097c7e9..ea438fe 100644
--- a/_layouts/default.html
+++ b/_layouts/default.html
@@ -13,12 +13,9 @@ layout: compress
{% endif %}
-
+
{% include head.html %}
- {% if jekyll.environment == 'production' and site.google_analytics %}
- {% include analytics.html %}
- {% endif %}
-
+
{% include sidebar.html lang=lang %}
@@ -78,13 +75,8 @@ layout: compress
{% endif %}
+ {% include js-selector.html lang=lang %}
- {% include js-selector.html %}
-
- {% if page.mermaid %}
- {% include mermaid.html %}
- {% endif %}
-
- {% include_cached search-loader.html %}
+ {% include_cached search-loader.html lang=lang %}
diff --git a/_layouts/home.html b/_layouts/home.html
index 4cf4c1d..e44efe8 100644
--- a/_layouts/home.html
+++ b/_layouts/home.html
@@ -49,7 +49,7 @@ refactor: true
{% if post.image %}
{% assign src = post.image.path | default: post.image %}
{% unless src contains '//' %}
- {% assign src = post.img_path | append: '/' | append: src | replace: '//', '/' %}
+ {% assign src = post.media_subpath | append: '/' | append: src | replace: '//', '/' %}
{% endunless %}
{% assign alt = post.image.alt | xml_escape | default: 'Preview Image' %}
@@ -72,10 +72,7 @@ refactor: true
{{ post.title }}
-
- {% include no-linenos.html content=post.content %}
- {{ content | markdownify | strip_html | truncate: 200 | escape }}
-
+ {% include post-description.html %}
diff --git a/_layouts/post.html b/_layouts/post.html
index f7818bf..f17ceea 100644
--- a/_layouts/post.html
+++ b/_layouts/post.html
@@ -14,6 +14,9 @@ tail_includes:
-
diff --git a/_sass/addon/commons.scss b/_sass/addon/commons.scss
new file mode 100644
index 0000000..c4c68e7
--- /dev/null
+++ b/_sass/addon/commons.scss
@@ -0,0 +1,1532 @@
+/* The common styles */
+
+html {
+ font-size: 16px;
+
+ @media (prefers-color-scheme: light) {
+ &:not([data-mode]),
+ &[data-mode='light'] {
+ @include light-scheme;
+ }
+
+ &[data-mode='dark'] {
+ @include dark-scheme;
+ }
+ }
+
+ @media (prefers-color-scheme: dark) {
+ &:not([data-mode]),
+ &[data-mode='dark'] {
+ @include dark-scheme;
+ }
+
+ &[data-mode='light'] {
+ @include light-scheme;
+ }
+ }
+}
+
+body {
+ background: var(--main-bg);
+ padding: env(safe-area-inset-top) env(safe-area-inset-right)
+ env(safe-area-inset-bottom) env(safe-area-inset-left);
+ color: var(--text-color);
+ -webkit-font-smoothing: antialiased;
+ font-family: $font-family-base;
+}
+
+/* --- Typography --- */
+
+@for $i from 1 through 5 {
+ h#{$i} {
+ @extend %heading;
+
+ @if $i > 1 {
+ @extend %section;
+ @extend %anchor;
+ }
+
+ @if $i < 5 {
+ $factor: 0.18rem;
+
+ @if $i == 1 {
+ $factor: 0.23rem;
+ }
+
+ font-size: 1rem + (5 - $i) * $factor;
+ } @else {
+ font-size: 1rem;
+ }
+ }
+}
+
+a {
+ @extend %link-color;
+
+ text-decoration: none;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+ transition: all 0.35s ease-in-out;
+
+ .blur & {
+ $blur: 20px;
+
+ -webkit-filter: blur($blur);
+ filter: blur($blur);
+ }
+}
+
+blockquote {
+ border-left: 5px solid var(--blockquote-border-color);
+ padding-left: 1rem;
+ color: var(--blockquote-text-color);
+
+ > p:last-child {
+ margin-bottom: 0;
+ }
+
+ &[class^='prompt-'] {
+ border-left: 0;
+ position: relative;
+ padding: 1rem 1rem 1rem 3rem;
+ color: var(--prompt-text-color);
+
+ @extend %rounded;
+
+ &::before {
+ text-align: center;
+ width: 3rem;
+ position: absolute;
+ left: 0.25rem;
+ margin-top: 0.4rem;
+ text-rendering: auto;
+ -webkit-font-smoothing: antialiased;
+ }
+ }
+
+ @include prompt('tip', '\f0eb', $fa-style: 'regular');
+ @include prompt('info', '\f06a', $rotate: 180);
+ @include prompt('warning', '\f06a');
+ @include prompt('danger', '\f071');
+}
+
+kbd {
+ font-family: inherit;
+ display: inline-block;
+ vertical-align: middle;
+ line-height: 1.3rem;
+ min-width: 1.75rem;
+ text-align: center;
+ margin: 0 0.3rem;
+ padding-top: 0.1rem;
+ color: var(--kbd-text-color);
+ background-color: var(--kbd-bg-color);
+ border-radius: 0.25rem;
+ border: solid 1px var(--kbd-wrap-color);
+ box-shadow: inset 0 -2px 0 var(--kbd-wrap-color);
+}
+
+hr {
+ border-color: var(--main-border-color);
+ opacity: 1;
+}
+
+footer {
+ background-color: var(--main-bg);
+ height: $footer-height;
+ border-top: 1px solid var(--main-border-color);
+
+ @extend %text-xs;
+
+ a {
+ @extend %text-highlight;
+
+ &:hover {
+ @extend %link-hover;
+ }
+ }
+
+ em {
+ @extend %text-highlight;
+ }
+
+ p {
+ text-align: center;
+ margin-bottom: 0;
+ }
+}
+
+/* fontawesome icons */
+i {
+ &.far,
+ &.fas {
+ @extend %no-cursor;
+ }
+}
+
+/* --- Panels --- */
+
+.access {
+ top: 2rem;
+ transition: top 0.2s ease-in-out;
+ margin-top: 3rem;
+ margin-bottom: 4rem;
+
+ &:only-child {
+ position: -webkit-sticky;
+ position: sticky;
+ }
+
+ > section {
+ padding-left: 1rem;
+ border-left: 1px solid var(--main-border-color);
+
+ &:not(:last-child) {
+ margin-bottom: 4rem;
+ }
+ }
+
+ .content {
+ font-size: 0.9rem;
+ }
+}
+
+#panel-wrapper {
+ /* the headings */
+ .panel-heading {
+ font-family: inherit;
+ line-height: inherit;
+
+ @include label(inherit);
+ }
+
+ .post-tag {
+ line-height: 1.05rem;
+ font-size: 0.85rem;
+ border-radius: 0.8rem;
+ padding: 0.3rem 0.5rem;
+ margin: 0 0.35rem 0.5rem 0;
+
+ &:hover {
+ transition: all 0.3s ease-in;
+ }
+ }
+}
+
+#access-lastmod {
+ a {
+ color: inherit;
+
+ &:hover {
+ @extend %link-hover;
+ }
+
+ @extend %no-bottom-border;
+ }
+}
+
+.footnotes > ol {
+ padding-left: 2rem;
+ margin-top: 0.5rem;
+
+ > li {
+ &:not(:last-child) {
+ margin-bottom: 0.3rem;
+ }
+
+ @extend %sup-fn-target;
+
+ > p {
+ margin-left: 0.25em;
+ margin-top: 0;
+ margin-bottom: 0;
+ }
+ }
+}
+
+.footnote {
+ @at-root a#{&} {
+ @include ml-mr(1px);
+ @include pl-pr(2px);
+
+ border-bottom-style: none !important;
+ }
+}
+
+sup {
+ @extend %sup-fn-target;
+}
+
+.reversefootnote {
+ @at-root a#{&} {
+ font-size: 0.6rem;
+ line-height: 1;
+ position: relative;
+ bottom: 0.25em;
+ margin-left: 0.25em;
+ border-bottom-style: none !important;
+ }
+}
+
+/* --- Begin of Markdown table style --- */
+
+/* it will be created by Liquid */
+.table-wrapper {
+ overflow-x: auto;
+ margin-bottom: 1.5rem;
+
+ > table {
+ min-width: 100%;
+ overflow-x: auto;
+ border-spacing: 0;
+
+ thead {
+ border-bottom: solid 2px rgba(210, 215, 217, 0.75);
+
+ th {
+ @extend %table-cell;
+ }
+ }
+
+ tbody {
+ tr {
+ border-bottom: 1px solid var(--tb-border-color);
+
+ &:nth-child(2n) {
+ background-color: var(--tb-even-bg);
+ }
+
+ &:nth-child(2n + 1) {
+ background-color: var(--tb-odd-bg);
+ }
+
+ td {
+ @extend %table-cell;
+ }
+ }
+ } /* tbody */
+ } /* table */
+}
+
+/* --- post --- */
+
+.preview-img {
+ aspect-ratio: 40 / 21;
+ width: 100%;
+ height: 100%;
+ overflow: hidden;
+
+ @extend %rounded;
+
+ &:not(.no-bg) {
+ background: var(--img-bg);
+ }
+
+ img {
+ height: 100%;
+ -o-object-fit: cover;
+ object-fit: cover;
+
+ @extend %rounded;
+
+ @at-root #post-list & {
+ width: 100%;
+ }
+ }
+}
+
+.post-preview {
+ @extend %rounded;
+
+ border: 0;
+ background: var(--card-bg);
+ box-shadow: var(--card-shadow);
+
+ &::before {
+ @extend %rounded;
+
+ content: '';
+ width: 100%;
+ height: 100%;
+ position: absolute;
+ background-color: var(--card-hovor-bg);
+ opacity: 0;
+ transition: opacity 0.35s ease-in-out;
+ }
+
+ &:hover {
+ &::before {
+ opacity: 0.3;
+ }
+ }
+}
+
+main {
+ line-height: 1.75;
+
+ h1 {
+ margin-top: 2rem;
+ }
+
+ p {
+ > a.popup {
+ &:not(.normal):not(.left):not(.right) {
+ @include align-center;
+ }
+ }
+ }
+
+ .categories,
+ #tags,
+ #archives {
+ a:not(:hover) {
+ @extend %no-bottom-border;
+ }
+ }
+}
+
+.post-meta {
+ @extend %text-sm;
+
+ a {
+ &:not([class]):hover {
+ @extend %link-hover;
+ }
+ }
+
+ em {
+ @extend %normal-font-style;
+ }
+}
+
+.content {
+ font-size: 1.08rem;
+ margin-top: 2rem;
+ overflow-wrap: break-word;
+
+ a {
+ &.popup {
+ @extend %no-cursor;
+ @extend %img-caption;
+ @include mt-mb(0.5rem);
+
+ cursor: zoom-in;
+ }
+
+ &:not(.img-link) {
+ @extend %link-underline;
+
+ &:hover {
+ @extend %link-hover;
+ }
+ }
+ }
+
+ ol,
+ ul {
+ &:not([class]),
+ &.task-list {
+ -webkit-padding-start: 1.75rem;
+ padding-inline-start: 1.75rem;
+
+ li {
+ margin: 0.25rem 0;
+ padding-left: 0.25rem;
+ }
+
+ ol,
+ ul {
+ -webkit-padding-start: 1.25rem;
+ padding-inline-start: 1.25rem;
+ margin: 0.5rem 0;
+ }
+ }
+ }
+
+ ul.task-list {
+ -webkit-padding-start: 1.25rem;
+ padding-inline-start: 1.25rem;
+
+ li {
+ list-style-type: none;
+ padding-left: 0;
+
+ /* checkbox icon */
+ > i {
+ width: 2rem;
+ margin-left: -1.25rem;
+ color: var(--checkbox-color);
+
+ &.checked {
+ color: var(--checkbox-checked-color);
+ }
+ }
+
+ ul {
+ -webkit-padding-start: 1.75rem;
+ padding-inline-start: 1.75rem;
+ }
+ }
+
+ input[type='checkbox'] {
+ margin: 0 0.5rem 0.2rem -1.3rem;
+ vertical-align: middle;
+ }
+ } /* ul */
+
+ dl > dd {
+ margin-left: 1rem;
+ }
+
+ ::marker {
+ color: var(--text-muted-color);
+ }
+} /* .content */
+
+.tag:hover {
+ @extend %tag-hover;
+}
+
+.post-tag {
+ display: inline-block;
+ min-width: 2rem;
+ text-align: center;
+ border-radius: 0.5rem;
+ border: 1px solid var(--btn-border-color);
+ padding: 0 0.4rem;
+ color: var(--text-muted-color);
+ line-height: 1.3rem;
+
+ &:not(:last-child) {
+ margin-right: 0.2rem;
+ }
+}
+
+.rounded-10 {
+ border-radius: 10px !important;
+}
+
+.img-link {
+ color: transparent;
+ display: inline-flex;
+}
+
+.shimmer {
+ overflow: hidden;
+ position: relative;
+ background: var(--img-bg);
+
+ &::before {
+ content: '';
+ position: absolute;
+ background: var(--shimmer-bg);
+ height: 100%;
+ width: 100%;
+ -webkit-animation: shimmer 1.3s infinite;
+ animation: shimmer 1.3s infinite;
+ }
+
+ @-webkit-keyframes shimmer {
+ 0% {
+ transform: translateX(-100%);
+ }
+
+ 100% {
+ transform: translateX(100%);
+ }
+ }
+
+ @keyframes shimmer {
+ 0% {
+ transform: translateX(-100%);
+ }
+
+ 100% {
+ transform: translateX(100%);
+ }
+ }
+}
+
+.embed-video {
+ width: 100%;
+ height: 100%;
+ margin-bottom: 1rem;
+ aspect-ratio: 16 / 9;
+
+ @extend %rounded;
+
+ &.twitch {
+ aspect-ratio: 310 / 189;
+ }
+
+ &.file {
+ display: block;
+ width: auto;
+ height: auto;
+ max-width: 100%;
+ max-height: 100%;
+ margin: auto;
+ margin-bottom: 0;
+ }
+
+ @extend %img-caption;
+}
+
+.embed-audio {
+ width: 100%;
+ display: block;
+
+ @extend %img-caption;
+}
+
+/* --- buttons --- */
+.btn-lang {
+ border: 1px solid !important;
+ padding: 1px 3px;
+ border-radius: 3px;
+ color: var(--link-color);
+
+ &:focus {
+ box-shadow: none;
+ }
+}
+
+/* --- Effects classes --- */
+
+.flex-grow-1 {
+ flex-grow: 1 !important;
+}
+
+.btn-box-shadow {
+ box-shadow: var(--card-shadow);
+}
+
+/* overwrite bootstrap muted */
+.text-muted {
+ color: var(--text-muted-color) !important;
+}
+
+/* Overwrite bootstrap tooltip */
+.tooltip-inner {
+ font-size: 0.7rem;
+ max-width: 220px;
+ text-align: left;
+}
+
+/* Overwrite bootstrap outline button */
+.btn.btn-outline-primary {
+ &:not(.disabled):hover {
+ border-color: #007bff !important;
+ }
+}
+
+.disabled {
+ color: rgb(206, 196, 196);
+ pointer-events: auto;
+ cursor: not-allowed;
+}
+
+.hide-border-bottom {
+ border-bottom: none !important;
+}
+
+.input-focus {
+ 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;
+}
+
+.left {
+ float: left;
+ margin: 0.75rem 1rem 1rem 0;
+}
+
+.right {
+ float: right;
+ margin: 0.75rem 0 1rem 1rem;
+}
+
+/* --- Overriding --- */
+
+/* mermaid */
+.mermaid {
+ text-align: center;
+}
+
+/* MathJax */
+mjx-container {
+ overflow-y: hidden;
+ min-width: auto !important;
+}
+
+/* --- sidebar layout --- */
+
+$sidebar-display: 'sidebar-display';
+$btn-border-width: 3px;
+$btn-mb: 0.5rem;
+
+#sidebar {
+ @include pl-pr(0);
+
+ position: fixed;
+ top: 0;
+ left: 0;
+ height: 100%;
+ overflow-y: auto;
+ width: $sidebar-width;
+ z-index: 99;
+ 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 */
+ scrollbar-width: none; /* Firefox */
+
+ /* Hide scrollbar for Chrome, Safari and Opera */
+ &::-webkit-scrollbar {
+ display: none;
+ }
+
+ %sidebar-link-hover {
+ &:hover {
+ color: var(--sidebar-active-color);
+ }
+ }
+
+ a {
+ @extend %sidebar-links;
+ }
+
+ #avatar {
+ display: block;
+ width: 7rem;
+ height: 7rem;
+ overflow: hidden;
+ box-shadow: var(--avatar-border-color) 0 0 0 2px;
+ transform: translateZ(0); /* fixed the zoom in Safari */
+
+ img {
+ transition: transform 0.5s;
+
+ &:hover {
+ transform: scale(1.2);
+ }
+ }
+ }
+
+ .profile-wrapper {
+ @include mt-mb(2.5rem);
+ @extend %clickable-transition;
+
+ padding-left: 2.5rem;
+ padding-right: 1.25rem;
+ width: 100%;
+ }
+
+ .site-title {
+ font-family: inherit;
+ font-weight: 900;
+ font-size: 1.75rem;
+ line-height: 1.2;
+ letter-spacing: 0.25px;
+ margin-top: 1.25rem;
+ margin-bottom: 0.5rem;
+
+ a {
+ @extend %clickable-transition;
+ @extend %sidebar-link-hover;
+
+ color: var(--site-title-color);
+ }
+ }
+
+ .site-subtitle {
+ font-size: 95%;
+ color: var(--site-subtitle-color);
+ margin-top: 0.25rem;
+ word-spacing: 1px;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+
+ ul {
+ margin-bottom: 2rem;
+
+ li.nav-item {
+ opacity: 0.9;
+ width: 100%;
+ padding-left: 1.5rem;
+ padding-right: 1.5rem;
+
+ a.nav-link {
+ @include pt-pb(0.6rem);
+
+ display: flex;
+ align-items: center;
+ border-radius: 0.75rem;
+ font-weight: 600;
+
+ &:hover {
+ background-color: var(--sidebar-hover-bg);
+ }
+
+ i {
+ font-size: 95%;
+ opacity: 0.8;
+ margin-right: 1.5rem;
+ }
+
+ span {
+ font-size: 90%;
+ letter-spacing: 0.2px;
+ }
+ }
+
+ &.active {
+ .nav-link {
+ color: var(--sidebar-active-color);
+ background-color: var(--sidebar-hover-bg);
+
+ span {
+ opacity: 1;
+ }
+ }
+ }
+
+ &:not(:first-child) {
+ margin-top: 0.25rem;
+ }
+ }
+ }
+
+ .sidebar-bottom {
+ padding-left: 2rem;
+ padding-right: 1rem;
+ margin-bottom: 1.5rem;
+
+ $btn-size: 1.75rem;
+
+ %button {
+ width: $btn-size;
+ height: $btn-size;
+ margin-bottom: $btn-mb; // multi line gap
+ border-radius: 50%;
+ color: var(--sidebar-btn-color);
+ background-color: var(--sidebar-btn-bg);
+ text-align: center;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+
+ &:not(:focus-visible) {
+ box-shadow: var(--sidebar-border-color) 0 0 0 1px;
+ }
+
+ &:hover {
+ background-color: var(--sidebar-hover-bg);
+ }
+ }
+
+ a {
+ @extend %button;
+ @extend %sidebar-link-hover;
+ @extend %clickable-transition;
+
+ &:not(:last-child) {
+ margin-right: $sb-btn-gap;
+ }
+ }
+
+ i {
+ line-height: $btn-size;
+ }
+
+ #mode-toggle {
+ @extend %button;
+ @extend %sidebar-links;
+ @extend %sidebar-link-hover;
+ }
+
+ .icon-border {
+ @extend %no-cursor;
+ @include ml-mr(calc(($sb-btn-gap - $btn-border-width) / 2));
+
+ background-color: var(--sidebar-btn-color);
+ content: '';
+ width: $btn-border-width;
+ height: $btn-border-width;
+ border-radius: 50%;
+ margin-bottom: $btn-mb;
+ }
+ } /* .sidebar-bottom */
+} /* #sidebar */
+
+@media (hover: hover) {
+ #sidebar ul > li:last-child::after {
+ transition: top 0.5s ease;
+ }
+
+ .nav-link {
+ transition: background-color 0.3s ease-in-out;
+ }
+
+ .post-preview {
+ transition: background-color 0.35s ease-in-out;
+ }
+}
+
+#search-result-wrapper {
+ display: none;
+ height: 100%;
+ width: 100%;
+ overflow: auto;
+
+ .content {
+ margin-top: 2rem;
+ }
+}
+
+/* --- top-bar --- */
+
+#topbar-wrapper {
+ height: $topbar-height;
+ background-color: var(--topbar-bg);
+}
+
+#topbar {
+ button i {
+ color: #999999;
+ }
+
+ #breadcrumb {
+ font-size: 1rem;
+ color: var(--text-muted-color);
+ padding-left: 0.5rem;
+
+ a:hover {
+ @extend %link-hover;
+ }
+
+ span {
+ &:not(:last-child) {
+ &::after {
+ content: '›';
+ padding: 0 0.3rem;
+ }
+ }
+ }
+ }
+} /* #topbar */
+
+::-webkit-input-placeholder {
+ @include placeholder;
+}
+
+::-moz-placeholder {
+ @include placeholder;
+}
+
+:-ms-input-placeholder {
+ @include placeholder;
+}
+
+::-ms-input-placeholder {
+ @include placeholder;
+}
+
+::placeholder {
+ @include placeholder;
+}
+
+:focus::-webkit-input-placeholder {
+ @include placeholder-focus;
+}
+
+:focus::-moz-placeholder {
+ @include placeholder-focus;
+}
+
+:focus:-ms-input-placeholder {
+ @include placeholder-focus;
+}
+
+:focus::-ms-input-placeholder {
+ @include placeholder-focus;
+}
+
+:focus::placeholder {
+ @include placeholder-focus;
+}
+
+search {
+ display: flex;
+ width: 100%;
+ border-radius: 1rem;
+ border: 1px solid var(--search-border-color);
+ background: var(--main-bg);
+ padding: 0 0.5rem;
+
+ i {
+ z-index: 2;
+ font-size: 0.9rem;
+ color: var(--search-icon-color);
+ }
+}
+
+#sidebar-trigger,
+#search-trigger {
+ display: none;
+}
+
+/* 'Cancel' link */
+#search-cancel {
+ color: var(--link-color);
+ display: none;
+ white-space: nowrap;
+
+ @extend %cursor-pointer;
+}
+
+#search-input {
+ background: center;
+ border: 0;
+ border-radius: 0;
+ padding: 0.18rem 0.3rem;
+ color: var(--text-color);
+ height: auto;
+
+ &:focus {
+ box-shadow: none;
+ }
+}
+
+#search-hints {
+ padding: 0 1rem;
+
+ h4 {
+ margin-bottom: 1.5rem;
+ }
+
+ .post-tag {
+ display: inline-block;
+ line-height: 1rem;
+ font-size: 1rem;
+ background: var(--search-tag-bg);
+ border: none;
+ padding: 0.5rem;
+ margin: 0 1.25rem 1rem 0;
+
+ &::before {
+ content: '#';
+ color: var(--text-muted-color);
+ padding-right: 0.2rem;
+ }
+
+ @extend %link-color;
+ }
+}
+
+#search-results {
+ padding-bottom: 3rem;
+
+ a {
+ font-size: 1.4rem;
+ line-height: 2.5rem;
+
+ &:hover {
+ @extend %link-hover;
+ }
+
+ @extend %link-color;
+ @extend %no-bottom-border;
+ @extend %heading;
+ }
+
+ > article {
+ width: 100%;
+
+ &:not(:last-child) {
+ margin-bottom: 1rem;
+ }
+
+ /* icons */
+ i {
+ color: #818182;
+ margin-right: 0.15rem;
+ font-size: 80%;
+ }
+
+ > p {
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 3;
+ -webkit-box-orient: vertical;
+ }
+ }
+} /* #search-results */
+
+#topbar-title {
+ display: none;
+ font-size: 1.1rem;
+ font-weight: 600;
+ font-family: sans-serif;
+ color: var(--topbar-text-color);
+ text-align: center;
+ width: 70%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: keep-all;
+ white-space: nowrap;
+}
+
+#mask {
+ display: none;
+ position: fixed;
+ inset: 0 0 0 0;
+ height: 100%;
+ width: 100%;
+ z-index: 1;
+
+ @at-root [#{$sidebar-display}] & {
+ display: block !important;
+ }
+}
+
+/* --- basic wrappers --- */
+
+#main-wrapper {
+ position: relative;
+
+ @include pl-pr(0);
+
+ > .container {
+ min-height: 100vh;
+ }
+}
+
+#topbar-wrapper.row,
+#main-wrapper > .container > .row,
+#search-result-wrapper > .row {
+ @include ml-mr(0);
+}
+
+#tail-wrapper {
+ > :not(script) {
+ margin-top: 3rem;
+ }
+}
+
+/* --- button back-to-top --- */
+
+#back-to-top {
+ visibility: hidden;
+ opacity: 0;
+ z-index: 1;
+ cursor: pointer;
+ position: fixed;
+ right: 1rem;
+ bottom: calc($footer-height-large - $back2top-size / 2);
+ background: var(--button-bg);
+ color: var(--btn-backtotop-color);
+ padding: 0;
+ width: $back2top-size;
+ height: $back2top-size;
+ border-radius: 50%;
+ border: 1px solid var(--btn-backtotop-border-color);
+ transition: opacity 0.5s ease-in-out, transform 0.2s ease-out;
+
+ &:hover {
+ transform: translate3d(0, -5px, 0);
+ -webkit-transform: translate3d(0, -5px, 0);
+ }
+
+ i {
+ line-height: $back2top-size;
+ position: relative;
+ bottom: 2px;
+ }
+
+ &.show {
+ opacity: 1;
+ visibility: visible;
+ }
+}
+
+#notification {
+ @-webkit-keyframes popup {
+ from {
+ opacity: 0;
+ bottom: 0;
+ }
+ }
+
+ @keyframes popup {
+ from {
+ opacity: 0;
+ bottom: 0;
+ }
+ }
+
+ .toast-header {
+ background: none;
+ border-bottom: none;
+ color: inherit;
+ }
+
+ .toast-body {
+ font-family: Lato, sans-serif;
+ line-height: 1.25rem;
+
+ button {
+ font-size: 90%;
+ min-width: 4rem;
+ }
+ }
+
+ &.toast {
+ &.show {
+ display: block;
+ min-width: 20rem;
+ border-radius: 0.5rem;
+ -webkit-backdrop-filter: blur(10px);
+ backdrop-filter: blur(10px);
+ background-color: rgba(255, 255, 255, 0.5);
+ color: #1b1b1eba;
+ position: fixed;
+ left: 50%;
+ bottom: 20%;
+ transform: translateX(-50%);
+ -webkit-animation: popup 0.8s;
+ animation: popup 0.8s;
+ }
+ }
+}
+
+/*
+ Responsive Design:
+
+ {sidebar, content, panel} >= 1200px screen width
+ {sidebar, content} >= 850px screen width
+ {content} <= 849px screen width
+
+*/
+
+@media all and (max-width: 576px) {
+ main {
+ .content {
+ > blockquote[class^='prompt-'] {
+ @include ml-mr(-1rem);
+
+ border-radius: 0;
+ max-width: none;
+ }
+ }
+ }
+
+ #avatar {
+ width: 5rem;
+ height: 5rem;
+ }
+}
+
+@media all and (max-width: 768px) {
+ %full-width {
+ max-width: 100%;
+ }
+
+ #topbar {
+ @extend %full-width;
+ }
+
+ #main-wrapper > .container {
+ @extend %full-width;
+ @include pl-pr(0);
+ }
+}
+
+/* hide sidebar and panel */
+@media all and (max-width: 849px) {
+ @mixin slide($append: null) {
+ $basic: transform 0.4s ease;
+
+ @if $append {
+ transition: $basic, $append;
+ } @else {
+ transition: $basic;
+ }
+ }
+
+ footer {
+ @include slide;
+
+ height: $footer-height-large;
+ padding: 1.5rem 0;
+ }
+
+ [#{$sidebar-display}] {
+ #sidebar {
+ transform: translateX(0);
+ }
+
+ #main-wrapper {
+ transform: translateX($sidebar-width);
+ }
+
+ #back-to-top {
+ visibility: hidden;
+ }
+ }
+
+ #sidebar {
+ @include slide;
+
+ transform: translateX(-$sidebar-width); /* hide */
+ -webkit-transform: translateX(-$sidebar-width);
+ }
+
+ #main-wrapper {
+ @include slide;
+ }
+
+ #topbar,
+ #main-wrapper > .container {
+ max-width: 100%;
+ }
+
+ #search-result-wrapper {
+ width: 100%;
+ }
+
+ #breadcrumb,
+ search {
+ display: none;
+ }
+
+ #topbar-wrapper {
+ @include slide(top 0.2s ease);
+
+ left: 0;
+ }
+
+ main,
+ #panel-wrapper {
+ margin-top: 0;
+ }
+
+ #topbar-title,
+ #sidebar-trigger,
+ #search-trigger {
+ display: block;
+ }
+
+ #search-result-wrapper .content {
+ letter-spacing: 0;
+ }
+
+ #tags {
+ justify-content: center !important;
+ }
+
+ h1.dynamic-title {
+ display: none;
+
+ ~ .content {
+ margin-top: 2.5rem;
+ }
+ }
+} /* max-width: 849px */
+
+/* Sidebar is visible */
+@media all and (min-width: 850px) {
+ /* Solved jumping scrollbar */
+ html {
+ overflow-y: scroll;
+ }
+
+ #main-wrapper {
+ margin-left: $sidebar-width;
+ }
+
+ #sidebar {
+ .profile-wrapper {
+ margin-top: 3rem;
+ }
+ }
+
+ #search-hints {
+ display: none;
+ }
+
+ search {
+ max-width: $search-max-width;
+ }
+
+ #search-result-wrapper {
+ max-width: $main-content-max-width;
+ justify-content: start !important;
+ }
+
+ main {
+ h1 {
+ margin-top: 3rem;
+ }
+ }
+
+ div.content .table-wrapper > table {
+ min-width: 70%;
+ }
+
+ /* button 'back-to-Top' position */
+ #back-to-top {
+ right: 5%;
+ bottom: calc($footer-height - $back2top-size / 2);
+ }
+
+ #topbar-title {
+ text-align: left;
+ }
+}
+
+/* Pad horizontal */
+@media all and (min-width: 992px) and (max-width: 1199px) {
+ #main-wrapper > .container .col-lg-11 {
+ flex: 0 0 96%;
+ max-width: 96%;
+ }
+}
+
+/* Compact icons in sidebar & panel hidden */
+@media all and (min-width: 850px) and (max-width: 1199px) {
+ #search-results > div {
+ max-width: 700px;
+ }
+
+ #breadcrumb {
+ width: 65%;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ word-break: keep-all;
+ white-space: nowrap;
+ }
+}
+
+/* panel hidden */
+@media all and (max-width: 1199px) {
+ #panel-wrapper {
+ display: none;
+ }
+
+ #main-wrapper > .container > div.row {
+ justify-content: center !important;
+ }
+}
+
+/* --- desktop mode, both sidebar and panel are visible --- */
+
+@media all and (min-width: 1200px) {
+ search {
+ margin-right: 4rem;
+ }
+
+ #search-input {
+ transition: all 0.3s ease-in-out;
+ }
+
+ #search-results > article {
+ width: 45%;
+
+ &:nth-child(odd) {
+ margin-right: 1.5rem;
+ }
+
+ &:nth-child(even) {
+ margin-left: 1.5rem;
+ }
+
+ &:last-child:nth-child(odd) {
+ position: relative;
+ right: 24.3%;
+ }
+ }
+
+ .content {
+ font-size: 1.03rem;
+ }
+}
+
+@media all and (min-width: 1400px) {
+ #back-to-top {
+ right: calc((100vw - $sidebar-width - 1140px) / 2 + 3rem);
+ }
+}
+
+@media all and (min-width: 1650px) {
+ $icon-gap: 1rem;
+
+ #main-wrapper {
+ margin-left: $sidebar-width-large;
+ }
+
+ #topbar-wrapper {
+ left: $sidebar-width-large;
+ }
+
+ search {
+ margin-right: calc(
+ $main-content-max-width / 4 - $search-max-width - 0.75rem
+ );
+ }
+
+ #main-wrapper > .container {
+ max-width: $main-content-max-width;
+ padding-left: 1.75rem !important;
+ padding-right: 1.75rem !important;
+ }
+
+ main.col-12,
+ #tail-wrapper {
+ padding-right: 4.5rem !important;
+ }
+
+ #back-to-top {
+ right: calc(
+ (100vw - $sidebar-width-large - $main-content-max-width) / 2 + 2rem
+ );
+ }
+
+ #sidebar {
+ width: $sidebar-width-large;
+
+ .profile-wrapper {
+ margin-top: 3.5rem;
+ margin-bottom: 2.5rem;
+ padding-left: 3.5rem;
+ }
+
+ ul {
+ li.nav-item {
+ @include pl-pr(2.75rem);
+ }
+ }
+
+ .sidebar-bottom {
+ padding-left: 2.75rem;
+ margin-bottom: 1.75rem;
+
+ a:not(:last-child) {
+ margin-right: $sb-btn-gap-lg;
+ }
+
+ .icon-border {
+ @include ml-mr(calc(($sb-btn-gap-lg - $btn-border-width) / 2));
+ }
+ }
+ }
+} /* min-width: 1650px */
diff --git a/_sass/addon/module.scss b/_sass/addon/module.scss
new file mode 100644
index 0000000..9e85adc
--- /dev/null
+++ b/_sass/addon/module.scss
@@ -0,0 +1,204 @@
+/*
+* Mainly scss modules, only imported to `assets/css/main.scss`
+*/
+
+/* ---------- scss placeholder --------- */
+
+%heading {
+ color: var(--heading-color);
+ font-weight: 400;
+ font-family: $font-family-heading;
+}
+
+%section {
+ main & {
+ margin-top: 2.5rem;
+ margin-bottom: 1.25rem;
+
+ &:focus {
+ outline: none; /* avoid outline in Safari */
+ }
+ }
+}
+
+%anchor {
+ .anchor {
+ font-size: 80%;
+ }
+
+ @media (hover: hover) {
+ .anchor {
+ visibility: hidden;
+ opacity: 0;
+ transition: opacity 0.25s ease-in, visibility 0s ease-in 0.25s;
+ }
+
+ &:hover {
+ .anchor {
+ visibility: visible;
+ opacity: 1;
+ transition: opacity 0.25s ease-in, visibility 0s ease-in 0s;
+ }
+ }
+ }
+}
+
+%tag-hover {
+ background: var(--tag-hover);
+ transition: background 0.35s ease-in-out;
+}
+
+%table-cell {
+ padding: 0.4rem 1rem;
+ font-size: 95%;
+ white-space: nowrap;
+}
+
+%link-hover {
+ color: #d2603a !important;
+ border-bottom: 1px solid #d2603a;
+ text-decoration: none;
+}
+
+%link-color {
+ color: var(--link-color);
+}
+
+%link-underline {
+ border-bottom: 1px solid var(--link-underline-color);
+}
+
+%clickable-transition {
+ transition: all 0.3s ease-in-out;
+}
+
+%no-cursor {
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+%no-bottom-border {
+ border-bottom: none;
+}
+
+%cursor-pointer {
+ cursor: pointer;
+}
+
+%normal-font-style {
+ font-style: normal;
+}
+
+%rounded {
+ border-radius: $base-radius;
+}
+
+%img-caption {
+ + em {
+ display: block;
+ text-align: center;
+ font-style: normal;
+ font-size: 80%;
+ padding: 0;
+ color: #6d6c6c;
+ }
+}
+
+%sidebar-links {
+ color: var(--sidebar-muted-color);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+}
+
+%text-clip {
+ display: -webkit-box;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+}
+
+%text-highlight {
+ color: var(--text-muted-highlight-color);
+ font-weight: 600;
+}
+
+%text-sm {
+ font-size: 0.85rem;
+}
+
+%text-xs {
+ font-size: 0.8rem;
+}
+
+%sup-fn-target {
+ &:target {
+ background-color: var(--footnote-target-bg);
+ width: -moz-fit-content;
+ width: -webkit-fit-content;
+ width: fit-content;
+ transition: background-color 1.75s ease-in-out;
+ }
+}
+
+/* ---------- scss mixin --------- */
+
+@mixin mt-mb($value) {
+ margin-top: $value;
+ margin-bottom: $value;
+}
+
+@mixin ml-mr($value) {
+ margin-left: $value;
+ margin-right: $value;
+}
+
+@mixin pt-pb($val) {
+ padding-top: $val;
+ padding-bottom: $val;
+}
+
+@mixin pl-pr($val) {
+ padding-left: $val;
+ padding-right: $val;
+}
+
+@mixin placeholder {
+ color: var(--text-muted-color) !important;
+}
+
+@mixin placeholder-focus {
+ opacity: 0.6;
+}
+
+@mixin label($font-size: 1rem, $font-weight: 600, $color: var(--label-color)) {
+ color: $color;
+ font-size: $font-size;
+ font-weight: $font-weight;
+}
+
+@mixin align-center {
+ position: relative;
+ left: 50%;
+ transform: translateX(-50%);
+}
+
+@mixin prompt($type, $fa-content, $fa-style: 'solid', $rotate: 0) {
+ &.prompt-#{$type} {
+ background-color: var(--prompt-#{$type}-bg);
+
+ &::before {
+ content: $fa-content;
+ color: var(--prompt-#{$type}-icon-color);
+ font: var(--fa-font-#{$fa-style});
+
+ @if $rotate != 0 {
+ transform: rotate(#{$rotate}deg);
+ }
+ }
+ }
+}
diff --git a/_sass/addon/syntax.scss b/_sass/addon/syntax.scss
new file mode 100644
index 0000000..b0efd20
--- /dev/null
+++ b/_sass/addon/syntax.scss
@@ -0,0 +1,292 @@
+/*
+* The syntax highlight.
+*/
+
+@import 'colors/syntax-light';
+@import 'colors/syntax-dark';
+
+html {
+ @media (prefers-color-scheme: light) {
+ &:not([data-mode]),
+ &[data-mode='light'] {
+ @include light-syntax;
+ }
+
+ &[data-mode='dark'] {
+ @include dark-syntax;
+ }
+ }
+
+ @media (prefers-color-scheme: dark) {
+ &:not([data-mode]),
+ &[data-mode='dark'] {
+ @include dark-syntax;
+ }
+
+ &[data-mode='light'] {
+ @include light-syntax;
+ }
+ }
+}
+
+/* -- code snippets -- */
+
+%code-snippet-bg {
+ background-color: var(--highlight-bg-color);
+}
+
+%code-snippet-padding {
+ padding-left: 1rem;
+ padding-right: 1.5rem;
+}
+
+.highlighter-rouge {
+ color: var(--highlighter-rouge-color);
+ margin-top: 0.5rem;
+ margin-bottom: 1.2em; /* Override BS Inline-code style */
+}
+
+.highlight {
+ @extend %rounded;
+ @extend %code-snippet-bg;
+
+ overflow: auto;
+ padding-bottom: 0.75rem;
+
+ @at-root figure#{&} {
+ @extend %code-snippet-bg;
+ }
+
+ pre {
+ margin-bottom: 0;
+ font-size: $code-font-size;
+ line-height: 1.4rem;
+ word-wrap: normal; /* Fixed Safari overflow-x */
+ }
+
+ table {
+ td {
+ &:first-child {
+ display: inline-block;
+ margin-left: 1rem;
+ margin-right: 0.75rem;
+ }
+
+ &:last-child {
+ padding-right: 2rem !important;
+ }
+
+ pre {
+ overflow: visible; /* Fixed iOS safari overflow-x */
+ word-break: normal; /* Fixed iOS safari linenos code break */
+ }
+ }
+ }
+
+ .lineno {
+ text-align: right;
+ color: var(--highlight-lineno-color);
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -o-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+ }
+} /* .highlight */
+
+code {
+ -webkit-hyphens: none;
+ -ms-hyphens: none;
+ hyphens: none;
+ color: var(--code-color);
+
+ &.highlighter-rouge {
+ font-size: $code-font-size;
+ padding: 3px 6px;
+ word-break: break-word;
+ border-radius: 4px;
+ background-color: var(--inline-code-bg);
+ }
+
+ &.filepath {
+ background-color: inherit;
+ color: var(--filepath-text-color);
+ font-weight: 600;
+ padding: 0;
+ }
+
+ a > &.highlighter-rouge {
+ padding-bottom: 0; /* show link's underlinke */
+ color: inherit;
+ }
+
+ a:hover > &.highlighter-rouge {
+ border-bottom: none;
+ }
+
+ blockquote & {
+ color: inherit;
+ }
+}
+
+td.rouge-code {
+ @extend %code-snippet-padding;
+
+ /*
+ Prevent some browser extends from
+ changing the URL string of code block.
+ */
+ a {
+ color: inherit !important;
+ border-bottom: none !important;
+ pointer-events: none;
+ }
+}
+
+div[class^='language-'] {
+ @extend %rounded;
+ @extend %code-snippet-bg;
+
+ box-shadow: var(--language-border-color) 0 0 0 1px;
+
+ .content > & {
+ @include ml-mr(-1rem);
+
+ border-radius: 0;
+ }
+
+ .highlight {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+}
+
+/* Hide line numbers for default, console, and terminal code snippets */
+div {
+ &.nolineno,
+ &.language-plaintext,
+ &.language-console,
+ &.language-terminal {
+ td:first-child {
+ padding: 0 !important;
+ margin-right: 0;
+
+ .lineno {
+ display: none;
+ }
+ }
+ }
+}
+
+.code-header {
+ @extend %no-cursor;
+
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ height: $code-header-height;
+ margin-left: 0.75rem;
+ margin-right: 0.25rem;
+
+ /* the label block */
+ span {
+ line-height: $code-header-height;
+
+ /* label icon */
+ i {
+ font-size: 1rem;
+ width: $code-icon-width;
+ color: var(--code-header-icon-color);
+
+ &.small {
+ font-size: 70%;
+ }
+ }
+
+ @at-root [file] #{&} > i {
+ position: relative;
+ top: 1px; /* center the file icon */
+ }
+
+ /* label text */
+ &::after {
+ content: attr(data-label-text);
+ font-size: 0.85rem;
+ font-weight: 600;
+ color: var(--code-header-text-color);
+ }
+ }
+
+ /* clipboard */
+ button {
+ @extend %cursor-pointer;
+ @extend %rounded;
+
+ border: 1px solid transparent;
+ height: $code-header-height;
+ width: $code-header-height;
+ padding: 0;
+ background-color: inherit;
+
+ i {
+ color: var(--code-header-icon-color);
+ }
+
+ &[timeout] {
+ &:hover {
+ border-color: var(--clipboard-checked-color);
+ }
+
+ i {
+ color: var(--clipboard-checked-color);
+ }
+ }
+
+ &:focus {
+ outline: none;
+ }
+
+ &:not([timeout]):hover {
+ background-color: rgba(128, 128, 128, 0.37);
+
+ i {
+ color: white;
+ }
+ }
+ }
+}
+
+@media all and (min-width: 576px) {
+ div[class^='language-'] {
+ .content > & {
+ @include ml-mr(0);
+
+ border-radius: $base-radius;
+ }
+
+ .code-header {
+ @include ml-mr(0);
+
+ $dot-margin: 1rem;
+
+ &::before {
+ content: '';
+ display: inline-block;
+ margin-left: $dot-margin;
+ width: $code-dot-size;
+ height: $code-dot-size;
+ border-radius: 50%;
+ background-color: var(--code-header-muted-color);
+ box-shadow: ($code-dot-size + $code-dot-gap) 0 0
+ var(--code-header-muted-color),
+ ($code-dot-size + $code-dot-gap) * 2 0 0
+ var(--code-header-muted-color);
+ }
+
+ span {
+ // center the text of label
+ margin-left: calc(($dot-margin + $code-dot-size) / 2 * -1);
+ }
+ }
+ }
+}
diff --git a/_sass/addon/variables.scss b/_sass/addon/variables.scss
new file mode 100644
index 0000000..8924a00
--- /dev/null
+++ b/_sass/addon/variables.scss
@@ -0,0 +1,33 @@
+/*
+ * The SCSS variables
+ */
+
+/* sidebar */
+
+$sidebar-width: 260px !default; /* the basic width */
+$sidebar-width-large: 300px !default; /* screen width: >= 1650px */
+$sb-btn-gap: 0.8rem !default;
+$sb-btn-gap-lg: 1rem !default;
+
+/* other framework sizes */
+
+$topbar-height: 3rem !default;
+$search-max-width: 200px !default;
+$footer-height: 5rem !default;
+$footer-height-large: 6rem !default; /* screen width: < 850px */
+$main-content-max-width: 1250px !default;
+$base-radius: 0.625rem !default;
+$back2top-size: 2.75rem !default;
+
+/* syntax highlight */
+
+$code-font-size: 0.85rem !default;
+$code-header-height: 2.25rem !default;
+$code-dot-size: 0.75rem !default;
+$code-dot-gap: 0.5rem !default;
+$code-icon-width: 1.75rem !default;
+
+/* fonts */
+
+$font-family-base: 'Source Sans Pro', 'Microsoft Yahei', sans-serif !default;
+$font-family-heading: Lato, 'Microsoft Yahei', sans-serif !default;
diff --git a/_sass/colors/syntax-dark.scss b/_sass/colors/syntax-dark.scss
new file mode 100644
index 0000000..eb92204
--- /dev/null
+++ b/_sass/colors/syntax-dark.scss
@@ -0,0 +1,164 @@
+/*
+ * The syntax dark mode styles.
+ */
+
+@mixin dark-syntax {
+ --language-border-color: #2d2d2d;
+ --highlight-bg-color: #151515;
+ --highlighter-rouge-color: #c9def1;
+ --highlight-lineno-color: #808080;
+ --inline-code-bg: rgba(255, 255, 255, 0.05);
+ --code-color: #b0b0b0;
+ --code-header-text-color: #6a6a6a;
+ --code-header-muted-color: #353535;
+ --code-header-icon-color: #565656;
+ --clipboard-checked-color: #2bcc2b;
+ --filepath-text-color: #cacaca;
+
+ .highlight .gp {
+ color: #87939d;
+ }
+
+ /* --- Syntax highlight theme from `rougify style base16.dark` --- */
+
+ .highlight table td {
+ padding: 5px;
+ }
+
+ .highlight table pre {
+ margin: 0;
+ }
+
+ .highlight,
+ .highlight .w {
+ color: #d0d0d0;
+ background-color: #151515;
+ }
+
+ .highlight .err {
+ color: #151515;
+ background-color: #ac4142;
+ }
+
+ .highlight .c,
+ .highlight .ch,
+ .highlight .cd,
+ .highlight .cm,
+ .highlight .cpf,
+ .highlight .c1,
+ .highlight .cs {
+ color: #848484;
+ }
+
+ .highlight .cp {
+ color: #f4bf75;
+ }
+
+ .highlight .nt {
+ color: #f4bf75;
+ }
+
+ .highlight .o,
+ .highlight .ow {
+ color: #d0d0d0;
+ }
+
+ .highlight .p,
+ .highlight .pi {
+ color: #d0d0d0;
+ }
+
+ .highlight .gi {
+ color: #90a959;
+ }
+
+ .highlight .gd {
+ color: #f08a8b;
+ background-color: #320000;
+ }
+
+ .highlight .gh {
+ color: #6a9fb5;
+ background-color: #151515;
+ font-weight: bold;
+ }
+
+ .highlight .k,
+ .highlight .kn,
+ .highlight .kp,
+ .highlight .kr,
+ .highlight .kv {
+ color: #aa759f;
+ }
+
+ .highlight .kc {
+ color: #d28445;
+ }
+
+ .highlight .kt {
+ color: #d28445;
+ }
+
+ .highlight .kd {
+ color: #d28445;
+ }
+
+ .highlight .s,
+ .highlight .sb,
+ .highlight .sc,
+ .highlight .dl,
+ .highlight .sd,
+ .highlight .s2,
+ .highlight .sh,
+ .highlight .sx,
+ .highlight .s1 {
+ color: #90a959;
+ }
+
+ .highlight .sa {
+ color: #aa759f;
+ }
+
+ .highlight .sr {
+ color: #75b5aa;
+ }
+
+ .highlight .si {
+ color: #b76d45;
+ }
+
+ .highlight .se {
+ color: #b76d45;
+ }
+
+ .highlight .nn {
+ color: #f4bf75;
+ }
+
+ .highlight .nc {
+ color: #f4bf75;
+ }
+
+ .highlight .no {
+ color: #f4bf75;
+ }
+
+ .highlight .na {
+ color: #6a9fb5;
+ }
+
+ .highlight .m,
+ .highlight .mb,
+ .highlight .mf,
+ .highlight .mh,
+ .highlight .mi,
+ .highlight .il,
+ .highlight .mo,
+ .highlight .mx {
+ color: #90a959;
+ }
+
+ .highlight .ss {
+ color: #90a959;
+ }
+}
diff --git a/_sass/colors/syntax-light.scss b/_sass/colors/syntax-light.scss
new file mode 100644
index 0000000..76aa669
--- /dev/null
+++ b/_sass/colors/syntax-light.scss
@@ -0,0 +1,210 @@
+/*
+ * The syntax light mode code snippet colors.
+ */
+
+@mixin light-syntax {
+ /* --- custom light colors --- */
+ --language-border-color: #ececec;
+ --highlight-bg-color: #f6f8fa;
+ --highlighter-rouge-color: #3f596f;
+ --highlight-lineno-color: #9e9e9e;
+ --inline-code-bg: rgba(25, 25, 28, 0.05);
+ --code-color: #3a3a3a;
+ --code-header-text-color: #a3a3a3;
+ --code-header-muted-color: #e5e5e5;
+ --code-header-icon-color: #c9c8c8;
+ --clipboard-checked-color: #43c743;
+
+ /* --- Syntax highlight theme from `rougify style github` --- */
+
+ .highlight table td {
+ padding: 5px;
+ }
+
+ .highlight table pre {
+ margin: 0;
+ }
+
+ .highlight,
+ .highlight .w {
+ color: #24292f;
+ background-color: #f6f8fa;
+ }
+
+ .highlight .k,
+ .highlight .kd,
+ .highlight .kn,
+ .highlight .kp,
+ .highlight .kr,
+ .highlight .kt,
+ .highlight .kv {
+ color: #cf222e;
+ }
+
+ .highlight .gr {
+ color: #f6f8fa;
+ }
+
+ .highlight .gd {
+ color: #82071e;
+ background-color: #ffebe9;
+ }
+
+ .highlight .nb {
+ color: #953800;
+ }
+
+ .highlight .nc {
+ color: #953800;
+ }
+
+ .highlight .no {
+ color: #953800;
+ }
+
+ .highlight .nn {
+ color: #953800;
+ }
+
+ .highlight .sr {
+ color: #116329;
+ }
+
+ .highlight .na {
+ color: #116329;
+ }
+
+ .highlight .nt {
+ color: #116329;
+ }
+
+ .highlight .gi {
+ color: #116329;
+ background-color: #dafbe1;
+ }
+
+ .highlight .kc {
+ color: #0550ae;
+ }
+
+ .highlight .l,
+ .highlight .ld,
+ .highlight .m,
+ .highlight .mb,
+ .highlight .mf,
+ .highlight .mh,
+ .highlight .mi,
+ .highlight .il,
+ .highlight .mo,
+ .highlight .mx {
+ color: #0550ae;
+ }
+
+ .highlight .sb {
+ color: #0550ae;
+ }
+
+ .highlight .bp {
+ color: #0550ae;
+ }
+
+ .highlight .ne {
+ color: #0550ae;
+ }
+
+ .highlight .nl {
+ color: #0550ae;
+ }
+
+ .highlight .py {
+ color: #0550ae;
+ }
+
+ .highlight .nv,
+ .highlight .vc,
+ .highlight .vg,
+ .highlight .vi,
+ .highlight .vm {
+ color: #0550ae;
+ }
+
+ .highlight .o,
+ .highlight .ow {
+ color: #0550ae;
+ }
+
+ .highlight .gh {
+ color: #0550ae;
+ font-weight: bold;
+ }
+
+ .highlight .gu {
+ color: #0550ae;
+ font-weight: bold;
+ }
+
+ .highlight .s,
+ .highlight .sa,
+ .highlight .sc,
+ .highlight .dl,
+ .highlight .sd,
+ .highlight .s2,
+ .highlight .se,
+ .highlight .sh,
+ .highlight .sx,
+ .highlight .s1,
+ .highlight .ss {
+ color: #0a3069;
+ }
+
+ .highlight .nd {
+ color: #8250df;
+ }
+
+ .highlight .nf,
+ .highlight .fm {
+ color: #8250df;
+ }
+
+ .highlight .err {
+ color: #f6f8fa;
+ background-color: #82071e;
+ }
+
+ .highlight .c,
+ .highlight .ch,
+ .highlight .cd,
+ .highlight .cm,
+ .highlight .cp,
+ .highlight .cpf,
+ .highlight .c1,
+ .highlight .cs {
+ color: #68717a;
+ }
+
+ .highlight .gl {
+ color: #68717a;
+ }
+
+ .highlight .gt {
+ color: #68717a;
+ }
+
+ .highlight .ni {
+ color: #24292f;
+ }
+
+ .highlight .si {
+ color: #24292f;
+ }
+
+ .highlight .ge {
+ color: #24292f;
+ font-style: italic;
+ }
+
+ .highlight .gs {
+ color: #24292f;
+ font-weight: bold;
+ }
+} /* light-syntax */
diff --git a/_sass/colors/typography-dark.scss b/_sass/colors/typography-dark.scss
new file mode 100644
index 0000000..12427ec
--- /dev/null
+++ b/_sass/colors/typography-dark.scss
@@ -0,0 +1,147 @@
+/*
+ * The main dark mode styles
+ */
+
+@mixin dark-scheme {
+ /* Framework color */
+ --main-bg: rgb(27, 27, 30);
+ --mask-bg: rgb(68, 69, 70);
+ --main-border-color: rgb(44, 45, 45);
+
+ /* Common color */
+ --text-color: rgb(175, 176, 177);
+ --text-muted-color: #868686;
+ --text-muted-highlight-color: #aeaeae;
+ --heading-color: #cccccc;
+ --label-color: #a7a7a7;
+ --blockquote-border-color: rgb(66, 66, 66);
+ --blockquote-text-color: #868686;
+ --link-color: rgb(138, 180, 248);
+ --link-underline-color: rgb(82, 108, 150);
+ --button-bg: #1e1e1e;
+ --btn-border-color: #2e2f31;
+ --btn-backtotop-color: var(--text-color);
+ --btn-backtotop-border-color: #212122;
+ --btn-box-shadow: var(--main-bg);
+ --card-header-bg: #292929;
+ --checkbox-color: rgb(118, 120, 121);
+ --checkbox-checked-color: var(--link-color);
+ --img-bg: radial-gradient(circle, rgb(22, 22, 24) 0%, rgb(32, 32, 32) 100%);
+ --shimmer-bg: linear-gradient(
+ 90deg,
+ rgba(255, 255, 255, 0) 0%,
+ rgba(58, 55, 55, 0.4) 50%,
+ rgba(255, 255, 255, 0) 100%
+ );
+
+ /* Sidebar */
+ --site-title-color: #717070;
+ --site-subtitle-color: #868686;
+ --sidebar-bg: #1e1e1e;
+ --sidebar-border-color: #292929;
+ --sidebar-muted-color: #868686;
+ --sidebar-active-color: rgb(255, 255, 255, 0.95);
+ --sidebar-hover-bg: #262626;
+ --sidebar-btn-bg: #232328;
+ --sidebar-btn-color: #787878;
+ --avatar-border-color: rgb(206, 206, 206, 0.9);
+
+ /* Topbar */
+ --topbar-bg: rgb(27, 27, 30, 0.64);
+ --topbar-text-color: var(--text-color);
+ --search-border-color: rgb(55, 55, 55);
+ --search-icon-color: rgb(100, 102, 105);
+ --input-focus-border-color: rgb(112, 114, 115);
+
+ /* Home page */
+ --post-list-text-color: rgb(175, 176, 177);
+ --btn-patinator-text-color: var(--text-color);
+ --btn-paginator-hover-color: #2e2e2e;
+
+ /* Posts */
+ --toc-highlight: rgb(116, 178, 243);
+ --tag-hover: rgb(43, 56, 62);
+ --tb-odd-bg: #252526; /* odd rows of the posts' table */
+ --tb-even-bg: rgb(31, 31, 34); /* even rows of the posts' table */
+ --tb-border-color: var(--tb-odd-bg);
+ --footnote-target-bg: rgb(63, 81, 181);
+ --btn-share-color: #6c757d;
+ --btn-share-hover-color: #bfc1ca;
+ --card-bg: #1e1e1e;
+ --card-hovor-bg: #464d51;
+ --card-shadow: rgb(21, 21, 21, 0.72) 0 6px 18px 0,
+ rgb(137, 135, 135, 0.24) 0 0 0 1px;
+ --kbd-wrap-color: #6a6a6a;
+ --kbd-text-color: #d3d3d3;
+ --kbd-bg-color: #242424;
+ --prompt-text-color: rgb(216, 212, 212, 0.75);
+ --prompt-tip-bg: rgb(22, 60, 36, 0.64);
+ --prompt-tip-icon-color: rgb(15, 164, 15, 0.81);
+ --prompt-info-bg: rgb(7, 59, 104, 0.8);
+ --prompt-info-icon-color: #0075d1;
+ --prompt-warning-bg: rgb(90, 69, 3, 0.88);
+ --prompt-warning-icon-color: rgb(255, 165, 0, 0.8);
+ --prompt-danger-bg: rgb(86, 28, 8, 0.8);
+ --prompt-danger-icon-color: #cd0202;
+
+ /* Tags */
+ --tag-border: rgb(59, 79, 88);
+ --tag-shadow: rgb(32, 33, 33);
+ --dash-color: rgb(63, 65, 68);
+ --search-tag-bg: #292828;
+
+ /* Categories */
+ --categories-border: rgb(64, 66, 69, 0.5);
+ --categories-hover-bg: rgb(73, 75, 76);
+ --categories-icon-hover-color: white;
+
+ /* Archive */
+ --timeline-node-bg: rgb(150, 152, 156);
+ --timeline-color: rgb(63, 65, 68);
+ --timeline-year-dot-color: var(--timeline-color);
+
+ color-scheme: dark;
+
+ .light {
+ display: none;
+ }
+
+ /* Categories */
+ .categories.card,
+ .list-group-item {
+ background-color: var(--card-bg);
+ }
+
+ .categories {
+ .card-header {
+ background-color: var(--card-header-bg);
+ }
+
+ .list-group-item {
+ border-left: none;
+ border-right: none;
+ padding-left: 2rem;
+ border-color: var(--categories-border);
+
+ &:last-child {
+ border-bottom-color: var(--card-bg);
+ }
+ }
+ }
+
+ #archives li:nth-child(odd) {
+ background-image: linear-gradient(
+ to left,
+ rgb(26, 26, 30),
+ rgb(39, 39, 45),
+ rgb(39, 39, 45),
+ rgb(39, 39, 45),
+ rgb(26, 26, 30)
+ );
+ }
+
+ /* stylelint-disable-next-line selector-id-pattern */
+ #disqus_thread {
+ color-scheme: none;
+ }
+} /* dark-scheme */
diff --git a/_sass/colors/typography-light.scss b/_sass/colors/typography-light.scss
new file mode 100644
index 0000000..7800074
--- /dev/null
+++ b/_sass/colors/typography-light.scss
@@ -0,0 +1,112 @@
+/*
+ * The syntax light mode typography colors
+ */
+
+@mixin light-scheme {
+ /* Framework color */
+ --main-bg: white;
+ --mask-bg: #c1c3c5;
+ --main-border-color: #f3f3f3;
+
+ /* Common color */
+ --text-color: #34343c;
+ --text-muted-color: #757575;
+ --text-muted-highlight-color: inherit;
+ --heading-color: #2a2a2a;
+ --label-color: #585858;
+ --blockquote-border-color: #eeeeee;
+ --blockquote-text-color: #757575;
+ --link-color: #0056b2;
+ --link-underline-color: #dee2e6;
+ --button-bg: #ffffff;
+ --btn-border-color: #e9ecef;
+ --btn-backtotop-color: #686868;
+ --btn-backtotop-border-color: #f1f1f1;
+ --btn-box-shadow: #eaeaea;
+ --checkbox-color: #c5c5c5;
+ --checkbox-checked-color: #07a8f7;
+ --img-bg: radial-gradient(
+ circle,
+ rgb(255, 255, 255) 0%,
+ rgb(239, 239, 239) 100%
+ );
+ --shimmer-bg: linear-gradient(
+ 90deg,
+ rgba(250, 250, 250, 0) 0%,
+ rgba(232, 230, 230, 1) 50%,
+ rgba(250, 250, 250, 0) 100%
+ );
+
+ /* Sidebar */
+ --site-title-color: rgb(113, 113, 113);
+ --site-subtitle-color: #717171;
+ --sidebar-bg: #f6f8fa;
+ --sidebar-border-color: #efefef;
+ --sidebar-muted-color: #545454;
+ --sidebar-active-color: #1d1d1d;
+ --sidebar-hover-bg: rgb(223, 233, 241, 0.64);
+ --sidebar-btn-bg: white;
+ --sidebar-btn-color: #8e8e8e;
+ --avatar-border-color: white;
+
+ /* Topbar */
+ --topbar-bg: rgb(255, 255, 255, 0.7);
+ --topbar-text-color: rgb(78, 78, 78);
+ --search-border-color: rgb(240, 240, 240);
+ --search-icon-color: #c2c6cc;
+ --input-focus-border-color: #b8b8b8;
+
+ /* Home page */
+ --post-list-text-color: dimgray;
+ --btn-patinator-text-color: #555555;
+ --btn-paginator-hover-color: var(--sidebar-bg);
+
+ /* Posts */
+ --toc-highlight: #0550ae;
+ --btn-share-color: gray;
+ --btn-share-hover-color: #0d6efd;
+ --card-bg: white;
+ --card-hovor-bg: #e2e2e2;
+ --card-shadow: rgb(104, 104, 104, 0.05) 0 2px 6px 0,
+ rgba(211, 209, 209, 0.15) 0 0 0 1px;
+ --footnote-target-bg: lightcyan;
+ --tb-odd-bg: #fbfcfd;
+ --tb-border-color: #eaeaea;
+ --dash-color: silver;
+ --kbd-wrap-color: #bdbdbd;
+ --kbd-text-color: var(--text-color);
+ --kbd-bg-color: white;
+ --prompt-text-color: rgb(46, 46, 46, 0.77);
+ --prompt-tip-bg: rgb(123, 247, 144, 0.2);
+ --prompt-tip-icon-color: #03b303;
+ --prompt-info-bg: #e1f5fe;
+ --prompt-info-icon-color: #0070cb;
+ --prompt-warning-bg: rgb(255, 243, 205);
+ --prompt-warning-icon-color: #ef9c03;
+ --prompt-danger-bg: rgb(248, 215, 218, 0.56);
+ --prompt-danger-icon-color: #df3c30;
+
+ /* Tags */
+ --tag-border: #dee2e6;
+ --tag-shadow: var(--btn-border-color);
+ --tag-hover: rgb(222, 226, 230);
+ --search-tag-bg: #f8f9fa;
+
+ /* Categories */
+ --categories-border: rgba(0, 0, 0, 0.125);
+ --categories-hover-bg: var(--btn-border-color);
+ --categories-icon-hover-color: darkslategray;
+
+ /* Archive */
+ --timeline-color: rgba(0, 0, 0, 0.075);
+ --timeline-node-bg: #c2c6cc;
+ --timeline-year-dot-color: #ffffff;
+
+ [class^='prompt-'] {
+ --link-underline-color: rgb(219, 216, 216);
+ }
+
+ .dark {
+ display: none;
+ }
+} /* light-scheme */
diff --git a/_sass/layout/archives.scss b/_sass/layout/archives.scss
new file mode 100644
index 0000000..3a2e86b
--- /dev/null
+++ b/_sass/layout/archives.scss
@@ -0,0 +1,144 @@
+/*
+ Style for Archives
+*/
+
+#archives {
+ letter-spacing: 0.03rem;
+
+ $timeline-width: 4px;
+
+ %timeline {
+ content: '';
+ width: $timeline-width;
+ position: relative;
+ float: left;
+ background-color: var(--timeline-color);
+ }
+
+ .year {
+ height: 3.5rem;
+ font-size: 1.5rem;
+ position: relative;
+ left: 2px;
+ margin-left: -$timeline-width;
+
+ &::before {
+ @extend %timeline;
+
+ height: 72px;
+ left: 79px;
+ bottom: 16px;
+ }
+
+ &:first-child::before {
+ @extend %timeline;
+
+ height: 32px;
+ top: 24px;
+ }
+
+ /* Year dot */
+ &::after {
+ content: '';
+ display: inline-block;
+ position: relative;
+ border-radius: 50%;
+ width: 12px;
+ height: 12px;
+ left: 21.5px;
+ border: 3px solid;
+ background-color: var(--timeline-year-dot-color);
+ border-color: var(--timeline-node-bg);
+ box-shadow: 0 0 2px 0 #c2c6cc;
+ z-index: 1;
+ }
+ }
+
+ ul {
+ li {
+ font-size: 1.1rem;
+ line-height: 3rem;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &:nth-child(odd) {
+ background-color: var(--main-bg, #ffffff);
+ background-image: linear-gradient(
+ to left,
+ #ffffff,
+ #fbfbfb,
+ #fbfbfb,
+ #fbfbfb,
+ #ffffff
+ );
+ }
+
+ &::before {
+ @extend %timeline;
+
+ top: 0;
+ left: 77px;
+ height: 3.1rem;
+ }
+ }
+
+ &:last-child li:last-child::before {
+ height: 1.5rem;
+ }
+ } /* #archives ul */
+
+ .date {
+ white-space: nowrap;
+ display: inline-block;
+ position: relative;
+ right: 0.5rem;
+
+ &.month {
+ width: 1.4rem;
+ text-align: center;
+ }
+
+ &.day {
+ font-size: 85%;
+ font-family: Lato, sans-serif;
+ }
+ }
+
+ a {
+ /* post title in Archvies */
+ margin-left: 2.5rem;
+ position: relative;
+ top: 0.1rem;
+
+ &:hover {
+ border-bottom: none;
+ }
+
+ &::before {
+ /* the dot before post title */
+ content: '';
+ display: inline-block;
+ position: relative;
+ border-radius: 50%;
+ width: 8px;
+ height: 8px;
+ float: left;
+ top: 1.35rem;
+ left: 71px;
+ background-color: var(--timeline-node-bg);
+ box-shadow: 0 0 3px 0 #c2c6cc;
+ z-index: 1;
+ }
+ }
+} /* #archives */
+
+@media all and (max-width: 576px) {
+ #archives {
+ margin-top: -1rem;
+
+ ul {
+ letter-spacing: 0;
+ }
+ }
+}
diff --git a/_sass/layout/categories.scss b/_sass/layout/categories.scss
new file mode 100644
index 0000000..330d3d3
--- /dev/null
+++ b/_sass/layout/categories.scss
@@ -0,0 +1,83 @@
+/*
+ Style for Tab Categories
+*/
+
+%category-icon-color {
+ color: gray;
+}
+
+.categories {
+ margin-bottom: 2rem;
+ border-color: var(--categories-border);
+
+ &.card,
+ .list-group {
+ @extend %rounded;
+ }
+
+ .card-header {
+ $radius: calc($base-radius - 1px);
+
+ padding: 0.75rem;
+ border-radius: $radius;
+ border-bottom: 0;
+
+ &.hide-border-bottom {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+ }
+ }
+
+ i {
+ @extend %category-icon-color;
+
+ font-size: 86%; /* fontawesome icons */
+ }
+
+ .list-group-item {
+ border-left: none;
+ border-right: none;
+ padding-left: 2rem;
+
+ &:first-child {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ }
+
+ &:last-child {
+ border-bottom: 0;
+ }
+ }
+} /* .categories */
+
+.category-trigger {
+ width: 1.7rem;
+ height: 1.7rem;
+ border-radius: 50%;
+ text-align: center;
+ color: #6c757d !important;
+
+ i {
+ position: relative;
+ height: 0.7rem;
+ width: 1rem;
+ transition: transform 300ms ease;
+ }
+
+ &:hover {
+ i {
+ color: var(--categories-icon-hover-color);
+ }
+ }
+}
+
+/* only works on desktop */
+@media (hover: hover) {
+ .category-trigger:hover {
+ background-color: var(--categories-hover-bg);
+ }
+}
+
+.rotate {
+ transform: rotate(-90deg);
+}
diff --git a/_sass/layout/category-tag.scss b/_sass/layout/category-tag.scss
new file mode 100644
index 0000000..9e43a91
--- /dev/null
+++ b/_sass/layout/category-tag.scss
@@ -0,0 +1,72 @@
+/*
+ Style for page Category and Tag
+*/
+
+.dash {
+ margin: 0 0.5rem 0.6rem 0.5rem;
+ border-bottom: 2px dotted var(--dash-color);
+}
+
+#page-category,
+#page-tag {
+ ul > li {
+ line-height: 1.5rem;
+ padding: 0.6rem 0;
+
+ /* dot */
+ &::before {
+ background: #999999;
+ width: 5px;
+ height: 5px;
+ border-radius: 50%;
+ display: block;
+ content: '';
+ position: relative;
+ top: 0.6rem;
+ margin-right: 0.5rem;
+ }
+
+ /* post's title */
+ > a {
+ @extend %no-bottom-border;
+
+ font-size: 1.1rem;
+ }
+ }
+}
+
+/* tag icon */
+#page-tag h1 > i {
+ font-size: 1.2rem;
+}
+
+#page-category h1 > i {
+ font-size: 1.25rem;
+}
+
+#page-category,
+#page-tag,
+#access-lastmod {
+ a:hover {
+ @extend %link-hover;
+
+ margin-bottom: -1px; /* Avoid jumping */
+ }
+}
+
+@media all and (max-width: 576px) {
+ #page-category,
+ #page-tag {
+ ul > li {
+ &::before {
+ margin: 0 0.5rem;
+ }
+
+ > a {
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+}
diff --git a/_sass/layout/home.scss b/_sass/layout/home.scss
new file mode 100644
index 0000000..7f9fd2e
--- /dev/null
+++ b/_sass/layout/home.scss
@@ -0,0 +1,189 @@
+/*
+ Style for Homepage
+*/
+
+#post-list {
+ margin-top: 2rem;
+
+ .card-wrapper {
+ &:hover {
+ text-decoration: none;
+ }
+
+ &:not(:last-child) {
+ margin-bottom: 1.25rem;
+ }
+ }
+
+ .card {
+ border: 0;
+ background: none;
+
+ %img-radius {
+ border-radius: $base-radius $base-radius 0 0;
+ }
+
+ .preview-img {
+ @extend %img-radius;
+
+ img {
+ @extend %img-radius;
+ }
+ }
+
+ .card-body {
+ height: 100%;
+ padding: 1rem;
+
+ .card-title {
+ @extend %text-clip;
+
+ color: var(--heading-color) !important;
+ font-size: 1.25rem;
+ }
+
+ %muted {
+ color: var(--text-muted-color) !important;
+ }
+
+ .card-text.content {
+ @extend %muted;
+
+ p {
+ @extend %text-clip;
+
+ line-height: 1.5;
+ margin: 0;
+ }
+ }
+
+ .post-meta {
+ @extend %muted;
+
+ i {
+ &:not(:first-child) {
+ margin-left: 1.5rem;
+ }
+ }
+
+ em {
+ @extend %normal-font-style;
+
+ color: inherit;
+ }
+
+ > div:first-child {
+ display: block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ }
+ }
+ }
+ }
+} /* #post-list */
+
+.pagination {
+ color: var(--text-color);
+ font-family: Lato, sans-serif;
+ justify-content: space-evenly;
+
+ a:hover {
+ text-decoration: none;
+ }
+
+ .page-item {
+ .page-link {
+ color: var(--btn-patinator-text-color);
+ padding: 0 0.6rem;
+ display: -webkit-box;
+ -webkit-box-pack: center;
+ -webkit-box-align: center;
+ border-radius: 0.5rem;
+ border: 0;
+ background-color: inherit;
+ }
+
+ &.active {
+ .page-link {
+ background-color: var(--btn-paginator-hover-color);
+ }
+ }
+
+ &:not(.active) {
+ .page-link {
+ &:hover {
+ box-shadow: inset var(--btn-border-color) 0 0 0 1px;
+ }
+ }
+ }
+
+ &.disabled {
+ cursor: not-allowed;
+
+ .page-link {
+ color: rgba(108, 117, 125, 0.57);
+ }
+ }
+ } /* .page-item */
+} /* .pagination */
+
+/* Tablet */
+@media all and (min-width: 768px) {
+ %img-radius {
+ border-radius: 0 $base-radius $base-radius 0;
+ }
+
+ #post-list {
+ .card {
+ .card-body {
+ padding: 1.75rem 1.75rem 1.25rem 1.75rem;
+
+ .card-text {
+ display: inherit !important;
+ }
+
+ .post-meta {
+ i {
+ &:not(:first-child) {
+ margin-left: 1.75rem;
+ }
+ }
+ }
+ }
+ }
+ }
+}
+
+/* Hide SideBar and TOC */
+@media all and (max-width: 830px) {
+ .pagination {
+ .page-item {
+ &:not(:first-child):not(:last-child) {
+ display: none;
+ }
+ }
+ }
+}
+
+/* Sidebar is visible */
+@media all and (min-width: 831px) {
+ #post-list {
+ margin-top: 2.5rem;
+ }
+
+ .pagination {
+ font-size: 0.85rem;
+ justify-content: center;
+
+ .page-item {
+ &:not(:last-child) {
+ margin-right: 0.7rem;
+ }
+ }
+
+ .page-index {
+ display: none;
+ }
+ } /* .pagination */
+}
diff --git a/_sass/layout/post.scss b/_sass/layout/post.scss
new file mode 100644
index 0000000..112541d
--- /dev/null
+++ b/_sass/layout/post.scss
@@ -0,0 +1,370 @@
+/*
+ Post-specific style
+*/
+
+%btn-post-nav {
+ width: 50%;
+ position: relative;
+ border-color: var(--btn-border-color);
+}
+
+@mixin dot($pl: 0.25rem, $pr: 0.25rem) {
+ content: '\2022';
+ padding-left: $pl;
+ padding-right: $pr;
+}
+
+header {
+ .post-desc {
+ @extend %heading;
+
+ font-size: 1.125rem;
+ line-height: 1.6;
+ }
+
+ .post-meta {
+ span + span::before {
+ @include dot;
+ }
+
+ em,
+ time {
+ @extend %text-highlight;
+ }
+
+ em {
+ a {
+ color: inherit;
+ }
+ }
+ }
+
+ h1 + .post-meta {
+ margin-top: 1.5rem;
+ }
+}
+
+.post-tail-wrapper {
+ @extend %text-sm;
+
+ margin-top: 6rem;
+ border-bottom: 1px double var(--main-border-color);
+
+ .license-wrapper {
+ line-height: 1.2rem;
+
+ > a {
+ @extend %text-highlight;
+
+ &:hover {
+ @extend %link-hover;
+ }
+ }
+
+ span:last-child {
+ @extend %text-sm;
+ }
+ } /* .license-wrapper */
+
+ .post-meta a:not(:hover) {
+ @extend %link-underline;
+ }
+
+ .share-wrapper {
+ vertical-align: middle;
+ -webkit-user-select: none;
+ -moz-user-select: none;
+ -ms-user-select: none;
+ user-select: none;
+
+ %icon-size {
+ font-size: 1.125rem;
+ }
+
+ .share-icons {
+ display: flex;
+
+ i {
+ color: var(--btn-share-color);
+
+ @extend %icon-size;
+ }
+
+ > * {
+ @extend %icon-size;
+
+ margin-left: 0.5rem;
+
+ &:hover {
+ i {
+ @extend %btn-share-hovor;
+ }
+ }
+ }
+
+ button {
+ padding: 0;
+ border: none;
+ line-height: inherit;
+
+ @extend %cursor-pointer;
+ }
+ } /* .share-icons */
+ } /* .share-wrapper */
+}
+
+.share-mastodon {
+ /* See: https://github.com/justinribeiro/share-to-mastodon#properties */
+ --wc-stm-font-family: $font-family-base;
+ --wc-stm-dialog-background-color: var(--card-bg);
+ --wc-stm-form-button-border: 1px solid var(--btn-border-color);
+ --wc-stm-form-submit-background-color: var(--sidebar-btn-bg);
+ --wc-stm-form-cancel-background-color: var(--sidebar-btn-bg);
+ --wc-stm-form-button-background-color-hover: #007bff;
+ --wc-stm-form-button-color-hover: white;
+
+ font-size: 1rem;
+}
+
+.post-tags {
+ line-height: 2rem;
+
+ .post-tag {
+ &:hover {
+ @extend %link-hover;
+ @extend %tag-hover;
+ @extend %no-bottom-border;
+ }
+ }
+}
+
+.post-navigation {
+ .btn {
+ @extend %btn-post-nav;
+
+ &:not(:hover) {
+ color: var(--link-color);
+ }
+
+ &:hover {
+ &:not(.disabled)::before {
+ color: whitesmoke;
+ }
+ }
+
+ &.disabled {
+ @extend %btn-post-nav;
+
+ pointer-events: auto;
+ cursor: not-allowed;
+ background: none;
+ color: gray;
+ }
+
+ &.btn-outline-primary.disabled:focus {
+ box-shadow: none;
+ }
+
+ &::before {
+ color: var(--text-muted-color);
+ font-size: 0.65rem;
+ text-transform: uppercase;
+ content: attr(aria-label);
+ }
+
+ &:first-child {
+ border-radius: $base-radius 0 0 $base-radius;
+ left: 0.5px;
+ }
+
+ &:last-child {
+ border-radius: 0 $base-radius $base-radius 0;
+ right: 0.5px;
+ }
+ }
+
+ p {
+ font-size: 1.1rem;
+ line-height: 1.5rem;
+ margin-top: 0.3rem;
+ white-space: normal;
+ }
+} /* .post-navigation */
+
+@media (hover: hover) {
+ .post-navigation {
+ .btn,
+ .btn::before {
+ transition: all 0.35s ease-in-out;
+ }
+ }
+}
+
+@-webkit-keyframes fade-up {
+ from {
+ opacity: 0;
+ position: relative;
+ top: 2rem;
+ }
+
+ to {
+ opacity: 1;
+ position: relative;
+ top: 0;
+ }
+}
+
+@keyframes fade-up {
+ from {
+ opacity: 0;
+ position: relative;
+ top: 2rem;
+ }
+
+ to {
+ opacity: 1;
+ position: relative;
+ top: 0;
+ }
+}
+
+#toc-wrapper {
+ border-left: 1px solid rgba(158, 158, 158, 0.17);
+ position: -webkit-sticky;
+ position: sticky;
+ top: 4rem;
+ transition: top 0.2s ease-in-out;
+ -webkit-animation: fade-up 0.8s;
+ animation: fade-up 0.8s;
+
+ ul {
+ list-style: none;
+ font-size: 0.85rem;
+ line-height: 1.25;
+ padding-left: 0;
+
+ li {
+ &:not(:last-child) {
+ margin: 0.4rem 0;
+ }
+
+ a {
+ padding: 0.2rem 0 0.2rem 1.25rem;
+ }
+ }
+
+ /* Overwrite TOC plugin style */
+
+ .toc-link {
+ display: block;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+
+ &:hover {
+ color: var(--toc-highlight);
+ text-decoration: none;
+ }
+
+ &::before {
+ display: none;
+ }
+ }
+
+ .is-active-link {
+ color: var(--toc-highlight) !important;
+ font-weight: 600;
+
+ &::before {
+ display: inline-block;
+ width: 1px;
+ left: -1px;
+ height: 1.25rem;
+ background-color: var(--toc-highlight) !important;
+ }
+ }
+
+ ul {
+ padding-left: 0.75rem;
+ }
+ }
+}
+
+/* --- Related Posts --- */
+
+#related-posts {
+ > h3 {
+ @include label(1.1rem, 600);
+ }
+
+ time {
+ @extend %normal-font-style;
+ @extend %text-xs;
+
+ color: var(--text-muted-color);
+ }
+
+ p {
+ font-size: 0.9rem;
+ margin-bottom: 0.5rem;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ display: -webkit-box;
+ -webkit-line-clamp: 2;
+ -webkit-box-orient: vertical;
+ }
+
+ .card {
+ h4 {
+ @extend %text-clip;
+ }
+ }
+}
+
+/* stylelint-disable-next-line selector-id-pattern */
+#disqus_thread {
+ min-height: 8.5rem;
+}
+
+.utterances {
+ max-width: 100%;
+}
+
+%btn-share-hovor {
+ color: var(--btn-share-hover-color) !important;
+}
+
+.share-label {
+ @include label(inherit, 400, inherit);
+
+ &::after {
+ content: ':';
+ }
+}
+
+@media all and (max-width: 576px) {
+ .post-tail-bottom {
+ flex-wrap: wrap-reverse !important;
+
+ > div:first-child {
+ width: 100%;
+ margin-top: 1rem;
+ }
+ }
+}
+
+@media all and (max-width: 768px) {
+ .content > p > img {
+ max-width: calc(100% + 1rem);
+ }
+}
+
+/* Hide SideBar and TOC */
+@media all and (max-width: 849px) {
+ .post-navigation {
+ padding-left: 0;
+ padding-right: 0;
+ margin-left: -0.5rem;
+ margin-right: -0.5rem;
+ }
+}
diff --git a/_sass/layout/tags.scss b/_sass/layout/tags.scss
new file mode 100644
index 0000000..4cf5d3b
--- /dev/null
+++ b/_sass/layout/tags.scss
@@ -0,0 +1,19 @@
+/*
+ Styles for Tab Tags
+*/
+
+.tag {
+ border-radius: 0.7em;
+ padding: 6px 8px 7px;
+ margin-right: 0.8rem;
+ line-height: 3rem;
+ letter-spacing: 0;
+ border: 1px solid var(--tag-border) !important;
+ box-shadow: 0 0 3px 0 var(--tag-shadow);
+
+ span {
+ margin-left: 0.6em;
+ font-size: 0.7em;
+ font-family: Oswald, sans-serif;
+ }
+}
diff --git a/_sass/main.bundle.scss b/_sass/main.bundle.scss
new file mode 100644
index 0000000..52e893f
--- /dev/null
+++ b/_sass/main.bundle.scss
@@ -0,0 +1,2 @@
+@import 'dist/bootstrap';
+@import 'main';
diff --git a/_sass/main.scss b/_sass/main.scss
new file mode 100644
index 0000000..1c2311d
--- /dev/null
+++ b/_sass/main.scss
@@ -0,0 +1,13 @@
+@import 'colors/typography-light';
+@import 'colors/typography-dark';
+@import 'addon/variables';
+@import 'variables-hook';
+@import 'addon/module';
+@import 'addon/syntax';
+@import 'addon/commons';
+@import 'layout/home';
+@import 'layout/post';
+@import 'layout/tags';
+@import 'layout/archives';
+@import 'layout/categories';
+@import 'layout/category-tag';
diff --git a/_sass/variables-hook.scss b/_sass/variables-hook.scss
new file mode 100644
index 0000000..f27e0eb
--- /dev/null
+++ b/_sass/variables-hook.scss
@@ -0,0 +1,3 @@
+/*
+ Appending custom SCSS variables will override the default ones in `_sass/addon/variables.scsss`
+*/
diff --git a/assets/404.html b/assets/404.html
new file mode 100644
index 0000000..5b46cc8
--- /dev/null
+++ b/assets/404.html
@@ -0,0 +1,14 @@
+---
+layout: page
+title: "404: Page not found"
+permalink: /404.html
+
+redirect_from:
+ - /norobots/
+ - /assets/
+ - /posts/
+---
+
+{% include lang.html %}
+
+ {{ site.data.locales[lang].not_found.statment }}
diff --git a/assets/css/jekyll-theme-chirpy.scss b/assets/css/jekyll-theme-chirpy.scss
new file mode 100644
index 0000000..d20545b
--- /dev/null
+++ b/assets/css/jekyll-theme-chirpy.scss
@@ -0,0 +1,10 @@
+---
+---
+
+@import 'main
+{%- if jekyll.environment == 'production' -%}
+ .bundle
+{%- endif -%}
+';
+
+/* append your custom style below */
diff --git a/assets/feed.xml b/assets/feed.xml
new file mode 100644
index 0000000..a244a56
--- /dev/null
+++ b/assets/feed.xml
@@ -0,0 +1,61 @@
+---
+layout: compress
+permalink: /feed.xml
+# Atom Feed, reference: https://validator.w3.org/feed/docs/atom.html
+---
+
+{% capture source %}
+
+ {{ "/" | absolute_url }}
+ {{ site.title }}
+ {{ site.description }}
+ {{ site.time | date_to_xmlschema }}
+
+ {{ site.social.name }}
+ {{ "/" | absolute_url }}
+
+
+
+ Jekyll
+ © {{ 'now' | date: '%Y' }} {{ site.social.name }}
+ {{ site.baseurl }}/assets/img/favicons/favicon.ico
+ {{ site.baseurl }}/assets/img/favicons/favicon-96x96.png
+
+{% for post in site.posts limit: 5 %}
+ {% assign post_absolute_url = post.url | absolute_url %}
+
+ {{ post.title }}
+
+ {{ post.date | date_to_xmlschema }}
+ {% if post.last_modified_at %}
+ {{ post.last_modified_at | date_to_xmlschema }}
+ {% else %}
+ {{ post.date | date_to_xmlschema }}
+ {% endif %}
+ {{ post_absolute_url }}
+
+
+ {{ post.author | default: site.social.name }}
+
+
+ {% if post.categories %}
+ {% for category in post.categories %}
+
+ {% endfor %}
+ {% endif %}
+
+ {% if post.summary %}
+ {{ post.summary | strip }}
+ {% else %}
+
+ {% include no-linenos.html content=post.content %}
+ {{ content | strip_html | truncate: 400 }}
+
+ {% endif %}
+
+
+{% endfor %}
+
+{% endcapture %}
+{{ source | replace: '&', '&' }}
diff --git a/assets/js/data/mathjax.js b/assets/js/data/mathjax.js
new file mode 100644
index 0000000..ca3d0de
--- /dev/null
+++ b/assets/js/data/mathjax.js
@@ -0,0 +1,25 @@
+---
+layout: compress
+# WARNING: Don't use '//' to comment out code, use '{% comment %}' and '{% endcomment %}' instead.
+---
+
+{%- comment -%}
+ See:
+{%- endcomment -%}
+
+MathJax = {
+ tex: {
+ {%- comment -%} start/end delimiter pairs for in-line math {%- endcomment -%}
+ inlineMath: [
+ ['$', '$'],
+ ['\\(', '\\)']
+ ],
+ {%- comment -%} start/end delimiter pairs for display math {%- endcomment -%}
+ displayMath: [
+ ['$$', '$$'],
+ ['\\[', '\\]']
+ ],
+ {%- comment -%} equation numbering {%- endcomment -%}
+ tags: 'ams'
+ }
+};
diff --git a/assets/js/data/search.json b/assets/js/data/search.json
new file mode 100644
index 0000000..2601ed0
--- /dev/null
+++ b/assets/js/data/search.json
@@ -0,0 +1,20 @@
+---
+layout: compress
+swcache: true
+---
+
+[
+ {% for post in site.posts %}
+ {
+ "title": {{ post.title | jsonify }},
+ "url": {{ post.url | relative_url | jsonify }},
+ "categories": {{ post.categories | join: ', ' | jsonify }},
+ "tags": {{ post.tags | join: ', ' | jsonify }},
+ "date": "{{ post.date }}",
+ {% include no-linenos.html content=post.content %}
+ {% assign _content = content | strip_html | strip_newlines %}
+ "snippet": {{ _content | truncate: 200 | jsonify }},
+ "content": {{ _content | jsonify }}
+ }{% unless forloop.last %},{% endunless %}
+ {% endfor %}
+]
diff --git a/assets/js/data/swconf.js b/assets/js/data/swconf.js
new file mode 100644
index 0000000..798888a
--- /dev/null
+++ b/assets/js/data/swconf.js
@@ -0,0 +1,47 @@
+---
+layout: compress
+permalink: '/:path/swconf.js'
+# Note that this file will be fetched by the ServiceWorker, so it will not be cached.
+---
+
+const swconf = {
+ {% if site.pwa.cache.enabled %}
+ cacheName: 'chirpy-{{ "now" | date: "%s" }}',
+
+ {%- comment -%} Resources added to the cache during PWA installation. {%- endcomment -%}
+ resources: [
+ '{{ "/assets/css/:THEME.css" | replace: ':THEME', site.theme | relative_url }}',
+ '{{ "/" | relative_url }}',
+ {% for tab in site.tabs %}
+ '{{- tab.url | relative_url -}}',
+ {% endfor %}
+
+ {% assign cache_list = site.static_files | where: 'swcache', true %}
+ {% for file in cache_list %}
+ '{{ file.path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
+ {% endfor %}
+ ],
+
+ interceptor: {
+ {%- comment -%} URLs containing the following paths will not be cached. {%- endcomment -%}
+ paths: [
+ {% for path in site.pwa.cache.deny_paths %}
+ {% unless path == empty %}
+ '{{ path | relative_url }}'{%- unless forloop.last -%},{%- endunless -%}
+ {% endunless %}
+ {% endfor %}
+ ],
+
+ {%- comment -%} URLs containing the following prefixes will not be cached. {%- endcomment -%}
+ urlPrefixes: [
+ {% if site.analytics.goatcounter.id != nil and site.pageviews.provider == 'goatcounter' %}
+ 'https://{{ site.analytics.goatcounter.id }}.goatcounter.com/counter/'
+ {% endif %}
+ ]
+ },
+
+ purge: false
+ {% else %}
+ purge: true
+ {% endif %}
+};
diff --git a/assets/robots.txt b/assets/robots.txt
new file mode 100644
index 0000000..45c34e0
--- /dev/null
+++ b/assets/robots.txt
@@ -0,0 +1,10 @@
+---
+permalink: /robots.txt
+# The robots rules
+---
+
+User-agent: *
+
+Disallow: /norobots/
+
+Sitemap: {{ '/sitemap.xml' | absolute_url }}
| | |
Comments powered by Disqus.
+