a new start
This commit is contained in:
16
.gitignore
vendored
Normal file
16
.gitignore
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
_site
|
||||
|
||||
node_modules
|
||||
vendor
|
||||
.bundle
|
||||
.sass-cache
|
||||
.jekyll-metadata
|
||||
.jekyll-cache
|
||||
.ruby-lsp
|
||||
*.gem
|
||||
|
||||
#<removed/js>
|
||||
#<removed/css>
|
||||
|
||||
# assets
|
||||
assets/js
|
||||
7
.prettierignore
Normal file
7
.prettierignore
Normal file
@@ -0,0 +1,7 @@
|
||||
package.json
|
||||
*.md
|
||||
*.html
|
||||
*.scss
|
||||
*.css
|
||||
*.yml
|
||||
_js/lib
|
||||
7
.prettierrc
Normal file
7
.prettierrc
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"printWidth": 120,
|
||||
"trailingComma": "all",
|
||||
"tabWidth": 2,
|
||||
"semi": true,
|
||||
"singleQuote": true
|
||||
}
|
||||
1
.rubocop.yml
Normal file
1
.rubocop.yml
Normal file
@@ -0,0 +1 @@
|
||||
inherit_from: ../../.rubocop.yml
|
||||
93
.scripts/build-css.js
Executable file
93
.scripts/build-css.js
Executable file
@@ -0,0 +1,93 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { basename, dirname, format, relative, resolve } = require("path");
|
||||
const { readdir, stat, readFile, writeFile, mkdir } = require("fs").promises;
|
||||
|
||||
const dedent = require("dedent");
|
||||
|
||||
const ENC = "utf-8";
|
||||
|
||||
const INLINE_REGEX = /(.*)\s*\/\/\s*inline\s*$/gimu;
|
||||
const DEFER_REGEX = /(.*)\s*\/\/\s*link\s*$/gimu;
|
||||
const INLINE_BLOCK_REGEX =
|
||||
/\/\/\s*>*\s*<{3,}\s*inline([\s\S]*?)\/\/\s*>{3,}.*/gimu;
|
||||
const DEFER_BLOCK_REGEX =
|
||||
/\/\/\s*>*\s*<{3,}\s*link([\s\S]*?)\/\/\s*>{3,}.*/gimu;
|
||||
|
||||
function genHeader(filename) {
|
||||
return dedent`
|
||||
// THIS FILE IS AUTOGENERATED, DO NOT MODIFY!
|
||||
//
|
||||
// To change the contents of this file,
|
||||
// edit \`${filename}\`
|
||||
// and run \`npm run build:css\`.
|
||||
//
|
||||
// During development you can run \`npm run watch:css\`
|
||||
// to continuosly rebuild this file.
|
||||
`;
|
||||
}
|
||||
|
||||
// <https://stackoverflow.com/a/45130990/870615>
|
||||
async function getFiles(dir) {
|
||||
const subdirs = await readdir(dir);
|
||||
const files = await Promise.all(
|
||||
subdirs.map(async (subdir) => {
|
||||
const res = resolve(dir, subdir);
|
||||
return (await stat(res)).isDirectory() ? getFiles(res) : res;
|
||||
}),
|
||||
);
|
||||
return files.reduce((a, f) => a.concat(f), []);
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
try {
|
||||
const files =
|
||||
process.argv.length > 2 ? [process.argv[2]] : await getFiles("_sass");
|
||||
await Promise.all(
|
||||
files
|
||||
.filter((f) => f.endsWith(".pre.scss"))
|
||||
.map(async (file) => {
|
||||
const content = await readFile(file, ENC);
|
||||
const name = basename(file, ".pre.scss");
|
||||
const filename = format({ name, ext: ".scss" });
|
||||
const dir = dirname(file);
|
||||
|
||||
const inline = content
|
||||
.replace(INLINE_REGEX, "$1")
|
||||
.replace(INLINE_BLOCK_REGEX, "$1")
|
||||
.replace(DEFER_REGEX, "// $1")
|
||||
.replace(DEFER_BLOCK_REGEX, "");
|
||||
|
||||
const defer = content
|
||||
.replace(DEFER_REGEX, "$1")
|
||||
.replace(DEFER_BLOCK_REGEX, "$1")
|
||||
.replace(INLINE_REGEX, "// $1")
|
||||
.replace(INLINE_BLOCK_REGEX, "");
|
||||
|
||||
const path = relative(resolve(), dirname(file));
|
||||
const header = genHeader([path, basename(file)].join("/"));
|
||||
|
||||
await Promise.all([
|
||||
mkdir(resolve(dir, "__inline__"), { recursive: true }),
|
||||
mkdir(resolve(dir, "__link__"), { recursive: true }),
|
||||
]);
|
||||
return Promise.all([
|
||||
writeFile(
|
||||
resolve(dir, "__inline__", filename),
|
||||
header + "\n\n" + inline,
|
||||
ENC,
|
||||
),
|
||||
writeFile(
|
||||
resolve(dir, "__link__", filename),
|
||||
header + "\n\n" + defer,
|
||||
ENC,
|
||||
),
|
||||
]);
|
||||
}),
|
||||
);
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error(e); // eslint-disable-line
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
108
.scripts/version.js
Executable file
108
.scripts/version.js
Executable file
@@ -0,0 +1,108 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
const { resolve } = require("path");
|
||||
const fs = require("fs");
|
||||
const { readdir, rename, unlink, readFile, writeFile, access } = fs.promises;
|
||||
const { promisify } = require("util");
|
||||
const exec = promisify(require("child_process").exec);
|
||||
|
||||
const vPrev = require("../assets/version.json").version;
|
||||
const vNext = require("../package.json").version;
|
||||
|
||||
const ENC = "utf-8";
|
||||
|
||||
const FILES = [
|
||||
"./jekyll-theme-pivoine.gemspec",
|
||||
"./_includes/body/scripts.html",
|
||||
"./_includes/body/footer.html",
|
||||
"./_includes/head/meta-static.html",
|
||||
"./_includes/head/links-static.html",
|
||||
"./_includes/head/styles-inline.html",
|
||||
"./_includes/head/styles-no-inline.html",
|
||||
"./_includes/header.txt",
|
||||
"./_layouts/compress.html",
|
||||
"./_js/lib/version.js",
|
||||
].map((f) => resolve(f));
|
||||
|
||||
/**
|
||||
* @param {string} dir
|
||||
* @returns {Promise<string[]>}
|
||||
* @see https://stackoverflow.com/a/45130990/870615
|
||||
*/
|
||||
async function getFiles(dir) {
|
||||
const dirents = await readdir(dir, { withFileTypes: true });
|
||||
const files = await Promise.all(
|
||||
dirents.map((dirent) => {
|
||||
const res = resolve(dir, dirent.name);
|
||||
return dirent.isDirectory() ? getFiles(res) : [res];
|
||||
}),
|
||||
);
|
||||
return Array.prototype.concat(...files);
|
||||
}
|
||||
|
||||
(async function main() {
|
||||
try {
|
||||
const prev = vPrev.replace(/\./g, "\\.");
|
||||
const prevRegExp = new RegExp(prev, "g");
|
||||
|
||||
// const args = await Promise.all([
|
||||
// getFiles("./hyde/_posts"),
|
||||
// getFiles("./pivoine/_posts"),
|
||||
// getFiles("./_projects"),
|
||||
// getFiles("./docs"),
|
||||
// ]);
|
||||
const args = [];
|
||||
|
||||
const files = Array.prototype.concat.call(FILES, ...args);
|
||||
|
||||
const pFiles = Promise.all(
|
||||
files
|
||||
.filter(([f]) => !f.startsWith("."))
|
||||
.map((f) => [f, readFile(f, ENC)])
|
||||
.map(async ([f, p]) => {
|
||||
const content = await p;
|
||||
|
||||
// if (f.includes("CHANGELOG")) {
|
||||
// const pattern = new RegExp(`([^v])${prev}`, "g");
|
||||
// return [f, content.replace(pattern, `$1${vNext}`)];
|
||||
// }
|
||||
|
||||
return [f, content.replace(prevRegExp, vNext)];
|
||||
})
|
||||
.map(async (p) => {
|
||||
const [f, content] = await p;
|
||||
return writeFile(f, content, ENC);
|
||||
}),
|
||||
);
|
||||
|
||||
const pUnlink = Promise.all(
|
||||
(await getFiles("./assets/js"))
|
||||
.filter((f) => f.match(/assets\/js\/(.*)pivoine-(.*)/i))
|
||||
.map(unlink),
|
||||
);
|
||||
|
||||
const pJSCSS = rename(
|
||||
resolve(`./assets/css/pivoine-${vPrev}.css`),
|
||||
resolve(`./assets/css/pivoine-${vNext}.css`),
|
||||
);
|
||||
|
||||
await Promise.all([pUnlink, pFiles, pJSCSS]);
|
||||
|
||||
await writeFile(
|
||||
"./assets/version.json",
|
||||
JSON.stringify({ version: vNext, prevVersion: vPrev }, null, 2),
|
||||
);
|
||||
|
||||
try {
|
||||
await access("../.scripts/version.js", fs.constants.X_OK);
|
||||
await exec("../.scripts/version.js");
|
||||
} catch (e) {
|
||||
console.warn(e);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
} catch (e) {
|
||||
console.error(e); // eslint-disable-line
|
||||
process.exit(1);
|
||||
}
|
||||
})();
|
||||
62
Gemfile
Normal file
62
Gemfile
Normal file
@@ -0,0 +1,62 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
source 'https://rubygems.org'
|
||||
|
||||
# Hello! This is where you manage which Jekyll version is used to run.
|
||||
# When you want to use a different version, change it below, save the
|
||||
# file and run `bundle install`. Run Jekyll with `bundle exec`, like so:
|
||||
#
|
||||
# bundle exec jekyll serve
|
||||
#
|
||||
# This will help ensure the proper Jekyll version is running.
|
||||
# Happy Jekylling!
|
||||
gem 'jekyll', '~> 4.4'
|
||||
|
||||
# If you are part of the ["Customers" team](https://github.com/orgs/hydecorp/teams/pro-customers),
|
||||
# you can fetch the theme from a private repository.
|
||||
# See [Deploy in the pivoine Docs](https://pivoine.com/docs/deploy) for details.
|
||||
|
||||
# gem "jekyll-theme-pivoine", git: "https://github.com/hydecorp/pivoine-pro", tag: "pro/v9.2.0"
|
||||
|
||||
# IMPORTANT: The followign gem is used to compile math formulas to
|
||||
# KaTeX during site building.
|
||||
#
|
||||
# There are a couple of things to know about this gem:
|
||||
# * It is not supported on GitHub Pages.
|
||||
# You have to build the site on your machine before uploading to GitHub,
|
||||
# or use a more permissive cloud building tool such as Netlify.
|
||||
# * You need some kind of JavaScript runtime on your machine.
|
||||
# Usually installing NodeJS will suffice.
|
||||
# For details, see <https://github.com/kramdown/math-katex#documentation>
|
||||
#
|
||||
# If you're using the MathJax math engine instead, free to remove the line below:
|
||||
gem 'kramdown-math-katex'
|
||||
|
||||
# A JavaScript runtime for Ruby that helps with running the katex gem above.
|
||||
gem 'duktape'
|
||||
|
||||
# Required for `jekyll serve` in Ruby 3
|
||||
gem 'webrick'
|
||||
|
||||
# Uncomment when using the `--lsi` option for `jekyll build`
|
||||
gem "classifier-reborn"
|
||||
|
||||
group :jekyll_plugins do
|
||||
gem 'jekyll-sass-converter'
|
||||
gem 'jekyll-compose'
|
||||
gem 'jekyll-default-layout'
|
||||
gem 'jekyll-feed'
|
||||
gem 'jekyll-include-cache'
|
||||
gem 'jekyll-last-modified-at'
|
||||
gem 'jekyll-optional-front-matter'
|
||||
gem 'jekyll-paginate-v2'
|
||||
gem 'jekyll-readme-index'
|
||||
gem 'jekyll-redirect-from'
|
||||
gem 'jekyll-relative-links'
|
||||
gem 'jekyll-seo-tag'
|
||||
gem 'jekyll-sitemap'
|
||||
gem 'jekyll-titles-from-headings'
|
||||
end
|
||||
|
||||
gem 'tzinfo-data' if Gem.win_platform?
|
||||
gem 'wdm' if Gem.win_platform?
|
||||
217
Gemfile.lock
Normal file
217
Gemfile.lock
Normal file
@@ -0,0 +1,217 @@
|
||||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
addressable (2.8.7)
|
||||
public_suffix (>= 2.0.2, < 7.0)
|
||||
base64 (0.3.0)
|
||||
bigdecimal (3.2.2)
|
||||
classifier-reborn (2.3.0)
|
||||
fast-stemmer (~> 1.0)
|
||||
matrix (~> 0.4)
|
||||
colorator (1.1.0)
|
||||
concurrent-ruby (1.3.5)
|
||||
csv (3.3.5)
|
||||
duktape (2.7.0.0)
|
||||
em-websocket (0.5.3)
|
||||
eventmachine (>= 0.12.9)
|
||||
http_parser.rb (~> 0)
|
||||
eventmachine (1.2.7)
|
||||
execjs (2.10.0)
|
||||
fast-stemmer (1.0.2)
|
||||
ffi (1.17.2)
|
||||
ffi (1.17.2-aarch64-linux-gnu)
|
||||
ffi (1.17.2-aarch64-linux-musl)
|
||||
ffi (1.17.2-arm-linux-gnu)
|
||||
ffi (1.17.2-arm-linux-musl)
|
||||
ffi (1.17.2-arm64-darwin)
|
||||
ffi (1.17.2-x86-linux-gnu)
|
||||
ffi (1.17.2-x86-linux-musl)
|
||||
ffi (1.17.2-x86_64-darwin)
|
||||
ffi (1.17.2-x86_64-linux-gnu)
|
||||
ffi (1.17.2-x86_64-linux-musl)
|
||||
forwardable-extended (2.6.0)
|
||||
google-protobuf (4.31.1)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-aarch64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-aarch64-linux-musl)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-arm64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86-linux-musl)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86_64-darwin)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86_64-linux-gnu)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
google-protobuf (4.31.1-x86_64-linux-musl)
|
||||
bigdecimal
|
||||
rake (>= 13)
|
||||
http_parser.rb (0.8.0)
|
||||
i18n (1.14.7)
|
||||
concurrent-ruby (~> 1.0)
|
||||
jekyll (4.4.1)
|
||||
addressable (~> 2.4)
|
||||
base64 (~> 0.2)
|
||||
colorator (~> 1.0)
|
||||
csv (~> 3.0)
|
||||
em-websocket (~> 0.5)
|
||||
i18n (~> 1.0)
|
||||
jekyll-sass-converter (>= 2.0, < 4.0)
|
||||
jekyll-watch (~> 2.0)
|
||||
json (~> 2.6)
|
||||
kramdown (~> 2.3, >= 2.3.1)
|
||||
kramdown-parser-gfm (~> 1.0)
|
||||
liquid (~> 4.0)
|
||||
mercenary (~> 0.3, >= 0.3.6)
|
||||
pathutil (~> 0.9)
|
||||
rouge (>= 3.0, < 5.0)
|
||||
safe_yaml (~> 1.0)
|
||||
terminal-table (>= 1.8, < 4.0)
|
||||
webrick (~> 1.7)
|
||||
jekyll-compose (0.12.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-default-layout (0.1.5)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-feed (0.17.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-include-cache (0.2.1)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-last-modified-at (1.3.2)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-optional-front-matter (0.3.2)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-paginate-v2 (3.0.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-readme-index (0.3.0)
|
||||
jekyll (>= 3.0, < 5.0)
|
||||
jekyll-redirect-from (0.16.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-relative-links (0.7.0)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-sass-converter (3.1.0)
|
||||
sass-embedded (~> 1.75)
|
||||
jekyll-seo-tag (2.8.0)
|
||||
jekyll (>= 3.8, < 5.0)
|
||||
jekyll-sitemap (1.4.0)
|
||||
jekyll (>= 3.7, < 5.0)
|
||||
jekyll-titles-from-headings (0.5.3)
|
||||
jekyll (>= 3.3, < 5.0)
|
||||
jekyll-watch (2.2.1)
|
||||
listen (~> 3.0)
|
||||
json (2.13.2)
|
||||
katex (0.10.0)
|
||||
execjs (~> 2.8)
|
||||
kramdown (2.5.1)
|
||||
rexml (>= 3.3.9)
|
||||
kramdown-math-katex (1.0.1)
|
||||
katex (~> 0.4)
|
||||
kramdown (~> 2.0)
|
||||
kramdown-parser-gfm (1.1.0)
|
||||
kramdown (~> 2.0)
|
||||
liquid (4.0.4)
|
||||
listen (3.9.0)
|
||||
rb-fsevent (~> 0.10, >= 0.10.3)
|
||||
rb-inotify (~> 0.9, >= 0.9.10)
|
||||
matrix (0.4.3)
|
||||
mercenary (0.4.0)
|
||||
pathutil (0.16.2)
|
||||
forwardable-extended (~> 2.6)
|
||||
public_suffix (6.0.2)
|
||||
rake (13.3.0)
|
||||
rb-fsevent (0.11.2)
|
||||
rb-inotify (0.11.1)
|
||||
ffi (~> 1.0)
|
||||
rexml (3.4.1)
|
||||
rouge (4.6.0)
|
||||
safe_yaml (1.0.5)
|
||||
sass-embedded (1.89.2)
|
||||
google-protobuf (~> 4.31)
|
||||
rake (>= 13)
|
||||
sass-embedded (1.89.2-aarch64-linux-android)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-aarch64-linux-gnu)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-aarch64-linux-musl)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-arm-linux-androideabi)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-arm-linux-gnueabihf)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-arm-linux-musleabihf)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-arm64-darwin)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-riscv64-linux-android)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-riscv64-linux-gnu)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-riscv64-linux-musl)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-x86_64-darwin)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-x86_64-linux-android)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-x86_64-linux-gnu)
|
||||
google-protobuf (~> 4.31)
|
||||
sass-embedded (1.89.2-x86_64-linux-musl)
|
||||
google-protobuf (~> 4.31)
|
||||
terminal-table (3.0.2)
|
||||
unicode-display_width (>= 1.1.1, < 3)
|
||||
unicode-display_width (2.6.0)
|
||||
webrick (1.9.1)
|
||||
|
||||
PLATFORMS
|
||||
aarch64-linux-android
|
||||
aarch64-linux-gnu
|
||||
aarch64-linux-musl
|
||||
arm-linux-androideabi
|
||||
arm-linux-gnu
|
||||
arm-linux-gnueabihf
|
||||
arm-linux-musl
|
||||
arm-linux-musleabihf
|
||||
arm64-darwin
|
||||
riscv64-linux-android
|
||||
riscv64-linux-gnu
|
||||
riscv64-linux-musl
|
||||
ruby
|
||||
x86-linux-gnu
|
||||
x86-linux-musl
|
||||
x86_64-darwin
|
||||
x86_64-linux-android
|
||||
x86_64-linux-gnu
|
||||
x86_64-linux-musl
|
||||
|
||||
DEPENDENCIES
|
||||
classifier-reborn
|
||||
duktape
|
||||
jekyll (~> 4.4)
|
||||
jekyll-compose
|
||||
jekyll-default-layout
|
||||
jekyll-feed
|
||||
jekyll-include-cache
|
||||
jekyll-last-modified-at
|
||||
jekyll-optional-front-matter
|
||||
jekyll-paginate-v2
|
||||
jekyll-readme-index
|
||||
jekyll-redirect-from
|
||||
jekyll-relative-links
|
||||
jekyll-sass-converter
|
||||
jekyll-seo-tag
|
||||
jekyll-sitemap
|
||||
jekyll-titles-from-headings
|
||||
kramdown-math-katex
|
||||
webrick
|
||||
|
||||
BUNDLED WITH
|
||||
2.7.1
|
||||
253
_config.yml
Normal file
253
_config.yml
Normal file
@@ -0,0 +1,253 @@
|
||||
lang: en
|
||||
title: Valknar's
|
||||
copyright: © 2025 Valknar. All rights reserved.
|
||||
description: Valknar's blog. Coding, Art, and Life.
|
||||
tagline: Coding, Art, and Life
|
||||
logo: /assets/img/logo.svg
|
||||
favicon: /assets/icons/favicon.svg
|
||||
author:
|
||||
# Used by `jekyll-feed`:
|
||||
name: Sebastian Krüger
|
||||
email: valknar@pivoine.art
|
||||
# Used by `jekyll-seo-tag`:
|
||||
twitter: bordeaux1981
|
||||
|
||||
url: https://pivoine.art
|
||||
baseurl: ''
|
||||
|
||||
exclude:
|
||||
- packages
|
||||
- gfx
|
||||
|
||||
include:
|
||||
- _pages
|
||||
|
||||
twitter:
|
||||
username: bordeaux1981
|
||||
|
||||
github:
|
||||
username: valknarogg
|
||||
|
||||
collections:
|
||||
posts:
|
||||
output: true
|
||||
permalink: /:categories/:slug
|
||||
|
||||
accent_image: /assets/bg/hypertown.webp
|
||||
accent_video: /assets/bg/hypertown.mp4
|
||||
accent_color: rgb(255, 168, 202)
|
||||
theme_color: rgb(255,255,255)
|
||||
|
||||
menu:
|
||||
legal:
|
||||
- title: About
|
||||
url: /about
|
||||
- title: Imprint
|
||||
url: /imprint
|
||||
|
||||
timezone: Europe/Berlin
|
||||
|
||||
featured_categories:
|
||||
- slug: Palina
|
||||
title: Palina
|
||||
url: /palina
|
||||
- slug: Odinsland
|
||||
title: Odinsland
|
||||
url: /odinsland
|
||||
- slug: Devilish
|
||||
title: Devilish
|
||||
url: /devilish
|
||||
- slug: Souls
|
||||
title: Souls
|
||||
url: /souls
|
||||
- slug: Sketches
|
||||
title: Sketches
|
||||
url: /sketches
|
||||
- slug: Music
|
||||
title: Music
|
||||
url: /music
|
||||
|
||||
featured_tags:
|
||||
- slug: Hyperloop
|
||||
title: Hyperloop
|
||||
url: /hyperloop
|
||||
|
||||
lsi: true
|
||||
|
||||
markdown: kramdown
|
||||
highlighter: rouge
|
||||
kramdown:
|
||||
syntax_highlighter: rouge
|
||||
permalink: pretty
|
||||
|
||||
plugins:
|
||||
- jekyll-default-layout
|
||||
- jekyll-feed
|
||||
- jekyll-optional-front-matter
|
||||
- jekyll-paginate-v2
|
||||
- jekyll-readme-index
|
||||
- jekyll-redirect-from
|
||||
- jekyll-relative-links
|
||||
- jekyll-seo-tag
|
||||
- jekyll-sitemap
|
||||
- jekyll-titles-from-headings
|
||||
- jekyll-include-cache
|
||||
- jekyll-last-modified-at
|
||||
|
||||
|
||||
pagination:
|
||||
# Site-wide kill switch, disabled here it doesn't run at all
|
||||
enabled: true
|
||||
# Set to 'true' to enable pagination debugging. This can be enabled in the site config or only for individual pagination pages
|
||||
debug: false
|
||||
# The default document collection to paginate if nothing is specified ('posts' is default)
|
||||
collection: all
|
||||
# How many objects per paginated page, used to be `paginate` (default: 0, means all)
|
||||
per_page: 5
|
||||
# The permalink structure for the paginated pages (this can be any level deep)
|
||||
permalink: '/page/:num/' # Pages are index.html inside this folder (default)
|
||||
sort_field: 'date'
|
||||
sort_reverse: true
|
||||
|
||||
autopages:
|
||||
# Site-wide kill switch, disable here and it doesn't run at all
|
||||
enabled: false
|
||||
|
||||
google_fonts: false
|
||||
|
||||
pivoine:
|
||||
# Configure the order of complementary content on blog posts
|
||||
post_addons: [about, newsletter, related, random, comments]
|
||||
|
||||
# Configure the order of complementary content on project pages
|
||||
project_addons: [about, newsletter, other, comments]
|
||||
|
||||
# Set to `true` if you don't want to show an icon indicating external links
|
||||
no_mark_external: true
|
||||
|
||||
# Set to `true` if third party plugins fail to work with dynamically loaded pages
|
||||
no_push_state: false
|
||||
|
||||
# Set to `true` if you want to disable the drawer
|
||||
no_drawer: false
|
||||
|
||||
# Set to `true` if you don't to use the auto-hiding (JavaScript based) navbar.
|
||||
# Note that this will not hide the navbar completely, only replace it with a static one.
|
||||
# Use custom css to hide completely, e.g. `#_navbar { display: none }`.
|
||||
no_navbar: false
|
||||
|
||||
# Set to true to disable the built-in search functionality.
|
||||
# Note that search is disabled during local use to save on build time.
|
||||
# Run Jekyll with the `JEKYLL_ENV` environment variable set to `production` to enable.
|
||||
no_search: false
|
||||
|
||||
# Set to `true` if you do not want parts of the css inlined in <head/>
|
||||
# This will increase site build speed dramatically!
|
||||
no_inline_css: false
|
||||
|
||||
# Set to `true` if you don't intend on changing the accent color on a per-page basis.
|
||||
# This will increase site build speed!
|
||||
no_page_style: false
|
||||
|
||||
# Code blocks and tables "break" the layout by spanning the full available width.
|
||||
# Set this to true if you want them to be the same width as other content.
|
||||
no_break_layout: true
|
||||
|
||||
# Set to `true` to disable the dynamic Table of Contents on large screens.
|
||||
no_toc: false
|
||||
|
||||
# When set to `true`, will not extend the content in the "third column" on large screens.
|
||||
# Instead, all content will remains within the center column.
|
||||
# Note that this will not affect the Table of Contents, use `no_toc` instead.
|
||||
no_third_column: false
|
||||
|
||||
# Set to `true` if you don't like oversized headlines on large screens.
|
||||
no_large_headings: false
|
||||
|
||||
# Set to `true` if you do not want to expose your resume and projects
|
||||
# in machine-readable formats.
|
||||
no_structured_data: false
|
||||
|
||||
# You can set this to `true` if you don't want to set the `theme-color` meta tag,
|
||||
# This only affects the meta tag, not the color specified in the app manifest.
|
||||
no_theme_color: false
|
||||
|
||||
# Disable the breadcrumbs above the title
|
||||
no_breadcrumbs: false
|
||||
|
||||
# Set to `true` when building with the `--lsi` option.
|
||||
# The net effect is to use the Jekyll-provided `site.related_posts` variable.
|
||||
use_lsi: true
|
||||
|
||||
# When using Google Analytics, set to `true` to display a cookie notice banner.
|
||||
# When enabled, no user-related data will be stored until the user gives consent.
|
||||
cookies_banner: false
|
||||
|
||||
# Set to `true` if you would like to add a "Powered by pivoine" link in the footer.
|
||||
# Note that this setting has no effect when using the free version.
|
||||
advertise: false
|
||||
|
||||
# Buyers of the PRO version can opt to hide all dates from the theme.
|
||||
# Frequent consumers of online content will know that nothing devalues a post like
|
||||
# seeing an old date.
|
||||
hide_dates: false
|
||||
|
||||
# Similarly, showing last modified date can devalue a post if it is too far in the past.
|
||||
hide_last_modified: true
|
||||
|
||||
# Note that dark mode only works in the PRO version of pivoine.
|
||||
dark_mode:
|
||||
# Set to `true` to always use the dark theme.
|
||||
always: false
|
||||
|
||||
# Set to `true` to use the dark theme based on visitors' preference (OS setting).
|
||||
dynamic: true
|
||||
|
||||
# Set to `true` to allow visitors to switch between light and dark mode.
|
||||
icon: true
|
||||
|
||||
search:
|
||||
icon: true
|
||||
|
||||
sound: true
|
||||
|
||||
umami:
|
||||
script: https://umami.pivoine.art/script.js
|
||||
id: 26158eb8-e0ae-4985-aa91-2f4a652f8ccb
|
||||
|
||||
# ⚡️ DANGER ZONE ⚡️
|
||||
# ----------------
|
||||
# This is an _experimental_ feature.
|
||||
# Only use if you know what Service Workers are and how they can impact your site!
|
||||
offline:
|
||||
enabled: false
|
||||
cache_version: 13
|
||||
precache_assets:
|
||||
- /assets/img/swipe.svg
|
||||
|
||||
optional_front_matter:
|
||||
remove_originals: true
|
||||
|
||||
readme_index:
|
||||
remove_originals: true
|
||||
with_frontmatter: true
|
||||
|
||||
relative_links:
|
||||
collections: true
|
||||
|
||||
titles_from_headings:
|
||||
strip_title: true
|
||||
collections: true
|
||||
|
||||
compress_html:
|
||||
clippings: []
|
||||
comments: []
|
||||
endings: []
|
||||
ignore:
|
||||
envs: []
|
||||
blanklines: false
|
||||
profile: false
|
||||
startings: []
|
||||
|
||||
sass:
|
||||
style: compressed
|
||||
31
_data/authors.yml
Normal file
31
_data/authors.yml
Normal file
@@ -0,0 +1,31 @@
|
||||
# The primary author of this blog.
|
||||
# Must be the same as `author` in `_config.yml`.
|
||||
# Change `qwtel` to your shortname.
|
||||
sk:
|
||||
name: Valknar
|
||||
email: valknark@pivoine.art
|
||||
|
||||
# Used at the bottom of each page and at the top of the `about` layout
|
||||
# Markdown enabled, can use multiple paragraphs (enabled by `|`)
|
||||
about: |
|
||||
I am Valknar.
|
||||
|
||||
Life doesn't make sense without you...
|
||||
|
||||
# This photo will be used in the about section
|
||||
picture:
|
||||
path: /assets/img/me.webp
|
||||
# srcset is optional, but can be used to provide higher res versions for retina displays
|
||||
# srcset:
|
||||
# 1x: /assets/img/me.webp
|
||||
# 2x: /assets/img/me@2x.webp
|
||||
|
||||
# Social media icons in sidebar
|
||||
# Comment/uncommet to show/hide
|
||||
# Rearrange to change the order in which they appear
|
||||
social:
|
||||
# github: valknarogg
|
||||
email: valknar@pivoine.art
|
||||
twitter: bordeaux1981
|
||||
sexy: https://sexy.pivoine.art
|
||||
code: valknar
|
||||
1500
_data/countries.yml
Normal file
1500
_data/countries.yml
Normal file
File diff suppressed because it is too large
Load Diff
270
_data/social.yml
Normal file
270
_data/social.yml
Normal file
@@ -0,0 +1,270 @@
|
||||
# This file contains meta data for various social media sites
|
||||
email:
|
||||
name: Email
|
||||
icon: icon-mail
|
||||
prepend: 'mailto:'
|
||||
amazon:
|
||||
name: Amazon
|
||||
icon: icon-amazon
|
||||
google:
|
||||
name: Google
|
||||
icon: icon-google
|
||||
google-plus:
|
||||
name: Google+
|
||||
icon: icon-google-plus
|
||||
prepend: 'https://plus.google.com/'
|
||||
google-drive:
|
||||
name: Google Drive
|
||||
icon: icon-google-drive
|
||||
facebook:
|
||||
name: Facebook
|
||||
icon: icon-facebook
|
||||
prepend: 'https://facebook.com/'
|
||||
instagram:
|
||||
name: Instagram
|
||||
icon: icon-instagram
|
||||
prepend: 'https://instagram.com/'
|
||||
whatsapp:
|
||||
name: WhatsApp
|
||||
icon: icon-whatsapp
|
||||
spotify:
|
||||
name: Spotify
|
||||
icon: icon-spotify
|
||||
prepend: 'https://play.spotify.com/'
|
||||
telegram:
|
||||
name: Telegram
|
||||
icon: icon-telegram
|
||||
twitter:
|
||||
name: X / Twitter
|
||||
icon: icon-twitter
|
||||
prepend: 'https://x.com/'
|
||||
twitter-old:
|
||||
name: Twitter
|
||||
icon: icon-twitter-old
|
||||
prepend: 'https://twitter.com/'
|
||||
x:
|
||||
name: X
|
||||
icon: icon-twitter
|
||||
prepend: 'https://x.com/'
|
||||
vine:
|
||||
name: Vine
|
||||
icon: icon-vine
|
||||
prepend: 'https://vine.com/'
|
||||
vk:
|
||||
name: VK
|
||||
icon: icon-vk
|
||||
prepend: 'https://vk.com/'
|
||||
renren:
|
||||
name: 人人网
|
||||
icon: icon-renren
|
||||
sina-weibo:
|
||||
name: Sina Weibo
|
||||
icon: icon-sina-weibo
|
||||
rss:
|
||||
name: RSS
|
||||
icon: icon-rss2
|
||||
youtube:
|
||||
name: YouTube
|
||||
icon: icon-youtube
|
||||
prepend: 'https://www.youtube.com/channel/'
|
||||
twitch:
|
||||
name: Twitch
|
||||
icon: icon-twitch
|
||||
prepend: 'https://www.twitch.tv/'
|
||||
vimeo:
|
||||
name: Vimeo
|
||||
icon: icon-vimeo
|
||||
prepend: 'https://www.vimeo.com/'
|
||||
lanyrd:
|
||||
name: Lanyrd
|
||||
icon: icon-lanyrd
|
||||
flickr:
|
||||
name: Flickr
|
||||
icon: icon-flickr2
|
||||
prepend: 'https://flickr.com/people/'
|
||||
dribbble:
|
||||
name: Dribbble
|
||||
icon: icon-dribbble
|
||||
prepend: 'https://dribbble.com/'
|
||||
behance:
|
||||
name: Behance
|
||||
icon: icon-behance
|
||||
prepend: 'https://www.behance.net/'
|
||||
deviantart:
|
||||
name: DeviantArt
|
||||
icon: icon-deviantart
|
||||
prepend: 'https://'
|
||||
append: '.deviantart.com'
|
||||
500px:
|
||||
name: 500px
|
||||
icon: icon-500px
|
||||
prepend: 'https://500px.com/'
|
||||
steam:
|
||||
name: Steam
|
||||
icon: icon-steam
|
||||
prepend: 'http://steamcommunity.com/profiles/'
|
||||
dropbox:
|
||||
name: Dropbox
|
||||
icon: icon-dropbox
|
||||
ondrive:
|
||||
name: OneDrive
|
||||
icon: icon-ondrive
|
||||
github:
|
||||
name: GitHub
|
||||
icon: icon-github
|
||||
prepend: 'https://github.com/'
|
||||
npm:
|
||||
name: npm
|
||||
icon: icon-npm
|
||||
prepend: 'https://npmjs.com/~'
|
||||
basecamp:
|
||||
name: Basecamp
|
||||
icon: icon-basecamp
|
||||
trello:
|
||||
name: Trello
|
||||
icon: icon-trello
|
||||
prepend: 'https://trello.com/'
|
||||
wordpress:
|
||||
name: WordPress
|
||||
icon: icon-wordpress
|
||||
joomla:
|
||||
name: Joomla!
|
||||
icon: icon-joomla
|
||||
ello:
|
||||
name: Ello
|
||||
icon: icon-ello
|
||||
prepend: 'https://ello.co/'
|
||||
blogger:
|
||||
name: Blogger
|
||||
icon: icon-blogger
|
||||
prepend: 'https://www.blogger.com/profile/'
|
||||
tumblr:
|
||||
name: Tumblr
|
||||
icon: icon-tumblr
|
||||
prepend: 'https://'
|
||||
append: '.tumblr.com'
|
||||
yahoo:
|
||||
name: Yahoo
|
||||
icon: icon-yahoo
|
||||
soundcloud:
|
||||
name: SoundCloud
|
||||
icon: icon-soundcloud
|
||||
prepend: 'https://soundcloud.com/'
|
||||
skype:
|
||||
name: Skype
|
||||
icon: icon-skype
|
||||
reddit:
|
||||
name: reddit
|
||||
icon: icon-reddit
|
||||
prepend: 'https://www.reddit.com/user/'
|
||||
hackernews:
|
||||
name: Hacker News
|
||||
icon: icon-hackernews
|
||||
prepend: 'https://news.ycombinator.com/user?id='
|
||||
wikipedia:
|
||||
name: Wikipedia
|
||||
icon: icon-wikipedia
|
||||
linkedin:
|
||||
name: LinkedIn
|
||||
icon: icon-linkedin2
|
||||
prepend: 'https://www.linkedin.com/in/'
|
||||
lastfm:
|
||||
name: Last.fm
|
||||
icon: icon-lastfm
|
||||
prepend: 'http://www.last.fm/user/'
|
||||
delicious:
|
||||
name: Delicious
|
||||
icon: icon-delicious
|
||||
prepend: 'http://del.icio.us/'
|
||||
stumbleupon:
|
||||
name: StumbleUpon
|
||||
icon: icon-stumbleupon
|
||||
prepend: 'http://www.stumbleupon.com/stumbler/'
|
||||
stackoverflow:
|
||||
name: Stack Overflow
|
||||
icon: icon-stackoverflow
|
||||
prepend: 'http://stackoverflow.com/users/'
|
||||
pinterest:
|
||||
name: Pinterest
|
||||
icon: icon-pinterest2
|
||||
prepend: 'https://www.pinterest.com/'
|
||||
xing:
|
||||
name: XING
|
||||
icon: icon-xing2
|
||||
prepend: 'https://www.xing.com/profile/'
|
||||
flattr:
|
||||
name: Flattr
|
||||
icon: icon-flattr
|
||||
foursquare:
|
||||
name: Foursquare
|
||||
icon: icon-foursquare
|
||||
prepend: 'https://foursquare.com/user/'
|
||||
yelp:
|
||||
name: Yelp
|
||||
icon: icon-yelp
|
||||
prepend: 'https://www.yelp.com/user_details?userid='
|
||||
paypal:
|
||||
name: PayPal
|
||||
icon: icon-paypal
|
||||
prepend: 'https://www.paypal.me/'
|
||||
keybase:
|
||||
name: Keybase
|
||||
icon: icon-key
|
||||
prepend: 'https://keybase.io/'
|
||||
download:
|
||||
name: Download
|
||||
icon: icon-box-add
|
||||
signal:
|
||||
name: Signal
|
||||
icon: icon-signal
|
||||
prepend: https://signal.group/
|
||||
threads:
|
||||
name: Threads
|
||||
icon: icon-threads
|
||||
prepend: https://threads.net/
|
||||
playstation:
|
||||
name: PlayStation
|
||||
icon: icon-playstation
|
||||
messenger:
|
||||
name: Messenger
|
||||
icon: icon-messenger
|
||||
prepend: https://messenger.com/
|
||||
stripe:
|
||||
name: Stripe
|
||||
icon: icon-stripe
|
||||
slack:
|
||||
name: Slack
|
||||
icon: icon-slack
|
||||
gitlab:
|
||||
name: GitLab
|
||||
icon: icon-gitlab
|
||||
prepend: 'https://gitlab.com/'
|
||||
line:
|
||||
name: Line
|
||||
icon: icon-line
|
||||
prepend: https://line.me/
|
||||
medium:
|
||||
name: Medium
|
||||
icon: icon-medium
|
||||
prepend: 'https://medium.com/'
|
||||
xbox:
|
||||
name: Xbox
|
||||
icon: icon-xbox
|
||||
prepend: https://xbox.com/play/user/
|
||||
wechat:
|
||||
name: WeChat
|
||||
icon: icon-wechat
|
||||
discord:
|
||||
name: Discord
|
||||
icon: icon-discord
|
||||
prepend: https://discord.com/invite/
|
||||
mastodon:
|
||||
name: Mastodon
|
||||
icon: icon-mastodon
|
||||
sexy:
|
||||
name: sexy.pivoine.art
|
||||
icon: gi gi-duality-mask
|
||||
code:
|
||||
name: code.pivoine.art
|
||||
icon: gi gi-book-cover
|
||||
prepend: https://code.pivoine.art/
|
||||
64
_data/sounds.yml
Normal file
64
_data/sounds.yml
Normal file
@@ -0,0 +1,64 @@
|
||||
-
|
||||
slug: hyperloop
|
||||
artist: Valknar
|
||||
title: Hyperloop
|
||||
default: true
|
||||
tracks:
|
||||
- Hyperloop I.mp3
|
||||
- Hyperloop II.mp3
|
||||
- Hyperloop III.mp3
|
||||
- Hyperloop IV.mp3
|
||||
- Hyperloop IX.mp3
|
||||
- Hyperloop V.mp3
|
||||
- Hyperloop VI.mp3
|
||||
- Hyperloop VII.mp3
|
||||
- Hyperloop VIII.mp3
|
||||
- Hyperloop X.mp3
|
||||
- Hyperloop XI.mp3
|
||||
- Hyperloop XII.mp3
|
||||
- Hyperloop.mp3
|
||||
-
|
||||
slug: massive
|
||||
artist: Valknar
|
||||
title: Massive
|
||||
tracks:
|
||||
- Massive.mp3
|
||||
- Sudden Death.mp3
|
||||
- Strength And Honour.mp3
|
||||
- Drums Of War.mp3
|
||||
- Drums Of War (Remix).mp3
|
||||
- Massive (Remix).mp3
|
||||
- The Moon (Remix).mp3
|
||||
-
|
||||
slug: pass-the-glock
|
||||
artist: Terror Squad
|
||||
title: Pass The Glock
|
||||
tracks:
|
||||
- Pass The Glock - Bulletproof Remix.mp3
|
||||
-
|
||||
slug: detroit
|
||||
artist: Valknar
|
||||
title: Detroit
|
||||
tracks:
|
||||
- Detroit I.mp3
|
||||
- Detroit II.mp3
|
||||
- Detroit III.mp3
|
||||
- Detroit IV.mp3
|
||||
- Detroit V.mp3
|
||||
- Detroit VI.mp3
|
||||
- Detroit VII.mp3
|
||||
- Detroit VIII.mp3
|
||||
-
|
||||
slug: stuttgart
|
||||
artist: Valknar
|
||||
title: Stuttgart
|
||||
tracks:
|
||||
- Stuttgart.mp3
|
||||
- Stuttgart (Remix).mp3
|
||||
|
||||
-
|
||||
slug: changed-her-mind-again
|
||||
artist: Cold Stares
|
||||
title: Changed Her Mind Again... - Breaks Remix
|
||||
tracks:
|
||||
- Changed Her Mind Again... - Breaks Remix.mp3
|
||||
154
_data/strings.yml
Normal file
154
_data/strings.yml
Normal file
@@ -0,0 +1,154 @@
|
||||
# Strings
|
||||
# =======================================================================================
|
||||
# This file contains all soft-coded strings of the theme.
|
||||
# You can edit them here if you want to change the wording of certain phrases,
|
||||
# e.g. maybe you prefer "Fetching…" to "Loading…", etc.
|
||||
#
|
||||
# You can also use this to "translate" the theme into different langauges,
|
||||
# but note that this may not work in all situations,
|
||||
# especially for languages that differ significantly from English.
|
||||
|
||||
# Free version
|
||||
# ---------------------------------------------------------------------------------------
|
||||
home: Home
|
||||
pages: Pages
|
||||
posts: Posts
|
||||
about: About
|
||||
related_posts: Related Posts
|
||||
other_projects: Other Projects
|
||||
comments: Comments
|
||||
jump_to: Jump to
|
||||
navigation: Navigation
|
||||
social: Social
|
||||
links: Links
|
||||
links_icon: 'icon-link'
|
||||
pagination: Pagination
|
||||
newer: Newer
|
||||
older: Older
|
||||
forward: Forward
|
||||
back: Back
|
||||
permalink: Permalink
|
||||
permalink_icon: 'content-hash'
|
||||
templates: Templates (for web app)
|
||||
present: present
|
||||
toc: Table of Contents
|
||||
note: Note
|
||||
description: Description
|
||||
search: Search
|
||||
search_placeholder: Type something…
|
||||
|
||||
loading: Loading…
|
||||
redirecting: Redirecting…
|
||||
download: Download
|
||||
|
||||
continue_reading: >
|
||||
Continue reading <!--post_title-->
|
||||
|
||||
cookies_banner:
|
||||
text: >
|
||||
This site uses cookies. [Cookies Policy](/cookies-policy/).
|
||||
okay: Okay
|
||||
|
||||
not_found:
|
||||
title: '404: Page Not Found'
|
||||
message: |
|
||||
Sorry, we've misplaced that URL or it's pointing to something that doesn't exist.
|
||||
<!--home_link--> to try finding it again.
|
||||
home_link: Head back home
|
||||
|
||||
offline:
|
||||
title: 'You are Offline'
|
||||
message: |
|
||||
Sorry, but you appear to be offline and this page has not been prepared for offline viewing.
|
||||
<!--home_link--> to try finding another page.
|
||||
home_link: Head back home
|
||||
|
||||
error:
|
||||
title: Error
|
||||
message: |
|
||||
Sorry, an error occurred while loading <!--link-->.
|
||||
|
||||
# Separators
|
||||
# ---------------------------------------------------------------------------------------
|
||||
posted: 'Posted'
|
||||
last_modified_at: 'Last modified at'
|
||||
last_modified_at_icon: 'icon-history'
|
||||
colon: ':'
|
||||
separator: '|'
|
||||
category_start: 'in '
|
||||
category_spearator: ' / '
|
||||
tag_start: 'on '
|
||||
tag_separator: ', '
|
||||
from_to_separator: '–'
|
||||
breadcrumbs_home: 'home'
|
||||
breadcrumbs_separator: '/'
|
||||
|
||||
# Date formats
|
||||
# ---------------------------------------------------------------------------------------
|
||||
date_formats:
|
||||
post: '%d %b %Y'
|
||||
last_modified_at: '%Y-%m-%d'
|
||||
related_post: '%d %b %Y'
|
||||
list_group_by: '%Y'
|
||||
list_entry: '%d %b'
|
||||
project: '%Y'
|
||||
projects_group_by: '%Y'
|
||||
resume: '%b %Y'
|
||||
|
||||
|
||||
# PRO Version
|
||||
# ---------------------------------------------------------------------------------------
|
||||
# If you don't own the PRO version of pivoine, modifying these will have no effect.
|
||||
|
||||
welcome:
|
||||
more_projects: See <!--projects_title--> for more
|
||||
more_posts: See <!--posts_title--> for more
|
||||
|
||||
resume:
|
||||
location: Location
|
||||
location_separator: ', '
|
||||
email: Email
|
||||
phone: Phone
|
||||
website: Website
|
||||
work: Experience
|
||||
work_title: <!--position--> at <!--company-->
|
||||
highlights: Highlights
|
||||
volunteer: Volunteer
|
||||
volunteer_title: <!--position--> at <!--organization-->
|
||||
education: Education
|
||||
education_title: <!--study_type--> in <!--area--> from <!--institution--> with GPA of <!--gpa-->
|
||||
courses: Courses
|
||||
awards: Awards
|
||||
awards_title: <!--title--> from <!--awarder-->
|
||||
publications: Publications
|
||||
publications_title: <!--name--> by <!--publisher-->
|
||||
references: References
|
||||
languages: Languages
|
||||
fluency: Fluency
|
||||
skills: Skills
|
||||
level: Level
|
||||
interests: Interests
|
||||
summary: Summary
|
||||
keywords: Keywords
|
||||
print: Print
|
||||
download_pdf: PDF
|
||||
download_vcf: vCard
|
||||
download_json: JSON
|
||||
icons:
|
||||
fallback: icon-link
|
||||
location: icon-location
|
||||
born: icon-quill
|
||||
citizenship: icon-flag
|
||||
maritalStatus: icon-man-woman
|
||||
email: icon-envelop
|
||||
phone: icon-phone
|
||||
website: icon-home3
|
||||
work: icon-briefcase
|
||||
volunteer: icon-earth
|
||||
education: icon-library
|
||||
awards: icon-trophy
|
||||
publications: icon-book
|
||||
references: icon-quotes-right
|
||||
languages: icon-bubbles
|
||||
skills: icon-wrench
|
||||
interests: icon-heart
|
||||
26
_data/variables.yml
Normal file
26
_data/variables.yml
Normal file
@@ -0,0 +1,26 @@
|
||||
---
|
||||
root_font_size: 15 #px
|
||||
root_font_size_medium: 16 #px
|
||||
root_font_size_large: 17 #px
|
||||
|
||||
root_font_size_print: 8 #pt
|
||||
|
||||
root_line_height: 1.75
|
||||
|
||||
font_weight: 400 #=normal
|
||||
font_weight_bold: 700 #=bold
|
||||
font_weight_heading: 900 #=black (heavy)
|
||||
|
||||
content_width: 42 #rem
|
||||
content_width_2: 48 #rem
|
||||
content_width_5: 54 #rem
|
||||
|
||||
sidebar_width: 21 #rem
|
||||
border_radius: 0.5 #rem
|
||||
|
||||
break_point_1: 42 #em
|
||||
break_point_2: 54 #em
|
||||
break_point_3: 64 #em
|
||||
break_point_4: 72 #em
|
||||
break_point_5: 86 #em
|
||||
break_point_font_large: 124 #em
|
||||
9
_includes/base-classes
Normal file
9
_includes/base-classes
Normal file
@@ -0,0 +1,9 @@
|
||||
{%- capture classes -%}
|
||||
{% if site.pivoine.dark_mode.always %}dark-mode{% endif %}
|
||||
{% if site.pivoine.no_toc %} no-toc{% endif %}
|
||||
{% if site.pivoine.no_break_layout %} no-break-layout{% endif %}
|
||||
{% if site.pivoine.no_large_headings %} no-large-headings{% endif %}
|
||||
{% if site.pivoine.no_third_column %} no-third-column{% endif %}
|
||||
{% if site.pivoine.no_drawer %} no-drawer{% endif %}
|
||||
{%- endcapture -%}
|
||||
{{- classes | strip_newlines | strip -}}
|
||||
43
_includes/body/analytics.html
Normal file
43
_includes/body/analytics.html
Normal file
@@ -0,0 +1,43 @@
|
||||
{% if site.google_analytics %}
|
||||
<script>!function(w, d) {
|
||||
w.ga=w.ga||function(){(ga.q=ga.q||[]).push(arguments)};ga.l=+new Date;
|
||||
|
||||
/*{% if site.pivoine.cookies_banner %}*/
|
||||
if (navigator.CookiesOK) {
|
||||
ga('create', '{{ site.google_analytics }}', 'auto');
|
||||
} else if (d.cookie.indexOf("hy--cookies-ok=true") > -1) {
|
||||
ga('create', '{{ site.google_analytics }}', {
|
||||
'storage': 'none',
|
||||
'clientId': localStorage ? localStorage.getItem('ga--client-id') : undefined
|
||||
});
|
||||
} else {
|
||||
ga('create', '{{ site.google_analytics }}', {
|
||||
'storage': 'none'
|
||||
});
|
||||
ga('set', 'forceSSL', true);
|
||||
ga('set', 'anonymizeIp', true);
|
||||
}
|
||||
/*{% else %}*/
|
||||
ga('create', '{{ site.google_analytics }}', 'auto');
|
||||
/*{% endif %}*/
|
||||
|
||||
var pushStateEl = d.getElementById('_pushState');
|
||||
var timeoutId;
|
||||
pushStateEl.addEventListener('hy-push-state-load', function() {
|
||||
w.clearTimeout(timeoutId);
|
||||
timeoutId = w.setTimeout(function() {
|
||||
ga('set', 'page', w.location.pathname);
|
||||
ga('send', 'pageview');
|
||||
}, 500);
|
||||
});
|
||||
|
||||
d.addEventListener('hy--cookies-ok', function () {
|
||||
w.ga(function(tracker) {
|
||||
w.ga("set", "anonymizeIp", undefined);
|
||||
localStorage && localStorage.setItem("ga--client-id", tracker.get("clientId"));
|
||||
});
|
||||
});
|
||||
|
||||
w.loadJSDeferred('https://www.google-analytics.com/analytics.js');
|
||||
}(window, document);</script>
|
||||
{% endif %}
|
||||
22
_includes/body/breadcrumbs.html
Normal file
22
_includes/body/breadcrumbs.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<nav id="breadcrumbs" class="screen-only"><ul>
|
||||
{% assign crumbs = include.url | remove:'index.html' | split: '/' %}
|
||||
{% if crumbs.size > 1 %}
|
||||
<li><a href="{{ '/' | relative_url }}">{{ site.data.strings.breadcrumbs_home | default:'home' }}</a></li>
|
||||
{% for crumb in crumbs offset: 1 %}
|
||||
{%- if crumb != "page" -%}
|
||||
<li>
|
||||
{% unless forloop.last %}
|
||||
<span>{{ site.data.strings.breadcrumbs_separator | default:'/' }}</span>
|
||||
{% assign crumb_limit = forloop.index | plus: 1 %}
|
||||
{% capture href %}{% for crumb1 in crumbs limit: crumb_limit %}{{ crumb1 | append: '/' }}{% endfor %}{% endcapture %}
|
||||
<a href="{{ href | relative_url }}">{{ crumb | url_decode }}</a>
|
||||
{% else %}
|
||||
<span>{{ site.data.strings.breadcrumbs_separator | default:'/' }}</span>
|
||||
<span>{{ crumbs | reverse | first | url_decode }}</span>
|
||||
{% endunless %}
|
||||
</li>
|
||||
{%- endif -%}
|
||||
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul></nav>
|
||||
6
_includes/body/comments.html
Normal file
6
_includes/body/comments.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% if page.comments %}
|
||||
<aside class="comments related" role="complementary">
|
||||
<h2 class="hr-bottom">{{ site.data.strings.comments | default:"Comments" }}</h2>
|
||||
{% include my-comments.html %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
18
_includes/body/footer.html
Normal file
18
_includes/body/footer.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% if site.copyright.size > 0 or site.legal.size > 0 or site.pivoine.advertise %}
|
||||
<footer class="content" role="contentinfo">
|
||||
<hr/>
|
||||
{% if site.copyright.size > 0 %}
|
||||
<p><small class="copyright">{{ site.copyright | markdownify | replace:'<p>','' | replace:'</p>','' }}</small></p>
|
||||
{% endif %}
|
||||
{% if site.legal.size > 0 %}
|
||||
<nav class="legal"><small>
|
||||
{% for node in site.legal %}
|
||||
{% assign url = node.url | default: node.href %}
|
||||
<a class="heading flip-title" href="{% include_cached smart-url url=url %}">{{ node.name | default:node.title }}</a>
|
||||
{% unless forloop.last %}{{ site.data.strings.separator | default:'|' }}{% endunless %}
|
||||
{% endfor %}
|
||||
</small></nav>
|
||||
{% endif %}
|
||||
<hr class="sr-only"/>
|
||||
</footer>
|
||||
{% endif %}
|
||||
23
_includes/body/index.html
Normal file
23
_includes/body/index.html
Normal file
@@ -0,0 +1,23 @@
|
||||
{% include_cached components/dark-mode.html %}
|
||||
|
||||
<hy-push-state
|
||||
id="_pushState"
|
||||
replace-selector="#_main"
|
||||
link-selector="a[href]:not([href^='{{ assets_url }}']):not(.external):not(.no-push-state)"
|
||||
script-selector="script"
|
||||
duration="500"
|
||||
hashchange
|
||||
>
|
||||
{% capture sidebar %}{% include_cached body/sidebar.html cover=page.cover invert=page.invert_sidebar theme_color=page.theme_color image=image color=color video=video %}{% endcapture %}
|
||||
{% if page.cover %}{{ sidebar }}{% endif %}
|
||||
{% include_cached body/menu.html %}
|
||||
{% include body/main.html %}
|
||||
{% unless page.cover %}{{ sidebar }}{% endunless %}
|
||||
</hy-push-state>
|
||||
|
||||
{% unless page.redirect %}
|
||||
{% include_cached body/scripts.html %}
|
||||
{% include my-body.html %}
|
||||
{% endunless %}
|
||||
|
||||
{% include_cached templates/index.html %}
|
||||
9
_includes/body/main.html
Normal file
9
_includes/body/main.html
Normal file
@@ -0,0 +1,9 @@
|
||||
<main
|
||||
id="_main"
|
||||
class="content layout-{{ page.layout }}"
|
||||
role="main"
|
||||
>
|
||||
{% unless site.pivoine.no_breadcrumbs %}{% include body/breadcrumbs.html url=page.url %}{% endunless %}
|
||||
{{ content }}
|
||||
{% include_cached body/footer.html %}
|
||||
</main>
|
||||
18
_includes/body/menu.html
Normal file
18
_includes/body/menu.html
Normal file
@@ -0,0 +1,18 @@
|
||||
<div id="_navbar" class="navbar fixed-top">
|
||||
<div class="content">
|
||||
<span class="sr-only">{{ site.data.strings.jump_to | default:"Jump to" }}{{ site.data.strings.colon | default:":" }}</span>
|
||||
<div class="nav-btn-bar">
|
||||
<a id="_menu" class="nav-btn no-hover" href="#_drawer--opened">
|
||||
<span class="sr-only">{{ site.data.strings.navigation | default:"Navigation" }}</span>
|
||||
<span class="icon-menu"></span>
|
||||
</a>
|
||||
<div class="nav-insert-marker"></div>
|
||||
{% if site.pivoine.search.icon %}
|
||||
<hm-search class="nav-span" hidden placeholder="{{ site.data.strings.search | default:"Search" }}"></hm-search>
|
||||
{% elsif site.pivoine.search.always %}
|
||||
<hm-search class="nav-span" placeholder="{{ site.data.strings.search | default:"Search" }}"></hm-search>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<hr class="sr-only" hidden />
|
||||
41
_includes/body/nav.html
Normal file
41
_includes/body/nav.html
Normal file
@@ -0,0 +1,41 @@
|
||||
<span class="sr-only">{{ site.data.strings.navigation | default:"Navigation" }}{{ site.data.strings.colon | default:":" }}</span>
|
||||
<ul>
|
||||
{% if site.menu %}
|
||||
{% for node in site.menu %}
|
||||
{% assign url = node.url | default: node.href %}
|
||||
<li>
|
||||
<a
|
||||
{% if forloop.first %}id="_drawer--opened"{% endif %}
|
||||
href="{% include_cached smart-url url=url %}"
|
||||
class="sidebar-nav-item {% if node.external %}external{% endif %}"
|
||||
{% if node.rel %}rel="{{ node.rel }}"{% endif %}
|
||||
>
|
||||
{{ node.name | default:node.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% assign pages = site.html_pages | where: "menu", true %}
|
||||
{% assign documents = site.documents | where: "menu", true %}
|
||||
{% assign nodes = pages | concat: documents | sort: "order" %}
|
||||
|
||||
{% for node in nodes %}
|
||||
{% unless node.redirect_to %}
|
||||
<li>
|
||||
<a
|
||||
{% if forloop.first %}id="_navigation"{% endif %}
|
||||
href="{{ node.url | relative_url }}"
|
||||
class="sidebar-nav-item"
|
||||
{% if node.rel %}rel="{{ node.rel }}"{% endif %}
|
||||
>
|
||||
{{ node.title }}
|
||||
</a>
|
||||
</li>
|
||||
{% else %}
|
||||
<li>
|
||||
<a href="{{ node.redirect_to }}" class="sidebar-nav-item external">{{ node.title }}</a>
|
||||
</li>
|
||||
{% endunless %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
7
_includes/body/scripts.html
Normal file
7
_includes/body/scripts.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<!--[if gt IE 10]><!---->
|
||||
<script nomodule>{% include scripts/nomodule.min.js %}</script>
|
||||
<script src="{{ '/assets/js/pivoine-1.0.0.js' | relative_url }}" type="module"></script>
|
||||
<script src="{{ '/assets/js/LEGACY-pivoine-1.0.0.js' | relative_url }}" nomodule defer></script>
|
||||
{% include my-scripts.html %}
|
||||
{% include body/analytics.html %}
|
||||
<!--<![endif]-->
|
||||
21
_includes/body/sidebar-bg.html
Normal file
21
_includes/body/sidebar-bg.html
Normal file
@@ -0,0 +1,21 @@
|
||||
{% assign image = include.image %}
|
||||
{% assign video = include.video %}
|
||||
{% assign color = include.color %}
|
||||
{% assign theme_color = include.theme_color %}
|
||||
|
||||
{% if image.background %}
|
||||
{% capture bg_style %}background:{{ image.style }}{% endcapture %}
|
||||
{% capture bg_class %}sidebar-bg {% if image.overlay %}sidebar-overlay{% endif %}{% endcapture %}
|
||||
{% else %}
|
||||
{% assign bg_color = theme_color | default:site.theme_color | default:color %}
|
||||
{% capture bg_style %}background-color:{{ bg_color }};{% if image != 'none' %}background-image:url({% include_cached smart-url url=image %}){% endif %};overflow: hidden;{% endcapture %}
|
||||
{% capture bg_class %}sidebar-bg {% if image != 'none' %}sidebar-overlay{% endif %}{% endcapture %}
|
||||
{% endif %}
|
||||
<div class="{{ bg_class }}" style="{{ bg_style }}">
|
||||
{% if video %}
|
||||
<video id="_video" style="min-width: 100%; height: 100%; object-fit: cover" autoplay muted loop>
|
||||
<source src="{% include_cached smart-url url=video %}" type="video/mp4">
|
||||
</video>
|
||||
{% endif %}
|
||||
|
||||
</div>
|
||||
33
_includes/body/sidebar-sticky.html
Normal file
33
_includes/body/sidebar-sticky.html
Normal file
@@ -0,0 +1,33 @@
|
||||
<div class="sidebar-sticky">
|
||||
<div class="sidebar-about">
|
||||
{% if site.logo %}
|
||||
<a class="no-hover" href="{{ '/' | relative_url }}" tabindex="-1">
|
||||
<img src="{% include_cached smart-url url=site.logo %}" class="logo" alt="{{ site.short_title | default:site.title }}" width="120" height="120" loading="lazy" />
|
||||
</a>
|
||||
{% endif %}
|
||||
<a class="sidebar-title" href="{{ '/' | relative_url }}"><h2 class="h1">{{ site.short_title | default:site.title }}</h2></a>
|
||||
{% assign text = site.tagline | default:site.description %}
|
||||
{% if text %}
|
||||
<p class="{% if text.size > 100 %}fine{% endif %}">
|
||||
{{ text | markdownify | replace:'<p>','' | replace:'</p>','' }}
|
||||
</p>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<nav class="sidebar-nav heading" role="navigation">
|
||||
{% include body/nav.html %}
|
||||
</nav>
|
||||
|
||||
{% assign author = site.data.authors.first[1] | default:site.author %}
|
||||
<div class="sidebar-social">
|
||||
{% include components/social.html author=author %}
|
||||
</div>
|
||||
{% if site.pivoine.sound %}
|
||||
{% assign sounds = site.data.sounds | where: 'default', true %}
|
||||
<div class="sound-player" data-featured="{{ sounds.first | jsonify | escape }}">
|
||||
<canvas class="hidden"></canvas>
|
||||
<a>[ SOUND ON ]</a>
|
||||
<audio hidden controls="false"></audio>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
14
_includes/body/sidebar.html
Normal file
14
_includes/body/sidebar.html
Normal file
@@ -0,0 +1,14 @@
|
||||
<hy-drawer
|
||||
id="_drawer"
|
||||
class="{% if include.cover %}cover{% endif %}"
|
||||
side="left"
|
||||
threshold="10"
|
||||
noscroll
|
||||
{% if include.cover %}opened{% endif %}
|
||||
>
|
||||
<header id="_sidebar" class="sidebar{% if include.invert %} invert{% endif %}" role="banner">
|
||||
{% include_cached body/sidebar-bg.html image=include.image color=include.color theme_color=include.theme_color video=include.video %}
|
||||
{% include_cached body/sidebar-sticky.html %}
|
||||
</header>
|
||||
</hy-drawer>
|
||||
<hr class="sr-only" hidden />
|
||||
8
_includes/components/about.html
Normal file
8
_includes/components/about.html
Normal file
@@ -0,0 +1,8 @@
|
||||
{% assign author = site.data.authors[include.author] | default:site.data.authors.first[1] | default:site.author %}
|
||||
|
||||
{% if author.about %}
|
||||
<aside class="about related mt4 mb4" role="complementary">
|
||||
{% assign about_heading = site.data.strings.about | default:"About" %}
|
||||
{% include components/author.html author=author heading=about_heading heading_tag='h2' %}
|
||||
</aside>
|
||||
{% endif %}
|
||||
24
_includes/components/author.html
Normal file
24
_includes/components/author.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% assign plugins = site.plugins | default:site.gems %}
|
||||
|
||||
<div class="author mt4">
|
||||
{% assign author = include.author %}
|
||||
|
||||
{% if author.picture %}
|
||||
{% include_cached components/hy-img.html class="avatar" img=author.picture alt=author.name width="120" height="120" %}
|
||||
{% elsif plugins contains 'jekyll-avatar' %}
|
||||
{% assign avatar = author.social.github | default:author.github.username | default:author.github %}
|
||||
{% include components/avatar-tag.html user=avatar %}
|
||||
{% endif %}
|
||||
|
||||
{% assign heading_tag = include.heading_tag | default:'h2' %}
|
||||
{% assign heading_id = include.heading_id %}
|
||||
<{{ heading_tag }} {% if heading_id %}id="{{ heading_id }}"{% endif %} class="page-title hr-bottom">
|
||||
{{ include.heading | default:author.name }}
|
||||
</{{ heading_tag }}>
|
||||
|
||||
{{ author.about | markdownify }}
|
||||
|
||||
<div class="sidebar-social">
|
||||
{% include components/social.html author=author %}
|
||||
</div>
|
||||
</div>
|
||||
4
_includes/components/avatar-tag.html
Normal file
4
_includes/components/avatar-tag.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% comment %}<!--
|
||||
Including `avatar` in a partial prevents a parse error when `jekyll-avatar` is not included.
|
||||
-->{% endcomment %}
|
||||
{% avatar user=include.user size=128 %}
|
||||
7
_includes/components/dark-mode.html
Normal file
7
_includes/components/dark-mode.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% if site.pivoine.dark_mode.dynamic %}
|
||||
<script>
|
||||
window._sunrise = {{ site.pivoine.dark_mode.sunrise | default:"6" }};
|
||||
window._sunset = {{ site.pivoine.dark_mode.sunset | default:"18" }};
|
||||
{% include scripts/dark-mode.min.js %}
|
||||
</script>
|
||||
{% endif %}
|
||||
6
_includes/components/dingbat.html
Normal file
6
_includes/components/dingbat.html
Normal file
@@ -0,0 +1,6 @@
|
||||
{% if site.clap_button %}
|
||||
<clap-button class="mb6" {% if include.hidden == true %}hidden{% endif %}></clap-button>
|
||||
{% if include.hidden == true %}<hr class="dingbat related mb6" />{% endif %}
|
||||
{% else %}
|
||||
<hr class="dingbat related mb6" />
|
||||
{% endif %}
|
||||
29
_includes/components/hy-img.html
Normal file
29
_includes/components/hy-img.html
Normal file
@@ -0,0 +1,29 @@
|
||||
{% assign sources = '' %}
|
||||
{% if include.img.src or include.img.path %}
|
||||
{% assign srcset = null %}
|
||||
|
||||
{% if include.img.srcset %}
|
||||
{% capture srcset %}{% for hash in include.img.srcset %}{% assign tmp = hash[1] %}{% include_cached smart-url url=tmp %} {{ hash[0] }}{% unless forloop.last %},{% endunless %}{% endfor %}{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
{% assign src = include.img.src | default:include.img.path %}
|
||||
{% capture sources %}
|
||||
src="{% include_cached smart-url url=src %}"
|
||||
{% if srcset %}srcset="{{ srcset | strip }}"{% endif %}
|
||||
{% if include.sizes %}sizes="{{ include.sizes | replace:' ', '' }}"{% endif %}
|
||||
{% endcapture %}
|
||||
{% else %}
|
||||
{% capture sources %}
|
||||
src="{% include_cached smart-url url=include.img %}"
|
||||
{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
<img
|
||||
{{ sources }}
|
||||
{% if include.alt %}alt="{{ include.alt }}"{% endif %}
|
||||
{% if include.class %}class="{{ include.class }}"{% endif %}
|
||||
{% if include.property %}property="{{ include.property }}"{% endif %}
|
||||
{% if include.width %}width="{{ include.width }}"{% endif %}
|
||||
{% if include.height %}height="{{ include.height }}"{% endif %}
|
||||
{% if include.width and include.height %}loading="lazy"{% endif %}
|
||||
/>
|
||||
3
_includes/components/iframe.html
Normal file
3
_includes/components/iframe.html
Normal file
@@ -0,0 +1,3 @@
|
||||
{% assign src = include.iframe.src | default:include.iframe.path %}
|
||||
|
||||
<iframe src="{% include_cached smart-url url=src %}" width="100%" frameborder="no" scrolling="no" allowfullscreen="true" style="width: 100%; aspect-ratio: calc(16/9);"></iframe>
|
||||
12
_includes/components/link.html
Normal file
12
_includes/components/link.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% if include.href.size > 0 %}
|
||||
<a
|
||||
class="{{ include.class }} {{ include.a_class }}"
|
||||
href="{{ include.href }}"
|
||||
{% if include.rel %}rel="{{ include.rel }}"{% endif %}
|
||||
{% if include.property %}property="{{ include.property }}"{% endif %}
|
||||
>
|
||||
{{- include.title -}}
|
||||
</a>
|
||||
{% else %}
|
||||
<span class="{{ include.class }} {{ include.span_class }}">{{ include.title }}</span>
|
||||
{% endif %}
|
||||
14
_includes/components/message.html
Normal file
14
_includes/components/message.html
Normal file
@@ -0,0 +1,14 @@
|
||||
{% assign alt = include.alt %}
|
||||
{% unless alt %}{% capture alt %}<div class="hr pb0"></div>{% endcapture %}{% endunless %}
|
||||
|
||||
{% if include.text.size > 0 %}
|
||||
{% unless include.hide %}
|
||||
<p class="{{ include.class | default:'note-sm' }}" {% if include.property %}property="{{ include.property }}"{% endif %}>
|
||||
{{ include.text | markdownify | replace:"<p>","" | replace:"</p>","" }}
|
||||
</p>
|
||||
{% else %}
|
||||
{{ alt }}
|
||||
{% endunless %}
|
||||
{% else %}
|
||||
{{ alt }}
|
||||
{% endif %}
|
||||
15
_includes/components/pagination.html
Normal file
15
_includes/components/pagination.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<h2 class="sr-only">{{ site.data.strings.pagination | default:"Pagination" }}</h2>
|
||||
<nav class="pagination heading clearfix" role="navigation">
|
||||
<ul>
|
||||
<li class="pagination-item older" >
|
||||
{% assign next_title = site.data.strings.older | default:"Older" %}
|
||||
{% assign next_href = paginator.next_page_path | relative_url %}
|
||||
{% include components/link.html rel="next" title=next_title href=next_href %}
|
||||
</li>
|
||||
<li class="pagination-item newer" >
|
||||
{% assign prev_title = site.data.strings.newer | default:"Newer" %}
|
||||
{% assign prev_href = paginator.previous_page_path | relative_url %}
|
||||
{% include components/link.html rel="prev" title=prev_title href=prev_href %}
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
7
_includes/components/post-list-item.html
Normal file
7
_includes/components/post-list-item.html
Normal file
@@ -0,0 +1,7 @@
|
||||
{% assign post = include.post %}
|
||||
{% assign format = include.format | default:site.data.strings.date_formats.related_post | default:"%d %b %Y" %}
|
||||
|
||||
<li class="h4">
|
||||
<a href="{{ post.url | relative_url }}" class="flip-title"><span>{{ post.title }}</span></a>
|
||||
<time class="faded fine" datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date:format }}</time>
|
||||
</li>
|
||||
102
_includes/components/post.html
Normal file
102
_includes/components/post.html
Normal file
@@ -0,0 +1,102 @@
|
||||
{% assign post = include.post %} {% assign no_link_title = include.no_link_title %} {% assign no_excerpt =
|
||||
include.no_excerpt %} {% assign hide_image = include.hide_image %} {% assign hide_description = include.hide_description
|
||||
%} {% assign show_gallery = include.show_gallery %} {% assign show_video = include.show_video %} {% assign show_iframe =
|
||||
include.show_iframe %} {% assign hide_dates = include.hide_dates | default:site.pivoine.hide_dates %} {% assign
|
||||
hide_posted_in = include.hide_posted_in %}
|
||||
|
||||
<article id="post{{ post.id | replace:'/','-' }}" class="page post mb6" role="article">
|
||||
<header>
|
||||
<h1 class="post-title flip-project-title">
|
||||
{% unless no_link_title %}<a href="{{ post.url | relative_url }}" class="flip-title"
|
||||
>{% endunless %} {{ post.title }} {% unless no_link_title %}</a
|
||||
>{% endunless %}
|
||||
</h1>
|
||||
|
||||
{%- unless hide_posted_in -%}
|
||||
<div class="post-date">
|
||||
{% capture foobar %} {%- unless hide_dates -%} {%- assign post_format = site.data.strings.date_formats.post |
|
||||
default:"%d %b %Y" -%}
|
||||
<time datetime="{{ post.date | date_to_xmlschema }}">{{ post.date | date:post_format }}</time>
|
||||
{%- else -%} {{- site.data.strings.posted | default:"Posted" -}} {%- endunless -%} {{ ' ' }} {%- assign
|
||||
category_start = site.data.strings.category_start | default:"in " -%} {%- assign category_separator =
|
||||
site.data.strings.category_separator | default:" / " -%} {%- include components/tag-list.html tags=post.categories
|
||||
meta=site.featured_categories start_with=category_start separator=category_separator -%} {{ ' ' }} {%- assign
|
||||
tag_start = site.data.strings.tag_start | default:"on " -%} {%- assign tag_separator =
|
||||
site.data.strings.tag_separator | default:", " -%} {%- include components/tag-list.html tags=post.tags
|
||||
meta=site.featured_tags start_with=tag_start separator=tag_separator -%} {% endcapture %}
|
||||
<span class="ellipsis mr1"> {{ foobar }} </span>
|
||||
{% unless hide_dates or site.pivoine.hide_last_modified or post.hide_last_modified %} {% if post.last_modified_at
|
||||
%} {% assign d1 = post.date | date:"%Y-%m-%d" %} {% assign d2 = post.last_modified_at | date:"%Y-%m-%d" %} {% if
|
||||
d1 != d2 %} {% assign label = site.data.strings.last_modified_at | default:"Last modified at" %} {% assign
|
||||
last_modified_at_format = site.data.strings.date_formats.last_modified_at | default:"%Y-%m-%d" %}
|
||||
<span
|
||||
class="ellipsis"
|
||||
data-tippy-content="{{ label }}{{ site.data.strings.colon }} {{ post.last_modified_at | date:post_format }}"
|
||||
>
|
||||
<span class="sr-only">{{ label }}{{ site.data.strings.colon }}</span>
|
||||
<span class="{{ site.data.strings.last_modified_icon | default:'icon-history' }}"></span>
|
||||
<time datetime="{{ post.last_modified_at | date_to_xmlschema }}"
|
||||
>{{ post.last_modified_at | date:last_modified_at_format }}</time
|
||||
>
|
||||
</span>
|
||||
{% endif %} {% endif %} {% endunless %}
|
||||
</div>
|
||||
{%- endunless -%} {% assign alt = false %} {% unless hide_image %}{% if post.image %} {% unless no_link_title %}<a
|
||||
href="{{ post.url | relative_url }}"
|
||||
class="no-hover no-print-link {% unless post.hide_image %}flip-project{% endunless %}"
|
||||
tabindex="-1"
|
||||
>{% endunless %}
|
||||
<div class="img-wrapper lead aspect-ratio flip-project-img {% unless no_link_title %}sixteen-nine{% endunless %}">
|
||||
<div>
|
||||
{% if show_video %} {% include_cached components/video.html video=post.video poster=post.image %} {% elsif
|
||||
show_iframe %} {% include_cached components/iframe.html iframe=post.iframe %} {% else %} {% include_cached
|
||||
components/hy-img.html img=post.image alt=post.title %} {% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% unless no_link_title %}</a
|
||||
>{% endunless %} {% assign alt = '' %} {% endif %}{% endunless %} {% include components/message.html
|
||||
text=post.description hide=hide_description alt=alt %}
|
||||
</header>
|
||||
|
||||
{% if no_excerpt %} {% if post.gallery %}
|
||||
<div class="gallery-wrapper lead">
|
||||
{% for image in post.gallery %}
|
||||
<a class="gallery-item" href="{{ image | relative_url }}" data-fslightbox target="_blank">
|
||||
{% if post.video_gallery %} {% assign i = image | replace: "mp4", "webp" %} {% include_cached
|
||||
components/hy-img.html img=i alt=post.title %} {% else %} {% include_cached components/hy-img.html img=image
|
||||
alt=post.title %} {% endif %}
|
||||
</a>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %} {% if post.sound %} {% assign sounds = site.data.sounds | where: 'slug', page.sound %}
|
||||
<div class="sound-wrapper" data-featured="{{ sounds.first | jsonify | escape }}">
|
||||
<table class="stretch-table dl-table">
|
||||
<tbody>
|
||||
{% for track in sounds.first.tracks %}
|
||||
<tr>
|
||||
<td style="text-align: left">
|
||||
<a class="sound-item" href="/assets/sounds/{{ page.sound }}/{{ track }}" target="_blank">
|
||||
{{ sounds.first.artist }} - {{ track | replace: ".mp3", "" }}
|
||||
</a>
|
||||
</td>
|
||||
<td style="text-align: right">
|
||||
<a download href="/assets/sounds/{{ page.sound }}/{{ track }}" target="_blank">
|
||||
{{ site.data.strings.download }}
|
||||
</a>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
{% endif %} {{ post.content }} {% else %} {% capture post_title %}<a
|
||||
class="heading flip-title"
|
||||
href="{{ post.url | relative_url }}"
|
||||
>{{ post.title }}</a
|
||||
>{% endcapture %} {% assign text = site.data.strings.continue_reading | default:"Continue reading
|
||||
<!--post_title-->" %}
|
||||
<footer>
|
||||
<p class="read-more">{{ text | replace:"<!--post_title-->", post_title }}</p>
|
||||
</footer>
|
||||
{% endif %}
|
||||
</article>
|
||||
35
_includes/components/related-posts.html
Normal file
35
_includes/components/related-posts.html
Normal file
@@ -0,0 +1,35 @@
|
||||
{% assign post = page %}
|
||||
|
||||
{% if page.related_posts %}
|
||||
{% if major >= 4 and minor >= 1 %}
|
||||
{% assign related_posts = site.posts | where_exp:"post", "page.related_posts contains post.path or page.related_posts contains post.url" %}
|
||||
{% else %}
|
||||
{% assign related_posts_1 = site.posts | where_exp:"post", "page.related_posts contains post.path" %}
|
||||
{% assign related_posts_2 = site.posts | where_exp:"post", "page.related_posts contains post.url" %}
|
||||
{% assign related_posts = related_posts_1 | concat:related_posts_2 %}
|
||||
{% endif %}
|
||||
{% elsif site.pivoine.use_lsi or site.use_lsi %}
|
||||
{% assign related_posts = site.related_posts %}
|
||||
{% elsif post.categories.first %}
|
||||
{% assign related_posts = site.categories[post.categories.first] | where_exp:"post", "post.url != page.url" %}
|
||||
{% elsif post.tags.first %}
|
||||
{% assign related_posts = site.tags[post.tags.first] | where_exp:"post", "post.url != page.url" %}
|
||||
{% else %}
|
||||
{% assign related_posts = site.related_posts %}
|
||||
{% endif %}
|
||||
|
||||
{% if related_posts.size > 0 %}
|
||||
<aside class="related mb4" role="complementary">
|
||||
<h2 class="hr-bottom">{{ site.data.strings.related_posts | default:"Related Posts" }}</h2>
|
||||
|
||||
<ul class="related-posts">
|
||||
{% for post in related_posts limit:3 %}
|
||||
{% if post %}
|
||||
{% include_cached components/post-list-item.html post=post %}
|
||||
{% else %}
|
||||
<li>Post with path <code>{{ post_path }}</code> not found.</li>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</aside>
|
||||
{% endif %}
|
||||
40
_includes/components/social-list-item.html
Normal file
40
_includes/components/social-list-item.html
Normal file
@@ -0,0 +1,40 @@
|
||||
{% assign platform = include.platform | downcase %}
|
||||
{% assign username = include.username %}
|
||||
|
||||
{% if username.size > 0 %}
|
||||
{% assign = data_social = site.data.social[platform] | default:site.data_social[platform] %}
|
||||
|
||||
{% assign name = data_social.name | default:include.platform %}
|
||||
{% assign icon = data_social.icon | default:'icon-link' %}
|
||||
{% assign app = data_social.append %}
|
||||
{% assign prep = data_social.prepend %}
|
||||
|
||||
{% unless data_social %}
|
||||
{% if platform == "email" %}
|
||||
{% assign name = "Email" %}
|
||||
{% assign icon = "icon-mail" %}
|
||||
{% assign prep = "mailto:" %}
|
||||
{% elsif platform == "twitter" %}
|
||||
{% assign name = "Twitter" %}
|
||||
{% assign icon = "icon-twitter" %}
|
||||
{% assign prep = "https://twitter.com/" %}
|
||||
{% elsif platform == "github" %}
|
||||
{% assign name = "GitHub" %}
|
||||
{% assign icon = "icon-github" %}
|
||||
{% assign prep = "https://github.com/" %}
|
||||
{% endif %}
|
||||
{% endunless %}
|
||||
|
||||
{% if username contains "//" or username contains "mailto:" %}
|
||||
{% assign url = username %}
|
||||
{% else %}
|
||||
{% assign url = username | prepend:prep | append:app %}
|
||||
{% endif %}
|
||||
|
||||
<li>
|
||||
<a href="{{ url }}" title="{{ name }}" class="no-mark-external">
|
||||
<span class="{{ icon }}"></span>
|
||||
<span class="sr-only">{{ name }}</span>
|
||||
</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
23
_includes/components/social.html
Normal file
23
_includes/components/social.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<span class="sr-only">{{ site.data.strings.social | default:"Social" }}{{ site.data.strings.colon }}</span>
|
||||
<ul>
|
||||
{% if include.author.social %}
|
||||
{% for link in include.author.social %}
|
||||
{% include components/social-list-item.html platform=link.first username=link.last %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% assign twitter_username = author.twitter.username | default: author.twitter | default:site.twitter.username | default:site.twitter | default:site.twitter_username %}
|
||||
{% if twitter_username %}
|
||||
{% include components/social-list-item.html platform="twitter" username=twitter_username %}
|
||||
{% endif %}
|
||||
|
||||
{% assign github_username = author.github.username | default: author.github | default:site.github.username | default:site.github | default:site.github_username %}
|
||||
{% if github_username %}
|
||||
{% include components/social-list-item.html platform="github" username=github_username %}
|
||||
{% endif %}
|
||||
|
||||
{% assign email = author.email | default: site.email %}
|
||||
{% if email %}
|
||||
{% include components/social-list-item.html platform="email" username=email %}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</ul>
|
||||
30
_includes/components/tag-list.html
Normal file
30
_includes/components/tag-list.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{%- assign tags = include.tags -%}
|
||||
{%- assign meta = include.meta -%}
|
||||
{%- assign start_with = include.start_with -%}
|
||||
{%- assign separator = include.separator -%}
|
||||
{%- assign end_with = include.end_with -%}
|
||||
|
||||
{%- assign content = '' -%}
|
||||
|
||||
{%- if tags.size > 0 -%}
|
||||
{%- assign content = start_with -%}
|
||||
{%- for tag_slug in tags -%}
|
||||
{%- capture iter_separator -%}{% if forloop.last %}{{ end_with }}{% else %}{{ separator }}{% endif %}{%- endcapture -%}
|
||||
|
||||
{%- if major >= 4 and minor >= 1 %}
|
||||
{%- assign tag = meta | find: "slug", tag_slug -%}
|
||||
{%- else -%}
|
||||
{%- assign tag = meta | where: "slug", tag_slug | first -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- if tag -%}
|
||||
{%- capture content_temp -%}{{ content }}<a href="{{ tag.url | relative_url }}" class="flip-title">{{ tag.title }}</a>{{ iter_separator }}{%- endcapture -%}
|
||||
{%- else -%}
|
||||
{%- capture content_temp -%}{{ content }}<span>{{ tag_slug | capitalize }}</span>{{ iter_separator }}{%- endcapture -%}
|
||||
{%- endif -%}
|
||||
|
||||
{%- assign content = content_temp -%}
|
||||
{%- endfor -%}
|
||||
{%- endif -%}
|
||||
|
||||
{{- content -}}
|
||||
31
_includes/components/video.html
Normal file
31
_includes/components/video.html
Normal file
@@ -0,0 +1,31 @@
|
||||
{% assign src = include.video.src | include.video.path | default:include.video %}
|
||||
{% assign poster = include.video.poster | default:include.poster %}
|
||||
|
||||
<media-controller>
|
||||
<video
|
||||
slot="media"
|
||||
src="{% include_cached smart-url url=src %}"
|
||||
{% if poster %}poster="{% include_cached smart-url url=poster %}"{% endif %}
|
||||
{% if include.video.class %}class="{{ include.video.class }}"{% endif %}
|
||||
{% if include.video.property %}property="{{ include.video.property }}"{% endif %}
|
||||
{% if include.video.width %}width="{{ include.video.width }}"{% endif %}
|
||||
{% if include.video.height %}height="{{ include.video.height }}"{% endif %}
|
||||
{% if include.video.width and include.video.height %}loading="lazy"{% endif %}
|
||||
{% if include.video.autoplay %}autoplay{% endif %}
|
||||
{% if include.video.loop %}loop{% endif %}
|
||||
{% if include.video.muted %}muted{% endif %}
|
||||
{% if include.video.preload %}preload="{{ include.video.preload }}"{% endif %}
|
||||
{% if include.video.crossorigin %}crossorigin="{{ include.video.crossorigin }}"{% endif %}
|
||||
></video>
|
||||
<media-loading-indicator slot="centered-chrome" noautohide></media-loading-indicator>
|
||||
<media-control-bar>
|
||||
<media-play-button></media-play-button>
|
||||
<media-mute-button></media-mute-button>
|
||||
<media-volume-range></media-volume-range>
|
||||
<media-time-display></media-time-display>
|
||||
<media-time-range></media-time-range>
|
||||
<media-duration-display></media-duration-display>
|
||||
<media-playback-rate-button></media-playback-rate-button>
|
||||
<media-fullscreen-button></media-fullscreen-button>
|
||||
</media-control-bar>
|
||||
</media-controller>
|
||||
1
_includes/head/css/inline
Normal file
1
_includes/head/css/inline
Normal file
@@ -0,0 +1 @@
|
||||
{% capture to_scssify %}{% include styles/inline.scss %}{% endcapture %}{{ to_scssify | scssify }}
|
||||
1
_includes/head/feed-tag.html
Normal file
1
_includes/head/feed-tag.html
Normal file
@@ -0,0 +1 @@
|
||||
{% feed_meta %}
|
||||
15
_includes/head/index.html
Normal file
15
_includes/head/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
{% assign google_fonts = site.google_fonts %}
|
||||
{% assign font_heading = site.font_heading %}
|
||||
{% assign font = site.font %}
|
||||
|
||||
{% include head/meta.html %}
|
||||
{% include_cached head/meta-static.html %}
|
||||
|
||||
{% include head/links.html lang=page.lang %}
|
||||
{% include_cached head/links-static.html %}
|
||||
|
||||
{% include_cached head/scripts.html %}
|
||||
|
||||
{% include_cached head/styles.html layout=page.layout color=color theme_color=theme_color %}
|
||||
|
||||
{% include my-head.html %}
|
||||
30
_includes/head/links-static.html
Normal file
30
_includes/head/links-static.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{% if plugins contains 'jekyll-feed' %}{% include head/feed-tag.html %}{% endif %}
|
||||
|
||||
<link rel="icon" type="image/png" href="/assets/icons/favicon-96x96.png" sizes="96x96" />
|
||||
<link rel="icon" type="image/svg+xml" href="/assets/icons/favicon.svg" />
|
||||
<link rel="shortcut icon" href="/assets/icons/favicon.ico" />
|
||||
<link rel="apple-touch-icon" sizes="180x180" href="/assets/icons/apple-touch-icon.png" />
|
||||
<link rel="manifest" href="/assets/icons/site.webmanifest" />
|
||||
|
||||
{% if site.google_fonts %}
|
||||
{%- capture google_fonts_url %}{{ site.google_fonts_url | default:'https://fonts.googleapis.com' }}{%- endcapture -%}
|
||||
<link rel="dns-prefetch" href="{{ google_fonts_url }}" />
|
||||
{%- if google_fonts_url == 'https://fonts.googleapis.com' -%}
|
||||
<link rel="dns-prefetch" href="https://fonts.gstatic.com" />
|
||||
{%- endif -%}
|
||||
{% endif %}
|
||||
|
||||
{% if site.google_analytics %}
|
||||
<link rel="dns-prefetch" href="https://www.google-analytics.com" />
|
||||
{% endif %}
|
||||
|
||||
{% if site.kramdown.math_engine == 'katex' %}
|
||||
{% capture katex_url %}{{ 'assets/bower_components/katex/dist/katex.min.css' | relative_url }}{% endcapture %}
|
||||
<link rel="dns-prefetch" href="{{ katex_url }}" id="_katexPreload" />
|
||||
<noscript><link rel="stylesheet" href="{{ katex_url }}"></noscript>
|
||||
{% endif %}
|
||||
|
||||
{% assign disqus = site.disqus | default:site.disqus_shortname %}
|
||||
{% if disqus %}
|
||||
<link rel="dns-prefetch" href="https://{{ disqus }}.disqus.com" id="_hrefDisqus" />
|
||||
{% endif %}
|
||||
2
_includes/head/links.html
Normal file
2
_includes/head/links.html
Normal file
@@ -0,0 +1,2 @@
|
||||
{% assign lang = include.lang | default:site.lang | default:'en' %}
|
||||
<link rel="alternate" href="{{ page.url | absolute_url }}" hreflang="{{ lang | downcase | replace:'_','-' }}" />
|
||||
13
_includes/head/meta-static.html
Normal file
13
_includes/head/meta-static.html
Normal file
@@ -0,0 +1,13 @@
|
||||
<meta charset="utf-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" />
|
||||
<meta http-equiv="x-ua-compatible" content="ie=edge" />
|
||||
|
||||
<meta name="mobile-web-app-capable" content="yes" />
|
||||
|
||||
<meta name="apple-mobile-web-app-capable" content="yes" />
|
||||
<meta name="apple-mobile-web-app-title" content="{{ site.title }}" />
|
||||
<meta name="apple-mobile-web-app-status-bar-style" content="default" />
|
||||
|
||||
<meta name="application-name" content="{{ site.title }}" />
|
||||
|
||||
<meta name="generator" content="pivoine v1.0.0" />
|
||||
30
_includes/head/meta.html
Normal file
30
_includes/head/meta.html
Normal file
@@ -0,0 +1,30 @@
|
||||
{% if page.noindex or page.no_index or page.sitemap == false %}
|
||||
<meta name="robots" content="noindex" />
|
||||
{% endif %}
|
||||
|
||||
{% unless page.redirect %}
|
||||
{% if plugins contains 'jekyll-seo-tag' %}
|
||||
{% include head/seo-tag.html %}
|
||||
{% else %}
|
||||
{% include head/seo-fallback.html %}
|
||||
{% endif %}
|
||||
|
||||
{% if site.keywords.size > 0 or page.keywords.size > 0 %}
|
||||
<meta name="keywords" content="{{ page.keywords | default:site.keywords | join:',' }}" />
|
||||
{% endif %}
|
||||
{% else %}
|
||||
<meta http-equiv="refresh" content="0; url={{ page.redirect.to }}" />
|
||||
<title>{{ site.data.strings.redirecting | default:"Redirecting..." }}</title>
|
||||
{% endunless %}
|
||||
|
||||
{% if site.pivoine.dark_mode.dynamic %}
|
||||
<meta name="color-scheme" content="dark light" />
|
||||
{% elsif site.pivoine.dark_mode.always %}
|
||||
<meta name="color-scheme" content="dark" />
|
||||
{% else %}
|
||||
<meta name="color-scheme" content="light" />
|
||||
{% endif %}
|
||||
|
||||
{% unless site.pivoine.no_theme_color %}
|
||||
<meta name="theme-color" content="{{ theme_color | default:'rgb(8,46,57)' }}" />
|
||||
{% endunless %}
|
||||
4
_includes/head/page-style.html
Normal file
4
_includes/head/page-style.html
Normal file
@@ -0,0 +1,4 @@
|
||||
<style id="_pageStyle">
|
||||
{% capture page_style %}{% include_cached styles/page-style.scss color=include.color theme_color=include.theme_color %}{% endcapture %}
|
||||
{{ page_style | scssify }}
|
||||
</style>
|
||||
24
_includes/head/scripts.html
Normal file
24
_includes/head/scripts.html
Normal file
@@ -0,0 +1,24 @@
|
||||
<script>{% include scripts/load-js.min.js %}{% include scripts/loadCSS.min.js %}{% include scripts/cssrelpreload.min.js %}!function(w) {
|
||||
w._baseURL = '{{ "/" | relative_url }}';
|
||||
w._publicPath = '{{ "/assets/js/" | relative_url }}';
|
||||
w._noPushState = {{ site.pivoine.no_push_state | default:site.disable_push_state | default:false }};
|
||||
w._noDrawer = {{ site.pivoine.no_drawer | default:site.disable_drawer | default:false }};
|
||||
w._noNavbar = {{ site.pivoine.no_navbar | default:false }};
|
||||
w._noToc = {{ site.pivoine.no_toc | default:false }};
|
||||
w._noSearch = {{ site.pivoine.no_search | default:false }};
|
||||
w._search = {
|
||||
DATA_URL: '{{ "/assets/sitedata.json?no-cache" | relative_url }}',
|
||||
STORAGE_KEY: 'mini-search{{ "/" | relative_url }}',
|
||||
INDEX_KEY: 'index--{{ site.time | date_to_xmlschema }}',
|
||||
};
|
||||
w._clapButton = {% if site.clap_button or jekyll.environment != 'production' %}true{% else %}false{% endif %};
|
||||
}(window);</script>
|
||||
|
||||
{% if site.kramdown.math_engine == 'mathjax' %}
|
||||
<script async src="{{ 'assets/bower_components/MathJax/es5/tex-mml-chtml.js' | relative_url }}" id="_MathJax"></script>
|
||||
{% endif %}
|
||||
|
||||
{% if jekyll.environment != 'production' and site.pivoine.umami %}
|
||||
<script defer src="{{ site.pivoine.umami.script }}" data-website-id="{{ site.pivoine.umami.id }}"></script>
|
||||
{% endif %}
|
||||
|
||||
13
_includes/head/seo-fallback.html
Normal file
13
_includes/head/seo-fallback.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% assign strings = site.data.strings %}
|
||||
{% capture title %}
|
||||
{% if page.url == "/" %}
|
||||
{{ site.title }}{% if site.tagline %} {{ strings.separator | default:"|" }} {{ site.tagline }}{% endif %}
|
||||
{% elsif page.title.size > 0 %}
|
||||
{{ page.title }} {{ strings.separator | default:"|" }} {{ site.title }}
|
||||
{% else %}
|
||||
{{ site.title }}
|
||||
{% endif %}
|
||||
{% endcapture %}
|
||||
<title>{{ title | strip }}</title>
|
||||
<meta name="description" content="{{ page.description | default:page.excerpt | default:site.description | markdownify | strip_html }}" />
|
||||
<link rel="canonical" href="{{ page.url | absolute_url }}" />
|
||||
18
_includes/head/seo-tag.html
Normal file
18
_includes/head/seo-tag.html
Normal file
@@ -0,0 +1,18 @@
|
||||
{% comment %}<!--
|
||||
Including `seo` in a partial prevents a parse error when `jekyll-seo-tag` is not included
|
||||
-->{% endcomment %}
|
||||
{% seo %}
|
||||
|
||||
{% if page.accent_video %}
|
||||
<meta property="og:video" content="{{ page.accent_video | absolute_url }}" />
|
||||
{% endif %}
|
||||
|
||||
{% if site.pivoine.sound and page.sound %}
|
||||
<meta property="og:type" content="music.album" />
|
||||
{% assign sounds = site.data.sounds | where: 'slug', page.sound %}
|
||||
{% for track in sounds.first.tracks %}
|
||||
{% assign track_url = "/assets/sounds/" | append:page.sound | append:"/" | append: track" %}
|
||||
<meta property="music:song" content="{{ track_url | absolute_url }}" />
|
||||
<meta property="music:song:track" content="{{ forloop.index + 1 }}" />
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
25
_includes/head/styles-inline.html
Normal file
25
_includes/head/styles-inline.html
Normal file
@@ -0,0 +1,25 @@
|
||||
{% assign google_fonts = site.google_fonts %}
|
||||
{% capture style_url %}{{ 'assets/css/pivoine-1.0.0.css' | relative_url }}{% endcapture %}
|
||||
{% capture icons_url %}{{ 'assets/icomoon/style.css' | relative_url }}{% endcapture %}
|
||||
{% capture game_icons_url %}{{ 'assets/game-icons/css/game-icons.css' | relative_url }}{% endcapture %}
|
||||
{% if google_fonts %}
|
||||
{% capture fonts_url %}{{ site.google_fonts_url | default:'https://fonts.googleapis.com' }}/css?family={{ google_fonts | uri_escape }}&display=swap{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
<link rel="preload" as="style" href="{{ style_url }}" id="_stylePreload" />
|
||||
<link rel="preload" as="style" href="{{ icons_url }}" id="_iconsPreload" />
|
||||
<link rel="preload" as="style" href="{{ game_icons_url }}" id="_gameIconsPreload" />
|
||||
{% if google_fonts %}<link rel="preload" as="style" href="{{ fonts_url }}" id="_fontsPreload" />{% endif %}
|
||||
|
||||
<script>
|
||||
setRel('_stylePreload');
|
||||
setRel('_iconsPreload');
|
||||
setRel('_gameIconsPreload');
|
||||
/*{% if google_fonts %}*/setRel('_fontsPreload');/*{% endif %}*/
|
||||
</script>
|
||||
<noscript>
|
||||
<link rel="stylesheet" href="{{ style_url }}" />
|
||||
<link rel="stylesheet" href="{{ icons_url }}" />
|
||||
<link rel="stylesheet" href="{{ game_icons_url }}" />
|
||||
{% if google_fonts %}<link rel="stylesheet" href="{{ fonts_url }}" />{% endif %}
|
||||
</noscript>
|
||||
4
_includes/head/styles-layout.html
Normal file
4
_includes/head/styles-layout.html
Normal file
@@ -0,0 +1,4 @@
|
||||
{% assign layout = include.layout %}
|
||||
<style id="_styleInline">
|
||||
{% include_cached head/css/inline %}
|
||||
</style>
|
||||
12
_includes/head/styles-no-inline.html
Normal file
12
_includes/head/styles-no-inline.html
Normal file
@@ -0,0 +1,12 @@
|
||||
{% assign google_fonts = site.google_fonts %}
|
||||
{% capture style_url %}{{ 'assets/css/pivoine-1.0.0.css' | relative_url }}{% endcapture %}
|
||||
{% capture icons_url %}{{ 'assets/icomoon/style.css' | relative_url }}{% endcapture %}
|
||||
{% capture game_icons_url %}{{ 'assets/game-icons/css/game-icons.css' | relative_url }}{% endcapture %}
|
||||
{% if google_fonts %}
|
||||
{% capture fonts_url %}{{ site.google_fonts_url | default:'https://fonts.googleapis.com' }}/css?family={{ google_fonts | uri_escape }}&display=swap{% endcapture %}
|
||||
{% endif %}
|
||||
|
||||
<link rel="stylesheet" href="{{ style_url }}" id="_stylePreload" />
|
||||
<link rel="stylesheet" href="{{ icons_url }}" id="_iconsPreload" />
|
||||
<link rel="stylesheet" as="style" href="{{ game_icons_url }}" id="_gameIconsPreload" />
|
||||
{% if google_fonts %}<link rel="stylesheet" href="{{ fonts_url }}" id="_fontsPreload" />{% endif %}
|
||||
12
_includes/head/styles.html
Normal file
12
_includes/head/styles.html
Normal file
@@ -0,0 +1,12 @@
|
||||
<!--[if gt IE 8]><!---->
|
||||
{% if site.pivoine.no_inline_css or jekyll.environment == 'development' %}
|
||||
{% include_cached head/styles-no-inline.html %}
|
||||
{% else %}
|
||||
{% include_cached head/styles-layout.html layout=include.layout %}
|
||||
{% include_cached head/styles-inline.html %}
|
||||
{% endif %}
|
||||
|
||||
{% unless site.pivoine.no_page_style %}
|
||||
{% include_cached head/page-style.html color=include.color theme_color=include.theme_color %}
|
||||
{% endunless %}
|
||||
<!--<![endif]-->
|
||||
12
_includes/header.txt
Normal file
12
_includes/header.txt
Normal file
@@ -0,0 +1,12 @@
|
||||
/*************************************************************
|
||||
*
|
||||
*
|
||||
* ____ ____ .__ __ /\
|
||||
* \ \ / /____ | | | | __ ____ _____ ______)/ ______
|
||||
* \ Y /\__ \ | | | |/ // \\__ \\_ __ \/ ___/
|
||||
* \ / / __ \| |_| <| | \/ __ \| | \/\___ \
|
||||
* \___/ (____ /____/__|_ \___| (____ /__| /____ >
|
||||
* \/ \/ \/ \/ \/
|
||||
*
|
||||
*
|
||||
*************************************************************/
|
||||
2
_includes/icon-github.html
Normal file
2
_includes/icon-github.html
Normal file
@@ -0,0 +1,2 @@
|
||||
<!-- This file is only here to prevent an error when using pivoine with Jekyll's default content. -->
|
||||
GitHub
|
||||
7
_includes/if-non-null
Normal file
7
_includes/if-non-null
Normal file
@@ -0,0 +1,7 @@
|
||||
{% capture maybe %}{% include {{ include.try }} %}{% endcapture %}
|
||||
{% assign maybe = maybe | strip_newlines %}
|
||||
{% if maybe.size > 0 %}
|
||||
{{ maybe }}
|
||||
{% elsif include.fallback %}
|
||||
{% include {{ include.fallback }} %}
|
||||
{% endif %}
|
||||
42
_includes/my-body.html
Normal file
42
_includes/my-body.html
Normal file
@@ -0,0 +1,42 @@
|
||||
{% comment %}
|
||||
<!--
|
||||
Example code for using Matamo as alternative analytics solution.
|
||||
-->
|
||||
{% if site.matomo_analytics %}
|
||||
<script>
|
||||
var _paq = _paq || [];
|
||||
|
||||
{% if site.matomo_analytics.no_cookies %}
|
||||
_paq.push(['disableCookies']);
|
||||
{% endif %}
|
||||
|
||||
_paq.push(['trackPageView']);
|
||||
_paq.push(['enableLinkTracking']);
|
||||
_paq.push(['setTrackerUrl', u+'piwik.php']);
|
||||
_paq.push(['setSiteId', '{{site.matomo_analytics.site_id}}']);
|
||||
|
||||
var pushStateEl = document.getElementById('_pushState');
|
||||
var timeStartLoadPage, referer, timeItTookToLoadPage;
|
||||
|
||||
pushStateEl.addEventListener('hy-push-state-start', function() {
|
||||
timeStartLoadPage = new Date().getTime();
|
||||
referer = window.location.toString();
|
||||
});
|
||||
|
||||
pushStateEl.addEventListener('hy-push-state-ready', function() {
|
||||
timeItTookToLoadPage = new Date().getTime() - timeStartLoadPage;
|
||||
});
|
||||
|
||||
pushStateEl.addEventListener('hy-push-state-after', function() {
|
||||
_paq.push(['setReferrerUrl', referer]);
|
||||
_paq.push(['setCustomUrl', window.location.toString()]);
|
||||
_paq.push(['setDocumentTitle', document.title]);
|
||||
_paq.push(['deleteCustomVariables', 'page']);
|
||||
_paq.push(['setGenerationTimeMs', timeItTookToLoadPage]);
|
||||
_paq.push(['trackPageView']);
|
||||
});
|
||||
|
||||
window.loadJSDeferred('{{site.matomo_analytics.root}}piwik.js');
|
||||
</script>
|
||||
{% endif %}
|
||||
{% endcomment %}
|
||||
24
_includes/my-comments.html
Normal file
24
_includes/my-comments.html
Normal file
@@ -0,0 +1,24 @@
|
||||
{% assign disqus = site.disqus | default:site.disqus_shortname %}
|
||||
{% if disqus %}
|
||||
<div id="disqus_thread"></div>
|
||||
<noscript>Please enable JavaScript to view the <a href="https://disqus.com/?ref_noscript" rel="nofollow">comments powered by Disqus.</a></noscript>
|
||||
<script>!function(w, d) {
|
||||
if (d.getElementById("disqus_thread")) {
|
||||
if (w.DISQUS) {
|
||||
w.DISQUS.reset({
|
||||
reload: true,
|
||||
config() {
|
||||
this.page.url = w.location.href;
|
||||
this.page.title = d.title;
|
||||
},
|
||||
});
|
||||
} else {
|
||||
w.disqus_config = function disqusConfig() {
|
||||
this.page.url = w.location.href;
|
||||
this.page.title = d.title;
|
||||
};
|
||||
w.loadJSDeferred(d.getElementById("_hrefDisqus").href + '/embed.js');
|
||||
}
|
||||
}
|
||||
}(window, document);</script>
|
||||
{% endif %}
|
||||
13
_includes/my-head.html
Normal file
13
_includes/my-head.html
Normal file
@@ -0,0 +1,13 @@
|
||||
{% comment %}
|
||||
<!--
|
||||
Using `preload` so that page rendering doesn't get blocked.
|
||||
Fill in `href`. You may have to adjust `as` and `onload`/`rel`, depending on content.
|
||||
-->
|
||||
<link href="<path/to/content.css>" rel="preload" as="style" onload="this.rel='stylesheet'">
|
||||
|
||||
<!--
|
||||
Fallback in case JavaScript isn't enabled.
|
||||
Fill in `href`. You may have to adjust `rel`.
|
||||
-->
|
||||
<noscript><link href="<path/to/content.css>" rel="stylesheet"></noscript>
|
||||
{% endcomment %}
|
||||
55
_includes/my-scripts.html
Normal file
55
_includes/my-scripts.html
Normal file
@@ -0,0 +1,55 @@
|
||||
{% comment %}
|
||||
<!--
|
||||
Example code for the CloudFlare mail protection script.
|
||||
CloudFlare will inject this on every page, but due to pivoine's push state approach to page loading,
|
||||
it will only run on the initial page.
|
||||
The snippet below will run the code on every `hy-push-state-load` event instead.
|
||||
-->
|
||||
<script>
|
||||
document.getElementById('_pushState').addEventListener('hy-push-state-load', function (e) {
|
||||
function e(e){
|
||||
(console.error?console.error:console.log).call(console,e)
|
||||
}
|
||||
|
||||
function t(e){
|
||||
return l.innerHTML='<a href="'+e.replace(/"/g,""")+'"></a>',l.childNodes[0].getAttribute("href")
|
||||
}
|
||||
|
||||
function r(e,t){
|
||||
var r=e.substr(t,2);return parseInt(r,16)
|
||||
}
|
||||
|
||||
function n(e,n){
|
||||
for(var o="",c=r(e,n),a=n+2;a<e.length;a+=2){
|
||||
var l=r(e,a)^c;
|
||||
o+=String.fromCharCode(l)
|
||||
}
|
||||
return t(o)
|
||||
}
|
||||
|
||||
var o="/cdn-cgi/l/email-protection#",
|
||||
c=".__cf_email__",
|
||||
a="data-cfemail",
|
||||
l=document.createElement("div");
|
||||
|
||||
!function(){
|
||||
for(var t=document.getElementsByTagName("a"),r=0;r<t.length;r++)
|
||||
try{
|
||||
var c=t[r],a=c.href.indexOf(o);
|
||||
a>-1&&(c.href="mailto:"+n(c.href,a+o.length))
|
||||
}catch(t){
|
||||
e(t)
|
||||
}
|
||||
}(),
|
||||
function(){
|
||||
for(var t=document.querySelectorAll(c),r=0;r<t.length;r++)
|
||||
try{
|
||||
var o=t[r],l=n(o.getAttribute(a),0),i=document.createTextNode(l);
|
||||
o.parentNode.replaceChild(i,o)
|
||||
}catch(t){
|
||||
e(t)
|
||||
}
|
||||
}()
|
||||
});
|
||||
</script>
|
||||
{% endcomment %}
|
||||
38
_includes/scripts/cssrelpreload.min.js
vendored
Normal file
38
_includes/scripts/cssrelpreload.min.js
vendored
Normal file
@@ -0,0 +1,38 @@
|
||||
!(function (a) {
|
||||
if (a.loadCSS) {
|
||||
var b = (loadCSS.relpreload = {});
|
||||
if (
|
||||
((b.support = function () {
|
||||
try {
|
||||
return a.document.createElement("link").relList.supports("preload");
|
||||
} catch (b) {
|
||||
return !1;
|
||||
}
|
||||
}),
|
||||
(b.poly = function () {
|
||||
for (
|
||||
var b = a.document.getElementsByTagName("link"), c = 0;
|
||||
c < b.length;
|
||||
c++
|
||||
) {
|
||||
var d = b[c];
|
||||
"preload" === d.rel &&
|
||||
"style" === d.getAttribute("as") &&
|
||||
(a.loadCSS(d.href, d, d.getAttribute("media")), (d.rel = null));
|
||||
}
|
||||
}),
|
||||
!b.support())
|
||||
) {
|
||||
b.poly();
|
||||
var c = a.setInterval(b.poly, 300);
|
||||
a.addEventListener &&
|
||||
a.addEventListener("load", function () {
|
||||
b.poly(), a.clearInterval(c);
|
||||
}),
|
||||
a.attachEvent &&
|
||||
a.attachEvent("onload", function () {
|
||||
a.clearInterval(c);
|
||||
});
|
||||
}
|
||||
}
|
||||
})(this);
|
||||
11
_includes/scripts/dark-mode.js
Normal file
11
_includes/scripts/dark-mode.js
Normal file
@@ -0,0 +1,11 @@
|
||||
!(function (window, document) {
|
||||
var LM = "light-mode";
|
||||
var DM = "dark-mode";
|
||||
var h = new Date().getHours();
|
||||
if ("matchMedia" in window && window.matchMedia("(prefers-color-scheme)"))
|
||||
return;
|
||||
var m = h <= window._sunrise || h >= window._sunset ? DM : LM;
|
||||
var n = m === DM ? LM : DM;
|
||||
document.body.classList.add(m);
|
||||
document.body.classList.remove(n);
|
||||
})(window, document);
|
||||
9
_includes/scripts/dark-mode.min.js
vendored
Normal file
9
_includes/scripts/dark-mode.min.js
vendored
Normal file
@@ -0,0 +1,9 @@
|
||||
((e, s) => {
|
||||
var d = "light-mode",
|
||||
a = "dark-mode",
|
||||
o = new Date().getHours();
|
||||
("matchMedia" in e && e.matchMedia("(prefers-color-scheme)")) ||
|
||||
((e = (o = o <= e._sunrise || o >= e._sunset ? a : d) == a ? d : a),
|
||||
s.body.classList.add(o),
|
||||
s.body.classList.remove(e));
|
||||
})(window, document);
|
||||
62
_includes/scripts/load-js.js
Normal file
62
_includes/scripts/load-js.js
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright (c) 2017 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Compress via uglify:
|
||||
// uglifyjs load-js.js -c -m > load-js.min.js
|
||||
!(function (window, document) {
|
||||
"use strict";
|
||||
|
||||
function addEvent(el, type, cb, opts) {
|
||||
if (el.addEventListener) el.addEventListener(type, cb, opts);
|
||||
else if (el.attachEvent) el.attachEvent("on" + type, cb);
|
||||
else el["on" + type] = cb;
|
||||
}
|
||||
|
||||
window.loadJS = function (src, cb) {
|
||||
var script = document.createElement("script");
|
||||
script.src = src;
|
||||
if (cb) addEvent(script, "load", cb, { once: true });
|
||||
var ref = document.scripts[0];
|
||||
ref.parentNode.insertBefore(script, ref);
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
window._loaded = false;
|
||||
window.loadJSDeferred = function (src, cb) {
|
||||
var script = document.createElement("script");
|
||||
script.src = src;
|
||||
|
||||
function insert() {
|
||||
window._loaded = true;
|
||||
if (cb) addEvent(script, "load", cb, { once: true });
|
||||
var ref = document.scripts[0];
|
||||
ref.parentNode.insertBefore(script, ref);
|
||||
}
|
||||
|
||||
if (window._loaded) insert();
|
||||
else addEvent(window, "load", insert, { once: true });
|
||||
|
||||
return script;
|
||||
};
|
||||
|
||||
window.setRel = window.setRelStylesheet = function (id) {
|
||||
var link = document.getElementById(id);
|
||||
function set() {
|
||||
this.rel = "stylesheet";
|
||||
}
|
||||
addEvent(link, "load", set, { once: true });
|
||||
};
|
||||
})(window, document);
|
||||
35
_includes/scripts/load-js.min.js
vendored
Normal file
35
_includes/scripts/load-js.min.js
vendored
Normal file
@@ -0,0 +1,35 @@
|
||||
((r, a) => {
|
||||
function d(e, t, n, o) {
|
||||
e.addEventListener
|
||||
? e.addEventListener(t, n, o)
|
||||
: e.attachEvent
|
||||
? e.attachEvent("on" + t, n)
|
||||
: (e["on" + t] = n);
|
||||
}
|
||||
(r.loadJS = function (e, t) {
|
||||
var n = a.createElement("script"),
|
||||
e = ((n.src = e), t && d(n, "load", t, { once: !0 }), a.scripts[0]);
|
||||
return e.parentNode.insertBefore(n, e), n;
|
||||
}),
|
||||
(r._loaded = !1),
|
||||
(r.loadJSDeferred = function (e, t) {
|
||||
var n = a.createElement("script");
|
||||
function o() {
|
||||
(r._loaded = !0), t && d(n, "load", t, { once: !0 });
|
||||
var e = a.scripts[0];
|
||||
e.parentNode.insertBefore(n, e);
|
||||
}
|
||||
return (n.src = e), r._loaded ? o() : d(r, "load", o, { once: !0 }), n;
|
||||
}),
|
||||
(r.setRel = r.setRelStylesheet =
|
||||
function (e) {
|
||||
d(
|
||||
a.getElementById(e),
|
||||
"load",
|
||||
function () {
|
||||
this.rel = "stylesheet";
|
||||
},
|
||||
{ once: !0 },
|
||||
);
|
||||
});
|
||||
})(window, document);
|
||||
44
_includes/scripts/loadCSS.min.js
vendored
Normal file
44
_includes/scripts/loadCSS.min.js
vendored
Normal file
@@ -0,0 +1,44 @@
|
||||
!(function (a) {
|
||||
"use strict";
|
||||
var b = function (b, c, d) {
|
||||
function e(a) {
|
||||
return h.body
|
||||
? a()
|
||||
: void setTimeout(function () {
|
||||
e(a);
|
||||
});
|
||||
}
|
||||
function f() {
|
||||
i.addEventListener && i.removeEventListener("load", f),
|
||||
(i.media = d || "all");
|
||||
}
|
||||
var g,
|
||||
h = a.document,
|
||||
i = h.createElement("link");
|
||||
if (c) g = c;
|
||||
else {
|
||||
var j = (h.body || h.getElementsByTagName("head")[0]).childNodes;
|
||||
g = j[j.length - 1];
|
||||
}
|
||||
var k = h.styleSheets;
|
||||
(i.rel = "stylesheet"),
|
||||
(i.href = b),
|
||||
(i.media = "only x"),
|
||||
e(function () {
|
||||
g.parentNode.insertBefore(i, c ? g : g.nextSibling);
|
||||
});
|
||||
var l = function (a) {
|
||||
for (var b = i.href, c = k.length; c--; ) if (k[c].href === b) return a();
|
||||
setTimeout(function () {
|
||||
l(a);
|
||||
});
|
||||
};
|
||||
return (
|
||||
i.addEventListener && i.addEventListener("load", f),
|
||||
(i.onloadcssdefined = l),
|
||||
l(f),
|
||||
i
|
||||
);
|
||||
};
|
||||
"undefined" != typeof exports ? (exports.loadCSS = b) : (a.loadCSS = b);
|
||||
})("undefined" != typeof global ? global : this);
|
||||
25
_includes/scripts/nomodule.js
Normal file
25
_includes/scripts/nomodule.js
Normal file
@@ -0,0 +1,25 @@
|
||||
// `script[nomodule]` polyfill for Safari 10.1.
|
||||
// Source: https://gist.github.com/samthor/64b114e4a4f539915a95b91ffd340acc
|
||||
(function () {
|
||||
var check = document.createElement("script");
|
||||
if (!("noModule" in check) && "onbeforeload" in check) {
|
||||
var support = false;
|
||||
document.addEventListener(
|
||||
"beforeload",
|
||||
function (e) {
|
||||
if (e.target === check) {
|
||||
support = true;
|
||||
} else if (!e.target.hasAttribute("nomodule") || !support) {
|
||||
return;
|
||||
}
|
||||
e.preventDefault();
|
||||
},
|
||||
true,
|
||||
);
|
||||
|
||||
check.type = "module";
|
||||
check.src = ".";
|
||||
document.head.appendChild(check);
|
||||
check.remove();
|
||||
}
|
||||
})();
|
||||
20
_includes/scripts/nomodule.min.js
vendored
Normal file
20
_includes/scripts/nomodule.min.js
vendored
Normal file
@@ -0,0 +1,20 @@
|
||||
(() => {
|
||||
var t,
|
||||
n = document.createElement("script");
|
||||
!("noModule" in n) &&
|
||||
"onbeforeload" in n &&
|
||||
((t = !1),
|
||||
document.addEventListener(
|
||||
"beforeload",
|
||||
function (e) {
|
||||
if (e.target === n) t = !0;
|
||||
else if (!e.target.hasAttribute("nomodule") || !t) return;
|
||||
e.preventDefault();
|
||||
},
|
||||
!0,
|
||||
),
|
||||
(n.type = "module"),
|
||||
(n.src = "."),
|
||||
document.head.appendChild(n),
|
||||
n.remove());
|
||||
})();
|
||||
10
_includes/smart-url
Normal file
10
_includes/smart-url
Normal file
@@ -0,0 +1,10 @@
|
||||
{%- assign url = include.url -%}
|
||||
{%- assign ch1 = url | slice:0 -%}
|
||||
{%- if url contains '://' -%}
|
||||
{{ url }}
|
||||
{%- elsif ch1 == '/' -%}
|
||||
{%- assign url = url | remove_first:site.baseurl -%}
|
||||
{{ url | relative_url }}
|
||||
{%- else -%}
|
||||
{{ url }}
|
||||
{%- endif -%}
|
||||
1
_includes/smart-url.txt
Normal file
1
_includes/smart-url.txt
Normal file
@@ -0,0 +1 @@
|
||||
{% comment %} Retained for backwards compatibility. Use smart-url (without .txt) instead. {% endcomment %}{% include smart-url url=include.url %}
|
||||
18
_includes/styles/common.scss
Normal file
18
_includes/styles/common.scss
Normal file
@@ -0,0 +1,18 @@
|
||||
.note:before {
|
||||
content: "{{ site.data.strings.note | default:'Note' }}";
|
||||
}
|
||||
.page > header > .note-sm:before {
|
||||
content: "{{ site.data.strings.description | default:'Description' }}";
|
||||
}
|
||||
#markdown-toc:before {
|
||||
content: "{{ site.data.strings.toc | default:'Table of Contents' }}";
|
||||
}
|
||||
.layout-resume .note-sm:before {
|
||||
content: "{{ site.data.strings.resume.summary | default:'Summary' }}";
|
||||
}
|
||||
|
||||
{% if site.pivoine.no_page_style %}
|
||||
{% assign color = site.accent_color | default:'rgb(79,177,186)' %}
|
||||
{% assign theme_color = site.theme_color | default:'rgb(8,46,57)' %}
|
||||
{% include_cached styles/page-style.scss color=color theme_color=theme_color %}
|
||||
{% endif %}
|
||||
57
_includes/styles/inline.scss
Normal file
57
_includes/styles/inline.scss
Normal file
@@ -0,0 +1,57 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{% include styles/variables.scss %}
|
||||
@import "variables";
|
||||
@import "my-variables";
|
||||
@import "mixins";
|
||||
|
||||
{% if site.pivoine.dark_mode.dynamic %}
|
||||
@import "pro/dark-mode-dynamic";
|
||||
{% else %}
|
||||
@import "pro/dark-mode";
|
||||
{% endif %}
|
||||
|
||||
@import "html";
|
||||
|
||||
@import "pooleparty/__inline__/base";
|
||||
@import "pooleparty/__inline__/type";
|
||||
@import "pooleparty/__inline__/table";
|
||||
@import "pooleparty/__inline__/footer";
|
||||
@import "pooleparty/__inline__/footnotes";
|
||||
@import "pooleparty/__inline__/code";
|
||||
@import "pooleparty/__inline__/posts";
|
||||
@import "pooleparty/__inline__/related";
|
||||
@import "pooleparty/__inline__/read-more";
|
||||
@import "pooleparty/__inline__/message";
|
||||
@import "pooleparty/__inline__/pagination";
|
||||
|
||||
@import "pivoine/__inline__/base";
|
||||
@import "pivoine/__inline__/utilities";
|
||||
@import "pivoine/__inline__/links";
|
||||
@import "pivoine/__inline__/images";
|
||||
@import "pivoine/__inline__/sidebar";
|
||||
@import "pivoine/__inline__/social";
|
||||
@import "pivoine/__inline__/menu";
|
||||
@import "pivoine/__inline__/toc";
|
||||
@import "pivoine/__inline__/content";
|
||||
@import "pivoine/__inline__/avatar";
|
||||
@import "pivoine/__inline__/katex";
|
||||
@import "pivoine/__inline__/footer";
|
||||
@import "pivoine/__inline__/sound";
|
||||
|
||||
@import "my-inline";
|
||||
|
||||
{% include styles/common.scss %}
|
||||
19
_includes/styles/page-style.scss
Normal file
19
_includes/styles/page-style.scss
Normal file
@@ -0,0 +1,19 @@
|
||||
@use "sass:color";
|
||||
@use "sass:math";
|
||||
|
||||
{% assign color = include.color %}
|
||||
{% assign theme_color = include.theme_color %}
|
||||
|
||||
html {
|
||||
--accent-color: {{ color }};
|
||||
--accent-color-faded: color.adjust({{ color }}, $alpha: -0.5);
|
||||
--accent-color-highlight: color.adjust({{ color }}, $alpha: -0.9);
|
||||
--accent-color-darkened: color.adjust({{ color }}, $lightness: -7.5%);
|
||||
{% if site.github and site.pivoine.dart_sass_2_compat != true %}
|
||||
--dark-mode-body-bg: #{hsl(color.channel({{ theme_color }}, "hue", $space: hsl), math.div(color.channel({{ theme_color }}, "saturation", $space: hsl), 8), 17.5%)};
|
||||
--dark-mode-border-color: #{hsl(color.channel({{ theme_color }}, "hue", $space: hsl), math.div(color.channel({{ theme_color }}, "saturation", $space: hsl), 8), 22.5%)};
|
||||
{% else %}
|
||||
--dark-mode-body-bg: #{hsl(hue({{ theme_color }}), calc(saturation({{ theme_color }}) / 8), 17.5%)};
|
||||
--dark-mode-border-color: #{hsl(hue({{ theme_color }}), calc(saturation({{ theme_color }}) / 8), 22.5%)};
|
||||
{% endif %}
|
||||
}
|
||||
116
_includes/styles/style.scss
Normal file
116
_includes/styles/style.scss
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
{% include styles/variables.scss %}
|
||||
@import "variables";
|
||||
@import "my-variables";
|
||||
@import "mixins";
|
||||
|
||||
{% if site.pivoine.dark_mode.dynamic %}
|
||||
@import "pro/dark-mode-dynamic";
|
||||
{% else %}
|
||||
@import "pro/dark-mode";
|
||||
{% endif %}
|
||||
|
||||
@import "reboot-mod";
|
||||
|
||||
{% unless site.pivoine.no_inline_css or jekyll.environment == 'development' %}
|
||||
@import "pooleparty/__link__/base";
|
||||
@import "pooleparty/__link__/type";
|
||||
@import "pooleparty/__link__/table";
|
||||
@import "pooleparty/__link__/footer";
|
||||
@import "pooleparty/__link__/footnotes";
|
||||
@import "pooleparty/__link__/code";
|
||||
@import "pooleparty/__link__/posts";
|
||||
@import "pooleparty/__link__/related";
|
||||
@import "pooleparty/__link__/read-more";
|
||||
@import "pooleparty/__link__/message";
|
||||
@import "pooleparty/__link__/pagination";
|
||||
|
||||
@import "pivoine/__link__/base";
|
||||
@import "pivoine/__link__/utilities";
|
||||
@import "pivoine/__link__/links";
|
||||
@import "pivoine/__link__/images";
|
||||
@import "pivoine/__link__/sidebar";
|
||||
@import "pivoine/__link__/search";
|
||||
@import "pivoine/__link__/social";
|
||||
@import "pivoine/__link__/menu";
|
||||
@import "pivoine/__link__/toc";
|
||||
@import "pivoine/__link__/content";
|
||||
@import "pivoine/__link__/avatar";
|
||||
@import "pivoine/__link__/katex";
|
||||
@import "pivoine/__link__/footer";
|
||||
|
||||
{% unless site.pivoine.no_mark_external or site.no_mark_external %}
|
||||
@import "pivoine/__link__/mark-external";
|
||||
{% endunless %}
|
||||
|
||||
{% unless site.pivoine.no_break_layout %}
|
||||
@import "pivoine/__link__/break-layout";
|
||||
{% endunless %}
|
||||
|
||||
@import "my-style";
|
||||
{% else %}
|
||||
@import "html";
|
||||
|
||||
@import "pooleparty/_base.pre.scss";
|
||||
@import "pooleparty/_type.pre.scss";
|
||||
@import "pooleparty/_table.pre.scss";
|
||||
@import "pooleparty/_footer.pre.scss";
|
||||
@import "pooleparty/_footnotes.pre.scss";
|
||||
@import "pooleparty/_code.pre.scss";
|
||||
@import "pooleparty/_posts.pre.scss";
|
||||
@import "pooleparty/_related.pre.scss";
|
||||
@import "pooleparty/_read-more.pre.scss";
|
||||
@import "pooleparty/_message.pre.scss";
|
||||
@import "pooleparty/_pagination.pre.scss";
|
||||
|
||||
@import "pivoine/_base.pre.scss";
|
||||
@import "pivoine/_utilities.pre.scss";
|
||||
@import "pivoine/_links.pre.scss";
|
||||
@import "pivoine/_images.pre.scss";
|
||||
@import "pivoine/_sidebar.pre.scss";
|
||||
@import "pivoine/_search.pre.scss";
|
||||
@import "pivoine/_social.pre.scss";
|
||||
@import "pivoine/_menu.pre.scss";
|
||||
@import "pivoine/_toc.pre.scss";
|
||||
@import "pivoine/_content.pre.scss";
|
||||
@import "pivoine/_avatar.pre.scss";
|
||||
@import "pivoine/_katex.pre.scss";
|
||||
@import "pivoine/_footer.pre.scss";
|
||||
@import "pivoine/_sound.pre.scss";
|
||||
|
||||
{% unless site.pivoine.no_mark_external or site.no_mark_external %}
|
||||
@import "pivoine/_mark-external.pre.scss";
|
||||
{% endunless %}
|
||||
|
||||
{% unless site.pivoine.no_break_layout %}
|
||||
@import "pivoine/_break-layout.pre.scss";
|
||||
{% endunless %}
|
||||
|
||||
@import "my-inline";
|
||||
@import "my-style";
|
||||
|
||||
{% include styles/common.scss %}
|
||||
{% endunless %}
|
||||
|
||||
@import "spinner";
|
||||
@import "tippy";
|
||||
|
||||
@import "syntax";
|
||||
@import "pro/syntax-dark";
|
||||
{% if site.pivoine.dark_mode.dynamic %}
|
||||
@import "pro/dark-mode-dynamic-syntax";
|
||||
{% endif %}
|
||||
69
_includes/styles/variables.scss
Normal file
69
_includes/styles/variables.scss
Normal file
@@ -0,0 +1,69 @@
|
||||
// Copyright (c) 2020 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// {% assign vars = site.data.variables %}
|
||||
// {% assign ui_font = 'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", Arial, sans-serif' %}
|
||||
// {% assign ui_font_code = 'ui-monospace, Menlo, Monaco, "Cascadia Mono", "Segoe UI Mono", "Roboto Mono", "Oxygen Mono", "Ubuntu Mono", "Source Code Pro", "Fira Mono", "Droid Sans Mono", "Consolas", "Courier New", monospace' %}
|
||||
|
||||
@use "sass:math";
|
||||
|
||||
$font-family: {{ vars.font | default:site.font | default:ui_font }};
|
||||
$font-family-heading: {{ vars.font_heading | default:site.font_heading | default:ui_font }};
|
||||
$code-font-family: {{ vars.font_code | default:site.font_code | default:ui_font_code }};
|
||||
$theme-color: {{ vars.theme_color | default:site.theme_color | default:'rgb(8,46,57)' }};
|
||||
|
||||
$root-font-size: {{ vars.root_font_size | default:15 }}px;
|
||||
$root-font-size-medium: {{ vars.root_font_size_medium | default:16 }}px;
|
||||
$root-font-size-large: {{ vars.root_font_size_large | default:17 }}px;
|
||||
$root-font-size-print: {{ vars.root_font_size_print | default:8 }}pt;
|
||||
$root-line-height: {{ vars.root_line_height | default:1.75 }};
|
||||
|
||||
$font-weight: {{ vars.font_weight | default:400 }};
|
||||
$font-weight-bold: {{ vars.font_weight_bold | default:700 }};
|
||||
$font-weight-heading: {{ vars.font_weight_heading | default:900 }};
|
||||
|
||||
$content-width: {{ vars.content_width | default:42 }}rem;
|
||||
$content-width-2: {{ vars.content_width_2 | default:48 }}rem;
|
||||
$content-width-5: {{ vars.content_width_5 | default:54 }}rem;
|
||||
|
||||
$content-padding: {{ vars.content_padding | default:1 }}rem;
|
||||
|
||||
$sidebar-width: {{ vars.sidebar_width | default:21 }}rem;
|
||||
|
||||
$break-point-1: {{ vars.break_point_1 | default:42 }}em;
|
||||
$break-point-2: {{ vars.break_point_2 | default:54 }}em;
|
||||
$break-point-3: {{ vars.break_point_3 | default:64 }}em;
|
||||
$break-point-4: {{ vars.break_point_4 | default:72 }}em;
|
||||
$break-point-5: {{ vars.break_point_5 | default:86 }}em;
|
||||
|
||||
$border-radius: {{ vars.border_radius | default:0.5 }}rem;
|
||||
|
||||
$break-point-font-large: {{ vars.break_point_font_large | default:124 }}em;
|
||||
|
||||
$content-margin-3: 3rem;
|
||||
$content-margin-5: 4rem;
|
||||
|
||||
$gh-pages-compat: {% if site.github and site.pivoine.dart_sass_2_compat != true %}true{% else %}false{% endif %};
|
||||
|
||||
// TODO: doc
|
||||
{% if site.github and site.pivoine.dart_sass_2_compat != true %}
|
||||
$half-content: (math.div($content-width-5, 2) + $content-margin-5);
|
||||
{% else %}
|
||||
$half-content: calc(math.div($content-width-5, 2) + $content-margin-5);
|
||||
{% endif %}
|
||||
|
||||
// The sidebar width starts adjusting dynamically when the content is at the center of the window.
|
||||
// This is the case when the window size has a `min-width` of content area + twice the sidebar (left, right):
|
||||
$break-point-dynamic: $content-width-5 + (2 * $content-margin-5) + (2 * $sidebar-width);
|
||||
8
_includes/templates/animation.html
Normal file
8
_includes/templates/animation.html
Normal file
@@ -0,0 +1,8 @@
|
||||
<template id="_animation-template">
|
||||
<div class="animation-main fixed-top">
|
||||
{% unless site.pivoine.no_breadcrumbs %}{% include body/breadcrumbs.html url="/" %}{% endunless %}
|
||||
<div class="content">
|
||||
<div class="page"></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
6
_includes/templates/dark-mode.html
Normal file
6
_includes/templates/dark-mode.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<template id="_dark-mode-template">
|
||||
<button id="_dark-mode" class="nav-btn no-hover" >
|
||||
<span class="sr-only">{{ site.data.strings.dark_mode | default:"Dark Mode" }}</span>
|
||||
<span class="icon-brightness-contrast"></span>
|
||||
</button>
|
||||
</template>
|
||||
10
_includes/templates/error.html
Normal file
10
_includes/templates/error.html
Normal file
@@ -0,0 +1,10 @@
|
||||
<template id="_error-template">
|
||||
<div class="page">
|
||||
<h1 class="page-title">{{ strings.error.title | default:"Error" }}</h1>
|
||||
{% capture link %}<a class="this-link" href=""></a>{% endcapture %}
|
||||
{% assign text = strings.error.message | default:"Sorry, an error occurred while loading: <!--link-->." %}
|
||||
<p class="lead">
|
||||
{{ text | replace:"<!--link-->",link }}
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
15
_includes/templates/index.html
Normal file
15
_includes/templates/index.html
Normal file
@@ -0,0 +1,15 @@
|
||||
<div hidden>
|
||||
{% assign strings = site.data.strings %}
|
||||
<h2 class="sr-only">{{ strings.templates | default: "Templates"}}{{ strings.colon | default:":" }}</h2>
|
||||
|
||||
{% include templates/animation.html %}
|
||||
{% include templates/loading.html %}
|
||||
{% include templates/error.html %}
|
||||
{% include templates/permalink.html %}
|
||||
{% if site.pivoine.dark_mode.icon %}
|
||||
{% include templates/dark-mode.html %}
|
||||
{% endif %}
|
||||
{% if site.pivoine.search.icon %}
|
||||
{% include templates/search.html %}
|
||||
{% endif %}
|
||||
</div>
|
||||
6
_includes/templates/loading.html
Normal file
6
_includes/templates/loading.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<template id="_loading-template">
|
||||
<div class="loading nav-btn fr">
|
||||
<span class="sr-only">{{ strings.loading | default:"Loading…" }}</span>
|
||||
<span class="icon-cog"></span>
|
||||
</div>
|
||||
</template>
|
||||
6
_includes/templates/permalink.html
Normal file
6
_includes/templates/permalink.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<template id="_permalink-template">
|
||||
<a href="#" class="permalink">
|
||||
<span class="sr-only">{{ strings.permalink | default:"Permalink" }}</span>
|
||||
<span class="{{ strings.permalink_icon | default:"content-hash" }}"></span>
|
||||
</a>
|
||||
</template>
|
||||
6
_includes/templates/search.html
Normal file
6
_includes/templates/search.html
Normal file
@@ -0,0 +1,6 @@
|
||||
<template id="_search-template">
|
||||
<button id="_search" class="nav-btn no-hover" >
|
||||
<span class="sr-only">{{ site.data.strings.search | default:"Search" }}</span>
|
||||
<span class="icon-search"></span>
|
||||
</button>
|
||||
</template>
|
||||
1491
_js/lib/modernizr-custom.js
Normal file
1491
_js/lib/modernizr-custom.js
Normal file
File diff suppressed because it is too large
Load Diff
75
_js/lib/version.js
Normal file
75
_js/lib/version.js
Normal file
@@ -0,0 +1,75 @@
|
||||
console.log(`
|
||||
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠈⠁⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠀⢀⣠⡶⠟⠀⠀⠀⠀⢀⣀⠤⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣀⡤⠤⠀⠀⠀⠀⠉⠀⠀⠀⣀⣤⠶⠛⠋⠀⠒⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣤⠶⠞⣋⡥⠔⣀⠄⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠒⠉⠉⡠⠤⠖⠒⠀⠀⢀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⣠⠴⠂⠀⠈⠁⠀⡤⠾⠿⠛⠉⠀⢀⣴⠶⠖⣋⠀⣀⡠⠄⢀⣠⡶⢀⣀⡠⠀⠀⠠⠖⠚⠉⠁⠀⠀⢀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠊⠁⡴⠾⠁⠈⠁⢀⣠⠖⢁⡀⠀⢀⡠⠄⠀⣀⣀⣠⠄⠀⠞⠛⠉⠁⠴⠾⠛⠛⠛⠋⢁⣤⣤⡶⠖⠋⠀⠀⠀⠐⠚⠋⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⣀⣀⣀⣀⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⠁⢀⠄⠀⠀⠀⠀⠀⢀⣤⠶⠛⠁⣈⠅⠀⣀⡤⠶⠚⣋⠽⠋⠀⣀⣠⠄⠀⢀⣠⣤⠶⠚⠉⠠⠞⠛⠋⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠒⠚⠛⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠉⠉⠉⠉⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡠⢒⢁⡤⢀⡤⠀⠀⣠⠖⠀⠚⣉⣡⣀⠤⠊⠁⠒⠉⠁⠀⠀⣈⣤⣴⡾⠿⠋⠁⣠⠾⡛⠉⠀⢀⡀⠀⠀⠀⠀⠀⠀⢀⡠⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⣀⡀⠀⠀⠀⠀⠤⠤⠤⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠔⣱⣯⠞⣁⣤⠐⣫⠴⠊⠀⠚⣛⣉⠁⠀⠴⠚⠉⠤⠴⠾⠿⢿⣿⠗⠀⢀⣠⠔⣲⣿⠴⠖⠋⠁⠀⠄⠀⣀⣀⠠⠚⢉⣠⣴⠶⠚⠁⠀⠀⠀⠀⢀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠔⡵⢁⡾⠛⠁⣨⣿⡷⠊⣁⠀⣀⣴⠟⠉⠀⣀⠤⠔⠲⠶⠛⠃⠀⠀⠋⠥⠒⠟⠋⣠⠞⢉⡠⠀⣀⠀⢀⣤⠶⠛⠉⠀⠀⠐⠉⠁⠀⠀⠀⠀⠀⢀⣤⠤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠤⣀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠂⠁⢀⠔⠁⡰⠢⠊⠴⠟⠚⣩⡷⠛⠁⠠⠖⠊⠉⣀⠤⠀⢀⣤⣶⣾⣿⠟⠋⢀⣠⠴⠊⠀⠀⣡⠴⠊⠁⠀⠀⠀⠀⠀⠀⠀⣀⡠⠄⠀⠀⠀⠀⣀⣀⣀⡀⠀⠀⢀⣠⣴⠶⠟⠋⠁⠀⠀⠀⣀⣉⣉⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠡⠟⡴⣡⠋⢀⡜⠁⠀⣠⠔⠂⢀⣁⠤⠀⣠⣴⣦⠖⠋⠀⠀⠚⠋⠁⠚⠉⠀⣀⡤⠤⠒⠂⠀⠐⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠒⠉⠁⠀⠀⠀⠀⠀⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢸⠁⢰⢃⣞⠔⢁⡴⠋⠀⡠⢊⠔⠈⡀⢉⡄⣠⡾⢛⡉⡠⣠⠴⣣⣶⣿⡥⢾⣿⠭⠃⠀⠀⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡀⠀⠀⠀⠀⠐⠀⠀⢀⣀⡤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠁⠀⠃⢈⠋⡀⠏⠀⣀⢈⡴⢋⠴⢋⢴⠿⣿⡿⣾⠋⠞⠉⠁⠚⠉⠉⠁⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⡶⠋⠀⠀⣀⠤⢀⣠⠔⠊⣉⣤⠔⠀⠀⣀⠤⠄⠀⠀⠀⠐⠒⠚⠓⢒⣲⣤⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠇⠀⠀⣠⠂⠔⠁⣠⠐⡽⠈⠀⢀⡔⠁⠀⠊⠁⠀⠀⠀⠀⠀⠀⠀⠀⢀⣠⣀⠀⠀⠀⠀⠀⢠⢞⡁⡠⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⠾⠋⠁⠀⠀⣀⣾⣿⣿⠟⠁⢀⠾⠫⠀⠀⠀⠀⠀⣠⣤⣶⣶⣶⣶⣿⣽⣿⣟⣋⡉⠉⠛⠒⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣼⡟⢀⢀⣧⡇⠀⠀⠀⠠⠊⠀⠀⠀⠀⠀⠀⠀⠀⢀⣤⠀⠠⡜⢿⣿⣿⣦⠀⠀⡆⢰⣱⣫⣮⡴⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⢞⣫⠿⠋⠁⠀⠀⠁⠀⠀⠀⠀⠀⠀⣉⣩⣽⣿⠿⠿⠟⠛⠛⠉⢉⣀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⣾⣿⠤⢾⣾⣿⣷⣾⣠⣴⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣾⣿⠀⠀⠀⠀⣿⣿⣿⠀⢀⠇⣼⣽⣿⡟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠐⠊⠁⠀⣂⠭⠒⡩⠔⢊⡀⠀⠀⠀⢀⣤⡶⣞⣥⣶⠿⠟⠛⠛⣒⣚⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣄⣀⠀⠀⠐⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣩⣴⣶⣶⡽⣿⡿⣿⣾⣷⡅⠀⠀⢀⡄⠀⠀⠀⢠⠀⠹⣿⣷⣤⣤⣼⣿⣿⠟⢀⡜⣰⠿⣾⠏⠀⠀⠀⠀⠀⠀⠀⠀⣀⠤⠀⠀⠔⢊⣡⠔⠋⣠⣶⣿⣾⣟⣥⣾⣿⣿⣿⡿⠿⠟⠛⢛⣉⣩⣿⣿⣿⣿⣿⣿⣿⣿⡿⠿⠶⠾⣿⣿⣿⣶⣶⣤⡤⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⣾⣿⣿⣿⣿⣂⠛⢙⣾⣿⣽⣿⠗⣲⣋⠄⠁⠡⠠⣀⠑⠠⢈⠛⠿⠿⠿⠛⢁⡠⠞⣈⣴⣟⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⢀⡠⠖⠋⢁⣴⡿⣿⣿⣿⣿⠿⠟⠻⠛⣉⣡⣴⣶⠾⠛⠁⠐⠒⠲⢿⣿⣿⡿⠿⠿⠛⠛⠷⠦⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣸⣿⣿⣿⣿⣿⣿⡯⣅⠨⣾⣿⣿⣿⣯⣭⣟⣔⠄⠀⠈⡩⡙⠓⠢⠬⠉⠀⠈⠉⠁⠀⣬⠿⠛⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠊⠁⠀⠠⠖⠛⢉⡨⣵⣾⠿⣂⣠⣴⣶⣿⣿⣿⣥⡤⠤⣀⣠⣤⣶⣶⣶⣶⣶⣶⣶⣶⣤⣤⣤⣀⡀⠀⠠⢤⣤⣤⣀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣯⡹⣿⣿⣟⠁⠀⠈⠙⠢⣽⣷⣿⡿⢿⣷⣾⣽⣿⣫⣴⣎⣴⣭⣀⣀⡀⠀⢀⣀⡠⠶⠿⠓⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠄⠀⠀⢀⣠⢴⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣟⡛⠛⠛⠛⠛⠛⠋⠉⠁⠀⠀⠀⠀⠀⠀⠘⠿⣛⠛⠶⠦⢤⣄⡀⠀⠙⠷⣶⣤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣴⣿⡿⢀⣿⢿⣿⡄⠀⠀⠀⢄⣲⣟⡵⠪⠏⠞⠟⠉⠋⠛⠻⠿⠿⣿⣿⣿⡿⠾⠶⠖⠛⠋⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠒⠂⠈⠁⠀⠀⢄⣺⠿⠛⣉⡩⢽⣿⣿⣿⣿⣿⣿⣿⣿⣿⠿⠿⠿⠛⠛⣛⣀⣤⣴⣶⣾⣿⣿⣷⣶⣤⣀⠀⠀⠀⠀⠉⠂⠀⠀⠀⠀⠁⠀⠀⠈⠓⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣼⣿⡿⠁⠈⢿⣿⣟⠿⣦⣤⣽⣿⣿⡥⠂⠀⠀⠀⣠⣴⣶⠿⠟⣛⣫⠭⠵⠒⠒⠒⣋⣉⣩⣥⣤⣄⡀⠀⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠊⠁⣀⡤⠤⢶⣾⣿⡿⠿⠿⠛⠛⠛⠒⠀⠀⠀⠀⠀⠀⠤⣾⣿⣿⣿⢿⣿⣟⡻⠿⣭⣭⣉⣙⠒⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣸⣽⡿⠁⠀⠀⠈⠻⠊⡻⠶⣬⣭⣭⣵⣶⠶⠶⣚⣛⣭⠴⠖⠛⠉⢁⣀⣒⣾⣿⣿⣿⣿⣿⣿⣿⣿⣶⣒⣂⣉⣉⠀⠀⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣉⣀⣀⣀⠤⠶⣿⣿⣷⣶⣶⣦⣤⣄⣀⠉⠛⠛⠛⠻⠿⠷⠦⠌⠉⠑⢂⠈⢙⡛⠿⣦⣄⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⡿⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠰⠿⣷⣾⠿⠋⠟⠉⠀⣀⣤⠶⠟⠉⠽⠶⣿⣿⣿⣿⣶⣶⣾⣿⣿⣿⣿⠿⠯⠄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠉⠉⠉⠉⢛⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣶⣤⣀⠀⠀⠀⠀⢀⣤⣶⢶⣽⣦⡉⠻⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣿⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⢀⣤⣶⠟⠋⠉⠀⢀⣠⣤⣤⣉⠩⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⣶⣶⣦⣤⣤⣤⣤⡤⠤⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠶⢶⣶⣶⠶⢶⠶⢭⣿⣿⣟⠿⢿⣷⣬⡉⢭⣭⣥⣀⠀⠀⠀⠉⠛⠻⠿⠿⠿⣦⡀⠉⠛⢿⣧⡀⠀⠀⠀⠀⠀⢄⣠⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠏⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡠⠊⠀⠀⠚⠉⠀⠀⠀⠀⠀⠀⠈⠛⠍⠫⢛⣿⣿⣿⢿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣯⣍⣉⡉⠉⠉⠁⠐⠒⠂⠀⠀⠀⠀⠀⠀⠀⠀⠒⠀⠀⠀⠀⠀⠀⠉⠓⠶⢤⣤⣈⡙⠛⠿⠦⣄⠀⠈⠀⣽⣿⣿⣷⣄⠀⠀⠀⠀⠀⠀⢠⡀⠈⠀⠀⠀⠈⠙⠆⠀⠀⠀⠀⠀⠻⣍⠛⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠔⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⠠⣀⢤⣻⣿⣮⣙⢿⣿⣿⣿⣿⣿⣿⣿⣿⣯⡛⠿⢟⠅⡀⠀⠀⠀⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠄⠀⠀⢄⣠⣌⣿⣿⣷⣤⣀⡠⢤⣀⣩⣶⣯⣉⠛⠛⠷⠄⠀⠀⠀⠈⠢⢽⣆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠓⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠔⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠀⠀⡈⢷⣹⡻⣿⣿⣿⢿⣿⣿⡿⣯⣛⠙⢿⣿⣿⡷⣤⠑⡠⡀⢄⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠻⣿⣿⣿⣿⢿⣿⣷⣌⠙⢿⣿⣿⣷⣄⠀⠀⡀⠀⠀⠀⠀⠀⠙⠆⠀⠀⠀⠀⠢⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠹⠟⢿⣌⢾⣿⣿⣿⣷⣄⢌⣿⣮⣿⣷⣝⣆⠁⠘⢌⠢⠀⠀⠀⠀⠀⠀⠀⠀⠀⠢⢄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠻⡙⠿⣧⡈⠓⠙⢷⣄⠙⣿⣯⠛⢧⡀⢌⢳⣄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡄⠀⠀⠀⠀⠹⠊⠟⣿⢿⣻⡿⣿⣿⣿⣿⣿⢻⣿⡆⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠹⣶⣄⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⣶⣄⠁⠀⠀⠀⠙⢧⠈⠻⣇⠀⠁⠀⠑⢿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣦⣴⣿⣿⣿⣶⣦⣀⠀⠀⠀⠀⠀⠀⠀⣷⣸⠀⠀⠀⠀⠀⠀⠸⠈⢷⠁⡿⡟⡏⢇⢻⡇⠏⠃⠀⠀⠀⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⢀⡉⠻⣄⠀⠀⠀⠀⠀⠀⠀⢲⣄⠈⢿⣿⣦⡀⠀⠀⠀⠀⠀⠉⠀⠀⠀⠀⠀⠙⢿⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣄⠀⢹⣷⣠⡀⢾⣿⠀⠀⠀⠠⠀⢀⠀⣀⣈⣤⣥⢁⣷⡜⡄⠃⠀⠀⠀⠀⠀⠀⠀⢀⠀⡈⣆⢢⣧⠀⠡⡀⠀⠸⣿⣶⡄⡀⠀⠀⠀⠀⠀⠀⠀⢿⣷⣤⣝⢿⣿⣦⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠙⠄⠀⠀⠀⠀⢠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣰⣫⣭⣻⡿⣿⢙⣛⠿⣿⣿⣿⣿⣷⣾⣿⣿⣿⣦⠻⠀⣼⠀⣡⣾⣿⣿⣿⣿⢟⡅⠈⠀⠇⠁⠀⠀⠀⠀⠀⠀⠀⠀⠈⣇⣿⣾⡞⣿⡇⡄⠘⡄⠀⢹⢿⣿⣮⠀⠀⠀⠀⠀⠀⠀⠀⠻⣿⣿⣧⡙⢿⣷⡀⠀⠀⠀⢢⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⣶⣾⣿⣿⣿⣿⣿⣶⣄⢻⣿⣮⡙⠟⣿⣏⣿⣿⣿⣷⣻⣧⢸⣏⣾⣿⣿⣿⣿⣿⣿⣿⡆⠀⠀⠀⠀⠀⠀⠐⠀⠀⠀⠀⠀⢷⣿⣿⣿⣿⣿⣿⣿⡀⠸⡄⢦⣷⣿⣿⣷⡀⠀⠀⠀⠀⠀⠀⠀⠈⠻⣿⣿⣄⠙⢧⠀⠀⠀⠀⢳⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢠⡀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠸⣿⣣⣶⣶⣬⣍⡻⣿⣿⢙⣿⣿⣿⣆⠈⢿⢹⣿⣿⣿⢁⣿⣇⢻⣿⣿⣿⣿⣿⣿⣿⡿⠁⠀⠀⠀⠀⢀⠀⢰⠀⢀⠀⢰⢰⢸⣿⣿⢿⢹⣿⣿⣿⡇⡆⣿⣼⣿⣿⣿⣟⣷⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⠏⢿⣧⠀⠀⠀⢠⠀⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢳⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣤⣤⣢⣶⣶⣶⣄⢣⣿⣿⣿⣿⡿⣛⣎⠉⣼⣿⣿⣶⣝⢧⠘⣿⣿⣿⣫⣌⠻⡿⢸⣿⣿⣿⣿⣿⠿⣋⠀⠀⠀⠀⠀⣴⠀⣰⡾⠀⣸⡆⠀⣸⣿⣿⣿⠈⢢⣿⣿⠹⡇⡇⣿⣿⣿⣿⢿⣿⣹⠇⠀⠀⠀⠀⢆⠀⠀⠀⠀⠀⠀⠹⣧⠀⠀⠈⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢷⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⣿⣿⣿⣿⣿⣿⣿⣿⡟⡙⠨⡕⢪⠢⠀⣿⣿⣿⣿⣾⣄⠀⢻⣿⣿⣿⣿⣷⠁⠸⢸⣿⣿⣿⣿⣿⣿⡇⠀⠀⢀⣾⠇⠀⡟⢡⣷⣿⣿⢠⣿⣿⣧⢹⡄⣾⣿⣿⡇⠃⠁⣿⣇⢻⣯⢣⢿⡏⠀⠀⠀⠀⢷⣄⢳⣄⠀⠀⠀⠀⠀⠘⡆⠀⠀⢸⣿⡄⠀⠀⠀⠘⡄⠀⠀⠀⠀⠀⠀⠀⠈⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⢿⣿⣿⣿⢿⣿⠿⠀⡯⠿⢅⣶⣾⣤⡀⣿⣿⣿⣿⣿⣿⣷⡌⠿⡟⣿⣿⣿⡇⠀⣾⣿⣿⣿⣿⣿⣿⠃⠀⠀⠎⠁⠀⠀⠀⢸⣿⣿⡟⣾⣿⣿⣿⠈⣷⣿⣿⣿⡇⠀⠀⣿⣿⠈⣿⠈⠘⣿⠀⠀⠀⢸⡜⣿⣷⣿⣷⡄⠀⠀⠀⠀⠀⠀⠀⠀⢿⣿⡀⠀⠀⠀⢱⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠘⣶⣴⣶⣶⣶⣦⣤⣀⡈⠻⣟⢉⣛⡛⠧⠈⠀⣾⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⠀⠇⣿⣿⢸⡇⣸⣿⠿⣛⢿⣿⣿⠏⠀⠀⠀⠀⠀⠀⠀⠀⢸⢿⡟⣼⢿⣿⣿⣿⠀⢿⣿⣿⣿⠀⠀⠀⣿⡿⠀⢿⠀⠀⢹⠀⠀⠀⠈⣿⡜⣿⣿⣿⣿⠀⠀⠀⠀⠀⠀⠀⠀⠀⢻⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠸⣻⢿⣿⣿⣿⣿⣿⠛⢽⡷⡌⢸⣿⡿⠗⠆⢠⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⢿⠸⠀⠀⣿⠇⣩⣶⣶⣭⣾⣿⣆⠹⠋⠀⠀⠀⠀⠀⠀⢀⡄⠀⠈⡼⣹⠏⣾⣿⣿⣿⡀⢸⣿⡿⠁⠀⠀⠀⢸⠇⠀⠈⠀⣄⠈⠀⠀⠀⠀⢹⣷⢹⣿⣿⣿⡇⣄⢀⠀⠀⠀⠀⠀⠀⠀⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⢀⣘⣿⣿⣿⣿⣿⣿⣧⠻⣿⡄⢈⣵⣯⣷⡢⡀⣿⣿⣿⣿⣿⣿⣿⣿⡿⡇⣿⣿⠂⣾⢦⠀⠙⢸⣿⠿⣿⣿⣿⣿⣿⠀⠀⢀⣀⠀⠀⢠⢃⣾⡤⠀⠀⢃⡟⠀⣿⣿⣿⣿⠁⢸⡿⠁⠀⠀⠀⠀⠈⠀⠀⠀⠀⣿⡀⠀⠀⠀⠀⠈⣿⠀⢿⣿⣿⡷⢿⠸⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠈⢙⡻⠿⠿⣿⣿⣿⣿⣧⢻⣿⡈⣿⣿⣿⣿⣧⢿⣿⣿⣿⣿⣿⣝⣗⠁⠀⣿⡿⠀⠛⠈⢀⣠⣌⣿⣿⣿⣿⣿⣿⣿⢀⣶⠏⠀⠀⠀⡠⢿⣿⡴⠁⠀⣼⠁⠀⣿⣿⣿⣿⠀⠈⠀⠀⡄⠀⠀⡄⠀⠀⠀⠀⠀⢸⡇⠀⠀⠀⠀⠀⢹⠀⠈⣿⣿⡇⢸⠄⡇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡼⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠙⢿⣿⣶⡩⡝⢿⣿⣷⣝⠣⠹⣿⣿⣿⣿⣯⠻⣿⣿⣿⣿⣿⡌⠀⠀⠘⢁⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⠇⠸⠀⠀⠀⢀⠜⢠⣿⡿⣡⢣⣷⡟⠀⠀⣿⢟⣿⡿⠀⠀⠀⣴⠃⠀⠀⣷⠀⠀⠀⠀⠀⢸⣿⠀⠀⡄⠀⠀⠀⠀⠀⢸⣿⡇⠸⠀⠇⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠺⣿⣿⣷⣝⢷⣬⣛⡻⢷⣦⣙⠿⠿⠟⠿⡷⠌⠊⠛⠻⠌⠁⠀⢀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⢻⣿⠏⠀⠀⠀⠀⢠⠏⠀⡾⠙⠁⢡⣿⣿⢃⡄⠀⠏⣼⣿⠇⢀⡆⣸⠏⠀⠀⣰⣿⠀⠀⠀⠀⠀⠘⡇⠀⢰⡇⠀⠀⠀⠀⠀⢸⡿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠘⢿⣿⣿⣷⡹⢿⡟⣣⣤⣤⣝⣻⣿⣿⠿⣿⣿⣶⣤⣀⡀⠀⣼⣿⣿⣿⣿⣿⣿⣿⣿⣿⡿⠁⠞⢁⡄⠀⠀⠀⠀⠎⠀⢸⠁⠀⣰⣿⡿⣿⣾⡇⠀⣸⣿⡟⢀⣾⢷⠏⠀⠀⢠⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⡾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠛⠻⠶⣝⡞⠻⣿⣿⣿⣿⣿⣿⣶⣭⣉⣛⠛⠉⢠⣿⣿⣿⣿⣿⣿⣿⣿⡿⠏⠀⠠⢴⣿⣅⠀⠀⠀⠀⠀⠀⡏⠀⠠⢋⡿⠁⣿⣿⡇⢀⣿⣿⠁⣾⠟⠈⠀⠀⠀⣾⡿⢁⠀⠀⠀⠀⠀⠀⠀⠀⣿⡇⠀⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⢀⣤⣴⣶⣶⣶⣶⣶⣄⡀⠀⠀⠀⠈⠙⠻⠿⣿⣿⣿⡿⠿⠟⠋⠀⠈⠛⠿⠿⠿⠿⠛⠛⠋⢀⣠⣼⣿⣿⠟⠁⠀⠀⠀⠀⠀⠀⢀⠀⠀⢸⠁⠀⣿⣿⠇⣾⣿⠃⠀⣿⠀⠀⠀⠀⢠⣿⣵⠃⠀⠀⠀⠀⠀⠀⠀⢰⣿⠃⢀⠀⠀⠀⠀⠀⠀⠀⠀⣰⠃⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⢸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣷⣶⡤⣀⣀⠤⣀⣤⣴⣶⡶⣿⣿⣛⣉⡥⠀⣠⡴⠊⠀⣠⣾⣿⣿⣿⣿⡿⠀⠀⠀⠀⠀⠀⠀⠀⣾⡆⠀⠀⠀⠀⣿⠿⢸⣿⠃⠀⠀⡇⠀⠀⠀⠀⣾⠟⠁⠀⠀⠀⠀⠀⠀⠀⠀⣼⠃⣴⠇⠀⠀⠀⠀⠀⠀⠀⢰⡟⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⣰⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣵⣿⣿⣿⣿⣿⣿⣿⣿⣿⠋⣠⠾⢋⣠⣴⣾⣿⣿⣿⣿⣿⣷⠆⠀⠀⢠⠎⠀⠀⠀⣸⠟⠀⠀⠀⠀⠀⠸⠀⣾⠃⠀⠀⠀⠁⠀⠀⠀⠘⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣡⡾⠃⠀⠀⠀⠀⠀⠀⠀⠀⠸⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⣨⣽⣿⣿⣿⣿⣿⣿⣿⣿⣿⢟⣽⣿⡿⣫⣾⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡏⠰⣶⣿⣿⣿⣿⣿⣿⣿⣿⣿⠛⠁⠀⢀⡴⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢿⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⢀⣨⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣬⣯⣿⠸⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⣿⡇⠀⡌⠙⠿⠿⣿⣿⡿⠟⠉⠀⠀⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠉⠻⠟⠿⢛⣿⣿⣿⣿⣿⣿⣿⣿⣿⡀⢏⣿⣿⣿⣿⣿⣿⣿⢿⡟⣿⠟⡡⠊⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠐⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠈⠉⠛⠱⣿⣻⣟⣿⠿⠋⠁⠨⣿⣿⣿⣿⣿⡿⠟⠀⠀⠀⠀⠀⠀⡀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡄⠀⠀⠂⠀⠀⠀⠈⠀⠐⠉⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⣿⠿⠋⠉⠀⠀⠀⠀⠀⠀⠠⢾⣶⣿⢂⣀⠀⠀⠀⠀⠀⠀⡠⠋⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠉⠀⠀⡠⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠁⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠠⠒⠈⠀⠀⠀⠀⢀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⢀⡴⠂⢀⣠⣤⠔⠀⠀⠀⠀⠀⡰⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠚⠁⢀⣠⠯⠟⠁⠀⠀⠀⠀⠀⠘⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠂⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⣠⡤⠖⠂⠀⠀⠀⠀⠀⠀⠀⠠⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠈⠉⠉⠁⠀⠀⠁⠀⠀⠀⠀⠀⠀⠀⠈⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀⠀
|
||||
|
||||
`);
|
||||
40
_js/src/clap-button.js
Normal file
40
_js/src/clap-button.js
Normal file
@@ -0,0 +1,40 @@
|
||||
// Copyright (c) 2020 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import "broadcastchannel-polyfill";
|
||||
import { webComponentsReady, stylesheetReady } from "./common";
|
||||
|
||||
(async () => {
|
||||
await Promise.all([
|
||||
...("customElements" in window
|
||||
? []
|
||||
: [
|
||||
import(
|
||||
/* webpackChunkName: "webcomponents" */ "./polyfills/webcomponents"
|
||||
).then(
|
||||
() =>
|
||||
import(/* webpackChunkName: "shadydom" */ "./polyfills/shadydom"),
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
await webComponentsReady;
|
||||
await stylesheetReady;
|
||||
|
||||
if (process.env.GET_CLAPS_API && !window.GET_CLAPS_API)
|
||||
window.GET_CLAPS_API = process.env.GET_CLAPS_API;
|
||||
|
||||
import(/* webpackMode: "eager" */ "@getclaps/button");
|
||||
})();
|
||||
202
_js/src/common.js
Normal file
202
_js/src/common.js
Normal file
@@ -0,0 +1,202 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Observable, of } from "rxjs";
|
||||
|
||||
// HACK: Temporary MS Edge fix
|
||||
// TODO: Move rx-element into separate file or module
|
||||
export {
|
||||
getScrollHeight,
|
||||
getScrollLeft,
|
||||
getScrollTop,
|
||||
} from "@hydecorp/component/lib/util";
|
||||
export { fromMediaQuery, fetchRx } from "@hydecorp/component/lib/creators";
|
||||
export { subscribeWhen, filterWhen } from "@hydecorp/component/lib/operators";
|
||||
export { createIntersectionObservable } from "@hydecorp/component/lib/observers";
|
||||
|
||||
const style = getComputedStyle(document.documentElement);
|
||||
|
||||
export const BREAK_POINT_3 = `(min-width: ${style.getPropertyValue("--break-point-3")})`;
|
||||
export const BREAK_POINT_DYNAMIC = `(min-width: ${style.getPropertyValue("--break-point-dynamic")})`;
|
||||
export const CONTENT_WIDTH_5 = parseFloat(
|
||||
style.getPropertyValue("--content-width-5"),
|
||||
);
|
||||
export const CONTENT_MARGIN_5 = parseFloat(
|
||||
style.getPropertyValue("--content-margin-5"),
|
||||
);
|
||||
export const DRAWER_WIDTH = parseFloat(
|
||||
style.getPropertyValue("--sidebar-width"),
|
||||
);
|
||||
export const HALF_CONTENT = parseFloat(
|
||||
style.getPropertyValue("--half-content"),
|
||||
);
|
||||
|
||||
// Check the user agent for Safari and iOS Safari, to give them some special treatment...
|
||||
const ua = navigator.userAgent.toLowerCase();
|
||||
export const isSafari = ua.indexOf("safari") > 0 && ua.indexOf("chrome") < 0;
|
||||
export const isMobile = ua.indexOf("mobile") > 0;
|
||||
export const isMobileSafari = isSafari && isMobile;
|
||||
export const isUCBrowser = ua.indexOf("ucbrowser") > 0;
|
||||
export const isFirefox = ua.indexOf("firefox") > 0;
|
||||
export const isFirefoxIOS = ua.indexOf("fxios") > 0 && ua.indexOf("safari") > 0;
|
||||
|
||||
export const hasCSSOM =
|
||||
"attributeStyleMap" in Element.prototype && "CSS" in window && CSS.number;
|
||||
|
||||
export const webComponentsReady = new Promise((res) => {
|
||||
if ("customElements" in window) res(true);
|
||||
else document.addEventListener("WebComponentsReady", res);
|
||||
});
|
||||
|
||||
// FIXME: Replace with something more robust!?
|
||||
export const stylesheetReady = new Promise(function checkCSS(
|
||||
res,
|
||||
rej,
|
||||
retries = 30,
|
||||
) {
|
||||
const drawerEl = document.querySelector("hy-drawer");
|
||||
if (!drawerEl) res(true);
|
||||
else if (getComputedStyle(drawerEl).getPropertyValue("--hy-drawer-width"))
|
||||
res(true);
|
||||
else if (retries <= 0) rej(Error("Stylesheet not loaded within 10 seconds"));
|
||||
else setTimeout(() => checkCSS(res, rej, retries - 1), 1000 / 3);
|
||||
});
|
||||
|
||||
export const once = (el, eventName) =>
|
||||
new Promise((res) => el.addEventListener(eventName, res, { once: true }));
|
||||
export const timeout = (t) => new Promise((res) => setTimeout(res, t));
|
||||
|
||||
// Takes an array of Modernizr feature tests and makes sure they all pass.
|
||||
export function hasFeatures(features) {
|
||||
if (!window.Modernizr) return true;
|
||||
return [...features].every((feature) => {
|
||||
const hasFeature = window.Modernizr[feature];
|
||||
if (!hasFeature && process.env.DEBUG)
|
||||
console.warn(`Feature '${feature}' missing!`);
|
||||
return hasFeature;
|
||||
});
|
||||
}
|
||||
|
||||
// Some functions to hide and show content.
|
||||
export function show() {
|
||||
this.style.display = "block";
|
||||
this.style.visibility = "visible";
|
||||
}
|
||||
|
||||
export function hide() {
|
||||
this.style.display = "none";
|
||||
this.style.visibility = "hidden";
|
||||
}
|
||||
|
||||
export function unshow() {
|
||||
this.style.display = "";
|
||||
this.style.visibility = "";
|
||||
}
|
||||
|
||||
export const unhide = unshow;
|
||||
|
||||
// Same as `el.innerHTML = ''`, but not quite so hacky.
|
||||
export function empty() {
|
||||
while (this?.firstChild) this.removeChild(this.firstChild);
|
||||
}
|
||||
|
||||
/**
|
||||
* An observable wrapper for the WebAnimations API.
|
||||
* Will return an observable that emits once when the animation finishes.
|
||||
* @param {HTMLElement|null} el
|
||||
* @param {AnimationKeyFrame | AnimationKeyFrame[] | null} effect
|
||||
* @param {number|AnimationEffectTiming} timing
|
||||
* @returns {Observable<Event>}
|
||||
*/
|
||||
export function animate(el, effect, timing) {
|
||||
if (!el) return of(new CustomEvent("finish"));
|
||||
|
||||
return Observable.create((observer) => {
|
||||
const anim = el.animate(effect, timing);
|
||||
|
||||
anim.addEventListener("finish", (e) => {
|
||||
observer.next(e);
|
||||
requestAnimationFrame(() => {
|
||||
requestAnimationFrame(() => observer.complete());
|
||||
});
|
||||
});
|
||||
|
||||
return () => {
|
||||
if (anim.playState !== "finished") anim.cancel();
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {string} templateId
|
||||
* @returns {HTMLElement|null}
|
||||
*/
|
||||
export function importTemplate(templateId) {
|
||||
const template = document.getElementById(templateId);
|
||||
return template && document.importNode(template.content, true);
|
||||
}
|
||||
|
||||
export const body = document.body || document.documentElement;
|
||||
export const rem = (units) =>
|
||||
units * parseFloat(getComputedStyle(body).fontSize);
|
||||
export const getViewWidth = () => window.innerWidth || body.clientWidth;
|
||||
export const getViewHeight = () => window.innerHeight || body.clientHeight;
|
||||
|
||||
/**
|
||||
* @template Q
|
||||
* @template S
|
||||
* @param {Worker} worker
|
||||
* @param {Q} message
|
||||
* @returns {Promise<S>}
|
||||
*/
|
||||
export function postMessage(worker, message) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const messageChannel = new MessageChannel();
|
||||
messageChannel.port1.onmessage = (event) => {
|
||||
if (event.data.error) {
|
||||
reject(event.data.error);
|
||||
} else {
|
||||
resolve(event.data);
|
||||
}
|
||||
};
|
||||
worker.postMessage(message, [messageChannel.port2]);
|
||||
});
|
||||
}
|
||||
|
||||
const promisifyLoad = (loadFn) => (href) =>
|
||||
new Promise((r) => loadFn(href).addEventListener("load", r));
|
||||
|
||||
/** @type {(href: string) => Promise<Event>} */
|
||||
export const loadJS = promisifyLoad(window.loadJS);
|
||||
|
||||
/** @type {(href: string) => Promise<Event>} */
|
||||
export const loadCSS = promisifyLoad(window.loadCSS);
|
||||
|
||||
/**
|
||||
* @param {ArrayLike<Element>} els
|
||||
* @param {IntersectionObserverInit} [options]
|
||||
* @returns {Promise<IntersectionObserverEntry>}
|
||||
*/
|
||||
export function intersectOnce(els, options) {
|
||||
return new Promise((res) => {
|
||||
const io = new IntersectionObserver((entries) => {
|
||||
if (entries.some((x) => x.isIntersecting)) {
|
||||
els.forEach((el) => io.unobserve(el));
|
||||
res(entries.find((x) => x.isIntersecting));
|
||||
}
|
||||
}, options);
|
||||
els.forEach((el) => io.observe(el));
|
||||
});
|
||||
}
|
||||
166
_js/src/cross-fader.js
Normal file
166
_js/src/cross-fader.js
Normal file
@@ -0,0 +1,166 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { EMPTY, of } from "rxjs";
|
||||
import { catchError, finalize, map, switchMap } from "rxjs/operators";
|
||||
|
||||
import { animate, fetchRx } from "./common";
|
||||
|
||||
const RE_CSS_URL = /url\s*\(['"]?(([^'"\\]|\\.)*)['"]?\)/u;
|
||||
|
||||
/** @param {Document} doc */
|
||||
const calcHash = (doc) => {
|
||||
const sidebar = doc.getElementById("_sidebar");
|
||||
const sidebarBg = sidebar?.querySelector(".sidebar-bg");
|
||||
const pageStyle = doc.getElementById("_pageStyle");
|
||||
const source = doc.getElementById("source");
|
||||
// const rule = Array.from(pageStyle?.sheet?.rules ?? []).find(r => r.selectorText === 'html');
|
||||
// const accentColor = rule?.style.getPropertyValue('--accent-color') ?? '';
|
||||
// const themeColor = rule?.style.getPropertyValue('--theme-color') ?? '';
|
||||
return [
|
||||
pageStyle?.innerText?.trim(),
|
||||
sidebar?.classList,
|
||||
sidebarBg?.classList,
|
||||
sidebarBg?.style.backgroundImage,
|
||||
source?.src,
|
||||
].join("\n");
|
||||
};
|
||||
|
||||
/**
|
||||
* Consider a URL external if either the protocol, hostname or port is different.
|
||||
* @param {URL} param0
|
||||
* @param {Location=} location
|
||||
*/
|
||||
function isExternal({ protocol, host }, location = window.location) {
|
||||
return protocol !== location.protocol || host !== location.host;
|
||||
}
|
||||
|
||||
const objectURLs = new WeakMap();
|
||||
|
||||
export class CrossFader {
|
||||
/** @param {number} fadeDuration */
|
||||
constructor(fadeDuration) {
|
||||
this.sidebar = document.getElementById("_sidebar");
|
||||
this.fadeDuration = fadeDuration;
|
||||
this.prevHash = calcHash(document);
|
||||
this.themeColorEl = document.querySelector('meta[name="theme-color"]');
|
||||
}
|
||||
|
||||
/** @param {Document} newDocument */
|
||||
fetchImage2(newDocument) {
|
||||
const sidebarBg = newDocument.querySelector(".sidebar-bg");
|
||||
const video = sidebarBg?.querySelector("source");
|
||||
const { backgroundImage = "" } = sidebarBg?.style ?? {};
|
||||
const result = RE_CSS_URL.exec(backgroundImage);
|
||||
const videoUrl = video?.src;
|
||||
if (!result) {
|
||||
return of("");
|
||||
}
|
||||
|
||||
const url = new URL(result[1], window.location.origin);
|
||||
|
||||
return fetchRx(url.href, {
|
||||
method: "GET",
|
||||
headers: { Accept: "image/*" },
|
||||
...(isExternal(url) ? { mode: "cors" } : {}),
|
||||
}).pipe(
|
||||
switchMap((r) => r.blob()),
|
||||
map((blob) => [URL.createObjectURL(blob), videoUrl]),
|
||||
catchError(() => of(url.href)),
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {Document} newDocument */
|
||||
fetchImage(newDocument) {
|
||||
const hash = calcHash(newDocument);
|
||||
if (hash === this.prevHash) return EMPTY;
|
||||
|
||||
return this.fetchImage2(newDocument).pipe(
|
||||
map(([objectUrl, videoUrl]) => {
|
||||
/** @type {HTMLDivElement} */
|
||||
const div =
|
||||
newDocument.querySelector(".sidebar-bg") ??
|
||||
document.createElement("div");
|
||||
|
||||
if (objectUrl) {
|
||||
div.style.backgroundImage = `url(${objectUrl})`;
|
||||
objectURLs.set(div, objectUrl);
|
||||
}
|
||||
|
||||
const video = div.querySelector("video");
|
||||
|
||||
if (video && videoUrl) {
|
||||
video.querySelector("source").src = videoUrl;
|
||||
}
|
||||
|
||||
return [div, hash, newDocument];
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
/** @param {Document} newDocument */
|
||||
updateStyle(newDocument) {
|
||||
const classList = newDocument.getElementById("_sidebar")?.classList;
|
||||
if (classList) this.sidebar.setAttribute("class", classList);
|
||||
|
||||
if (this.themeColorEl) {
|
||||
const themeColor = newDocument.head.querySelector(
|
||||
'meta[name="theme-color"]',
|
||||
)?.content;
|
||||
if (themeColor) {
|
||||
window.setTimeout(() => {
|
||||
if (this.themeColorEl) {
|
||||
this.themeColorEl.content = themeColor;
|
||||
}
|
||||
}, 250);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
const pageStyle = document.getElementById("_pageStyle");
|
||||
const newPageStyle = newDocument.getElementById("_pageStyle");
|
||||
if (!newPageStyle) return;
|
||||
pageStyle?.parentNode?.replaceChild(newPageStyle, pageStyle);
|
||||
} catch (e) {
|
||||
if (process.env.DEBUG) console.error(e);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {[HTMLDivElement]} param0
|
||||
* @param {[HTMLDListElement, string, Document]} param1
|
||||
*/
|
||||
fade([prevDiv], [div, hash, newDocument]) {
|
||||
prevDiv?.parentNode?.insertBefore(div, prevDiv.nextElementSibling);
|
||||
|
||||
this.updateStyle(newDocument);
|
||||
|
||||
// Only update the prev hash after we're actually in the fade stage
|
||||
this.prevHash = hash;
|
||||
|
||||
return animate(div, [{ opacity: 0 }, { opacity: 1 }], {
|
||||
duration: this.fadeDuration,
|
||||
easing: "ease",
|
||||
}).pipe(
|
||||
finalize(() => {
|
||||
if (objectURLs.has(prevDiv))
|
||||
URL.revokeObjectURL(objectURLs.get(prevDiv));
|
||||
prevDiv?.parentNode?.removeChild(prevDiv);
|
||||
|
||||
div.querySelector("video").play();
|
||||
}),
|
||||
);
|
||||
}
|
||||
}
|
||||
81
_js/src/dark-mode.js
Normal file
81
_js/src/dark-mode.js
Normal file
@@ -0,0 +1,81 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
|
||||
import { importTemplate, stylesheetReady, once } from "./common";
|
||||
|
||||
const SEL_NAVBAR_BTN_BAR = "#_navbar > .content > .nav-btn-bar";
|
||||
|
||||
(async () => {
|
||||
await stylesheetReady;
|
||||
|
||||
const darkMode = importTemplate("_dark-mode-template");
|
||||
if (darkMode) {
|
||||
const navbarEl = document.querySelector(SEL_NAVBAR_BTN_BAR);
|
||||
navbarEl?.insertBefore(
|
||||
darkMode,
|
||||
navbarEl.querySelector(".nav-insert-marker"),
|
||||
);
|
||||
|
||||
const metaEl = document.querySelector('meta[name="color-scheme"]');
|
||||
let tId;
|
||||
|
||||
const navbarBtn = document.getElementById("_dark-mode");
|
||||
navbarBtn?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
clearTimeout(tId);
|
||||
const list = document.body.classList;
|
||||
if (
|
||||
list.contains("dark-mode") ||
|
||||
("_sunset" in window &&
|
||||
!list.contains("light-mode") &&
|
||||
matchMedia("(prefers-color-scheme: dark)").matches)
|
||||
) {
|
||||
list.remove("dark-mode");
|
||||
list.add("light-mode");
|
||||
tId = setTimeout(() => {
|
||||
if (metaEl) metaEl.content = "light";
|
||||
document.documentElement.style.colorScheme = "light";
|
||||
}, 250);
|
||||
navbarBtn.dispatchEvent(
|
||||
new CustomEvent("pivoine-dark-mode-toggle", {
|
||||
detail: false,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
} else {
|
||||
list.remove("light-mode");
|
||||
list.add("dark-mode");
|
||||
tId = setTimeout(() => {
|
||||
if (metaEl) metaEl.content = "dark";
|
||||
document.documentElement.style.colorScheme = "dark";
|
||||
}, 250);
|
||||
navbarBtn.dispatchEvent(
|
||||
new CustomEvent("pivoine-dark-mode-toggle", {
|
||||
detail: true,
|
||||
bubbles: true,
|
||||
}),
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
await once(document, "click");
|
||||
|
||||
const styleSheets = Array.from(document.styleSheets);
|
||||
const inlineSheet = styleSheets.find(
|
||||
(s) => s.ownerNode?.id === "_styleInline",
|
||||
);
|
||||
const linkSheet = styleSheets.find(
|
||||
(s) => s.ownerNode?.id === "_stylePreload",
|
||||
);
|
||||
const setRule = (sheet) => {
|
||||
if (!sheet) return;
|
||||
const rule = Array.from(sheet.rules).find((rule) =>
|
||||
rule.selectorText.startsWith(".color-transition"),
|
||||
);
|
||||
if (rule)
|
||||
rule.style.transition =
|
||||
"background-color 1s ease, border-color 1s ease";
|
||||
};
|
||||
setRule(inlineSheet);
|
||||
setRule(linkSheet);
|
||||
}
|
||||
})();
|
||||
275
_js/src/drawer.js
Normal file
275
_js/src/drawer.js
Normal file
@@ -0,0 +1,275 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { fromEvent, merge, NEVER, combineLatest } from "rxjs";
|
||||
import {
|
||||
distinctUntilChanged,
|
||||
map,
|
||||
filter,
|
||||
startWith,
|
||||
switchMap,
|
||||
tap,
|
||||
throttleTime,
|
||||
withLatestFrom,
|
||||
} from "rxjs/operators";
|
||||
|
||||
import {
|
||||
BREAK_POINT_3,
|
||||
BREAK_POINT_DYNAMIC,
|
||||
isSafari,
|
||||
isMobile,
|
||||
isMobileSafari,
|
||||
hasCSSOM,
|
||||
webComponentsReady,
|
||||
stylesheetReady,
|
||||
getScrollTop,
|
||||
getViewWidth,
|
||||
fromMediaQuery,
|
||||
} from "./common";
|
||||
|
||||
(async () => {
|
||||
await Promise.all([
|
||||
...("customElements" in window
|
||||
? []
|
||||
: [
|
||||
import(
|
||||
/* webpackChunkName: "webcomponents" */ "./polyfills/webcomponents"
|
||||
).then(
|
||||
() =>
|
||||
import(/* webpackChunkName: "shadydom" */ "./polyfills/shadydom"),
|
||||
),
|
||||
]),
|
||||
...("ResizeObserver" in window
|
||||
? []
|
||||
: [
|
||||
import(
|
||||
/* webpackChunkName: "resize-observer" */ "./polyfills/resize-observer"
|
||||
),
|
||||
]),
|
||||
]);
|
||||
|
||||
await webComponentsReady;
|
||||
await stylesheetReady;
|
||||
|
||||
const MOBILE = 1;
|
||||
const DESKTOP = 2;
|
||||
const LARGE_DESKTOP = 3;
|
||||
|
||||
const subscribeWhen = (p$) => (source) => {
|
||||
if (process.env.DEBUG && !p$) throw Error();
|
||||
return p$.pipe(switchMap((p) => (p ? source : NEVER)));
|
||||
};
|
||||
|
||||
// Determines the range from which to draw the drawer in pixels, counted from the left edge.
|
||||
// It depends on the browser, e.g. Safari has a native gesture when sliding form the side,
|
||||
// so we ignore the first 35 pixels (roughly the range for the native gesture),
|
||||
// to avoid triggering both gestures.
|
||||
function getRange(drawerWidth, size) {
|
||||
if (size >= DESKTOP) return [0, drawerWidth];
|
||||
if (isMobileSafari) return [35, 150];
|
||||
return [0, 150];
|
||||
}
|
||||
|
||||
// The functions below add an svg graphic to the sidebar
|
||||
// that indicate that the sidebar can be drawn using touch gestures.
|
||||
function setupIcon(drawerEl) {
|
||||
const img = document.getElementById("_hrefSwipeSVG");
|
||||
if (img) {
|
||||
const svg = document.createElement("img");
|
||||
svg.id = "_swipe";
|
||||
svg.src = img.href;
|
||||
svg.alt = "Swipe image";
|
||||
svg.addEventListener("click", () => drawerEl.close());
|
||||
document.getElementById("_sidebar")?.appendChild(svg);
|
||||
}
|
||||
}
|
||||
|
||||
function removeIcon() {
|
||||
const svg = document.getElementById("_swipe");
|
||||
svg?.parentNode?.removeChild(svg);
|
||||
}
|
||||
|
||||
const detectSize = () =>
|
||||
window.matchMedia(BREAK_POINT_DYNAMIC).matches
|
||||
? LARGE_DESKTOP
|
||||
: window.matchMedia(BREAK_POINT_3).matches
|
||||
? DESKTOP
|
||||
: MOBILE;
|
||||
|
||||
// First we get hold of some DOM elements.
|
||||
const drawerEl = document.getElementById("_drawer");
|
||||
const sidebarEl = document.getElementById("_sidebar");
|
||||
const contentEl = sidebarEl?.querySelector(".sidebar-sticky");
|
||||
if (!drawerEl || !sidebarEl || !contentEl) return;
|
||||
|
||||
document.getElementById("_menu")?.addEventListener("click", (e) => {
|
||||
e.preventDefault();
|
||||
e.stopPropagation();
|
||||
drawerEl.toggle();
|
||||
});
|
||||
|
||||
sidebarEl
|
||||
.querySelectorAll('a[href^="/"]:not(.external)')
|
||||
.forEach((el) => el.addEventListener("click", () => drawerEl.close()));
|
||||
|
||||
if (isSafari) drawerEl.setAttribute("threshold", "0");
|
||||
if (!isMobile) drawerEl.setAttribute("mouseevents", "");
|
||||
|
||||
const [tValue, oValue] = hasCSSOM
|
||||
? [
|
||||
new CSSTransformValue([new CSSTranslate(CSS.px(0), CSS.px(0))]),
|
||||
CSS.number(1),
|
||||
]
|
||||
: [null, null];
|
||||
|
||||
const updateSidebar = (t, size, distance) => {
|
||||
const value = distance * t;
|
||||
const opacity = size >= DESKTOP ? 1 : 1 - t;
|
||||
if (hasCSSOM) {
|
||||
tValue[0].x.value = value;
|
||||
oValue.value = opacity;
|
||||
sidebarEl.attributeStyleMap.set("transform", tValue);
|
||||
contentEl.attributeStyleMap.set("opacity", oValue);
|
||||
} else {
|
||||
sidebarEl.style.transform = `translateX(${value}px)`;
|
||||
contentEl.style.opacity = opacity;
|
||||
}
|
||||
};
|
||||
|
||||
// A flag for the 3 major viewport sizes we support
|
||||
const size$ = merge(
|
||||
fromMediaQuery(window.matchMedia(BREAK_POINT_3)),
|
||||
fromMediaQuery(window.matchMedia(BREAK_POINT_DYNAMIC)),
|
||||
).pipe(startWith({}), map(detectSize));
|
||||
|
||||
// An observable keeping track of the drawer (peek) width.
|
||||
const peekWidth$ = fromEvent(drawerEl, "peek-width-change").pipe(
|
||||
map((e) => e.detail),
|
||||
);
|
||||
|
||||
// An observable keeping track the viewport width
|
||||
const viewWidth$ = fromEvent(window, "resize", { passive: true }).pipe(
|
||||
startWith({}),
|
||||
map(getViewWidth),
|
||||
);
|
||||
|
||||
// An observable keeping track of the distance between
|
||||
// the middle point of the screen and the middle point of the drawer.
|
||||
const distance$ = combineLatest(peekWidth$, viewWidth$).pipe(
|
||||
map(([drawerWidth, viewWidth]) => viewWidth / 2 - drawerWidth / 2),
|
||||
);
|
||||
|
||||
const t$ = merge(
|
||||
distance$.pipe(
|
||||
map(() =>
|
||||
drawerEl.opacity !== undefined ? 1 - drawerEl.opacity : opened ? 0 : 1,
|
||||
),
|
||||
),
|
||||
fromEvent(drawerEl, "hy-drawer-move").pipe(
|
||||
map(({ detail: { opacity } }) => {
|
||||
return 1 - opacity;
|
||||
}),
|
||||
),
|
||||
);
|
||||
|
||||
drawerEl.addEventListener("hy-drawer-prepare", () => {
|
||||
sidebarEl.style.willChange = "transform";
|
||||
contentEl.style.willChange = "opacity";
|
||||
});
|
||||
|
||||
drawerEl.addEventListener("hy-drawer-transitioned", () => {
|
||||
sidebarEl.style.willChange = "";
|
||||
contentEl.style.willChange = "";
|
||||
});
|
||||
|
||||
// Save scroll position before the drawer gets initialized.
|
||||
const scrollTop = getScrollTop();
|
||||
|
||||
// Start the drawer in `opened` state when the cover class is present,
|
||||
// and the user hasn't started scrolling already.
|
||||
const opened =
|
||||
drawerEl.classList.contains("cover") &&
|
||||
scrollTop <= 0 &&
|
||||
!(history.state && history.state.closedOnce);
|
||||
|
||||
if (!opened) {
|
||||
if (!history.state) history.replaceState({}, document.title);
|
||||
history.state.closedOnce = true;
|
||||
drawerEl.removeAttribute("opened");
|
||||
}
|
||||
|
||||
const opened$ = fromEvent(drawerEl, "hy-drawer-transitioned").pipe(
|
||||
map((e) => e.detail),
|
||||
distinctUntilChanged(),
|
||||
tap((opened) => {
|
||||
if (!opened) {
|
||||
removeIcon();
|
||||
if (!history.state) history.replaceState({}, document.title);
|
||||
history.state.closedOnce = true;
|
||||
}
|
||||
}),
|
||||
startWith(opened),
|
||||
);
|
||||
|
||||
// We need the height of the drawer in case we need to reset the scroll position
|
||||
const drawerHeight = opened ? null : drawerEl.getBoundingClientRect().height;
|
||||
|
||||
drawerEl.addEventListener(
|
||||
"hy-drawer-init",
|
||||
() => {
|
||||
drawerEl.classList.add("loaded");
|
||||
|
||||
setupIcon(drawerEl);
|
||||
|
||||
if (drawerHeight && scrollTop >= drawerHeight) {
|
||||
window.scrollTo(0, scrollTop - drawerHeight);
|
||||
}
|
||||
},
|
||||
{ once: true },
|
||||
);
|
||||
|
||||
await import(/* webpackMode: "eager" */ "@hydecorp/drawer");
|
||||
|
||||
window._drawer = drawerEl;
|
||||
|
||||
t$.pipe(
|
||||
withLatestFrom(size$, distance$),
|
||||
tap((args) => updateSidebar(...args)),
|
||||
).subscribe();
|
||||
|
||||
// Keeping the drawer updated.
|
||||
peekWidth$
|
||||
.pipe(
|
||||
withLatestFrom(size$),
|
||||
map((args) => getRange(...args)),
|
||||
tap((range) => {
|
||||
drawerEl.range = range;
|
||||
}),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
// Hacky way of letting the cover page close when scrolling
|
||||
fromEvent(document, "wheel", { passive: false })
|
||||
.pipe(
|
||||
subscribeWhen(opened$),
|
||||
filter((e) => e.deltaY > 0),
|
||||
tap((e) => {
|
||||
if (drawerEl.translateX > 0) e.preventDefault();
|
||||
}),
|
||||
throttleTime(500),
|
||||
tap(() => drawerEl.close()),
|
||||
)
|
||||
.subscribe();
|
||||
})();
|
||||
78
_js/src/entry.js
Normal file
78
_js/src/entry.js
Normal file
@@ -0,0 +1,78 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import "@babel/polyfill";
|
||||
|
||||
import "../lib/version";
|
||||
import "../lib/modernizr-custom";
|
||||
import { hasFeatures } from "./common";
|
||||
|
||||
__webpack_public_path__ = window._publicPath;
|
||||
|
||||
const BASELINE = ["classlist", "eventlistener", "queryselector", "template"];
|
||||
const DARK_MODE_FEATURES = ["customproperties"];
|
||||
const DRAWER_FEATURES = [
|
||||
"customproperties",
|
||||
"history",
|
||||
"matchmedia",
|
||||
"opacity",
|
||||
];
|
||||
const PUSH_STATE_FEATURES = [
|
||||
"history",
|
||||
"matchmedia",
|
||||
"opacity",
|
||||
"cssanimations",
|
||||
"cssremunit",
|
||||
"documentfragment",
|
||||
];
|
||||
const CLAP_BUTTON_FEATURES = [
|
||||
"customproperties",
|
||||
"cssanimations",
|
||||
"cssremunit",
|
||||
];
|
||||
const TOC_FEATURES = ["matchmedia", "cssremunit"];
|
||||
|
||||
if (hasFeatures(BASELINE)) {
|
||||
import(/* webpackMode: "eager" */ "./upgrades");
|
||||
|
||||
if (!window._noNavbar) import(/* webpackChunkName: "navbar" */ "./navbar");
|
||||
if (!window._noLightbox)
|
||||
import(/* webpackChunkName: "lightbox" */ "./lightbox");
|
||||
if (!window._noSound) import(/* webpackChunkName: "sound" */ "./sound");
|
||||
|
||||
if (hasFeatures(DARK_MODE_FEATURES)) {
|
||||
// import(/* webpackMode: "eager" */ './pro/cookies-banner');
|
||||
import(/* webpackMode: "eager" */ "./dark-mode");
|
||||
}
|
||||
|
||||
if (!window._noSearch) import(/* webpackChunkName: "search" */ "./search");
|
||||
|
||||
if (window._clapButton && hasFeatures(CLAP_BUTTON_FEATURES)) {
|
||||
import(/* webpackChunkName: "clap-button" */ "./clap-button");
|
||||
}
|
||||
|
||||
// A list of Modernizr tests that are required for the drawer to work.
|
||||
if (!window._noDrawer && hasFeatures(DRAWER_FEATURES)) {
|
||||
import(/* webpackChunkName: "drawer" */ "./drawer");
|
||||
}
|
||||
|
||||
if (!window._noPushState && hasFeatures(PUSH_STATE_FEATURES)) {
|
||||
import(/* webpackChunkName: "push-state" */ "./push-state");
|
||||
}
|
||||
|
||||
// if (!window._noToc && hasFeatures(TOC_FEATURES)) {
|
||||
// import(/* webpackChunkName: "toc" */ './pro/toc');
|
||||
// }
|
||||
}
|
||||
29
_js/src/flip/index.js
Normal file
29
_js/src/flip/index.js
Normal file
@@ -0,0 +1,29 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { merge } from "rxjs";
|
||||
import { filter } from "rxjs/operators";
|
||||
|
||||
import { setupFLIPTitle } from "./title";
|
||||
|
||||
const FLIP_TYPES = ["title"];
|
||||
|
||||
export function setupFLIP(start$, ready$, fadeIn$, options) {
|
||||
const other$ = start$.pipe(
|
||||
filter(({ flipType }) => !FLIP_TYPES.includes(flipType)),
|
||||
);
|
||||
|
||||
return merge(setupFLIPTitle(start$, ready$, fadeIn$, options), other$);
|
||||
}
|
||||
116
_js/src/flip/title.js
Normal file
116
_js/src/flip/title.js
Normal file
@@ -0,0 +1,116 @@
|
||||
// Copyright (c) 2019 Florian Klampfer <https://qwtel.com/>
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU General Public License
|
||||
// along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import { Observable, of, zip } from "rxjs";
|
||||
import { tap, finalize, filter, map, switchMap } from "rxjs/operators";
|
||||
|
||||
import { animate, empty } from "../common";
|
||||
|
||||
const TITLE_SELECTOR = ".page-title, .post-title";
|
||||
|
||||
/**
|
||||
* @param {Observable<any>} start$
|
||||
* @param {Observable<any>} ready$
|
||||
* @param {Observable<any>} fadeIn$
|
||||
* @param {any} opts
|
||||
*/
|
||||
export function setupFLIPTitle(
|
||||
start$,
|
||||
ready$,
|
||||
fadeIn$,
|
||||
{ animationMain, settings },
|
||||
) {
|
||||
if (!animationMain) return start$;
|
||||
|
||||
const flip$ = start$.pipe(
|
||||
filter(({ flipType }) => flipType === "title"),
|
||||
switchMap(({ anchor }) => {
|
||||
if (!anchor) return of({});
|
||||
|
||||
const title = document.createElement("h1");
|
||||
|
||||
title.classList.add("page-title");
|
||||
title.textContent = anchor.textContent;
|
||||
title.style.transformOrigin = "left top";
|
||||
|
||||
const page = animationMain.querySelector(".page");
|
||||
if (!page) return of({});
|
||||
|
||||
empty.call(page);
|
||||
page.appendChild(title);
|
||||
|
||||
animationMain.style.position = "fixed";
|
||||
animationMain.style.opacity = 1;
|
||||
|
||||
const first = anchor.getBoundingClientRect();
|
||||
const last = title.getBoundingClientRect();
|
||||
const firstFontSize = parseInt(getComputedStyle(anchor).fontSize, 10);
|
||||
const lastFontSize = parseInt(getComputedStyle(title).fontSize, 10);
|
||||
|
||||
const invertX = first.left - last.left;
|
||||
const invertY = first.top - last.top;
|
||||
const invertScale = firstFontSize / lastFontSize;
|
||||
|
||||
anchor.style.opacity = 0;
|
||||
|
||||
const transform = [
|
||||
{
|
||||
transform: `translate3d(${invertX}px, ${invertY}px, 0) scale(${invertScale})`,
|
||||
},
|
||||
{ transform: "translate3d(0, 0, 0) scale(1)" },
|
||||
];
|
||||
|
||||
return animate(title, transform, settings).pipe(
|
||||
tap({
|
||||
complete() {
|
||||
animationMain.style.position = "absolute";
|
||||
},
|
||||
}),
|
||||
);
|
||||
}),
|
||||
);
|
||||
|
||||
start$
|
||||
.pipe(
|
||||
switchMap(({ flipType }) =>
|
||||
zip(
|
||||
ready$.pipe(
|
||||
filter(() => flipType === "title"),
|
||||
map(({ replaceEls: [main] }) => {
|
||||
const title = main.querySelector(TITLE_SELECTOR);
|
||||
if (title) title.style.opacity = 0;
|
||||
return title;
|
||||
}),
|
||||
),
|
||||
fadeIn$,
|
||||
).pipe(
|
||||
map(([x]) => x),
|
||||
tap((title) => {
|
||||
if (title) title.style.opacity = 1;
|
||||
animationMain.style.opacity = 0;
|
||||
}),
|
||||
finalize(() => {
|
||||
animationMain.style.opacity = 0;
|
||||
|
||||
const page = animationMain.querySelector(".page");
|
||||
empty.call(page);
|
||||
}),
|
||||
),
|
||||
),
|
||||
)
|
||||
.subscribe();
|
||||
|
||||
return flip$;
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user