diff options
Diffstat (limited to 'themes/hugo-theme-stack/assets')
53 files changed, 4251 insertions, 0 deletions
diff --git a/themes/hugo-theme-stack/assets/icons/archives.svg b/themes/hugo-theme-stack/assets/icons/archives.svg new file mode 100644 index 0000000..cd96cbe --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/archives.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <rect x="3" y="4" width="18" height="4" rx="2" /> + <path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" /> + <line x1="10" y1="12" x2="14" y2="12" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/arrow-back.svg b/themes/hugo-theme-stack/assets/icons/arrow-back.svg new file mode 100644 index 0000000..0f7c5f4 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/arrow-back.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-back" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/back.svg b/themes/hugo-theme-stack/assets/icons/back.svg new file mode 100644 index 0000000..ee52db4 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/back.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <polyline points="15 6 9 12 15 18" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/brand-github.svg b/themes/hugo-theme-stack/assets/icons/brand-github.svg new file mode 100644 index 0000000..1fe7e0b --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/brand-github.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z" fill="none"/> + <path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/brand-twitter.svg b/themes/hugo-theme-stack/assets/icons/brand-twitter.svg new file mode 100644 index 0000000..17ab1b1 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/brand-twitter.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-twitter" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z" fill="none"/> + <path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/categories.svg b/themes/hugo-theme-stack/assets/icons/categories.svg new file mode 100644 index 0000000..e00ab1d --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/categories.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <line x1="5" y1="9" x2="19" y2="9" /> + <line x1="5" y1="15" x2="19" y2="15" /> + <line x1="11" y1="4" x2="7" y2="20" /> + <line x1="17" y1="4" x2="13" y2="20" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/clock.svg b/themes/hugo-theme-stack/assets/icons/clock.svg new file mode 100644 index 0000000..a7a8be6 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/clock.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="12" cy="12" r="9" /> + <polyline points="12 7 12 12 15 15" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/copyright.svg b/themes/hugo-theme-stack/assets/icons/copyright.svg new file mode 100644 index 0000000..2ac45ff --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/copyright.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="12" cy="12" r="9" /> + <path d="M14.5 9a3.5 4 0 1 0 0 6" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/date.svg b/themes/hugo-theme-stack/assets/icons/date.svg new file mode 100644 index 0000000..ed92a90 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/date.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" /> + <circle cx="18" cy="18" r="4" /> + <path d="M15 3v4" /> + <path d="M7 3v4" /> + <path d="M3 11h16" /> + <path d="M18 16.496v1.504l1 1" /> +</svg>
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/icons/hash.svg b/themes/hugo-theme-stack/assets/icons/hash.svg new file mode 100644 index 0000000..e00ab1d --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/hash.svg @@ -0,0 +1,9 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <line x1="5" y1="9" x2="19" y2="9" /> + <line x1="5" y1="15" x2="19" y2="15" /> + <line x1="11" y1="4" x2="7" y2="20" /> + <line x1="17" y1="4" x2="13" y2="20" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/home.svg b/themes/hugo-theme-stack/assets/icons/home.svg new file mode 100644 index 0000000..ed5fd53 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/home.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <polyline points="5 12 3 12 12 3 21 12 19 12" /> + <path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" /> + <path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/infinity.svg b/themes/hugo-theme-stack/assets/icons/infinity.svg new file mode 100644 index 0000000..fb40b24 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/infinity.svg @@ -0,0 +1,6 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M9.828 9.172a4 4 0 1 0 0 5.656 a10 10 0 0 0 2.172 -2.828a10 10 0 0 1 2.172 -2.828 a4 4 0 1 1 0 5.656a10 10 0 0 1 -2.172 -2.828a10 10 0 0 0 -2.172 -2.828" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/language.svg b/themes/hugo-theme-stack/assets/icons/language.svg new file mode 100644 index 0000000..66ede1c --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/language.svg @@ -0,0 +1,10 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z" fill="none"/> + <path d="M4 5h7" /> + <path d="M9 3v2c0 4.418 -2.239 8 -5 8" /> + <path d="M5 9c-.003 2.144 2.952 3.908 6.7 4" /> + <path d="M12 20l4 -9l4 9" /> + <path d="M19.1 18h-6.2" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/link.svg b/themes/hugo-theme-stack/assets/icons/link.svg new file mode 100644 index 0000000..3c87803 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/link.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" /> + <path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/messages.svg b/themes/hugo-theme-stack/assets/icons/messages.svg new file mode 100644 index 0000000..725d75c --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/messages.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-messages" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" /> + <path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/rss.svg b/themes/hugo-theme-stack/assets/icons/rss.svg new file mode 100644 index 0000000..b92ea8f --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/rss.svg @@ -0,0 +1,8 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rss" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="5" cy="19" r="1" /> + <path d="M4 4a16 16 0 0 1 16 16" /> + <path d="M4 11a9 9 0 0 1 9 9" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/search.svg b/themes/hugo-theme-stack/assets/icons/search.svg new file mode 100644 index 0000000..a0b0ddc --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/search.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="10" cy="10" r="7" /> + <line x1="21" y1="21" x2="15" y2="15" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/tag.svg b/themes/hugo-theme-stack/assets/icons/tag.svg new file mode 100644 index 0000000..ef7a63d --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/tag.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <path d="M11 3L20 12a1.5 1.5 0 0 1 0 2L14 20a1.5 1.5 0 0 1 -2 0L3 11v-4a4 4 0 0 1 4 -4h4" /> + <circle cx="9" cy="9" r="2" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/toggle-left.svg b/themes/hugo-theme-stack/assets/icons/toggle-left.svg new file mode 100644 index 0000000..c3048d4 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/toggle-left.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="8" cy="12" r="2" /> + <rect x="2" y="6" width="20" height="12" rx="6" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/toggle-right.svg b/themes/hugo-theme-stack/assets/icons/toggle-right.svg new file mode 100644 index 0000000..d3edd63 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/toggle-right.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="16" cy="12" r="2" /> + <rect x="2" y="6" width="20" height="12" rx="6" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/icons/user.svg b/themes/hugo-theme-stack/assets/icons/user.svg new file mode 100644 index 0000000..9c92c87 --- /dev/null +++ b/themes/hugo-theme-stack/assets/icons/user.svg @@ -0,0 +1,7 @@ +<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"> + <path stroke="none" d="M0 0h24v24H0z"/> + <circle cx="12" cy="7" r="4" /> + <path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" /> +</svg> + + diff --git a/themes/hugo-theme-stack/assets/img/avatar.png b/themes/hugo-theme-stack/assets/img/avatar.png Binary files differnew file mode 100644 index 0000000..d90352c --- /dev/null +++ b/themes/hugo-theme-stack/assets/img/avatar.png diff --git a/themes/hugo-theme-stack/assets/scss/breakpoints.scss b/themes/hugo-theme-stack/assets/scss/breakpoints.scss new file mode 100644 index 0000000..e9e9de7 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/breakpoints.scss @@ -0,0 +1,17 @@ +$breakpoints: ( + sm: 640px, + md: 768px, + lg: 1024px, + xl: 1280px, + 2xl: 1536px, +); + +@mixin respond($breakpoint) { + @if not map-has-key($breakpoints, $breakpoint) { + @warn "'#{$breakpoint}' is not a valid breakpoint"; + } @else { + @media (min-width: map-get($breakpoints, $breakpoint)) { + @content; + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/custom.scss b/themes/hugo-theme-stack/assets/scss/custom.scss new file mode 100644 index 0000000..61fa80f --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/custom.scss @@ -0,0 +1 @@ +/* Place your custom SCSS in HUGO_SITE_FOLDER/assets/scss/custom.scss */
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/scss/external/normalize.scss b/themes/hugo-theme-stack/assets/scss/external/normalize.scss new file mode 100644 index 0000000..c45a85f --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/external/normalize.scss @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + + html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ + } + + /* Sections + ========================================================================== */ + + /** + * Remove the margin in all browsers. + */ + + body { + margin: 0; + } + + /** + * Render the `main` element consistently in IE. + */ + + main { + display: block; + } + + /** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + + h1 { + font-size: 2em; + margin: 0.67em 0; + } + + /* Grouping content + ========================================================================== */ + + /** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + + hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /* Text-level semantics + ========================================================================== */ + + /** + * Remove the gray background on active links in IE 10. + */ + + a { + background-color: transparent; + } + + /** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + + abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ + } + + /** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + + b, + strong { + font-weight: bolder; + } + + /** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + + code, + kbd, + samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ + } + + /** + * Add the correct font size in all browsers. + */ + + small { + font-size: 80%; + } + + /** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + + sub, + sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; + } + + sub { + bottom: -0.25em; + } + + sup { + top: -0.5em; + } + + /* Embedded content + ========================================================================== */ + + /** + * Remove the border on images inside links in IE 10. + */ + + img { + border-style: none; + } + + /* Forms + ========================================================================== */ + + /** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + + button, + input, + optgroup, + select, + textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ + } + + /** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + + button, + input { /* 1 */ + overflow: visible; + } + + /** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + + button, + select { /* 1 */ + text-transform: none; + } + + /** + * Correct the inability to style clickable types in iOS and Safari. + */ + + button, + [type="button"], + [type="reset"], + [type="submit"] { + -webkit-appearance: button; + } + + /** + * Remove the inner border and padding in Firefox. + */ + + button::-moz-focus-inner, + [type="button"]::-moz-focus-inner, + [type="reset"]::-moz-focus-inner, + [type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; + } + + /** + * Restore the focus styles unset by the previous rule. + */ + + button:-moz-focusring, + [type="button"]:-moz-focusring, + [type="reset"]:-moz-focusring, + [type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; + } + + /** + * Correct the padding in Firefox. + */ + + fieldset { + padding: 0.35em 0.75em 0.625em; + } + + /** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + + legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ + } + + /** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + + progress { + vertical-align: baseline; + } + + /** + * Remove the default vertical scrollbar in IE 10+. + */ + + textarea { + overflow: auto; + } + + /** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + + [type="checkbox"], + [type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ + } + + /** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + + [type="number"]::-webkit-inner-spin-button, + [type="number"]::-webkit-outer-spin-button { + height: auto; + } + + /** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + + [type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ + } + + /** + * Remove the inner padding in Chrome and Safari on macOS. + */ + + [type="search"]::-webkit-search-decoration { + -webkit-appearance: none; + } + + /** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + + ::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ + } + + /* Interactive + ========================================================================== */ + + /* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + + details { + display: block; + } + + /* + * Add the correct display in all browsers. + */ + + summary { + display: list-item; + } + + /* Misc + ========================================================================== */ + + /** + * Add the correct display in IE 10+. + */ + + template { + display: none; + } + + /** + * Add the correct display in IE 10. + */ + + [hidden] { + display: none; + }
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/scss/general.scss b/themes/hugo-theme-stack/assets/scss/general.scss new file mode 100644 index 0000000..e12bb0e --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/general.scss @@ -0,0 +1,31 @@ +a { + text-decoration: none; + color: var(--accent-color); + + &:hover { + color: var(--accent-color-darker); + } + + &.link { + box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset; + transition: all 0.3s ease; + + &:hover { + box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset; + } + } +} + +.section-title { + text-transform: uppercase; + margin-top: 0; + margin-bottom: 10px; + display: block; + font-size: 1.6rem; + font-weight: bold; + color: var(--body-text-color); + + a { + color: var(--body-text-color); + } +} diff --git a/themes/hugo-theme-stack/assets/scss/grid.scss b/themes/hugo-theme-stack/assets/scss/grid.scss new file mode 100644 index 0000000..9284da9 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/grid.scss @@ -0,0 +1,101 @@ +.container { + margin-left: auto; + margin-right: auto; + + .left-sidebar { + order: -3; + max-width: var(--left-sidebar-max-width); + } + + .right-sidebar { + order: -1; + max-width: var(--right-sidebar-max-width); + + /// Display right sidebar when min-width: lg + @include respond(lg) { + display: flex; + } + } + + &.extended { + @include respond(md) { + max-width: 1024px; + --left-sidebar-max-width: 25%; + --right-sidebar-max-width: 30%; + } + + @include respond(lg) { + max-width: 1280px; + --left-sidebar-max-width: 20%; + --right-sidebar-max-width: 30%; + } + + @include respond(xl) { + max-width: 1536px; + --left-sidebar-max-width: 15%; + --right-sidebar-max-width: 25%; + } + } + + &.compact { + @include respond(md) { + --left-sidebar-max-width: 25%; + max-width: 768px; + } + + @include respond(lg) { + max-width: 1024px; + --left-sidebar-max-width: 20%; + } + + @include respond(xl) { + max-width: 1280px; + } + } +} + +.flex { + display: flex; + flex-direction: row; + + &.column { + flex-direction: column; + } + + &.on-phone--column { + flex-direction: column; + @include respond(md) { + flex-direction: unset; + } + } + + .full-width { + width: 100%; + } +} + +main.main { + order: -2; + min-width: 0; + max-width: 100%; + flex-grow: 1; + display: flex; + flex-direction: column; + gap: var(--section-separation); + + @include respond(md) { + padding-top: var(--main-top-padding); + } +} + +.main-container { + min-height: 100vh; + align-items: flex-start; + padding: 0 15px; + gap: var(--section-separation); + padding-top: var(--main-top-padding); + + @include respond(md) { + padding: 0 20px; + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/article.scss b/themes/hugo-theme-stack/assets/scss/partials/article.scss new file mode 100644 index 0000000..6c2feb1 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/article.scss @@ -0,0 +1,278 @@ +/* Default article style */ +.article-list { + display: flex; + flex-direction: column; + gap: var(--section-separation); + + article { + display: flex; + flex-direction: column; + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + overflow: hidden; + + transition: box-shadow 0.3s ease; + + &:hover { + box-shadow: var(--shadow-l2); + } + + .article-image { + img { + width: 100%; + height: 150px; + object-fit: cover; + + @include respond(md) { + height: 200px; + } + + @include respond(xl) { + height: 250px; + } + } + } + + @for $i from 1 through length($defaultTagBackgrounds) { + &:nth-child(#{length($defaultTagBackgrounds)}n + #{$i}) { + .article-category a { + background: nth($defaultTagBackgrounds, $i); + color: nth($defaultTagColors, $i); + } + } + } + } +} + +.article-details { + display: flex; + flex-direction: column; + justify-content: center; + padding: var(--card-padding); + gap: 15px; +} + +.article-title { + font-family: var(--article-font-family); + font-weight: 600; + margin: 0; + color: var(--card-text-color-main); + font-size: 2.2rem; + + @include respond(xl) { + font-size: 2.4rem; + } + + a { + color: var(--card-text-color-main); + + &:hover { + color: var(--card-text-color-main); + } + } +} + +.article-subtitle { + font-weight: normal; + color: var(--card-text-color-secondary); + line-height: 1.5; + margin: 0; + font-size: 1.75rem; + @include respond(xl) { + font-size: 2rem; + } +} + +.article-title-wrapper { + display: flex; + flex-direction: column; + gap: 8px; +} + +.article-time, +.article-translations { + display: flex; + color: var(--card-text-color-tertiary); + gap: 15px; + + svg { + vertical-align: middle; + width: 20px; + height: 20px; + stroke-width: 1.33; + flex-shrink: 0; + } + + time, + a { + font-size: 1.4rem; + color: var(--card-text-color-tertiary); + } + + & > div { + display: inline-flex; + align-items: center; + gap: 15px; + } +} + +.article-time { + flex-wrap: wrap; +} + +.article-translations { + & > div { + flex-wrap: wrap; + } +} + +.article-category, +.article-tags { + display: flex; + gap: 10px; + flex-wrap: wrap; + + a { + color: var(--accent-color-text); + background-color: var(--accent-color); + padding: 8px 16px; + border-radius: var(--tag-border-radius); + display: inline-block; + font-size: 1.4rem; + transition: background-color 0.5s ease; + + &:hover { + color: var(--accent-color-text); + background-color: var(--accent-color-darker); + } + } +} + +/* Compact style article list */ +.article-list--compact { + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + background-color: var(--card-background); + --image-size: 50px; + + @include respond(md) { + --image-size: 60px; + } + + article { + & > a { + display: flex; + align-items: center; + padding: var(--small-card-padding); + gap: 15px; + } + + &:not(:last-of-type) { + border-bottom: 1.5px solid var(--card-separator-color); + } + + .article-details { + flex-grow: 1; + padding: 0; + min-height: var(--image-size); + gap: 10px; + } + + .article-title { + margin: 0; + font-size: 1.6rem; + + @include respond(md) { + font-size: 1.8rem; + } + } + + .article-image { + img { + width: var(--image-size); + height: var(--image-size); + object-fit: cover; + } + } + + .article-time { + font-size: 1.4rem; + } + + .article-preview { + font-size: 1.4rem; + color: var(--card-text-color-tertiary); + margin-top: 10px; + line-height: 1.5; + } + } +} + +/* Tile style article list */ +.article-list--tile { + article { + border-radius: var(--card-border-radius); + overflow: hidden; + position: relative; + height: 350px; + width: 250px; + box-shadow: var(--shadow-l1); + transition: box-shadow 0.3s ease; + background-color: var(--card-background); + + &:hover { + box-shadow: var(--shadow-l2); + } + + &.has-image { + .article-details { + background-color: rgba(#000, 0.25); + } + + .article-title { + color: #fff; + } + } + + .article-image { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + + img { + width: 100%; + height: 100%; + object-fit: cover; + } + } + + .article-details { + border-radius: var(--card-border-radius); + position: relative; + height: 100%; + width: 100%; + display: flex; + flex-direction: column; + justify-content: flex-end; + z-index: 2; + padding: 15px; + + @include respond(sm) { + padding: 20px; + } + } + + .article-title { + font-size: 2rem; + font-weight: 500; + color: var(--card-text-color-main); + + @include respond(sm) { + font-size: 2.2rem; + } + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/base.scss b/themes/hugo-theme-stack/assets/scss/partials/base.scss new file mode 100644 index 0000000..efb4b8f --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/base.scss @@ -0,0 +1,38 @@ +html { + font-size: 62.5%; + overflow-y: scroll; +} + +* { + box-sizing: border-box; +} + +body { + background: var(--body-background); + margin: 0; + font-family: var(--base-font-family); + font-size: 1.6rem; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* scrollbar styles for Firefox */ +* { + scrollbar-width: auto; + scrollbar-color: var(--scrollbar-thumb) transparent; +} +/**/ + +/* scrollbar styles for Chromium */ +::-webkit-scrollbar { + height: auto; +} + +::-webkit-scrollbar-thumb { + background-color: var(--scrollbar-thumb); +} + +::-webkit-scrollbar-track { + background-color: transparent; +} +/**/
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/scss/partials/comments/disqusjs.scss b/themes/hugo-theme-stack/assets/scss/partials/comments/disqusjs.scss new file mode 100644 index 0000000..eb270e3 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/comments/disqusjs.scss @@ -0,0 +1,394 @@ +.disqus-container { + background-color: var(--card-background); + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + padding: var(--card-padding); +} + +#dsqjs * { + margin: 0; + padding: 0 +} + +#dsqjs a { + text-decoration: none; + color: #076dd0 +} + +#dsqjs .dsqjs-hide { + display: none!important +} + +#dsqjs .dsqjs-disabled { + cursor: not-allowed; + opacity: .5 +} + +#dsqjs #dsqjs-msg { + text-align: center; + margin-top: 4px; + margin-bottom: 4px; + font-size: 14px +} + +#dsqjs #dsqjs-msg .dsqjs-msg-btn { + cursor: pointer +} + +#dsqjs .dsqjs-bullet { + line-height: 1.4; + margin: 0 2px +} + +#dsqjs .dsqjs-bullet:after { + color: #c2c6cc; + content: "·"; + font-weight: 700 +} + +#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before { + display: table; + content: ""; + line-height: 0; + clear: both +} + +#dsqjs .dsqjs-nav { + position: relative; + margin: 0 0 20px; + border-bottom: 2px solid #e7e9ee +} + +#dsqjs ol,#dsqjs ul { + list-style: none; + list-style-type: none +} + +#dsqjs .dsqjs-no-comment { + text-align: center; + font-size: 16px; + line-height: 1.5; + word-wrap: break-word; + overflow: hidden; + color: #2a2e2e; + margin-bottom: 6px +} + +#dsqjs .dsqjs-nav-tab { + float: left; + text-transform: capitalize; + font-size: 15px; + padding: 12px 8px; + color: #656c7a; + display: block; + margin: 0 15px 0 0; + font-weight: 700; + line-height: 1; + position: relative; + transition: all .2s ease-in-out +} + +#dsqjs .dsqjs-nav-tab:last-child { + margin: 0 +} + +#dsqjs .dsqjs-tab-active { + color: #2a2e2e +} + +#dsqjs .dsqjs-tab-active>span:after { + content: " "; + display: block; + height: 2px; + background-color: #076dd0!important; + position: absolute; + bottom: -5px; + left: 0; + right: 0 +} + +#dsqjs .dsqjs-post-list .dsqjs-post-item { + position: relative; + margin-bottom: 16px +} + +#dsqjs .dsqjs-post-list .dsqjs-post-avatar { + float: left; + margin-right: 10px; + position: relative; + background: #dbdfe4; + padding: 0; + display: block; + border-radius: 4px +} + +#dsqjs .dsqjs-post-list .dsqjs-post-avatar img { + width: 44px; + height: 44px; + display: block; + border-radius: 4px +} + +#dsqjs .dsqjs-post-list .dsqjs-post-header { + line-height: 1; + font-size: 14px; + margin-bottom: 3px +} + +#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author { + color: #656c7a; + font-weight: 700 +} + +#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge { + color: #fff; + background: #687a86; + padding: 1px 3px; + margin-left: 4px; + font-size: 12px; + line-height: 1; + font-weight: 700; + border-radius: 3px; + display: inline-block; + position: relative; + top: -1px; + left: 1px +} + +#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta { + display: inline-block; + font-size: 12px; + color: #656c7a +} + +#dsqjs .dsqjs-post-body { + font-size: 15px; + line-height: 1.5; + word-wrap: break-word; + overflow: hidden; + color: #2a2e2e +} + +#dsqjs .dsqjs-post-body code { + padding: .2em .4em; + margin: 0; + font-size: 85%; + background: #f5f5f5; + color: inherit; + border-radius: 3px +} + +#dsqjs .dsqjs-post-body pre { + padding: .5em; + overflow: auto; + font-size: 85%; + line-height: 1.45; + border-radius: 3px; + background: #f5f5f5; + margin: .5em 0 +} + +#dsqjs .dsqjs-post-body blockquote { + padding: 0 .8em; + margin: .5em 0; + color: #6a737d; + border-left: .25em solid #dfe2e5 +} + +#dsqjs .dsqjs-post-body p:last-child { + margin: 0 +} + +#dsqjs .dsqjs-post-list.dsqjs-children>li { + margin-left: 30px +} + +#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img { + width: 38px; + height: 38px +} + +#dsqjs .dsqjs-load-more { + font-size: 14px; + font-weight: 400; + display: block; + text-align: center; + padding: 11px 14px; + margin: 0 0 24px; + background: #687a86; + color: #fff; + cursor: pointer +} + +#dsqjs .dsqjs-load-more:hover { + opacity: .8 +} + +#dsqjs footer { + text-align: right; + line-height: 1.5; + padding-top: 10px; + padding-right: 10px; + border-top: 2px solid #e7e9ee; + margin-top: 12px; + font-weight: 700; + font-size: 16px; + color: #555 +} + +#dsqjs .dsqjs-disqus-logo { + background-image: url(https://c.disquscdn.com/next/embed/assets/img/sprite.654110a9206fd22f08cca0798e34a65e.png); + background-repeat: no-repeat; + display: inline-block; + background-size: 86px 40.5px; + height: 16.5px; + width: 86px; +} + +#dsqjs .dsqjs-order { + display: flex; + float: right; + align-items: center; + margin-top: 10px; + margin-bottom: 12px +} + +#dsqjs .dsqjs-order-radio { + display: none +} + +#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label { + color: #fff; + background-color: #888 +} + +#dsqjs .dsqjs-order-label { + display: block; + height: 20px; + line-height: 20px; + margin-right: 10px; + font-size: 12px; + border-radius: 2px; + padding: 0 5px; + background-color: #dcdcdc; + cursor: pointer +} + +#dsqjs p.dsqjs-has-more { + margin-bottom: 24px; + margin-left: 48px; + font-size: 13px; + line-height: 15px +} + +#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn { + color: #656c7a; + text-decoration: underline; + cursor: pointer +} + +@media (min-width: 768px) { + #dsqjs .dsqjs-post-list.dsqjs-children>li { + margin-left:48px + } + + #dsqjs .dsqjs-post-list .dsqjs-post-avatar { + margin-right: 12px + } + + #dsqjs .dsqjs-post-list .dsqjs-post-item { + margin-bottom: 20px + } +} + +@media (min-width: 1024px) { + #dsqjs .dsqjs-post-list.dsqjs-children>li { + margin-left:60px + } +} + +:root[data-scheme="light"] { + #dsqjs .dsqjs-disqus-logo { + background-position: 0 -7px; + } +} + +:root[data-scheme="dark"] { + #dsqjs { + --t-s: rgba(255,255,255,0.9); + --alt: #3e4b5e; + --link-hover: #47a2e0; + --hover-bg: #3e4b5e; + --tag: #3e4b5e; + --border: #435266; + --pre: #3c495b; + --c-bg: #2f3947; + --code: #c3c7cb; + --kbd: #4e5f77; + --hl: #abb2bf; + --hlc: #808895; + --hlk: #c678dd; + --hln: #e06c75; + --hll: #56b6c2; + --hls: #98c379; + --hlt: #e6c07b; + --hlv: #d19a66; + --bg: #181c27; + --main: #252d38; + --t: rgba(255,255,255,0.86); + --t-l: rgba(255,255,255,0.66); + --logo: #fff; + --link: #38a3fd; + --title: rgba(255,255,255,0.92); + --fab: #364151; + --shadow: none; + } + + #disqus_thread { + color: var(--body-text-color) + } + + #dsqjs #dsqjs-msg { + color: var(--t) + } + + #dsqjs a { + color:var(--link) + } + + #dsqjs a:focus,#dsqjs a:hover { + color: var(--link-hover) + } + + #dsqjs .dsqjs-disqus-logo { + background-position: 0 -24px; + } + + #dsqjs .dsqjs-nav,#dsqjs footer { + border-color: var(--hlc) + } + + #dsqjs .dsqjs-load-more,#dsqjs .dsqjs-load-more:hover,#dsqjs .dsqjs-nav-tab,#dsqjs .dsqjs-no-comment,#dsqjs .dsqjs-post-content { + color: var(--t) + } + + #dsqjs .dsqjs-order-label { + background-color: var(--hlc) + } + + #dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label { + background-color: var(--kbd) + } + + #dsqjs .dsqjs-tab-active>span:after { + background-color: #2e9fff + } + + #dsqjs .dsqjs-footer,#dsqjs .dsqjs-meta { + color: var(--t-l) + } + + #dsqjs .dsqjs-post-body blockquote { + border-color: var(--border) + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/footer.scss b/themes/hugo-theme-stack/assets/scss/partials/footer.scss new file mode 100644 index 0000000..ccb7394 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/footer.scss @@ -0,0 +1,30 @@ +footer.site-footer { + padding: 20px 0 var(--section-separation) 0; + font-size: 1.4rem; + line-height: 1.75; + + &:before { + content: ""; + display: block; + height: 3px; + width: 50px; + background: var(--body-text-color); + margin-bottom: 20px; + } + + .copyright { + color: var(--accent-color); + font-weight: bold; + margin-bottom: 5px; + } + + .powerby { + color: var(--body-text-color); + font-weight: normal; + font-size: 1.2rem; + + a { + color: var(--body-text-color); + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/highlight/common.scss b/themes/hugo-theme-stack/assets/scss/partials/highlight/common.scss new file mode 100644 index 0000000..c680fa4 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/highlight/common.scss @@ -0,0 +1,428 @@ +/* Background */ +.chroma { + color: $color; + background-color: $background-color; +} + +/* Other */ +.chroma .x { +} + +/* Error */ +.chroma .err { + color: $error-color; +} + +/* LineTableTD */ +.chroma .lntd { + vertical-align: top; + padding: 0; + margin: 0; + border: 0; +} + +/* LineTable */ +.chroma .lntable { + border-spacing: 0; + padding: 0; + margin: 0; + border: 0; + width: 100%; + display: block; + + > tbody { + display: block; + width: 100%; + > tr { + display: flex; + width: 100%; + > td:last-child { + overflow-x: auto; + } + } + } +} + +/* LineHighlight */ +.chroma .hl { + display: block; + width: 100%; + background-color: #ffffcc; +} + +/* LineNumbersTable */ +.chroma .lnt { + margin-right: 0.4em; + padding: 0 0.4em 0 0.4em; + color: #7f7f7f; + display: block; +} + +/* LineNumbers */ +.chroma .ln { + margin-right: 0.4em; + padding: 0 0.4em 0 0.4em; + color: #7f7f7f; +} + +/* Keyword */ +.chroma .k { + color: $keyword-color; +} + +/* KeywordConstant */ +.chroma .kc { + color: $keyword-color; +} + +/* KeywordDeclaration */ +.chroma .kd { + color: $keyword-color; +} + +/* KeywordNamespace */ +.chroma .kn { + color: #f92672; +} + +/* KeywordPseudo */ +.chroma .kp { + color: $keyword-color; +} + +/* KeywordReserved */ +.chroma .kr { + color: $keyword-color; +} + +/* KeywordType */ +.chroma .kt { + color: $keyword-color; +} + +/* Name */ +.chroma .n { + color: $text-color; +} + +/* NameAttribute */ +.chroma .na { + color: $name-color; +} + +/* NameBuiltin */ +.chroma .nb { + color: $text-color; +} + +/* NameBuiltinPseudo */ +.chroma .bp { + color: $text-color; +} + +/* NameClass */ +.chroma .nc { + color: $name-color; +} + +/* NameConstant */ +.chroma .no { + color: $keyword-color; +} + +/* NameDecorator */ +.chroma .nd { + color: $name-color; +} + +/* NameEntity */ +.chroma .ni { + color: $text-color; +} + +/* NameException */ +.chroma .ne { + color: $name-color; +} + +/* NameFunction */ +.chroma .nf { + color: $name-color; +} + +/* NameFunctionMagic */ +.chroma .fm { + color: $text-color; +} + +/* NameLabel */ +.chroma .nl { + color: $text-color; +} + +/* NameNamespace */ +.chroma .nn { + color: $text-color; +} + +/* NameOther */ +.chroma .nx { + color: $name-color; +} + +/* NameProperty */ +.chroma .py { + color: $text-color; +} + +/* NameTag */ +.chroma .nt { + color: #f92672; +} + +/* NameVariable */ +.chroma .nv { + color: $text-color; +} + +/* NameVariableClass */ +.chroma .vc { + color: $text-color; +} + +/* NameVariableGlobal */ +.chroma .vg { + color: $text-color; +} + +/* NameVariableInstance */ +.chroma .vi { + color: $text-color; +} + +/* NameVariableMagic */ +.chroma .vm { + color: $text-color; +} + +/* Literal */ +.chroma .l { + color: #ae81ff; +} + +/* LiteralDate */ +.chroma .ld { + color: $literal-color; +} + +/* LiteralString */ +.chroma .s { + color: $literal-color; +} + +/* LiteralStringAffix */ +.chroma .sa { + color: $literal-color; +} + +/* LiteralStringBacktick */ +.chroma .sb { + color: $literal-color; +} + +/* LiteralStringChar */ +.chroma .sc { + color: $literal-color; +} + +/* LiteralStringDelimiter */ +.chroma .dl { + color: $literal-color; +} + +/* LiteralStringDoc */ +.chroma .sd { + color: $literal-color; +} + +/* LiteralStringDouble */ +.chroma .s2 { + color: $literal-color; +} + +/* LiteralStringEscape */ +.chroma .se { + color: #ae81ff; +} + +/* LiteralStringHeredoc */ +.chroma .sh { + color: $literal-color; +} + +/* LiteralStringInterpol */ +.chroma .si { + color: $literal-color; +} + +/* LiteralStringOther */ +.chroma .sx { + color: $literal-color; +} + +/* LiteralStringRegex */ +.chroma .sr { + color: $literal-color; +} + +/* LiteralStringSingle */ +.chroma .s1 { + color: $literal-color; +} + +/* LiteralStringSymbol */ +.chroma .ss { + color: $literal-color; +} + +/* LiteralNumber */ +.chroma .m { + color: #ae81ff; +} + +/* LiteralNumberBin */ +.chroma .mb { + color: #ae81ff; +} + +/* LiteralNumberFloat */ +.chroma .mf { + color: #ae81ff; +} + +/* LiteralNumberHex */ +.chroma .mh { + color: #ae81ff; +} + +/* LiteralNumberInteger */ +.chroma .mi { + color: #ae81ff; +} + +/* LiteralNumberIntegerLong */ +.chroma .il { + color: #ae81ff; +} + +/* LiteralNumberOct */ +.chroma .mo { + color: #ae81ff; +} + +/* Operator */ +.chroma .o { + color: #f92672; +} + +/* OperatorWord */ +.chroma .ow { + color: #f92672; +} + +/* Punctuation */ +.chroma .p { + color: $text-color; +} + +/* Comment */ +.chroma .c { + color: #75715e; +} + +/* CommentHashbang */ +.chroma .ch { + color: #75715e; +} + +/* CommentMultiline */ +.chroma .cm { + color: #75715e; +} + +/* CommentSingle */ +.chroma .c1 { + color: #75715e; +} + +/* CommentSpecial */ +.chroma .cs { + color: #75715e; +} + +/* CommentPreproc */ +.chroma .cp { + color: #75715e; +} + +/* CommentPreprocFile */ +.chroma .cpf { + color: #75715e; +} + +/* Generic */ +.chroma .g { +} + +/* GenericDeleted */ +.chroma .gd { + color: #f92672; +} + +/* GenericEmph */ +.chroma .ge { + font-style: italic; +} + +/* GenericError */ +.chroma .gr { +} + +/* GenericHeading */ +.chroma .gh { +} + +/* GenericInserted */ +.chroma .gi { + color: $name-color; +} + +/* GenericOutput */ +.chroma .go { +} + +/* GenericPrompt */ +.chroma .gp { +} + +/* GenericStrong */ +.chroma .gs { + font-weight: bold; +} + +/* GenericSubheading */ +.chroma .gu { + color: #75715e; +} + +/* GenericTraceback */ +.chroma .gt { +} + +/* GenericUnderline */ +.chroma .gl { +} + +/* TextWhitespace */ +.chroma .w { +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/highlight/dark.scss b/themes/hugo-theme-stack/assets/scss/partials/highlight/dark.scss new file mode 100644 index 0000000..0d3f330 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/highlight/dark.scss @@ -0,0 +1,14 @@ +/* +* Style: monokai +* https://xyproto.github.io/splash/docs/monokai.html +*/ + +$color: #f8f8f2; +$background-color: #272822; +$error-color: #bb0064; +$keyword-color: #66d9ef; +$text-color: $color; +$name-color: #a6e22e; +$literal-color: #e6db74; + +@import "common.scss"; diff --git a/themes/hugo-theme-stack/assets/scss/partials/highlight/light.scss b/themes/hugo-theme-stack/assets/scss/partials/highlight/light.scss new file mode 100644 index 0000000..174b649 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/highlight/light.scss @@ -0,0 +1,14 @@ +/* +* Style: monokailight +* https://xyproto.github.io/splash/docs/monokailight.html +*/ + +$color: #272822; +$background-color: #fafafa; +$error-color: #960050; +$keyword-color: #00a8c8; +$text-color: #111111; +$name-color: #75af00; +$literal-color: #d88200; + +@import "common.scss"; diff --git a/themes/hugo-theme-stack/assets/scss/partials/layout/404.scss b/themes/hugo-theme-stack/assets/scss/partials/layout/404.scss new file mode 100644 index 0000000..d9d8752 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/layout/404.scss @@ -0,0 +1,6 @@ +.not-found-card { + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + padding: var(--card-padding); +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/layout/article.scss b/themes/hugo-theme-stack/assets/scss/partials/layout/article.scss new file mode 100644 index 0000000..10e2706 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/layout/article.scss @@ -0,0 +1,461 @@ +.article-page { + &.hide-sidebar-sm .left-sidebar { + display: none; + + @include respond(md) { + display: inherit; + } + } + + .main-article { + background: var(--card-background); + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + overflow: hidden; + + .article-header { + .article-image { + img { + height: auto; + width: 100%; + max-height: 50vh; + object-fit: cover; + } + } + + .article-details { + padding: var(--card-padding); + padding-bottom: 0; + } + } + + .article-content { + margin: var(--card-padding) 0; + color: var(--card-text-color-main); + + .footnotes { + font-family: var(--base-font-family); + } + + img { + max-width: 100%; + height: auto; + } + } + + .article-footer { + margin: var(--card-padding); + margin-top: 0; + + section:not(:first-child) { + margin-top: var(--card-padding); + } + + section { + color: var(--card-text-color-tertiary); + text-transform: uppercase; + display: flex; + align-items: center; + font-size: 1.4rem; + gap: 15px; + + svg { + width: 20px; + height: 20px; + stroke-width: 1.33; + } + } + + .article-tags { + text-transform: unset; + } + + .article-copyright, + .article-lastmod { + a { + color: var(--body-text-color); + } + + a.link { + box-shadow: unset; + } + } + } + } +} + +.widget--toc { + background-color: var(--card-background); + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + display: flex; + flex-direction: column; + color: var(--card-text-color-main); + overflow: hidden; + + ::-webkit-scrollbar-thumb { + background-color: var(--card-separator-color); + } + + #TableOfContents { + overflow-x: auto; + max-height: 75vh; + + ol, + ul { + margin: 0; + padding: 0; + } + + ol { + list-style-type: none; + counter-reset: item; + + li a:first-of-type::before { + counter-increment: item; + content: counters(item, ".") ". "; + font-weight: bold; + margin-right: 5px; + } + } + + & > ul { + padding: 0 1em; + } + + li { + margin: 15px 0 15px 20px; + padding: 5px; + + & > ol, + & > ul { + margin-top: 10px; + padding-left: 10px; + margin-bottom: -5px; + + & > li:last-child { + margin-bottom: 0; + } + } + } + li.active-class > a { + border-left: var(--heading-border-size) solid var(--accent-color); + font-weight: bold; + } + + ul li.active-class > a { + display: block; + } + + @function repeat($str, $n) { + $result: ""; + @for $_ from 0 to $n { + $result: $result + $str; + } + @return $result; + } + + // Support up to 6 levels of indentation for lists in ToCs + @for $i from 0 to 5 { + & > ul #{repeat("> li > ul", $i)} > li.active-class > a { + $n: 25 + $i * 35; + margin-left: calc(-#{$n}px - 1em); + padding-left: calc(#{$n}px + 1em - var(--heading-border-size)); + } + + & > ol #{repeat("> li > ol", $i)} > li.active-class > a { + $n: 9 + $i * 35; + margin-left: calc(-#{$n}px - 1em); + padding-left: calc(#{$n}px + 1em - var(--heading-border-size)); + display: block; + } + } + } +} + +.related-content { + overflow-x: auto; + padding-bottom: 15px; + + & > .flex { + float: left; + } + + article { + margin-right: 15px; + flex-shrink: 0; + overflow: hidden; + width: 250px; + height: 150px; + + .article-title { + font-size: 1.8rem; + margin: 0; + } + + &.has-image { + .article-details { + padding: 20px; + background: linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%); + } + } + } +} + +.article-content { + font-family: var(--article-font-family); + font-size: var(--article-font-size); + padding: 0 var(--card-padding); + line-height: var(--article-line-height); + + & > p { + margin: 1.5em 0; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-inline-start: calc((var(--card-padding)) * -1); + padding-inline-start: calc(var(--card-padding) - var(--heading-border-size)); + border-inline-start: var(--heading-border-size) solid var(--accent-color); + position: relative; + + a.header-anchor { + transition: opacity 0.3s ease; + opacity: 0; + position: absolute; + left: 0; + width: var(--card-padding); + text-align: center; + color: var(--accent-color); + + &:before { + content: "#"; + } + } + + &:hover, + &:focus { + a.header-anchor { + opacity: 1; + } + } + } + + figure { + text-align: center; + + figcaption { + font-size: 1.4rem; + color: var(--card-text-color-secondary); + } + } + + blockquote { + position: relative; + margin: 1.5em 0; + border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color); + padding: 15px calc(var(--card-padding) - var(--blockquote-border-size)); + background-color: var(--blockquote-background-color); + + .cite { + display: block; + text-align: right; + font-size: 0.75em; + + a { + text-decoration: underline; + } + } + } + + hr { + width: 100px; + margin: 40px auto; + background: var(--card-text-color-tertiary); + height: 2px; + border: 0; + opacity: 0.55; + } + + code { + color: var(--code-text-color); + background-color: var(--code-background-color); + padding: 2px 4px; + border-radius: var(--tag-border-radius); + font-family: var(--code-font-family); + } + + a, + code { + word-break: break-word; + } + + .gallery { + position: relative; + display: flex; + flex-direction: row; + justify-content: center; + margin: 1.5em 0; + gap: 10px; + + figure { + margin: 0; + } + } + + pre { + overflow-x: auto; + display: block; + background-color: var(--pre-background-color); + color: var(--pre-text-color); + font-family: var(--code-font-family); + line-height: 1.428571429; + word-break: break-all; + padding: var(--card-padding); + // keep Codeblocks LTR + [dir="rtl"] & { + direction: ltr; + } + code { + color: unset; + border: none; + background: none; + padding: 0; + } + } + + .highlight { + background-color: var(--pre-background-color); + padding: var(--card-padding); + position: relative; + + &:hover { + .copyCodeButton { + opacity: 1; + } + } + // keep Codeblocks LTR + [dir="rtl"] & { + direction: ltr; + } + pre { + margin: initial; + padding: 0; + margin: 0; + width: auto; + } + } + + .copyCodeButton { + position: absolute; + top: calc(var(--card-padding)); + right: calc(var(--card-padding)); + background: var(--card-background); + border: none; + box-shadow: var(--shadow-l2); + border-radius: var(--tag-border-radius); + padding: 8px 16px; + color: var(--card-text-color-main); + cursor: pointer; + font-size: 14px; + opacity: 0; + transition: opacity 0.3s ease; + } + + .table-wrapper { + padding: 0 var(--card-padding); + overflow-x: auto; + display: block; + } + + table { + width: 100%; + border-collapse: collapse; + border-spacing: 0; + margin-bottom: 1.5em; + font-size: 0.96em; + } + + th, + td { + text-align: left; + padding: 4px 8px 4px 10px; + border: 1px solid var(--table-border-color); + } + + td { + vertical-align: top; + } + + tr:nth-child(even) { + background-color: var(--tr-even-background-color); + } + + .twitter-tweet { + color: var(--card-text-color-main); + } + + .video-wrapper { + position: relative; + width: 100%; + height: 0; + padding-bottom: 56.25%; + overflow: hidden; + + & > iframe, + & > video { + position: absolute; + width: 100%; + height: 100%; + left: 0; + top: 0; + border: 0; + } + } + + .gitlab-embed-snippets { + margin: 0 !important; + + .file-holder.snippet-file-content { + margin-block-end: 0 !important; + margin-block-start: 0 !important; + margin-left: calc((var(--card-padding)) * -1) !important; + margin-right: calc((var(--card-padding)) * -1) !important; + padding: 0 var(--card-padding) !important; + } + } + + /// Negative margins + blockquote, + figure, + .highlight, + pre, + .gallery, + .video-wrapper, + .table-wrapper, + .s_video_simple { + margin-left: calc((var(--card-padding)) * -1); + margin-right: calc((var(--card-padding)) * -1); + width: calc(100% + var(--card-padding) * 2); + } + + /// Make long KaTeX equations scrollable in the x-axis + .katex-display > .katex { + overflow-x: auto; + overflow-y: hidden; + } + + kbd { + border: 1px solid var(--kbd-border-color); + font-weight: bold; + font-size: 0.9em; + line-height: 1; + padding: 2px 4px; + border-radius: 4px; + display: inline-block; + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/layout/list.scss b/themes/hugo-theme-stack/assets/scss/partials/layout/list.scss new file mode 100644 index 0000000..d7815ca --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/layout/list.scss @@ -0,0 +1,71 @@ +.section-card { + border-radius: var(--card-border-radius); + background-color: var(--card-background); + padding: var(--small-card-padding); + box-shadow: var(--shadow-l1); + display: flex; + align-items: center; + gap: 20px; + + --separation: 15px; + + .section-term { + font-size: 2.2rem; + margin: 0; + color: var(--card-text-color-main); + } + + .section-description { + font-weight: normal; + color: var(--card-text-color-secondary); + font-size: 1.6rem; + margin: 0; + } + + .section-details { + flex-grow: 1; + display: flex; + flex-direction: column; + gap: 8px; + } + + .section-image { + img { + width: 60px; + height: 60px; + } + } + + .section-count { + color: var(--card-text-color-tertiary); + font-size: 1.4rem; + margin: 0; + font-weight: bold; + text-transform: uppercase; + } +} + +.subsection-list { + overflow-x: auto; + + .article-list--tile { + display: flex; + padding-bottom: 15px; + + article { + width: 250px; + height: 150px; + margin-right: 20px; + flex-shrink: 0; + + .article-title { + margin: 0; + font-size: 1.8rem; + } + + .article-details { + padding: 20px; + } + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/layout/search.scss b/themes/hugo-theme-stack/assets/scss/partials/layout/search.scss new file mode 100644 index 0000000..89cdcef --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/layout/search.scss @@ -0,0 +1,82 @@ +.search-form { + position: relative; + --button-size: 80px; + + &.widget { + --button-size: 60px; + + label { + font-size: 1.3rem; + top: 10px; + } + + input { + font-size: 1.5rem; + padding: 30px 20px 15px 20px; + } + } + + p { + position: relative; + margin: 0; + } + + label { + position: absolute; + top: 15px; + inset-inline-start: 20px; + font-size: 1.4rem; + color: var(--card-text-color-tertiary); + } + + input { + padding: 40px 20px 20px; + border-radius: var(--card-border-radius); + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + color: var(--card-text-color-main); + width: 100%; + border: 0; + -webkit-appearance: none; + + transition: box-shadow 0.3s ease; + + font-size: 1.8rem; + + &:focus { + outline: 0; + box-shadow: var(--shadow-l2); + } + } + + button { + position: absolute; + inset-inline-end: 0; + top: 0; + height: 100%; + width: var(--button-size); + cursor: pointer; + background-color: transparent; + border: 0; + + padding: 0 10px; + + &:focus { + outline: 0; + + svg { + stroke-width: 2; + color: var(--accent-color); + } + } + + svg { + color: var(--card-text-color-secondary); + stroke-width: 1.33; + transition: all 0.3s ease; + width: 20px; + height: 20px; + } + } + +}
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/scss/partials/menu.scss b/themes/hugo-theme-stack/assets/scss/partials/menu.scss new file mode 100644 index 0000000..5b6be17 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/menu.scss @@ -0,0 +1,229 @@ +/*! + * Hamburgers + * @description Tasty CSS-animated hamburgers + * @author Jonathan Suh @jonsuh + * @site https://jonsuh.com/hamburgers + * @link https://github.com/jonsuh/hamburgers + */ + +.hamburger { + padding-top: 10px; + display: inline-block; + cursor: pointer; + transition-property: opacity, filter; + transition-duration: 0.15s; + transition-timing-function: linear; + font: inherit; + color: inherit; + text-transform: none; + background-color: transparent; + border: 0; + margin: 0; + overflow: visible; +} +.hamburger:hover { + opacity: 0.7; +} +.hamburger.is-active:hover { + opacity: 0.7; +} +.hamburger.is-active .hamburger-inner, +.hamburger.is-active .hamburger-inner::before, +.hamburger.is-active .hamburger-inner::after { + background-color: #000; +} + +.hamburger-box { + width: 30px; + height: 24px; + display: inline-block; + position: relative; +} + +.hamburger-inner { + display: block; + top: 50%; + margin-top: -2px; +} + +.hamburger-inner, +.hamburger-inner::before, +.hamburger-inner::after { + width: 30px; + height: 2px; + background-color: var(--card-text-color-main); + border-radius: 4px; + position: absolute; + transition-property: transform; + transition-duration: 0.15s; + transition-timing-function: ease; +} +.hamburger-inner::before, +.hamburger-inner::after { + content: ""; + display: block; +} +.hamburger-inner::before { + top: -10px; +} +.hamburger-inner::after { + bottom: -10px; +} + +.hamburger--spin .hamburger-inner { + transition-duration: 0.22s; + transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19); +} +.hamburger--spin .hamburger-inner::before { + transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in; +} +.hamburger--spin .hamburger-inner::after { + transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19); +} + +.hamburger--spin.is-active .hamburger-inner { + transform: rotate(225deg); + transition-delay: 0.12s; + transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1); +} +.hamburger--spin.is-active .hamburger-inner::before { + top: 0; + opacity: 0; + transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out; +} +.hamburger--spin.is-active .hamburger-inner::after { + bottom: 0; + transform: rotate(-90deg); + transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1); +} + +#toggle-menu { + background: none; + border: none; + position: absolute; + right: 0; + top: 0; + z-index: 2; + cursor: pointer; + + [dir="rtl"] & { + left: 0; + right: auto; + } + + @include respond(md) { + display: none; + } + + outline: none; + + &.is-active { + .hamburger-inner, + .hamburger-inner::before, + .hamburger-inner::after { + background-color: var(--accent-color); + } + } +} + +/* Menu style */ +#main-menu { + list-style: none; + overflow-y: auto; + flex-grow: 1; + font-size: 1.4rem; + background-color: var(--card-background); + + box-shadow: var(--shadow-l1); + display: none; + margin: 0 calc(var(--container-padding) * -1); + + padding: 30px 30px; + + @include respond(xl) { + padding: 15px 0; + } + + &, .menu-bottom-section ol { + flex-direction: column; + gap: 30px; + + @include respond(xl) { + gap: 25px; + } + } + + &.show { + display: flex; + } + + @include respond(md) { + align-items: flex-end; + display: flex; + background-color: transparent; + padding: 0; + box-shadow: none; + margin: 0; + } + + li { + position: relative; + vertical-align: middle; + padding: 0; + + @include respond(md) { + width: 100%; + } + + svg { + stroke: currentColor; + stroke-width: 1.33; + width: 20px; + height: 20px; + } + + a { + height: 100%; + display: inline-flex; + align-items: center; + color: var(--body-text-color); + gap: var(--menu-icon-separation); + } + + span { + flex: 1; + } + + &.current { + a { + color: var(--accent-color); + font-weight: bold; + } + } + + &.menu-bottom-section { + margin-top: auto; + + ol { + display: flex; + padding-left: 0; + } + } + } +} + +.menu-social { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: row; + gap: 10px; + + svg { + width: 24px; + height: 24px; + stroke: var(--body-text-color); + stroke-width: 1.33; + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/pagination.scss b/themes/hugo-theme-stack/assets/scss/partials/pagination.scss new file mode 100644 index 0000000..ca46780 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/pagination.scss @@ -0,0 +1,21 @@ +.pagination { + display: flex; + background-color: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--card-border-radius); + overflow: hidden; + flex-wrap: wrap; + + .page-link { + padding: 16px 32px; + display: inline-flex; + + &.current { + font-weight: bold; + background-color: var(--card-background-selected); + color: var(--card-text-color-main); + } + + color: var(--card-text-color-secondary); + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/sidebar.scss b/themes/hugo-theme-stack/assets/scss/partials/sidebar.scss new file mode 100644 index 0000000..a6a77c5 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/sidebar.scss @@ -0,0 +1,199 @@ +.sidebar { + &.sticky { + @include respond(md) { + position: sticky; + } + } +} + +.left-sidebar { + display: flex; + flex-direction: column; + flex-shrink: 0; + align-self: stretch; + gap: var(--sidebar-element-separation); + max-width: none; + width: 100%; + position: relative; + + --sidebar-avatar-size: 100px; + --sidebar-element-separation: 20px; + --emoji-size: 40px; + --emoji-font-size: 20px; + + @include respond(md) { + width: auto; + padding-top: var(--main-top-padding); + padding-bottom: var(--main-top-padding); + max-height: 100vh; + } + + @include respond(2xl) { + --sidebar-avatar-size: 120px; + --sidebar-element-separation: 25px; + --emoji-size: 40px; + } + + &.sticky { + top: 0; + } + + &.compact { + --sidebar-avatar-size: 80px; + --emoji-size: 30px; + --emoji-font-size: 15px; + + header { + @include respond(lg) { + flex-direction: row; + } + + .site-meta { + gap: 5px; + } + + .site-name { + font-size: 1.4rem; + + @include respond(2xl) { + font-size: 1.75rem; + } + } + + .site-description { + font-size: 1.4rem; + } + } + } +} + +.right-sidebar { + width: 100%; + display: none; + flex-direction: column; + gap: var(--widget-separation); + + &.sticky { + top: 0; + } + + @include respond(lg) { + padding-top: var(--main-top-padding); + padding-bottom: var(--main-top-padding); + } +} + +.sidebar header { + z-index: 1; + transition: box-shadow 0.5s ease; + display: flex; + flex-direction: column; + gap: var(--sidebar-element-separation); + + @include respond(md) { + padding: 0; + } + + .site-avatar { + position: relative; + margin: 0; + width: var(--sidebar-avatar-size); + height: var(--sidebar-avatar-size); + flex-shrink: 0; + + .site-logo { + width: 100%; + height: 100%; + border-radius: 100%; + box-shadow: var(--shadow-l1); + } + + .emoji { + position: absolute; + width: var(--emoji-size); + height: var(--emoji-size); + line-height: var(--emoji-size); + border-radius: 100%; + bottom: 0; + right: 0; + text-align: center; + font-size: var(--emoji-font-size); + background-color: var(--card-background); + box-shadow: var(--shadow-l2); + } + } + + .site-meta { + display: flex; + flex-direction: column; + gap: 10px; + justify-content: center; + } + + .site-name { + color: var(--accent-color); + margin: 0; + font-size: 1.6rem; + + @include respond(2xl) { + font-size: 1.8rem; + } + } + + .site-description { + color: var(--body-text-color); + font-weight: normal; + margin: 0; + font-size: 1.4rem; + + @include respond(2xl) { + font-size: 1.6rem; + } + } +} + +[data-scheme="dark"] { + #dark-mode-toggle { + color: var(--accent-color); + font-weight: 700; + + .icon-tabler-toggle-left { + display: none; + } + + .icon-tabler-toggle-right { + display: unset; + } + } +} + +#dark-mode-toggle { + margin-top: auto; + color: var(--body-text-color); + display: flex; + align-items: center; + cursor: pointer; + gap: var(--menu-icon-separation); + + .icon-tabler-toggle-right { + display: none; + } +} + +#i18n-switch { + color: var(--body-text-color); + display: inline-flex; + align-content: center; + gap: var(--menu-icon-separation); + + select { + border: 0; + background-color: transparent; + color: var(--body-text-color); + + option { + color: var(--card-text-color-main); + background-color: var(--card-background); + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/partials/widgets.scss b/themes/hugo-theme-stack/assets/scss/partials/widgets.scss new file mode 100644 index 0000000..42cfcc2 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/partials/widgets.scss @@ -0,0 +1,67 @@ +.widget { + display: flex; + flex-direction: column; + + .widget-icon { + svg { + width: 32px; + height: 32px; + stroke-width: 1.6; + color: var(--body-text-color); + } + } +} + +/* Tag cloud widget */ +.tagCloud { + .tagCloud-tags { + display: flex; + flex-wrap: wrap; + gap: 10px; + + a { + background: var(--card-background); + box-shadow: var(--shadow-l1); + border-radius: var(--tag-border-radius); + padding: 8px 20px; + color: var(--card-text-color-main); + font-size: 1.4rem; + transition: box-shadow 0.3s ease; + + &:hover { + box-shadow: var(--shadow-l2); + } + } + } +} + +/* Archives widget */ +.widget.archives { + .widget-archive--list { + border-radius: var(--card-border-radius); + box-shadow: var(--shadow-l1); + background-color: var(--card-background); + } + + .archives-year { + &:not(:last-of-type) { + border-bottom: 1.5px solid var(--card-separator-color); + } + + a { + font-size: 1.4rem; + padding: 18px 25px; + display: flex; + + span.year { + flex: 1; + color: var(--card-text-color-main); + font-weight: bold; + } + + span.count { + color: var(--card-text-color-tertiary); + } + } + } +} diff --git a/themes/hugo-theme-stack/assets/scss/style.scss b/themes/hugo-theme-stack/assets/scss/style.scss new file mode 100644 index 0000000..e50d400 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/style.scss @@ -0,0 +1,28 @@ +/*! +* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack +*/ + +@import "breakpoints.scss"; +@import "variables.scss"; +@import "grid.scss"; + +@import "external/normalize.scss"; + +@import "partials/menu.scss"; +@import "partials/article.scss"; +@import "partials/widgets.scss"; +@import "partials/footer.scss"; +@import "partials/pagination.scss"; +@import "partials/sidebar.scss"; +@import "partials/base.scss"; +@import "partials/layout/article.scss"; +@import "partials/layout/list.scss"; +@import "partials/layout/404.scss"; +@import "partials/layout/search.scss"; + +@import "general.scss"; +@import "custom.scss"; diff --git a/themes/hugo-theme-stack/assets/scss/variables.scss b/themes/hugo-theme-stack/assets/scss/variables.scss new file mode 100644 index 0000000..97810a1 --- /dev/null +++ b/themes/hugo-theme-stack/assets/scss/variables.scss @@ -0,0 +1,167 @@ +$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6; +$defaultTagColors: #fff, #fff, #fff, #fff, #fff; + +/* +* Global style +*/ +:root { + --main-top-padding: 35px; + + @include respond(xl) { + --main-top-padding: 50px; + } + + --body-background: #f5f5fa; + + --accent-color: #34495e; + --accent-color-darker: #2c3e50; + --accent-color-text: #fff; + --body-text-color: #707070; + + --tag-border-radius: 4px; + + --section-separation: 40px; + + --scrollbar-thumb: hsl(0, 0%, 85%); + --scrollbar-track: var(--body-background); + + &[data-scheme="dark"] { + --body-background: #303030; + --accent-color: #ecf0f1; + --accent-color-darker: #bdc3c7; + --accent-color-text: #000; + --body-text-color: rgba(255, 255, 255, 0.7); + --scrollbar-thumb: hsl(0, 0%, 40%); + --scrollbar-track: var(--body-background); + } +} + +/** +* Global font family +*/ +:root { + --sys-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue"; + --zh-font-family: "PingFang SC", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei"; + + --base-font-family: "Lato", var(--sys-font-family), var(--zh-font-family), sans-serif; + --code-font-family: Menlo, Monaco, Consolas, "Courier New", var(--zh-font-family), monospace; +} + +/* +* Card style +*/ +:root { + --card-background: #fff; + --card-background-selected: #eaeaea; + + --card-text-color-main: #000; + --card-text-color-secondary: #747474; + --card-text-color-tertiary: #767676; + --card-separator-color: rgba(218, 218, 218, 0.5); + + --card-border-radius: 10px; + + --card-padding: 20px; + + @include respond(md) { + --card-padding: 25px; + } + + @include respond(xl) { + --card-padding: 30px; + } + + --small-card-padding: 25px 20px; + + @include respond(md) { + --small-card-padding: 25px; + } + + &[data-scheme="dark"] { + --card-background: #424242; + --card-background-selected: rgba(255, 255, 255, 0.16); + --card-text-color-main: rgba(255, 255, 255, 0.9); + --card-text-color-secondary: rgba(255, 255, 255, 0.7); + --card-text-color-tertiary: rgba(255, 255, 255, 0.5); + --card-separator-color: rgba(255, 255, 255, 0.12); + } +} + +/** +* Article content font settings +*/ +:root { + --article-font-family: var(--base-font-family); + --article-font-size: 1.6rem; + + @include respond(md) { + --article-font-size: 1.7rem; + } + + --article-line-height: 1.85; +} + +/* +* Article content style +*/ +:root { + --blockquote-border-size: 4px; + --blockquote-background-color: rgb(248 248 248); + + --heading-border-size: 4px; + + --link-background-color: 189, 195, 199; + --link-background-opacity: 0.5; + --link-background-opacity-hover: 0.7; + + --pre-background-color: #272822; + --pre-text-color: #f8f8f2; + + --code-background-color: rgba(0, 0, 0, 0.12); + --code-text-color: #808080; + + --table-border-color: #dadada; + --tr-even-background-color: #efefee; + + --kbd-border-color: #dadada; + + &[data-scheme="dark"] { + --code-background-color: #272822; + --code-text-color: rgba(255, 255, 255, 0.9); + + --table-border-color: #717171; + --tr-even-background-color: #545454; + + --blockquote-background-color: rgb(75 75 75); + } +} + +/* +* Shadow style +* Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker +*/ +:root { + --shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l2: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l3: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04); + --shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04), + 0px 0px 1px rgba(0, 0, 0, 0.04); +} + +[data-scheme="light"] { + --pre-text-color: #272822; + --pre-background-color: #fafafa; + @import "partials/highlight/light.scss"; +} + +[data-scheme="dark"] { + --pre-text-color: #f8f8f2; + --pre-background-color: #272822; + @import "partials/highlight/dark.scss"; +} + +:root { + --menu-icon-separation: 40px; + --container-padding: 15px; + --widget-separation: var(--section-separation); +} diff --git a/themes/hugo-theme-stack/assets/ts/color.ts b/themes/hugo-theme-stack/assets/ts/color.ts new file mode 100644 index 0000000..50581d1 --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/color.ts @@ -0,0 +1,63 @@ +interface colorScheme { + hash: string, /// Regenerate color scheme when the image hash is changed + DarkMuted: { + hex: string, + rgb: Number[], + bodyTextColor: string + }, + Vibrant: { + hex: string, + rgb: Number[], + bodyTextColor: string + } +} + +let colorsCache: { [key: string]: colorScheme } = {}; + +if (localStorage.hasOwnProperty('StackColorsCache')) { + try { + colorsCache = JSON.parse(localStorage.getItem('StackColorsCache')); + } + catch (e) { + colorsCache = {}; + } +} + +async function getColor(key: string, hash: string, imageURL: string) { + if (!key) { + /** + * If no key is provided, do not cache the result + */ + return await Vibrant.from(imageURL).getPalette(); + } + + if (!colorsCache.hasOwnProperty(key) || colorsCache[key].hash !== hash) { + /** + * If key is provided, but not found in cache, or the hash mismatches => Regenerate color scheme + */ + const palette = await Vibrant.from(imageURL).getPalette(); + + colorsCache[key] = { + hash: hash, + Vibrant: { + hex: palette.Vibrant.hex, + rgb: palette.Vibrant.rgb, + bodyTextColor: palette.Vibrant.bodyTextColor + }, + DarkMuted: { + hex: palette.DarkMuted.hex, + rgb: palette.DarkMuted.rgb, + bodyTextColor: palette.DarkMuted.bodyTextColor + } + } + + /* Save the result in localStorage */ + localStorage.setItem('StackColorsCache', JSON.stringify(colorsCache)); + } + + return colorsCache[key]; +} + +export { + getColor +}
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/colorScheme.ts b/themes/hugo-theme-stack/assets/ts/colorScheme.ts new file mode 100644 index 0000000..978e98e --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/colorScheme.ts @@ -0,0 +1,92 @@ +type colorScheme = 'light' | 'dark' | 'auto'; + +class StackColorScheme { + private localStorageKey = 'StackColorScheme'; + private currentScheme: colorScheme; + private systemPreferScheme: colorScheme; + + constructor(toggleEl: HTMLElement) { + this.bindMatchMedia(); + this.currentScheme = this.getSavedScheme(); + if (window.matchMedia('(prefers-color-scheme: dark)').matches === true) + this.systemPreferScheme = 'dark' + else + this.systemPreferScheme = 'light'; + + this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme); + + if (toggleEl) + this.bindClick(toggleEl); + + if (document.body.style.transition == '') + document.body.style.setProperty('transition', 'background-color .3s ease'); + } + + private saveScheme() { + localStorage.setItem(this.localStorageKey, this.currentScheme); + } + + private bindClick(toggleEl: HTMLElement) { + toggleEl.addEventListener('click', (e) => { + if (this.isDark()) { + /// Disable dark mode + this.currentScheme = 'light'; + } + else { + this.currentScheme = 'dark'; + } + + this.setBodyClass(); + + if (this.currentScheme == this.systemPreferScheme) { + /// Set to auto + this.currentScheme = 'auto'; + } + + this.saveScheme(); + }) + } + + private isDark() { + return (this.currentScheme == 'dark' || this.currentScheme == 'auto' && this.systemPreferScheme == 'dark'); + } + + private dispatchEvent(colorScheme: colorScheme) { + const event = new CustomEvent('onColorSchemeChange', { + detail: colorScheme + }); + window.dispatchEvent(event); + } + + private setBodyClass() { + if (this.isDark()) { + document.documentElement.dataset.scheme = 'dark'; + } + else { + document.documentElement.dataset.scheme = 'light'; + } + + this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme); + } + + private getSavedScheme(): colorScheme { + const savedScheme = localStorage.getItem(this.localStorageKey); + + if (savedScheme == 'light' || savedScheme == 'dark' || savedScheme == 'auto') return savedScheme; + else return 'auto'; + } + + private bindMatchMedia() { + window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => { + if (e.matches) { + this.systemPreferScheme = 'dark'; + } + else { + this.systemPreferScheme = 'light'; + } + this.setBodyClass(); + }); + } +} + +export default StackColorScheme; diff --git a/themes/hugo-theme-stack/assets/ts/createElement.ts b/themes/hugo-theme-stack/assets/ts/createElement.ts new file mode 100644 index 0000000..3a1e85e --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/createElement.ts @@ -0,0 +1,34 @@ +/** + * createElement + * Edited from: + * @link https://stackoverflow.com/a/42405694 + */ +function createElement(tag, attrs, children) { + var element = document.createElement(tag); + + for (let name in attrs) { + if (name && attrs.hasOwnProperty(name)) { + let value = attrs[name]; + + if (name == "dangerouslySetInnerHTML") { + element.innerHTML = value.__html; + } + else if (value === true) { + element.setAttribute(name, name); + } else if (value !== false && value != null) { + element.setAttribute(name, value.toString()); + } + } + } + for (let i = 2; i < arguments.length; i++) { + let child = arguments[i]; + if (child) { + element.appendChild( + child.nodeType == null ? + document.createTextNode(child.toString()) : child); + } + } + return element; +} + +export default createElement;
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/gallery.ts b/themes/hugo-theme-stack/assets/ts/gallery.ts new file mode 100644 index 0000000..9840f1e --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/gallery.ts @@ -0,0 +1,186 @@ +declare global { + interface Window { + PhotoSwipe: any; + PhotoSwipeUI_Default: any + } +} + +interface PhotoSwipeItem { + w: number; + h: number; + src: string; + msrc: string; + title?: string; + el: HTMLElement; +} + +class StackGallery { + private galleryUID: number; + private items: PhotoSwipeItem[] = []; + + constructor(container: HTMLElement, galleryUID = 1) { + if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) { + console.error("PhotoSwipe lib not loaded."); + return; + } + + this.galleryUID = galleryUID; + + StackGallery.createGallery(container); + this.loadItems(container); + this.bindClick(); + } + + private loadItems(container: HTMLElement) { + this.items = []; + + const figures = container.querySelectorAll('figure.gallery-image'); + + for (const el of figures) { + const figcaption = el.querySelector('figcaption'), + img = el.querySelector('img'); + + let aux: PhotoSwipeItem = { + w: parseInt(img.getAttribute('width')), + h: parseInt(img.getAttribute('height')), + src: img.src, + msrc: img.getAttribute('data-thumb') || img.src, + el: el + } + + if (figcaption) { + aux.title = figcaption.innerHTML; + } + + this.items.push(aux); + } + } + + public static createGallery(container: HTMLElement) { + /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook + /// because it can not detect whether image is being wrapped by a link or not + /// and it lead to a invalid HTML construction (<a><figure><img></figure></a>) + + const images = container.querySelectorAll('img.gallery-image'); + for (const img of Array.from(images)) { + /// Images are wrapped with figure tag if the paragraph has only images without texts + /// This is done to allow inline images within paragraphs + const paragraph = img.closest('p'); + + if (!paragraph || !container.contains(paragraph)) continue; + + if (paragraph.textContent.trim() == '') { + /// Once we insert figcaption, this check no longer works + /// So we add a class to paragraph to mark it + paragraph.classList.add('no-text'); + } + + let isNewLineImage = paragraph.classList.contains('no-text'); + if (!isNewLineImage) continue; + + const hasLink = img.parentElement.tagName == 'A'; + + let el: HTMLElement = img; + /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes + const figure = document.createElement('figure'); + figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1'); + figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0'); + if (hasLink) { + /// Wrap <a> if it exists + el = img.parentElement; + } + el.parentElement.insertBefore(figure, el); + figure.appendChild(el); + + /// Add figcaption if it exists + if (img.hasAttribute('alt')) { + const figcaption = document.createElement('figcaption'); + figcaption.innerText = img.getAttribute('alt'); + figure.appendChild(figcaption); + } + + /// Wrap img tag with <a> tag if image was not wrapped by <a> tag + if (!hasLink) { + figure.className = 'gallery-image'; + + const a = document.createElement('a'); + a.href = img.src; + a.setAttribute('target', '_blank'); + img.parentNode.insertBefore(a, img); + a.appendChild(img); + } + } + + const figuresEl = container.querySelectorAll('figure.gallery-image'); + + let currentGallery = []; + + for (const figure of figuresEl) { + if (!currentGallery.length) { + /// First iteration + currentGallery = [figure]; + } + else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) { + /// Adjacent figures + currentGallery.push(figure); + } + else if (currentGallery.length) { + /// End gallery + StackGallery.wrap(currentGallery); + currentGallery = [figure]; + } + } + + if (currentGallery.length > 0) { + StackGallery.wrap(currentGallery); + } + } + + /** + * Wrap adjacent figure tags with div.gallery + * @param figures + */ + public static wrap(figures: HTMLElement[]) { + const galleryContainer = document.createElement('div'); + galleryContainer.className = 'gallery'; + + const parentNode = figures[0].parentNode, + first = figures[0]; + + parentNode.insertBefore(galleryContainer, first) + + for (const figure of figures) { + galleryContainer.appendChild(figure); + } + } + + public open(index: number) { + const pswp = document.querySelector('.pswp') as HTMLDivElement; + const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, { + index: index, + galleryUID: this.galleryUID, + getThumbBoundsFn: (index) => { + const thumbnail = this.items[index].el.getElementsByTagName('img')[0], + pageYScroll = window.pageYOffset || document.documentElement.scrollTop, + rect = thumbnail.getBoundingClientRect(); + + return { x: rect.left, y: rect.top + pageYScroll, w: rect.width }; + } + }); + + ps.init(); + } + + private bindClick() { + for (const [index, item] of this.items.entries()) { + const a = item.el.querySelector('a'); + + a.addEventListener('click', (e) => { + e.preventDefault(); + this.open(index); + }) + } + } +} + +export default StackGallery;
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/main.ts b/themes/hugo-theme-stack/assets/ts/main.ts new file mode 100644 index 0000000..f3160ae --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/main.ts @@ -0,0 +1,112 @@ +/*! +* Hugo Theme Stack +* +* @author: Jimmy Cai +* @website: https://jimmycai.com +* @link: https://github.com/CaiJimmy/hugo-theme-stack +*/ +import StackGallery from "ts/gallery"; +import { getColor } from 'ts/color'; +import menu from 'ts/menu'; +import createElement from 'ts/createElement'; +import StackColorScheme from 'ts/colorScheme'; +import { setupScrollspy } from 'ts/scrollspy'; +import { setupSmoothAnchors } from "ts/smoothAnchors"; + +let Stack = { + init: () => { + /** + * Bind menu event + */ + menu(); + + const articleContent = document.querySelector('.article-content') as HTMLElement; + if (articleContent) { + new StackGallery(articleContent); + setupSmoothAnchors(); + setupScrollspy(); + } + + /** + * Add linear gradient background to tile style article + */ + const articleTile = document.querySelector('.article-list--tile'); + if (articleTile) { + let observer = new IntersectionObserver(async (entries, observer) => { + entries.forEach(entry => { + if (!entry.isIntersecting) return; + observer.unobserve(entry.target); + + const articles = entry.target.querySelectorAll('article.has-image'); + articles.forEach(async articles => { + const image = articles.querySelector('img'), + imageURL = image.src, + key = image.getAttribute('data-key'), + hash = image.getAttribute('data-hash'), + articleDetails: HTMLDivElement = articles.querySelector('.article-details'); + + const colors = await getColor(key, hash, imageURL); + + articleDetails.style.background = ` + linear-gradient(0deg, + rgba(${colors.DarkMuted.rgb[0]}, ${colors.DarkMuted.rgb[1]}, ${colors.DarkMuted.rgb[2]}, 0.5) 0%, + rgba(${colors.Vibrant.rgb[0]}, ${colors.Vibrant.rgb[1]}, ${colors.Vibrant.rgb[2]}, 0.75) 100%)`; + }) + }) + }); + + observer.observe(articleTile) + } + + + /** + * Add copy button to code block + */ + const highlights = document.querySelectorAll('.article-content div.highlight'); + const copyText = `Copy`, + copiedText = `Copied!`; + + highlights.forEach(highlight => { + const copyButton = document.createElement('button'); + copyButton.innerHTML = copyText; + copyButton.classList.add('copyCodeButton'); + highlight.appendChild(copyButton); + + const codeBlock = highlight.querySelector('code[data-lang]'); + if (!codeBlock) return; + + copyButton.addEventListener('click', () => { + navigator.clipboard.writeText(codeBlock.textContent) + .then(() => { + copyButton.textContent = copiedText; + + setTimeout(() => { + copyButton.textContent = copyText; + }, 1000); + }) + .catch(err => { + alert(err) + console.log('Something went wrong', err); + }); + }); + }); + + new StackColorScheme(document.getElementById('dark-mode-toggle')); + } +} + +window.addEventListener('load', () => { + setTimeout(function () { + Stack.init(); + }, 0); +}) + +declare global { + interface Window { + createElement: any; + Stack: any + } +} + +window.Stack = Stack; +window.createElement = createElement;
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/menu.ts b/themes/hugo-theme-stack/assets/ts/menu.ts new file mode 100644 index 0000000..34615ba --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/menu.ts @@ -0,0 +1,83 @@ +/** + * Slide up/down + * Code from https://dev.to/bmsvieira/vanilla-js-slidedown-up-4dkn + * @param target + * @param duration + */ +let slideUp = (target: HTMLElement, duration = 500) => { + target.classList.add('transiting'); + target.style.transitionProperty = 'height, margin, padding'; + target.style.transitionDuration = duration + 'ms'; + ///target.style.boxSizing = 'border-box'; + target.style.height = target.offsetHeight + 'px'; + target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = "0"; + target.style.paddingTop = "0"; + target.style.paddingBottom = "0"; + target.style.marginTop = "0"; + target.style.marginBottom = "0"; + window.setTimeout(() => { + target.classList.remove('show') + target.style.removeProperty('height'); + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + target.classList.remove('transiting'); + }, duration); +} + +let slideDown = (target: HTMLElement, duration = 500) => { + target.classList.add('transiting'); + target.style.removeProperty('display'); + + target.classList.add('show'); + + let height = target.offsetHeight; + target.style.overflow = 'hidden'; + target.style.height = "0"; + target.style.paddingTop = "0"; + target.style.paddingBottom = "0"; + target.style.marginTop = "0"; + target.style.marginBottom = "0"; + target.offsetHeight; + ///target.style.boxSizing = 'border-box'; + target.style.transitionProperty = "height, margin, padding"; + target.style.transitionDuration = duration + 'ms'; + target.style.height = height + 'px'; + target.style.removeProperty('padding-top'); + target.style.removeProperty('padding-bottom'); + target.style.removeProperty('margin-top'); + target.style.removeProperty('margin-bottom'); + window.setTimeout(() => { + target.style.removeProperty('height'); + target.style.removeProperty('overflow'); + target.style.removeProperty('transition-duration'); + target.style.removeProperty('transition-property'); + target.classList.remove('transiting'); + }, duration); +} + +let slideToggle = (target, duration = 500) => { + if (window.getComputedStyle(target).display === 'none') { + return slideDown(target, duration); + } else { + return slideUp(target, duration); + } +} + +export default function () { + const toggleMenu = document.getElementById('toggle-menu'); + if (toggleMenu) { + toggleMenu.addEventListener('click', () => { + if (document.getElementById('main-menu').classList.contains('transiting')) return; + document.body.classList.toggle('show-menu'); + slideToggle(document.getElementById('main-menu'), 300); + toggleMenu.classList.toggle('is-active'); + }); + } +}
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/scrollspy.ts b/themes/hugo-theme-stack/assets/ts/scrollspy.ts new file mode 100644 index 0000000..8a14085 --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/scrollspy.ts @@ -0,0 +1,131 @@ +// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed. + +// Inspired from https://gomakethings.com/debouncing-your-javascript-events/ +function debounced(func: Function) { + let timeout; + return () => { + if (timeout) { + window.cancelAnimationFrame(timeout); + } + + timeout = window.requestAnimationFrame(() => func()); + } +} + +const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]"; +const tocQuery = "#TableOfContents"; +const navigationQuery = "#TableOfContents li"; +const activeClass = "active-class"; + +function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) { + let textHeight = tocElement.querySelector("a").offsetHeight; + let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop; + if (scrollTop < 0) { + scrollTop = 0; + } + scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" }); +} + +type IdToElementMap = { [key: string]: HTMLElement }; + +function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap { + const sectionLinkRef: IdToElementMap = {}; + navigation.forEach((navigationElement: HTMLElement) => { + const link = navigationElement.querySelector("a"); + const href = link.getAttribute("href"); + if (href.startsWith("#")) { + sectionLinkRef[href.slice(1)] = navigationElement; + } + }); + + return sectionLinkRef; +} + +function computeOffsets(headers: NodeListOf<Element>) { + let sectionsOffsets = []; + headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) }); + sectionsOffsets.sort((a, b) => a.offset - b.offset); + return sectionsOffsets; +} + +function setupScrollspy() { + let headers = document.querySelectorAll(headersQuery); + if (!headers) { + console.warn("No header matched query", headers); + return; + } + + let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined; + if (!scrollableNavigation) { + console.warn("No toc matched query", tocQuery); + return; + } + + let navigation = document.querySelectorAll(navigationQuery); + if (!navigation) { + console.warn("No navigation matched query", navigationQuery); + return; + } + + let sectionsOffsets = computeOffsets(headers); + + // We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC, + // we would scroll their view, which is not optimal usability-wise. + let tocHovered: boolean = false; + scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true)); + scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false)); + + let activeSectionLink: Element; + + let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation); + + function scrollHandler() { + let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop; + + let newActiveSection: HTMLElement | undefined; + + // Find the section that is currently active. + // It is possible for no section to be active, so newActiveSection may be undefined. + sectionsOffsets.forEach((section) => { + if (scrollPosition >= section.offset - 20) { + newActiveSection = document.getElementById(section.id); + } + }); + + // Find the link for the active section. Once again, there are a few edge cases: + // - No active section = no link => undefined + // - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined + let newActiveSectionLink: HTMLElement | undefined + if (newActiveSection) { + newActiveSectionLink = idToNavigationElement[newActiveSection.id]; + } + + if (newActiveSection && !newActiveSectionLink) { + // The active section does not have a link in the ToC, so we can't scroll to it. + console.debug("No link found for section", newActiveSection); + } else if (newActiveSectionLink !== activeSectionLink) { + if (activeSectionLink) + activeSectionLink.classList.remove(activeClass); + if (newActiveSectionLink) { + newActiveSectionLink.classList.add(activeClass); + if (!tocHovered) { + // Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check) + scrollToTocElement(newActiveSectionLink, scrollableNavigation); + } + } + activeSectionLink = newActiveSectionLink; + } + } + + window.addEventListener("scroll", debounced(scrollHandler)); + + // Resizing may cause the offset values to change: recompute them. + function resizeHandler() { + sectionsOffsets = computeOffsets(headers); + scrollHandler(); + } + + window.addEventListener("resize", debounced(resizeHandler)); +} + +export { setupScrollspy };
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/search.tsx b/themes/hugo-theme-stack/assets/ts/search.tsx new file mode 100644 index 0000000..1c81dd1 --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/search.tsx @@ -0,0 +1,333 @@ +interface pageData { + title: string, + date: string, + permalink: string, + content: string, + image?: string, + preview: string, + matchCount: number +} + +interface match { + start: number, + end: number +} + +/** + * Escape HTML tags as HTML entities + * Edited from: + * @link https://stackoverflow.com/a/5499821 + */ +const tagsToReplace = { + '&': '&', + '<': '<', + '>': '>', + '"': '"', + '…': '…' +}; + +function replaceTag(tag) { + return tagsToReplace[tag] || tag; +} + +function replaceHTMLEnt(str) { + return str.replace(/[&<>"]/g, replaceTag); +} + +function escapeRegExp(string) { + return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&'); +} + +class Search { + private data: pageData[]; + private form: HTMLFormElement; + private input: HTMLInputElement; + private list: HTMLDivElement; + private resultTitle: HTMLHeadElement; + private resultTitleTemplate: string; + + constructor({ form, input, list, resultTitle, resultTitleTemplate }) { + this.form = form; + this.input = input; + this.list = list; + this.resultTitle = resultTitle; + this.resultTitleTemplate = resultTitleTemplate; + + /// Check if there's already value in the search input + if (this.input.value.trim() !== '') { + this.doSearch(this.input.value.split(' ')); + } + else { + this.handleQueryString(); + } + + this.bindQueryStringChange(); + this.bindSearchForm(); + } + + /** + * Processes search matches + * @param str original text + * @param matches array of matches + * @param ellipsis whether to add ellipsis to the end of each match + * @param charLimit max length of preview string + * @param offset how many characters before and after the match to include in preview + * @returns preview string + */ + private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string { + matches.sort((a, b) => { + return a.start - b.start; + }); + + let i = 0, + lastIndex = 0, + charCount = 0; + + const resultArray: string[] = []; + + while (i < matches.length) { + const item = matches[i]; + + /// item.start >= lastIndex (equal only for the first iteration) + /// because of the while loop that comes after, iterating over variable j + + if (ellipsis && item.start - offset > lastIndex) { + resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `); + resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`); + charCount += offset * 2; + } + else { + /// If the match is too close to the end of last match, don't add ellipsis + resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start))); + charCount += item.start - lastIndex; + } + + let j = i + 1, + end = item.end; + + /// Include as many matches as possible + /// [item.start, end] is the range of the match + while (j < matches.length && matches[j].start <= end) { + end = Math.max(matches[j].end, end); + ++j; + } + + resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`); + charCount += end - item.start; + + i = j; + lastIndex = end; + + if (ellipsis && charCount > charLimit) break; + } + + /// Add the rest of the string + if (lastIndex < str.length) { + let end = str.length; + if (ellipsis) end = Math.min(end, lastIndex + offset); + + resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`); + + if (ellipsis && end != str.length) { + resultArray.push(` [...]`); + } + } + + return resultArray.join(''); + } + + private async searchKeywords(keywords: string[]) { + const rawData = await this.getData(); + const results: pageData[] = []; + + const regex = new RegExp(keywords.filter((v, index, arr) => { + arr[index] = escapeRegExp(v); + return v.trim() !== ''; + }).join('|'), 'gi'); + + for (const item of rawData) { + const titleMatches: match[] = [], + contentMatches: match[] = []; + + let result = { + ...item, + preview: '', + matchCount: 0 + } + + const contentMatchAll = item.content.matchAll(regex); + for (const match of Array.from(contentMatchAll)) { + contentMatches.push({ + start: match.index, + end: match.index + match[0].length + }); + } + + const titleMatchAll = item.title.matchAll(regex); + for (const match of Array.from(titleMatchAll)) { + titleMatches.push({ + start: match.index, + end: match.index + match[0].length + }); + } + + if (titleMatches.length > 0) result.title = Search.processMatches(result.title, titleMatches, false); + if (contentMatches.length > 0) { + result.preview = Search.processMatches(result.content, contentMatches); + } + else { + /// If there are no matches in the content, use the first 140 characters as preview + result.preview = replaceHTMLEnt(result.content.substring(0, 140)); + } + + result.matchCount = titleMatches.length + contentMatches.length; + if (result.matchCount > 0) results.push(result); + } + + /// Result with more matches appears first + return results.sort((a, b) => { + return b.matchCount - a.matchCount; + }); + } + + private async doSearch(keywords: string[]) { + const startTime = performance.now(); + + const results = await this.searchKeywords(keywords); + this.clear(); + + for (const item of results) { + this.list.append(Search.render(item)); + } + + const endTime = performance.now(); + + this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1)); + } + + private generateResultTitle(resultLen, time) { + return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen).replace("#TIME_SECONDS", time); + } + + public async getData() { + if (!this.data) { + /// Not fetched yet + const jsonURL = this.form.dataset.json; + this.data = await fetch(jsonURL).then(res => res.json()); + const parser = new DOMParser(); + + for (const item of this.data) { + item.content = parser.parseFromString(item.content, 'text/html').body.innerText; + } + } + + return this.data; + } + + private bindSearchForm() { + let lastSearch = ''; + + const eventHandler = (e) => { + e.preventDefault(); + const keywords = this.input.value.trim(); + + Search.updateQueryString(keywords, true); + + if (keywords === '') { + lastSearch = ''; + return this.clear(); + } + + if (lastSearch === keywords) return; + lastSearch = keywords; + + this.doSearch(keywords.split(' ')); + } + + this.input.addEventListener('input', eventHandler); + this.input.addEventListener('compositionend', eventHandler); + } + + private clear() { + this.list.innerHTML = ''; + this.resultTitle.innerText = ''; + } + + private bindQueryStringChange() { + window.addEventListener('popstate', (e) => { + this.handleQueryString() + }) + } + + private handleQueryString() { + const pageURL = new URL(window.location.toString()); + const keywords = pageURL.searchParams.get('keyword'); + this.input.value = keywords; + + if (keywords) { + this.doSearch(keywords.split(' ')); + } + else { + this.clear() + } + } + + private static updateQueryString(keywords: string, replaceState = false) { + const pageURL = new URL(window.location.toString()); + + if (keywords === '') { + pageURL.searchParams.delete('keyword') + } + else { + pageURL.searchParams.set('keyword', keywords); + } + + if (replaceState) { + window.history.replaceState('', '', pageURL.toString()); + } + else { + window.history.pushState('', '', pageURL.toString()); + } + } + + public static render(item: pageData) { + return <article> + <a href={item.permalink}> + <div class="article-details"> + <h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2> + <section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section> + </div> + {item.image && + <div class="article-image"> + <img src={item.image} loading="lazy" /> + </div> + } + </a> + </article>; + } +} + +declare global { + interface Window { + searchResultTitleTemplate: string; + } +} + +window.addEventListener('load', () => { + setTimeout(function () { + const searchForm = document.querySelector('.search-form') as HTMLFormElement, + searchInput = searchForm.querySelector('input') as HTMLInputElement, + searchResultList = document.querySelector('.search-result--list') as HTMLDivElement, + searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement; + + new Search({ + form: searchForm, + input: searchInput, + list: searchResultList, + resultTitle: searchResultTitle, + resultTitleTemplate: window.searchResultTitleTemplate + }); + }, 0); +}) + +export default Search;
\ No newline at end of file diff --git a/themes/hugo-theme-stack/assets/ts/smoothAnchors.ts b/themes/hugo-theme-stack/assets/ts/smoothAnchors.ts new file mode 100644 index 0000000..16ab6a3 --- /dev/null +++ b/themes/hugo-theme-stack/assets/ts/smoothAnchors.ts @@ -0,0 +1,37 @@ +// Implements smooth scrolling when clicking on an anchor link. +// This is required instead of using modern CSS because Chromium does not currently support scrolling +// one element with scrollTo while another element is scrolled because of a click on a link. This would +// thus not work with the ToC scrollspy and e.g. footnotes. + +// Here are additional links about this issue: +// - https://stackoverflow.com/questions/49318497/google-chrome-simultaneously-smooth-scrollintoview-with-more-elements-doesn +// - https://stackoverflow.com/questions/57214373/scrollintoview-using-smooth-function-on-multiple-elements-in-chrome +// - https://bugs.chromium.org/p/chromium/issues/detail?id=833617 +// - https://bugs.chromium.org/p/chromium/issues/detail?id=1043933 +// - https://bugs.chromium.org/p/chromium/issues/detail?id=1121151 + +const anchorLinksQuery = "a[href]"; + +function setupSmoothAnchors() { + document.querySelectorAll(anchorLinksQuery).forEach(aElement => { + let href = aElement.getAttribute("href"); + if (!href.startsWith("#")) { + return; + } + aElement.addEventListener("click", clickEvent => { + clickEvent.preventDefault(); + + const targetId = decodeURI(aElement.getAttribute("href").substring(1)), + target = document.getElementById(targetId) as HTMLElement, + offset = target.getBoundingClientRect().top - document.documentElement.getBoundingClientRect().top; + + window.history.pushState({}, "", aElement.getAttribute("href")); + scrollTo({ + top: offset, + behavior: "smooth" + }); + }); + }); +} + +export { setupSmoothAnchors };
\ No newline at end of file |