fix: inline stack theme
@ -1 +0,0 @@
|
|||||||
Subproject commit 3e123a30b79b5d52a3a8e88a9dd678fcfd28e418
|
|
||||||
68
themes/stack/.ci/build.sh
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
# @file
|
||||||
|
# Builds a Hugo site hosted on a Cloudflare Worker.
|
||||||
|
#
|
||||||
|
# The Cloudflare Worker automatically installs Node.js dependencies.
|
||||||
|
#------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
main() {
|
||||||
|
DART_SASS_VERSION=1.97.1
|
||||||
|
GO_VERSION=1.25.5
|
||||||
|
HUGO_VERSION=0.160.1
|
||||||
|
NODE_VERSION=24.12.0
|
||||||
|
|
||||||
|
export TZ=Europe/Oslo
|
||||||
|
|
||||||
|
# Install Dart Sass
|
||||||
|
echo "Installing Dart Sass ${DART_SASS_VERSION}..."
|
||||||
|
curl -sLJO "https://github.com/sass/dart-sass/releases/download/${DART_SASS_VERSION}/dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz"
|
||||||
|
tar -C "${HOME}/.local" -xf "dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz"
|
||||||
|
rm "dart-sass-${DART_SASS_VERSION}-linux-x64.tar.gz"
|
||||||
|
export PATH="${HOME}/.local/dart-sass:${PATH}"
|
||||||
|
|
||||||
|
# Install Go
|
||||||
|
echo "Installing Go ${GO_VERSION}..."
|
||||||
|
curl -sLJO "https://go.dev/dl/go${GO_VERSION}.linux-amd64.tar.gz"
|
||||||
|
tar -C "${HOME}/.local" -xf "go${GO_VERSION}.linux-amd64.tar.gz"
|
||||||
|
rm "go${GO_VERSION}.linux-amd64.tar.gz"
|
||||||
|
export PATH="${HOME}/.local/go/bin:${PATH}"
|
||||||
|
|
||||||
|
# Install Hugo
|
||||||
|
echo "Installing Hugo ${HUGO_VERSION}..."
|
||||||
|
curl -sLJO "https://github.com/gohugoio/hugo/releases/download/v${HUGO_VERSION}/hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
|
||||||
|
mkdir "${HOME}/.local/hugo"
|
||||||
|
tar -C "${HOME}/.local/hugo" -xf "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
|
||||||
|
rm "hugo_extended_${HUGO_VERSION}_linux-amd64.tar.gz"
|
||||||
|
export PATH="${HOME}/.local/hugo:${PATH}"
|
||||||
|
|
||||||
|
# Install Node.js
|
||||||
|
echo "Installing Node.js ${NODE_VERSION}..."
|
||||||
|
curl -sLJO "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-x64.tar.xz"
|
||||||
|
tar -C "${HOME}/.local" -xf "node-v${NODE_VERSION}-linux-x64.tar.xz"
|
||||||
|
rm "node-v${NODE_VERSION}-linux-x64.tar.xz"
|
||||||
|
export PATH="${HOME}/.local/node-v${NODE_VERSION}-linux-x64/bin:${PATH}"
|
||||||
|
|
||||||
|
# Verify installations
|
||||||
|
echo "Verifying installations..."
|
||||||
|
echo Dart Sass: "$(sass --version)"
|
||||||
|
echo Go: "$(go version)"
|
||||||
|
echo Hugo: "$(hugo version)"
|
||||||
|
echo Node.js: "$(node --version)"
|
||||||
|
|
||||||
|
# Configure Git
|
||||||
|
echo "Configuring Git..."
|
||||||
|
git config core.quotepath false
|
||||||
|
if [ "$(git rev-parse --is-shallow-repository)" = "true" ]; then
|
||||||
|
git fetch --unshallow
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Build the site
|
||||||
|
echo "Building the site..."
|
||||||
|
cd demo
|
||||||
|
hugo --gc --minify --themesDir=/opt/buildhome --theme=repo
|
||||||
|
}
|
||||||
|
|
||||||
|
set -euo pipefail
|
||||||
|
main "$@"
|
||||||
2
themes/stack/.github/FUNDING.yml
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
ko_fi: jimmycai
|
||||||
|
github: CaiJimmy
|
||||||
65
themes/stack/.github/ISSUE_TEMPLATE/bug_report.yml
vendored
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
name: Bug Report
|
||||||
|
description: File a bug report
|
||||||
|
body:
|
||||||
|
- type: markdown
|
||||||
|
attributes:
|
||||||
|
value: |
|
||||||
|
Thanks for taking the time to fill out this bug report! Please provide as much information as possible and make sure you have checked the [documentation](https://stack.jimmycai.com/guide/).
|
||||||
|
- type: textarea
|
||||||
|
id: what-happened
|
||||||
|
attributes:
|
||||||
|
label: What happened?
|
||||||
|
description: Also tell us, what did you expect to happen? Please be as detailed as possible, including screenshots and any other information that might help us reproduce the problem.
|
||||||
|
placeholder: Tell us what you see!
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: hugo-version
|
||||||
|
attributes:
|
||||||
|
label: Hugo version
|
||||||
|
description: "What is the version of Hugo you are using? (Note: this theme does not support non-extended version of Hugo)"
|
||||||
|
placeholder: ex. 0.100.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: input
|
||||||
|
id: theme-version
|
||||||
|
attributes:
|
||||||
|
label: Theme version
|
||||||
|
description: "What is the version of Stack theme you are using?"
|
||||||
|
placeholder: ex. 3.12.0
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
|
- type: dropdown
|
||||||
|
id: browsers
|
||||||
|
attributes:
|
||||||
|
label: What browsers are you seeing the problem on?
|
||||||
|
multiple: true
|
||||||
|
options:
|
||||||
|
- Firefox
|
||||||
|
- Chrome
|
||||||
|
- Safari
|
||||||
|
- Microsoft Edge
|
||||||
|
- type: input
|
||||||
|
id: browser-info
|
||||||
|
attributes:
|
||||||
|
label: More information about the browser
|
||||||
|
description: "E.g: Browser version, OS version, etc."
|
||||||
|
placeholder: ex. Chrome 104, Windows 11
|
||||||
|
validations:
|
||||||
|
required: false
|
||||||
|
- type: textarea
|
||||||
|
id: logs
|
||||||
|
attributes:
|
||||||
|
label: Relevant log output
|
||||||
|
description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks.
|
||||||
|
render: shell
|
||||||
|
- type: input
|
||||||
|
id: minimal-reproduction-url
|
||||||
|
attributes:
|
||||||
|
label: Link to Minimal Reproducible Example
|
||||||
|
description: |
|
||||||
|
Use [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJimmy/hugo-theme-stack-starter) to create a minimal reproduction of the problem.
|
||||||
|
A minimal reproduction is required so that others can help debug your issue. If a report is vague (e.g. just a generic error message) and has no reproduction, it may be auto-closed.
|
||||||
|
placeholder: https://github.com/username/your-repository
|
||||||
|
validations:
|
||||||
|
required: true
|
||||||
5
themes/stack/.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
blank_issues_enabled: false
|
||||||
|
contact_links:
|
||||||
|
- name: Discussions
|
||||||
|
url: https://github.com/CaiJimmy/hugo-theme-stack/discussions
|
||||||
|
about: Please ask and answer questions here.
|
||||||
4
themes/stack/.gitignore
vendored
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
public
|
||||||
|
resources
|
||||||
|
assets/jsconfig.json
|
||||||
|
.hugo_build.lock
|
||||||
674
themes/stack/LICENSE
Normal file
@ -0,0 +1,674 @@
|
|||||||
|
GNU GENERAL PUBLIC LICENSE
|
||||||
|
Version 3, 29 June 2007
|
||||||
|
|
||||||
|
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
|
||||||
|
Everyone is permitted to copy and distribute verbatim copies
|
||||||
|
of this license document, but changing it is not allowed.
|
||||||
|
|
||||||
|
Preamble
|
||||||
|
|
||||||
|
The GNU General Public License is a free, copyleft license for
|
||||||
|
software and other kinds of works.
|
||||||
|
|
||||||
|
The licenses for most software and other practical works are designed
|
||||||
|
to take away your freedom to share and change the works. By contrast,
|
||||||
|
the GNU General Public License is intended to guarantee your freedom to
|
||||||
|
share and change all versions of a program--to make sure it remains free
|
||||||
|
software for all its users. We, the Free Software Foundation, use the
|
||||||
|
GNU General Public License for most of our software; it applies also to
|
||||||
|
any other work released this way by its authors. You can apply it to
|
||||||
|
your programs, too.
|
||||||
|
|
||||||
|
When we speak of free software, we are referring to freedom, not
|
||||||
|
price. Our General Public Licenses are designed to make sure that you
|
||||||
|
have the freedom to distribute copies of free software (and charge for
|
||||||
|
them if you wish), that you receive source code or can get it if you
|
||||||
|
want it, that you can change the software or use pieces of it in new
|
||||||
|
free programs, and that you know you can do these things.
|
||||||
|
|
||||||
|
To protect your rights, we need to prevent others from denying you
|
||||||
|
these rights or asking you to surrender the rights. Therefore, you have
|
||||||
|
certain responsibilities if you distribute copies of the software, or if
|
||||||
|
you modify it: responsibilities to respect the freedom of others.
|
||||||
|
|
||||||
|
For example, if you distribute copies of such a program, whether
|
||||||
|
gratis or for a fee, you must pass on to the recipients the same
|
||||||
|
freedoms that you received. You must make sure that they, too, receive
|
||||||
|
or can get the source code. And you must show them these terms so they
|
||||||
|
know their rights.
|
||||||
|
|
||||||
|
Developers that use the GNU GPL protect your rights with two steps:
|
||||||
|
(1) assert copyright on the software, and (2) offer you this License
|
||||||
|
giving you legal permission to copy, distribute and/or modify it.
|
||||||
|
|
||||||
|
For the developers' and authors' protection, the GPL clearly explains
|
||||||
|
that there is no warranty for this free software. For both users' and
|
||||||
|
authors' sake, the GPL requires that modified versions be marked as
|
||||||
|
changed, so that their problems will not be attributed erroneously to
|
||||||
|
authors of previous versions.
|
||||||
|
|
||||||
|
Some devices are designed to deny users access to install or run
|
||||||
|
modified versions of the software inside them, although the manufacturer
|
||||||
|
can do so. This is fundamentally incompatible with the aim of
|
||||||
|
protecting users' freedom to change the software. The systematic
|
||||||
|
pattern of such abuse occurs in the area of products for individuals to
|
||||||
|
use, which is precisely where it is most unacceptable. Therefore, we
|
||||||
|
have designed this version of the GPL to prohibit the practice for those
|
||||||
|
products. If such problems arise substantially in other domains, we
|
||||||
|
stand ready to extend this provision to those domains in future versions
|
||||||
|
of the GPL, as needed to protect the freedom of users.
|
||||||
|
|
||||||
|
Finally, every program is threatened constantly by software patents.
|
||||||
|
States should not allow patents to restrict development and use of
|
||||||
|
software on general-purpose computers, but in those that do, we wish to
|
||||||
|
avoid the special danger that patents applied to a free program could
|
||||||
|
make it effectively proprietary. To prevent this, the GPL assures that
|
||||||
|
patents cannot be used to render the program non-free.
|
||||||
|
|
||||||
|
The precise terms and conditions for copying, distribution and
|
||||||
|
modification follow.
|
||||||
|
|
||||||
|
TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
0. Definitions.
|
||||||
|
|
||||||
|
"This License" refers to version 3 of the GNU General Public License.
|
||||||
|
|
||||||
|
"Copyright" also means copyright-like laws that apply to other kinds of
|
||||||
|
works, such as semiconductor masks.
|
||||||
|
|
||||||
|
"The Program" refers to any copyrightable work licensed under this
|
||||||
|
License. Each licensee is addressed as "you". "Licensees" and
|
||||||
|
"recipients" may be individuals or organizations.
|
||||||
|
|
||||||
|
To "modify" a work means to copy from or adapt all or part of the work
|
||||||
|
in a fashion requiring copyright permission, other than the making of an
|
||||||
|
exact copy. The resulting work is called a "modified version" of the
|
||||||
|
earlier work or a work "based on" the earlier work.
|
||||||
|
|
||||||
|
A "covered work" means either the unmodified Program or a work based
|
||||||
|
on the Program.
|
||||||
|
|
||||||
|
To "propagate" a work means to do anything with it that, without
|
||||||
|
permission, would make you directly or secondarily liable for
|
||||||
|
infringement under applicable copyright law, except executing it on a
|
||||||
|
computer or modifying a private copy. Propagation includes copying,
|
||||||
|
distribution (with or without modification), making available to the
|
||||||
|
public, and in some countries other activities as well.
|
||||||
|
|
||||||
|
To "convey" a work means any kind of propagation that enables other
|
||||||
|
parties to make or receive copies. Mere interaction with a user through
|
||||||
|
a computer network, with no transfer of a copy, is not conveying.
|
||||||
|
|
||||||
|
An interactive user interface displays "Appropriate Legal Notices"
|
||||||
|
to the extent that it includes a convenient and prominently visible
|
||||||
|
feature that (1) displays an appropriate copyright notice, and (2)
|
||||||
|
tells the user that there is no warranty for the work (except to the
|
||||||
|
extent that warranties are provided), that licensees may convey the
|
||||||
|
work under this License, and how to view a copy of this License. If
|
||||||
|
the interface presents a list of user commands or options, such as a
|
||||||
|
menu, a prominent item in the list meets this criterion.
|
||||||
|
|
||||||
|
1. Source Code.
|
||||||
|
|
||||||
|
The "source code" for a work means the preferred form of the work
|
||||||
|
for making modifications to it. "Object code" means any non-source
|
||||||
|
form of a work.
|
||||||
|
|
||||||
|
A "Standard Interface" means an interface that either is an official
|
||||||
|
standard defined by a recognized standards body, or, in the case of
|
||||||
|
interfaces specified for a particular programming language, one that
|
||||||
|
is widely used among developers working in that language.
|
||||||
|
|
||||||
|
The "System Libraries" of an executable work include anything, other
|
||||||
|
than the work as a whole, that (a) is included in the normal form of
|
||||||
|
packaging a Major Component, but which is not part of that Major
|
||||||
|
Component, and (b) serves only to enable use of the work with that
|
||||||
|
Major Component, or to implement a Standard Interface for which an
|
||||||
|
implementation is available to the public in source code form. A
|
||||||
|
"Major Component", in this context, means a major essential component
|
||||||
|
(kernel, window system, and so on) of the specific operating system
|
||||||
|
(if any) on which the executable work runs, or a compiler used to
|
||||||
|
produce the work, or an object code interpreter used to run it.
|
||||||
|
|
||||||
|
The "Corresponding Source" for a work in object code form means all
|
||||||
|
the source code needed to generate, install, and (for an executable
|
||||||
|
work) run the object code and to modify the work, including scripts to
|
||||||
|
control those activities. However, it does not include the work's
|
||||||
|
System Libraries, or general-purpose tools or generally available free
|
||||||
|
programs which are used unmodified in performing those activities but
|
||||||
|
which are not part of the work. For example, Corresponding Source
|
||||||
|
includes interface definition files associated with source files for
|
||||||
|
the work, and the source code for shared libraries and dynamically
|
||||||
|
linked subprograms that the work is specifically designed to require,
|
||||||
|
such as by intimate data communication or control flow between those
|
||||||
|
subprograms and other parts of the work.
|
||||||
|
|
||||||
|
The Corresponding Source need not include anything that users
|
||||||
|
can regenerate automatically from other parts of the Corresponding
|
||||||
|
Source.
|
||||||
|
|
||||||
|
The Corresponding Source for a work in source code form is that
|
||||||
|
same work.
|
||||||
|
|
||||||
|
2. Basic Permissions.
|
||||||
|
|
||||||
|
All rights granted under this License are granted for the term of
|
||||||
|
copyright on the Program, and are irrevocable provided the stated
|
||||||
|
conditions are met. This License explicitly affirms your unlimited
|
||||||
|
permission to run the unmodified Program. The output from running a
|
||||||
|
covered work is covered by this License only if the output, given its
|
||||||
|
content, constitutes a covered work. This License acknowledges your
|
||||||
|
rights of fair use or other equivalent, as provided by copyright law.
|
||||||
|
|
||||||
|
You may make, run and propagate covered works that you do not
|
||||||
|
convey, without conditions so long as your license otherwise remains
|
||||||
|
in force. You may convey covered works to others for the sole purpose
|
||||||
|
of having them make modifications exclusively for you, or provide you
|
||||||
|
with facilities for running those works, provided that you comply with
|
||||||
|
the terms of this License in conveying all material for which you do
|
||||||
|
not control copyright. Those thus making or running the covered works
|
||||||
|
for you must do so exclusively on your behalf, under your direction
|
||||||
|
and control, on terms that prohibit them from making any copies of
|
||||||
|
your copyrighted material outside their relationship with you.
|
||||||
|
|
||||||
|
Conveying under any other circumstances is permitted solely under
|
||||||
|
the conditions stated below. Sublicensing is not allowed; section 10
|
||||||
|
makes it unnecessary.
|
||||||
|
|
||||||
|
3. Protecting Users' Legal Rights From Anti-Circumvention Law.
|
||||||
|
|
||||||
|
No covered work shall be deemed part of an effective technological
|
||||||
|
measure under any applicable law fulfilling obligations under article
|
||||||
|
11 of the WIPO copyright treaty adopted on 20 December 1996, or
|
||||||
|
similar laws prohibiting or restricting circumvention of such
|
||||||
|
measures.
|
||||||
|
|
||||||
|
When you convey a covered work, you waive any legal power to forbid
|
||||||
|
circumvention of technological measures to the extent such circumvention
|
||||||
|
is effected by exercising rights under this License with respect to
|
||||||
|
the covered work, and you disclaim any intention to limit operation or
|
||||||
|
modification of the work as a means of enforcing, against the work's
|
||||||
|
users, your or third parties' legal rights to forbid circumvention of
|
||||||
|
technological measures.
|
||||||
|
|
||||||
|
4. Conveying Verbatim Copies.
|
||||||
|
|
||||||
|
You may convey verbatim copies of the Program's source code as you
|
||||||
|
receive it, in any medium, provided that you conspicuously and
|
||||||
|
appropriately publish on each copy an appropriate copyright notice;
|
||||||
|
keep intact all notices stating that this License and any
|
||||||
|
non-permissive terms added in accord with section 7 apply to the code;
|
||||||
|
keep intact all notices of the absence of any warranty; and give all
|
||||||
|
recipients a copy of this License along with the Program.
|
||||||
|
|
||||||
|
You may charge any price or no price for each copy that you convey,
|
||||||
|
and you may offer support or warranty protection for a fee.
|
||||||
|
|
||||||
|
5. Conveying Modified Source Versions.
|
||||||
|
|
||||||
|
You may convey a work based on the Program, or the modifications to
|
||||||
|
produce it from the Program, in the form of source code under the
|
||||||
|
terms of section 4, provided that you also meet all of these conditions:
|
||||||
|
|
||||||
|
a) The work must carry prominent notices stating that you modified
|
||||||
|
it, and giving a relevant date.
|
||||||
|
|
||||||
|
b) The work must carry prominent notices stating that it is
|
||||||
|
released under this License and any conditions added under section
|
||||||
|
7. This requirement modifies the requirement in section 4 to
|
||||||
|
"keep intact all notices".
|
||||||
|
|
||||||
|
c) You must license the entire work, as a whole, under this
|
||||||
|
License to anyone who comes into possession of a copy. This
|
||||||
|
License will therefore apply, along with any applicable section 7
|
||||||
|
additional terms, to the whole of the work, and all its parts,
|
||||||
|
regardless of how they are packaged. This License gives no
|
||||||
|
permission to license the work in any other way, but it does not
|
||||||
|
invalidate such permission if you have separately received it.
|
||||||
|
|
||||||
|
d) If the work has interactive user interfaces, each must display
|
||||||
|
Appropriate Legal Notices; however, if the Program has interactive
|
||||||
|
interfaces that do not display Appropriate Legal Notices, your
|
||||||
|
work need not make them do so.
|
||||||
|
|
||||||
|
A compilation of a covered work with other separate and independent
|
||||||
|
works, which are not by their nature extensions of the covered work,
|
||||||
|
and which are not combined with it such as to form a larger program,
|
||||||
|
in or on a volume of a storage or distribution medium, is called an
|
||||||
|
"aggregate" if the compilation and its resulting copyright are not
|
||||||
|
used to limit the access or legal rights of the compilation's users
|
||||||
|
beyond what the individual works permit. Inclusion of a covered work
|
||||||
|
in an aggregate does not cause this License to apply to the other
|
||||||
|
parts of the aggregate.
|
||||||
|
|
||||||
|
6. Conveying Non-Source Forms.
|
||||||
|
|
||||||
|
You may convey a covered work in object code form under the terms
|
||||||
|
of sections 4 and 5, provided that you also convey the
|
||||||
|
machine-readable Corresponding Source under the terms of this License,
|
||||||
|
in one of these ways:
|
||||||
|
|
||||||
|
a) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by the
|
||||||
|
Corresponding Source fixed on a durable physical medium
|
||||||
|
customarily used for software interchange.
|
||||||
|
|
||||||
|
b) Convey the object code in, or embodied in, a physical product
|
||||||
|
(including a physical distribution medium), accompanied by a
|
||||||
|
written offer, valid for at least three years and valid for as
|
||||||
|
long as you offer spare parts or customer support for that product
|
||||||
|
model, to give anyone who possesses the object code either (1) a
|
||||||
|
copy of the Corresponding Source for all the software in the
|
||||||
|
product that is covered by this License, on a durable physical
|
||||||
|
medium customarily used for software interchange, for a price no
|
||||||
|
more than your reasonable cost of physically performing this
|
||||||
|
conveying of source, or (2) access to copy the
|
||||||
|
Corresponding Source from a network server at no charge.
|
||||||
|
|
||||||
|
c) Convey individual copies of the object code with a copy of the
|
||||||
|
written offer to provide the Corresponding Source. This
|
||||||
|
alternative is allowed only occasionally and noncommercially, and
|
||||||
|
only if you received the object code with such an offer, in accord
|
||||||
|
with subsection 6b.
|
||||||
|
|
||||||
|
d) Convey the object code by offering access from a designated
|
||||||
|
place (gratis or for a charge), and offer equivalent access to the
|
||||||
|
Corresponding Source in the same way through the same place at no
|
||||||
|
further charge. You need not require recipients to copy the
|
||||||
|
Corresponding Source along with the object code. If the place to
|
||||||
|
copy the object code is a network server, the Corresponding Source
|
||||||
|
may be on a different server (operated by you or a third party)
|
||||||
|
that supports equivalent copying facilities, provided you maintain
|
||||||
|
clear directions next to the object code saying where to find the
|
||||||
|
Corresponding Source. Regardless of what server hosts the
|
||||||
|
Corresponding Source, you remain obligated to ensure that it is
|
||||||
|
available for as long as needed to satisfy these requirements.
|
||||||
|
|
||||||
|
e) Convey the object code using peer-to-peer transmission, provided
|
||||||
|
you inform other peers where the object code and Corresponding
|
||||||
|
Source of the work are being offered to the general public at no
|
||||||
|
charge under subsection 6d.
|
||||||
|
|
||||||
|
A separable portion of the object code, whose source code is excluded
|
||||||
|
from the Corresponding Source as a System Library, need not be
|
||||||
|
included in conveying the object code work.
|
||||||
|
|
||||||
|
A "User Product" is either (1) a "consumer product", which means any
|
||||||
|
tangible personal property which is normally used for personal, family,
|
||||||
|
or household purposes, or (2) anything designed or sold for incorporation
|
||||||
|
into a dwelling. In determining whether a product is a consumer product,
|
||||||
|
doubtful cases shall be resolved in favor of coverage. For a particular
|
||||||
|
product received by a particular user, "normally used" refers to a
|
||||||
|
typical or common use of that class of product, regardless of the status
|
||||||
|
of the particular user or of the way in which the particular user
|
||||||
|
actually uses, or expects or is expected to use, the product. A product
|
||||||
|
is a consumer product regardless of whether the product has substantial
|
||||||
|
commercial, industrial or non-consumer uses, unless such uses represent
|
||||||
|
the only significant mode of use of the product.
|
||||||
|
|
||||||
|
"Installation Information" for a User Product means any methods,
|
||||||
|
procedures, authorization keys, or other information required to install
|
||||||
|
and execute modified versions of a covered work in that User Product from
|
||||||
|
a modified version of its Corresponding Source. The information must
|
||||||
|
suffice to ensure that the continued functioning of the modified object
|
||||||
|
code is in no case prevented or interfered with solely because
|
||||||
|
modification has been made.
|
||||||
|
|
||||||
|
If you convey an object code work under this section in, or with, or
|
||||||
|
specifically for use in, a User Product, and the conveying occurs as
|
||||||
|
part of a transaction in which the right of possession and use of the
|
||||||
|
User Product is transferred to the recipient in perpetuity or for a
|
||||||
|
fixed term (regardless of how the transaction is characterized), the
|
||||||
|
Corresponding Source conveyed under this section must be accompanied
|
||||||
|
by the Installation Information. But this requirement does not apply
|
||||||
|
if neither you nor any third party retains the ability to install
|
||||||
|
modified object code on the User Product (for example, the work has
|
||||||
|
been installed in ROM).
|
||||||
|
|
||||||
|
The requirement to provide Installation Information does not include a
|
||||||
|
requirement to continue to provide support service, warranty, or updates
|
||||||
|
for a work that has been modified or installed by the recipient, or for
|
||||||
|
the User Product in which it has been modified or installed. Access to a
|
||||||
|
network may be denied when the modification itself materially and
|
||||||
|
adversely affects the operation of the network or violates the rules and
|
||||||
|
protocols for communication across the network.
|
||||||
|
|
||||||
|
Corresponding Source conveyed, and Installation Information provided,
|
||||||
|
in accord with this section must be in a format that is publicly
|
||||||
|
documented (and with an implementation available to the public in
|
||||||
|
source code form), and must require no special password or key for
|
||||||
|
unpacking, reading or copying.
|
||||||
|
|
||||||
|
7. Additional Terms.
|
||||||
|
|
||||||
|
"Additional permissions" are terms that supplement the terms of this
|
||||||
|
License by making exceptions from one or more of its conditions.
|
||||||
|
Additional permissions that are applicable to the entire Program shall
|
||||||
|
be treated as though they were included in this License, to the extent
|
||||||
|
that they are valid under applicable law. If additional permissions
|
||||||
|
apply only to part of the Program, that part may be used separately
|
||||||
|
under those permissions, but the entire Program remains governed by
|
||||||
|
this License without regard to the additional permissions.
|
||||||
|
|
||||||
|
When you convey a copy of a covered work, you may at your option
|
||||||
|
remove any additional permissions from that copy, or from any part of
|
||||||
|
it. (Additional permissions may be written to require their own
|
||||||
|
removal in certain cases when you modify the work.) You may place
|
||||||
|
additional permissions on material, added by you to a covered work,
|
||||||
|
for which you have or can give appropriate copyright permission.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, for material you
|
||||||
|
add to a covered work, you may (if authorized by the copyright holders of
|
||||||
|
that material) supplement the terms of this License with terms:
|
||||||
|
|
||||||
|
a) Disclaiming warranty or limiting liability differently from the
|
||||||
|
terms of sections 15 and 16 of this License; or
|
||||||
|
|
||||||
|
b) Requiring preservation of specified reasonable legal notices or
|
||||||
|
author attributions in that material or in the Appropriate Legal
|
||||||
|
Notices displayed by works containing it; or
|
||||||
|
|
||||||
|
c) Prohibiting misrepresentation of the origin of that material, or
|
||||||
|
requiring that modified versions of such material be marked in
|
||||||
|
reasonable ways as different from the original version; or
|
||||||
|
|
||||||
|
d) Limiting the use for publicity purposes of names of licensors or
|
||||||
|
authors of the material; or
|
||||||
|
|
||||||
|
e) Declining to grant rights under trademark law for use of some
|
||||||
|
trade names, trademarks, or service marks; or
|
||||||
|
|
||||||
|
f) Requiring indemnification of licensors and authors of that
|
||||||
|
material by anyone who conveys the material (or modified versions of
|
||||||
|
it) with contractual assumptions of liability to the recipient, for
|
||||||
|
any liability that these contractual assumptions directly impose on
|
||||||
|
those licensors and authors.
|
||||||
|
|
||||||
|
All other non-permissive additional terms are considered "further
|
||||||
|
restrictions" within the meaning of section 10. If the Program as you
|
||||||
|
received it, or any part of it, contains a notice stating that it is
|
||||||
|
governed by this License along with a term that is a further
|
||||||
|
restriction, you may remove that term. If a license document contains
|
||||||
|
a further restriction but permits relicensing or conveying under this
|
||||||
|
License, you may add to a covered work material governed by the terms
|
||||||
|
of that license document, provided that the further restriction does
|
||||||
|
not survive such relicensing or conveying.
|
||||||
|
|
||||||
|
If you add terms to a covered work in accord with this section, you
|
||||||
|
must place, in the relevant source files, a statement of the
|
||||||
|
additional terms that apply to those files, or a notice indicating
|
||||||
|
where to find the applicable terms.
|
||||||
|
|
||||||
|
Additional terms, permissive or non-permissive, may be stated in the
|
||||||
|
form of a separately written license, or stated as exceptions;
|
||||||
|
the above requirements apply either way.
|
||||||
|
|
||||||
|
8. Termination.
|
||||||
|
|
||||||
|
You may not propagate or modify a covered work except as expressly
|
||||||
|
provided under this License. Any attempt otherwise to propagate or
|
||||||
|
modify it is void, and will automatically terminate your rights under
|
||||||
|
this License (including any patent licenses granted under the third
|
||||||
|
paragraph of section 11).
|
||||||
|
|
||||||
|
However, if you cease all violation of this License, then your
|
||||||
|
license from a particular copyright holder is reinstated (a)
|
||||||
|
provisionally, unless and until the copyright holder explicitly and
|
||||||
|
finally terminates your license, and (b) permanently, if the copyright
|
||||||
|
holder fails to notify you of the violation by some reasonable means
|
||||||
|
prior to 60 days after the cessation.
|
||||||
|
|
||||||
|
Moreover, your license from a particular copyright holder is
|
||||||
|
reinstated permanently if the copyright holder notifies you of the
|
||||||
|
violation by some reasonable means, this is the first time you have
|
||||||
|
received notice of violation of this License (for any work) from that
|
||||||
|
copyright holder, and you cure the violation prior to 30 days after
|
||||||
|
your receipt of the notice.
|
||||||
|
|
||||||
|
Termination of your rights under this section does not terminate the
|
||||||
|
licenses of parties who have received copies or rights from you under
|
||||||
|
this License. If your rights have been terminated and not permanently
|
||||||
|
reinstated, you do not qualify to receive new licenses for the same
|
||||||
|
material under section 10.
|
||||||
|
|
||||||
|
9. Acceptance Not Required for Having Copies.
|
||||||
|
|
||||||
|
You are not required to accept this License in order to receive or
|
||||||
|
run a copy of the Program. Ancillary propagation of a covered work
|
||||||
|
occurring solely as a consequence of using peer-to-peer transmission
|
||||||
|
to receive a copy likewise does not require acceptance. However,
|
||||||
|
nothing other than this License grants you permission to propagate or
|
||||||
|
modify any covered work. These actions infringe copyright if you do
|
||||||
|
not accept this License. Therefore, by modifying or propagating a
|
||||||
|
covered work, you indicate your acceptance of this License to do so.
|
||||||
|
|
||||||
|
10. Automatic Licensing of Downstream Recipients.
|
||||||
|
|
||||||
|
Each time you convey a covered work, the recipient automatically
|
||||||
|
receives a license from the original licensors, to run, modify and
|
||||||
|
propagate that work, subject to this License. You are not responsible
|
||||||
|
for enforcing compliance by third parties with this License.
|
||||||
|
|
||||||
|
An "entity transaction" is a transaction transferring control of an
|
||||||
|
organization, or substantially all assets of one, or subdividing an
|
||||||
|
organization, or merging organizations. If propagation of a covered
|
||||||
|
work results from an entity transaction, each party to that
|
||||||
|
transaction who receives a copy of the work also receives whatever
|
||||||
|
licenses to the work the party's predecessor in interest had or could
|
||||||
|
give under the previous paragraph, plus a right to possession of the
|
||||||
|
Corresponding Source of the work from the predecessor in interest, if
|
||||||
|
the predecessor has it or can get it with reasonable efforts.
|
||||||
|
|
||||||
|
You may not impose any further restrictions on the exercise of the
|
||||||
|
rights granted or affirmed under this License. For example, you may
|
||||||
|
not impose a license fee, royalty, or other charge for exercise of
|
||||||
|
rights granted under this License, and you may not initiate litigation
|
||||||
|
(including a cross-claim or counterclaim in a lawsuit) alleging that
|
||||||
|
any patent claim is infringed by making, using, selling, offering for
|
||||||
|
sale, or importing the Program or any portion of it.
|
||||||
|
|
||||||
|
11. Patents.
|
||||||
|
|
||||||
|
A "contributor" is a copyright holder who authorizes use under this
|
||||||
|
License of the Program or a work on which the Program is based. The
|
||||||
|
work thus licensed is called the contributor's "contributor version".
|
||||||
|
|
||||||
|
A contributor's "essential patent claims" are all patent claims
|
||||||
|
owned or controlled by the contributor, whether already acquired or
|
||||||
|
hereafter acquired, that would be infringed by some manner, permitted
|
||||||
|
by this License, of making, using, or selling its contributor version,
|
||||||
|
but do not include claims that would be infringed only as a
|
||||||
|
consequence of further modification of the contributor version. For
|
||||||
|
purposes of this definition, "control" includes the right to grant
|
||||||
|
patent sublicenses in a manner consistent with the requirements of
|
||||||
|
this License.
|
||||||
|
|
||||||
|
Each contributor grants you a non-exclusive, worldwide, royalty-free
|
||||||
|
patent license under the contributor's essential patent claims, to
|
||||||
|
make, use, sell, offer for sale, import and otherwise run, modify and
|
||||||
|
propagate the contents of its contributor version.
|
||||||
|
|
||||||
|
In the following three paragraphs, a "patent license" is any express
|
||||||
|
agreement or commitment, however denominated, not to enforce a patent
|
||||||
|
(such as an express permission to practice a patent or covenant not to
|
||||||
|
sue for patent infringement). To "grant" such a patent license to a
|
||||||
|
party means to make such an agreement or commitment not to enforce a
|
||||||
|
patent against the party.
|
||||||
|
|
||||||
|
If you convey a covered work, knowingly relying on a patent license,
|
||||||
|
and the Corresponding Source of the work is not available for anyone
|
||||||
|
to copy, free of charge and under the terms of this License, through a
|
||||||
|
publicly available network server or other readily accessible means,
|
||||||
|
then you must either (1) cause the Corresponding Source to be so
|
||||||
|
available, or (2) arrange to deprive yourself of the benefit of the
|
||||||
|
patent license for this particular work, or (3) arrange, in a manner
|
||||||
|
consistent with the requirements of this License, to extend the patent
|
||||||
|
license to downstream recipients. "Knowingly relying" means you have
|
||||||
|
actual knowledge that, but for the patent license, your conveying the
|
||||||
|
covered work in a country, or your recipient's use of the covered work
|
||||||
|
in a country, would infringe one or more identifiable patents in that
|
||||||
|
country that you have reason to believe are valid.
|
||||||
|
|
||||||
|
If, pursuant to or in connection with a single transaction or
|
||||||
|
arrangement, you convey, or propagate by procuring conveyance of, a
|
||||||
|
covered work, and grant a patent license to some of the parties
|
||||||
|
receiving the covered work authorizing them to use, propagate, modify
|
||||||
|
or convey a specific copy of the covered work, then the patent license
|
||||||
|
you grant is automatically extended to all recipients of the covered
|
||||||
|
work and works based on it.
|
||||||
|
|
||||||
|
A patent license is "discriminatory" if it does not include within
|
||||||
|
the scope of its coverage, prohibits the exercise of, or is
|
||||||
|
conditioned on the non-exercise of one or more of the rights that are
|
||||||
|
specifically granted under this License. You may not convey a covered
|
||||||
|
work if you are a party to an arrangement with a third party that is
|
||||||
|
in the business of distributing software, under which you make payment
|
||||||
|
to the third party based on the extent of your activity of conveying
|
||||||
|
the work, and under which the third party grants, to any of the
|
||||||
|
parties who would receive the covered work from you, a discriminatory
|
||||||
|
patent license (a) in connection with copies of the covered work
|
||||||
|
conveyed by you (or copies made from those copies), or (b) primarily
|
||||||
|
for and in connection with specific products or compilations that
|
||||||
|
contain the covered work, unless you entered into that arrangement,
|
||||||
|
or that patent license was granted, prior to 28 March 2007.
|
||||||
|
|
||||||
|
Nothing in this License shall be construed as excluding or limiting
|
||||||
|
any implied license or other defenses to infringement that may
|
||||||
|
otherwise be available to you under applicable patent law.
|
||||||
|
|
||||||
|
12. No Surrender of Others' Freedom.
|
||||||
|
|
||||||
|
If conditions are imposed on you (whether by court order, agreement or
|
||||||
|
otherwise) that contradict the conditions of this License, they do not
|
||||||
|
excuse you from the conditions of this License. If you cannot convey a
|
||||||
|
covered work so as to satisfy simultaneously your obligations under this
|
||||||
|
License and any other pertinent obligations, then as a consequence you may
|
||||||
|
not convey it at all. For example, if you agree to terms that obligate you
|
||||||
|
to collect a royalty for further conveying from those to whom you convey
|
||||||
|
the Program, the only way you could satisfy both those terms and this
|
||||||
|
License would be to refrain entirely from conveying the Program.
|
||||||
|
|
||||||
|
13. Use with the GNU Affero General Public License.
|
||||||
|
|
||||||
|
Notwithstanding any other provision of this License, you have
|
||||||
|
permission to link or combine any covered work with a work licensed
|
||||||
|
under version 3 of the GNU Affero General Public License into a single
|
||||||
|
combined work, and to convey the resulting work. The terms of this
|
||||||
|
License will continue to apply to the part which is the covered work,
|
||||||
|
but the special requirements of the GNU Affero General Public License,
|
||||||
|
section 13, concerning interaction through a network will apply to the
|
||||||
|
combination as such.
|
||||||
|
|
||||||
|
14. Revised Versions of this License.
|
||||||
|
|
||||||
|
The Free Software Foundation may publish revised and/or new versions of
|
||||||
|
the GNU General Public License from time to time. Such new versions will
|
||||||
|
be similar in spirit to the present version, but may differ in detail to
|
||||||
|
address new problems or concerns.
|
||||||
|
|
||||||
|
Each version is given a distinguishing version number. If the
|
||||||
|
Program specifies that a certain numbered version of the GNU General
|
||||||
|
Public License "or any later version" applies to it, you have the
|
||||||
|
option of following the terms and conditions either of that numbered
|
||||||
|
version or of any later version published by the Free Software
|
||||||
|
Foundation. If the Program does not specify a version number of the
|
||||||
|
GNU General Public License, you may choose any version ever published
|
||||||
|
by the Free Software Foundation.
|
||||||
|
|
||||||
|
If the Program specifies that a proxy can decide which future
|
||||||
|
versions of the GNU General Public License can be used, that proxy's
|
||||||
|
public statement of acceptance of a version permanently authorizes you
|
||||||
|
to choose that version for the Program.
|
||||||
|
|
||||||
|
Later license versions may give you additional or different
|
||||||
|
permissions. However, no additional obligations are imposed on any
|
||||||
|
author or copyright holder as a result of your choosing to follow a
|
||||||
|
later version.
|
||||||
|
|
||||||
|
15. Disclaimer of Warranty.
|
||||||
|
|
||||||
|
THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
|
||||||
|
APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
|
||||||
|
HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
|
||||||
|
OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||||
|
PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
|
||||||
|
IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
|
||||||
|
ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
|
||||||
|
|
||||||
|
16. Limitation of Liability.
|
||||||
|
|
||||||
|
IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
|
||||||
|
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
|
||||||
|
THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
|
||||||
|
GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
|
||||||
|
USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
|
||||||
|
DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
|
||||||
|
PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
|
||||||
|
EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
|
||||||
|
SUCH DAMAGES.
|
||||||
|
|
||||||
|
17. Interpretation of Sections 15 and 16.
|
||||||
|
|
||||||
|
If the disclaimer of warranty and limitation of liability provided
|
||||||
|
above cannot be given local legal effect according to their terms,
|
||||||
|
reviewing courts shall apply local law that most closely approximates
|
||||||
|
an absolute waiver of all civil liability in connection with the
|
||||||
|
Program, unless a warranty or assumption of liability accompanies a
|
||||||
|
copy of the Program in return for a fee.
|
||||||
|
|
||||||
|
END OF TERMS AND CONDITIONS
|
||||||
|
|
||||||
|
How to Apply These Terms to Your New Programs
|
||||||
|
|
||||||
|
If you develop a new program, and you want it to be of the greatest
|
||||||
|
possible use to the public, the best way to achieve this is to make it
|
||||||
|
free software which everyone can redistribute and change under these terms.
|
||||||
|
|
||||||
|
To do so, attach the following notices to the program. It is safest
|
||||||
|
to attach them to the start of each source file to most effectively
|
||||||
|
state the exclusion of warranty; and each file should have at least
|
||||||
|
the "copyright" line and a pointer to where the full notice is found.
|
||||||
|
|
||||||
|
<one line to give the program's name and a brief idea of what it does.>
|
||||||
|
Copyright (C) <year> <name of author>
|
||||||
|
|
||||||
|
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 <https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
Also add information on how to contact you by electronic and paper mail.
|
||||||
|
|
||||||
|
If the program does terminal interaction, make it output a short
|
||||||
|
notice like this when it starts in an interactive mode:
|
||||||
|
|
||||||
|
<program> Copyright (C) <year> <name of author>
|
||||||
|
This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
|
||||||
|
This is free software, and you are welcome to redistribute it
|
||||||
|
under certain conditions; type `show c' for details.
|
||||||
|
|
||||||
|
The hypothetical commands `show w' and `show c' should show the appropriate
|
||||||
|
parts of the General Public License. Of course, your program's commands
|
||||||
|
might be different; for a GUI interface, you would use an "about box".
|
||||||
|
|
||||||
|
You should also get your employer (if you work as a programmer) or school,
|
||||||
|
if any, to sign a "copyright disclaimer" for the program, if necessary.
|
||||||
|
For more information on this, and how to apply and follow the GNU GPL, see
|
||||||
|
<https://www.gnu.org/licenses/>.
|
||||||
|
|
||||||
|
The GNU General Public License does not permit incorporating your program
|
||||||
|
into proprietary programs. If your program is a subroutine library, you
|
||||||
|
may consider it more useful to permit linking proprietary applications with
|
||||||
|
the library. If this is what you want to do, use the GNU Lesser General
|
||||||
|
Public License instead of this License. But first, please read
|
||||||
|
<https://www.gnu.org/licenses/why-not-lgpl.html>.
|
||||||
28
themes/stack/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|

|
||||||
|
|
||||||
|
# Hugo Theme Stack
|
||||||
|
|
||||||
|
<img align="right" width="150" alt="logo" src="https://user-images.githubusercontent.com/5889006/190859553-5b229b4f-c476-4cbd-928f-890f5265ca4c.png">
|
||||||
|
|
||||||
|
Card-style Hugo theme designed for bloggers.
|
||||||
|
|
||||||
|
## Quickstart
|
||||||
|
|
||||||
|
Use this template: [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJimmy/hugo-theme-stack-starter)
|
||||||
|
|
||||||
|
## Demo
|
||||||
|
|
||||||
|
Check [demo.stack.cai.im](https://demo.stack.cai.im)
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [English](https://stack.cai.im)
|
||||||
|
- [中文](https://stack.cai.im/zh)
|
||||||
|
|
||||||
|
## Copyright
|
||||||
|
|
||||||
|
**Licensed under the GNU General Public License v3.0**
|
||||||
|
|
||||||
|
Please do not remove the "*Theme Stack designed by Jimmy*" text and link.
|
||||||
|
|
||||||
|
If you want to port this theme to another blogging platform, please let me know🙏.
|
||||||
7
themes/stack/archetypes/categories.md
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
---
|
||||||
|
title: "{{ replace .Name "-" " " | title }}"
|
||||||
|
image:
|
||||||
|
style:
|
||||||
|
background: "#2a9d8f"
|
||||||
|
color: "#fff"
|
||||||
|
---
|
||||||
12
themes/stack/archetypes/default.md
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
---
|
||||||
|
title: "{{ replace .Name "-" " " | title }}"
|
||||||
|
description:
|
||||||
|
date: {{ .Date }}
|
||||||
|
image:
|
||||||
|
math:
|
||||||
|
license:
|
||||||
|
comments: true
|
||||||
|
draft: true
|
||||||
|
build:
|
||||||
|
list: always # Change to "never" to hide the page from the list
|
||||||
|
---
|
||||||
5
themes/stack/archetypes/tags.md
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
---
|
||||||
|
title: "{{ replace .Name "-" " " | title }}"
|
||||||
|
description:
|
||||||
|
image:
|
||||||
|
---
|
||||||
8
themes/stack/assets/icons/archives.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<rect x="3" y="4" width="18" height="4" rx="2" />
|
||||||
|
<path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" />
|
||||||
|
<line x1="10" y1="12" x2="14" y2="12" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 432 B |
6
themes/stack/assets/icons/arrow-back.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-back" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 338 B |
6
themes/stack/assets/icons/back.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<polyline points="15 6 9 12 15 18" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 323 B |
6
themes/stack/assets/icons/brand-github.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 603 B |
6
themes/stack/assets/icons/brand-twitter.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-twitter" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 638 B |
9
themes/stack/assets/icons/categories.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<line x1="5" y1="9" x2="19" y2="9" />
|
||||||
|
<line x1="5" y1="15" x2="19" y2="15" />
|
||||||
|
<line x1="11" y1="4" x2="7" y2="20" />
|
||||||
|
<line x1="17" y1="4" x2="13" y2="20" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 440 B |
1
themes/stack/assets/icons/chevron-left.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-left"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M15 6l-6 6l6 6" /></svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
1
themes/stack/assets/icons/chevron-right.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-chevron-right"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M9 6l6 6l-6 6" /></svg>
|
||||||
|
After Width: | Height: | Size: 336 B |
7
themes/stack/assets/icons/clock.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
<polyline points="12 7 12 12 15 15" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 352 B |
7
themes/stack/assets/icons/copyright.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="12" cy="12" r="9" />
|
||||||
|
<path d="M14.5 9a3.5 4 0 1 0 0 6" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 354 B |
9
themes/stack/assets/icons/date.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" />
|
||||||
|
<circle cx="18" cy="18" r="4" />
|
||||||
|
<path d="M15 3v4" />
|
||||||
|
<path d="M7 3v4" />
|
||||||
|
<path d="M3 11h16" />
|
||||||
|
<path d="M18 16.496v1.504l1 1" />
|
||||||
|
</svg>
|
||||||
|
After Width: | Height: | Size: 508 B |
1
themes/stack/assets/icons/dots.svg
Normal file
@ -0,0 +1 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-dots"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M5 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M12 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /><path d="M19 12m-1 0a1 1 0 1 0 2 0a1 1 0 1 0 -2 0" /></svg>
|
||||||
|
After Width: | Height: | Size: 459 B |
9
themes/stack/assets/icons/hash.svg
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<line x1="5" y1="9" x2="19" y2="9" />
|
||||||
|
<line x1="5" y1="15" x2="19" y2="15" />
|
||||||
|
<line x1="11" y1="4" x2="7" y2="20" />
|
||||||
|
<line x1="17" y1="4" x2="13" y2="20" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 440 B |
8
themes/stack/assets/icons/home.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<polyline points="5 12 3 12 12 3 21 12 19 12" />
|
||||||
|
<path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" />
|
||||||
|
<path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 441 B |
6
themes/stack/assets/icons/infinity.svg
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M9.828 9.172a4 4 0 1 0 0 5.656 a10 10 0 0 0 2.172 -2.828a10 10 0 0 1 2.172 -2.828 a4 4 0 1 1 0 5.656a10 10 0 0 1 -2.172 -2.828a10 10 0 0 0 -2.172 -2.828" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 447 B |
10
themes/stack/assets/icons/language.svg
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
|
||||||
|
<path d="M4 5h7" />
|
||||||
|
<path d="M9 3v2c0 4.418 -2.239 8 -5 8" />
|
||||||
|
<path d="M5 9c-.003 2.144 2.952 3.908 6.7 4" />
|
||||||
|
<path d="M12 20l4 -9l4 9" />
|
||||||
|
<path d="M19.1 18h-6.2" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 467 B |
7
themes/stack/assets/icons/link.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" />
|
||||||
|
<path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 418 B |
7
themes/stack/assets/icons/messages.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-messages" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" />
|
||||||
|
<path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 431 B |
8
themes/stack/assets/icons/rss.svg
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rss" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="5" cy="19" r="1" />
|
||||||
|
<path d="M4 4a16 16 0 0 1 16 16" />
|
||||||
|
<path d="M4 11a9 9 0 0 1 9 9" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 381 B |
7
themes/stack/assets/icons/search.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="10" cy="10" r="7" />
|
||||||
|
<line x1="21" y1="21" x2="15" y2="15" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 355 B |
7
themes/stack/assets/icons/tag.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<path d="M11 3L20 12a1.5 1.5 0 0 1 0 2L14 20a1.5 1.5 0 0 1 -2 0L3 11v-4a4 4 0 0 1 4 -4h4" />
|
||||||
|
<circle cx="9" cy="9" r="2" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 402 B |
7
themes/stack/assets/icons/toggle-left.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="8" cy="12" r="2" />
|
||||||
|
<rect x="2" y="6" width="20" height="12" rx="6" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 369 B |
7
themes/stack/assets/icons/toggle-right.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="16" cy="12" r="2" />
|
||||||
|
<rect x="2" y="6" width="20" height="12" rx="6" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 371 B |
7
themes/stack/assets/icons/user.svg
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||||
|
<path stroke="none" d="M0 0h24v24H0z"/>
|
||||||
|
<circle cx="12" cy="7" r="4" />
|
||||||
|
<path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
|
||||||
|
</svg>
|
||||||
|
|
||||||
|
|
||||||
|
After Width: | Height: | Size: 366 B |
17
themes/stack/assets/scss/breakpoints.scss
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
$breakpoints: (
|
||||||
|
sm: 640px,
|
||||||
|
md: 768px,
|
||||||
|
lg: 1024px,
|
||||||
|
xl: 1280px,
|
||||||
|
2xl: 1536px,
|
||||||
|
);
|
||||||
|
|
||||||
|
@mixin respond($breakpoint) {
|
||||||
|
@if not map-has-key($breakpoints, $breakpoint) {
|
||||||
|
@warn "'#{$breakpoint}' is not a valid breakpoint";
|
||||||
|
} @else {
|
||||||
|
@media (min-width: map-get($breakpoints, $breakpoint)) {
|
||||||
|
@content;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1
themes/stack/assets/scss/custom.scss
Normal file
@ -0,0 +1 @@
|
|||||||
|
/* Place your custom SCSS in HUGO_SITE_FOLDER/assets/scss/custom.scss */
|
||||||
349
themes/stack/assets/scss/external/normalize.scss
vendored
Normal file
@ -0,0 +1,349 @@
|
|||||||
|
/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
|
||||||
|
|
||||||
|
/* Document
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the line height in all browsers.
|
||||||
|
* 2. Prevent adjustments of font size after orientation changes in iOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
html {
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
-webkit-text-size-adjust: 100%; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Sections
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the margin in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
body {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render the `main` element consistently in IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
main {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the font size and margin on `h1` elements within `section` and
|
||||||
|
* `article` contexts in Chrome, Firefox, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin: 0.67em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Grouping content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in Firefox.
|
||||||
|
* 2. Show the overflow in Edge and IE.
|
||||||
|
*/
|
||||||
|
|
||||||
|
hr {
|
||||||
|
box-sizing: content-box; /* 1 */
|
||||||
|
height: 0; /* 1 */
|
||||||
|
overflow: visible; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
pre {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Text-level semantics
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the gray background on active links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
a {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Remove the bottom border in Chrome 57-
|
||||||
|
* 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
abbr[title] {
|
||||||
|
border-bottom: none; /* 1 */
|
||||||
|
text-decoration: underline; /* 2 */
|
||||||
|
text-decoration: underline dotted; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font weight in Chrome, Edge, and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
b,
|
||||||
|
strong {
|
||||||
|
font-weight: bolder;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inheritance and scaling of font size in all browsers.
|
||||||
|
* 2. Correct the odd `em` font sizing in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
code,
|
||||||
|
kbd,
|
||||||
|
samp {
|
||||||
|
font-family: monospace, monospace; /* 1 */
|
||||||
|
font-size: 1em; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct font size in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
small {
|
||||||
|
font-size: 80%;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prevent `sub` and `sup` elements from affecting the line height in
|
||||||
|
* all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
sub,
|
||||||
|
sup {
|
||||||
|
font-size: 75%;
|
||||||
|
line-height: 0;
|
||||||
|
position: relative;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
sub {
|
||||||
|
bottom: -0.25em;
|
||||||
|
}
|
||||||
|
|
||||||
|
sup {
|
||||||
|
top: -0.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Embedded content
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the border on images inside links in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
img {
|
||||||
|
border-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Forms
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Change the font styles in all browsers.
|
||||||
|
* 2. Remove the margin in Firefox and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input,
|
||||||
|
optgroup,
|
||||||
|
select,
|
||||||
|
textarea {
|
||||||
|
font-family: inherit; /* 1 */
|
||||||
|
font-size: 100%; /* 1 */
|
||||||
|
line-height: 1.15; /* 1 */
|
||||||
|
margin: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Show the overflow in IE.
|
||||||
|
* 1. Show the overflow in Edge.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
input { /* 1 */
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inheritance of text transform in Edge, Firefox, and IE.
|
||||||
|
* 1. Remove the inheritance of text transform in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
select { /* 1 */
|
||||||
|
text-transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button,
|
||||||
|
[type="button"],
|
||||||
|
[type="reset"],
|
||||||
|
[type="submit"] {
|
||||||
|
-webkit-appearance: button;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner border and padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button::-moz-focus-inner,
|
||||||
|
[type="button"]::-moz-focus-inner,
|
||||||
|
[type="reset"]::-moz-focus-inner,
|
||||||
|
[type="submit"]::-moz-focus-inner {
|
||||||
|
border-style: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the focus styles unset by the previous rule.
|
||||||
|
*/
|
||||||
|
|
||||||
|
button:-moz-focusring,
|
||||||
|
[type="button"]:-moz-focusring,
|
||||||
|
[type="reset"]:-moz-focusring,
|
||||||
|
[type="submit"]:-moz-focusring {
|
||||||
|
outline: 1px dotted ButtonText;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the padding in Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
fieldset {
|
||||||
|
padding: 0.35em 0.75em 0.625em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the text wrapping in Edge and IE.
|
||||||
|
* 2. Correct the color inheritance from `fieldset` elements in IE.
|
||||||
|
* 3. Remove the padding so developers are not caught out when they zero out
|
||||||
|
* `fieldset` elements in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
legend {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
color: inherit; /* 2 */
|
||||||
|
display: table; /* 1 */
|
||||||
|
max-width: 100%; /* 1 */
|
||||||
|
padding: 0; /* 3 */
|
||||||
|
white-space: normal; /* 1 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct vertical alignment in Chrome, Firefox, and Opera.
|
||||||
|
*/
|
||||||
|
|
||||||
|
progress {
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the default vertical scrollbar in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
textarea {
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Add the correct box sizing in IE 10.
|
||||||
|
* 2. Remove the padding in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="checkbox"],
|
||||||
|
[type="radio"] {
|
||||||
|
box-sizing: border-box; /* 1 */
|
||||||
|
padding: 0; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Correct the cursor style of increment and decrement buttons in Chrome.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="number"]::-webkit-inner-spin-button,
|
||||||
|
[type="number"]::-webkit-outer-spin-button {
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the odd appearance in Chrome and Safari.
|
||||||
|
* 2. Correct the outline style in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"] {
|
||||||
|
-webkit-appearance: textfield; /* 1 */
|
||||||
|
outline-offset: -2px; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove the inner padding in Chrome and Safari on macOS.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[type="search"]::-webkit-search-decoration {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 1. Correct the inability to style clickable types in iOS and Safari.
|
||||||
|
* 2. Change font properties to `inherit` in Safari.
|
||||||
|
*/
|
||||||
|
|
||||||
|
::-webkit-file-upload-button {
|
||||||
|
-webkit-appearance: button; /* 1 */
|
||||||
|
font: inherit; /* 2 */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Interactive
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in Edge, IE 10+, and Firefox.
|
||||||
|
*/
|
||||||
|
|
||||||
|
details {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Add the correct display in all browsers.
|
||||||
|
*/
|
||||||
|
|
||||||
|
summary {
|
||||||
|
display: list-item;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Misc
|
||||||
|
========================================================================== */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10+.
|
||||||
|
*/
|
||||||
|
|
||||||
|
template {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add the correct display in IE 10.
|
||||||
|
*/
|
||||||
|
|
||||||
|
[hidden] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
31
themes/stack/assets/scss/general.scss
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: var(--accent-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent-color-darker);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.link {
|
||||||
|
box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-title {
|
||||||
|
text-transform: uppercase;
|
||||||
|
margin-top: 0;
|
||||||
|
margin-bottom: var(--spacing-sm);
|
||||||
|
display: block;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
99
themes/stack/assets/scss/grid.scss
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
.container {
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
|
||||||
|
.left-sidebar {
|
||||||
|
order: -3;
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--left-sidebar-max-width);
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sidebar {
|
||||||
|
order: -1;
|
||||||
|
width: 100%;
|
||||||
|
max-width: var(--right-sidebar-max-width);
|
||||||
|
|
||||||
|
/// Display right sidebar when min-width: lg
|
||||||
|
@include respond(lg) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.extended {
|
||||||
|
@include respond(md) {
|
||||||
|
max-width: 1024px;
|
||||||
|
--left-sidebar-max-width: 25%;
|
||||||
|
--right-sidebar-max-width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(lg) {
|
||||||
|
max-width: 1280px;
|
||||||
|
--left-sidebar-max-width: 20%;
|
||||||
|
--right-sidebar-max-width: 30%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
max-width: 1536px;
|
||||||
|
--left-sidebar-max-width: 15%;
|
||||||
|
--right-sidebar-max-width: 25%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
@include respond(md) {
|
||||||
|
--left-sidebar-max-width: 25%;
|
||||||
|
max-width: 768px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(lg) {
|
||||||
|
max-width: 1024px;
|
||||||
|
--left-sidebar-max-width: 20%;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
max-width: 1280px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.flex {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
|
||||||
|
&.on-phone--column {
|
||||||
|
flex-direction: column;
|
||||||
|
@include respond(md) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.full-width {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
main.main {
|
||||||
|
order: -2;
|
||||||
|
min-width: 0;
|
||||||
|
max-width: 100%;
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--section-separation);
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
padding-top: var(--main-top-padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-container {
|
||||||
|
min-height: 100vh;
|
||||||
|
align-items: flex-start;
|
||||||
|
padding: 0 var(--container-padding);
|
||||||
|
gap: var(--section-separation);
|
||||||
|
padding-top: var(--main-top-padding);
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
5
themes/stack/assets/scss/mixins.scss
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
@mixin card-style {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
}
|
||||||
400
themes/stack/assets/scss/partials/article.scss
Normal file
@ -0,0 +1,400 @@
|
|||||||
|
/* Default article style */
|
||||||
|
.article-list {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--section-separation);
|
||||||
|
|
||||||
|
article {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
@include card-style;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-image {
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: var(--article-image-height);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
padding: var(--card-padding);
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-family: var(--article-font-family);
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 2.2rem;
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
font-size: 2.4rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-subtitle {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
line-height: 1.5;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.75rem;
|
||||||
|
@include respond(xl) {
|
||||||
|
font-size: 2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title-wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
vertical-align: middle;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
stroke-width: 1.33;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&,
|
||||||
|
time,
|
||||||
|
a,
|
||||||
|
span {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
& > .inline-meta {
|
||||||
|
display: inline-flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-category,
|
||||||
|
.article-tags {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
flex-wrap: wrap;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--accent-color-text);
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
padding: 8px 16px;
|
||||||
|
border-radius: var(--tag-border-radius);
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transition: background-color 0.5s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent-color-text);
|
||||||
|
background-color: var(--accent-color-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Compact style article list */
|
||||||
|
.article-list--compact {
|
||||||
|
@include card-style;
|
||||||
|
--image-size: var(--article-compact-image-size);
|
||||||
|
|
||||||
|
article {
|
||||||
|
& > a {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: var(--small-card-padding);
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-bottom: 1.5px solid var(--card-separator-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
flex-grow: 1;
|
||||||
|
padding: 0;
|
||||||
|
min-height: var(--image-size);
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-image {
|
||||||
|
img {
|
||||||
|
width: var(--image-size);
|
||||||
|
height: var(--image-size);
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-preview {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tile style article list */
|
||||||
|
.article-list--tile {
|
||||||
|
article {
|
||||||
|
position: relative;
|
||||||
|
width: var(--article-tile-width);
|
||||||
|
height: var(--article-tile-height);
|
||||||
|
@include card-style;
|
||||||
|
overflow: hidden;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&.has-image {
|
||||||
|
.article-details {
|
||||||
|
background-color: var(--article-tile-overlay-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
color: var(--article-tile-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-image {
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
|
||||||
|
img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
width: 100%;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: flex-end;
|
||||||
|
z-index: 2;
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
@include respond(sm) {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
font-size: 2rem;
|
||||||
|
font-weight: 500;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
|
||||||
|
@include respond(sm) {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid:not([data-processed]) {
|
||||||
|
background: var(--card-background);
|
||||||
|
min-height: 150px;
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
font-size: 0 !important;
|
||||||
|
line-height: 0;
|
||||||
|
color: transparent;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
visibility: visible;
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
width: 28px;
|
||||||
|
height: 28px;
|
||||||
|
margin: -14px 0 0 -14px;
|
||||||
|
border: 3px solid var(--card-background);
|
||||||
|
border-top-color: var(--accent-color, #3273dc);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: mermaid-spinner 0.8s linear infinite;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes mermaid-spinner {
|
||||||
|
to {
|
||||||
|
transform: rotate(360deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mermaid diagram wrapper with expand toolbar
|
||||||
|
.mermaid-wrapper {
|
||||||
|
position: relative;
|
||||||
|
margin: 1rem 0;
|
||||||
|
|
||||||
|
.mermaid {
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-toolbar {
|
||||||
|
position: absolute;
|
||||||
|
top: 0.75rem;
|
||||||
|
right: 0.75rem;
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
z-index: 10;
|
||||||
|
|
||||||
|
button {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: var(--mermaid-toolbar-btn-bg);
|
||||||
|
border: 2px solid var(--mermaid-toolbar-btn-text);
|
||||||
|
border-radius: 6px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 1rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--mermaid-toolbar-btn-text);
|
||||||
|
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3);
|
||||||
|
transition: all 0.2s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--mermaid-toolbar-btn-text);
|
||||||
|
color: var(--card-background);
|
||||||
|
transform: scale(1.05);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mermaid fullscreen modal
|
||||||
|
.mermaid-modal {
|
||||||
|
display: none;
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100vw;
|
||||||
|
height: 100vh;
|
||||||
|
background: var(--mermaid-modal-bg);
|
||||||
|
z-index: 9999;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal-header {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
padding: 0.75rem 1rem;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal-controls {
|
||||||
|
display: flex;
|
||||||
|
gap: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal-controls button,
|
||||||
|
.mermaid-modal-close {
|
||||||
|
padding: 0.5rem 1rem;
|
||||||
|
background: var(--mermaid-modal-btn-bg);
|
||||||
|
border: none;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--mermaid-modal-btn-hover);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal-body {
|
||||||
|
flex: 1;
|
||||||
|
overflow: hidden;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-modal-content {
|
||||||
|
position: absolute;
|
||||||
|
top: 1rem;
|
||||||
|
left: 1rem;
|
||||||
|
right: 1rem;
|
||||||
|
bottom: 1rem;
|
||||||
|
border-radius: 8px;
|
||||||
|
overflow: hidden;
|
||||||
|
background: var(--mermaid-modal-content-bg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid-panzoom-container {
|
||||||
|
display: inline-block;
|
||||||
|
transform-origin: 0 0;
|
||||||
|
visibility: hidden;
|
||||||
|
|
||||||
|
&.ready {
|
||||||
|
visibility: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Off-screen container for pre-rendering alternate theme
|
||||||
|
.mermaid-offscreen {
|
||||||
|
position: absolute;
|
||||||
|
left: -9999px;
|
||||||
|
visibility: hidden;
|
||||||
|
width: 800px;
|
||||||
|
}
|
||||||
21
themes/stack/assets/scss/partials/base.scss
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
html {
|
||||||
|
font-size: 62.5%;
|
||||||
|
overflow-y: scroll;
|
||||||
|
scrollbar-gutter: stable;
|
||||||
|
}
|
||||||
|
|
||||||
|
* {
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
body {
|
||||||
|
background: var(--body-background);
|
||||||
|
margin: 0;
|
||||||
|
font-family: var(--base-font-family);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
text-autospace: ideograph-alpha ideograph-numeric punctuation insert;
|
||||||
|
text-spacing-trim: trim-start allow-end;
|
||||||
|
-ms-text-autospace: ideograph-alpha;
|
||||||
|
-webkit-font-smoothing: antialiased;
|
||||||
|
-moz-osx-font-smoothing: grayscale;
|
||||||
|
}
|
||||||
80
themes/stack/assets/scss/partials/comments/artalk.scss
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
@import "../../breakpoints";
|
||||||
|
@import "../../mixins";
|
||||||
|
|
||||||
|
.artalk-container {
|
||||||
|
@include card-style;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
padding: var(--card-padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artalk {
|
||||||
|
--at-color-bg: var(--card-background);
|
||||||
|
--at-color-font: var(--card-text-color-main);
|
||||||
|
--at-color-sub: var(--card-text-color-secondary);
|
||||||
|
--at-color-main: var(--accent-color);
|
||||||
|
--at-color-border: var(--card-separator-color);
|
||||||
|
--at-border-radius: var(--card-border-radius);
|
||||||
|
--at-color-link: var(--accent-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.artalk a {
|
||||||
|
color: var(--at-color-link);
|
||||||
|
text-decoration: none;
|
||||||
|
transition: color 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent-color-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.artalk .atk-content a,
|
||||||
|
.artalk .atk-editor-plug-preview a {
|
||||||
|
color: var(--accent-color);
|
||||||
|
box-shadow: 0px -2px 0px rgba(var(--link-background-color), var(--link-background-opacity)) inset;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent-color-darker);
|
||||||
|
box-shadow: 0px calc(-1rem * var(--article-line-height)) 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="dark"] .artalk {
|
||||||
|
--at-color-main: var(--accent-color);
|
||||||
|
--at-color-link: var(--accent-color);
|
||||||
|
--at-color-bg: var(--card-background);
|
||||||
|
--at-color-bg-transl: rgba(66, 66, 66, 0.95);
|
||||||
|
--at-color-bg-grey: rgba(255, 255, 255, 0.08);
|
||||||
|
--at-color-bg-grey-transl: rgba(255, 255, 255, 0.06);
|
||||||
|
--at-color-bg-light: rgba(255, 255, 255, 0.08);
|
||||||
|
--at-color-border: var(--card-separator-color);
|
||||||
|
--at-color-font: var(--card-text-color-main);
|
||||||
|
--at-color-deep: var(--card-text-color-main);
|
||||||
|
--at-color-sub: var(--card-text-color-secondary);
|
||||||
|
--at-color-meta: var(--card-text-color-secondary);
|
||||||
|
--at-color-grey: var(--card-text-color-secondary);
|
||||||
|
--at-color-gradient: linear-gradient(
|
||||||
|
180deg,
|
||||||
|
transparent,
|
||||||
|
var(--card-background)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
.artalk code {
|
||||||
|
font-family: var(--code-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
.artalk .atk-avatar img {
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="dark"] .artalk .atk-avatar img {
|
||||||
|
filter: brightness(0.75);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="dark"] .artalk .atk-send-btn {
|
||||||
|
color: var(--accent-color-text);
|
||||||
|
}
|
||||||
394
themes/stack/assets/scss/partials/comments/disqusjs.scss
Normal file
@ -0,0 +1,394 @@
|
|||||||
|
.disqus-container {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
padding: var(--card-padding);
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs * {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs a {
|
||||||
|
text-decoration: none;
|
||||||
|
color: #076dd0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-hide {
|
||||||
|
display: none!important
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
opacity: .5
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs #dsqjs-msg {
|
||||||
|
text-align: center;
|
||||||
|
margin-top: 4px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
font-size: 14px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs #dsqjs-msg .dsqjs-msg-btn {
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-bullet {
|
||||||
|
line-height: 1.4;
|
||||||
|
margin: 0 2px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-bullet:after {
|
||||||
|
color: #c2c6cc;
|
||||||
|
content: "·";
|
||||||
|
font-weight: 700
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before {
|
||||||
|
display: table;
|
||||||
|
content: "";
|
||||||
|
line-height: 0;
|
||||||
|
clear: both
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-nav {
|
||||||
|
position: relative;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
border-bottom: 2px solid #e7e9ee
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs ol,#dsqjs ul {
|
||||||
|
list-style: none;
|
||||||
|
list-style-type: none
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-no-comment {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 16px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #2a2e2e;
|
||||||
|
margin-bottom: 6px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-nav-tab {
|
||||||
|
float: left;
|
||||||
|
text-transform: capitalize;
|
||||||
|
font-size: 15px;
|
||||||
|
padding: 12px 8px;
|
||||||
|
color: #656c7a;
|
||||||
|
display: block;
|
||||||
|
margin: 0 15px 0 0;
|
||||||
|
font-weight: 700;
|
||||||
|
line-height: 1;
|
||||||
|
position: relative;
|
||||||
|
transition: all .2s ease-in-out
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-nav-tab:last-child {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-tab-active {
|
||||||
|
color: #2a2e2e
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-tab-active>span:after {
|
||||||
|
content: " ";
|
||||||
|
display: block;
|
||||||
|
height: 2px;
|
||||||
|
background-color: #076dd0!important;
|
||||||
|
position: absolute;
|
||||||
|
bottom: -5px;
|
||||||
|
left: 0;
|
||||||
|
right: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-item {
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 16px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
|
||||||
|
float: left;
|
||||||
|
margin-right: 10px;
|
||||||
|
position: relative;
|
||||||
|
background: #dbdfe4;
|
||||||
|
padding: 0;
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-avatar img {
|
||||||
|
width: 44px;
|
||||||
|
height: 44px;
|
||||||
|
display: block;
|
||||||
|
border-radius: 4px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-header {
|
||||||
|
line-height: 1;
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author {
|
||||||
|
color: #656c7a;
|
||||||
|
font-weight: 700
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge {
|
||||||
|
color: #fff;
|
||||||
|
background: #687a86;
|
||||||
|
padding: 1px 3px;
|
||||||
|
margin-left: 4px;
|
||||||
|
font-size: 12px;
|
||||||
|
line-height: 1;
|
||||||
|
font-weight: 700;
|
||||||
|
border-radius: 3px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
top: -1px;
|
||||||
|
left: 1px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta {
|
||||||
|
display: inline-block;
|
||||||
|
font-size: 12px;
|
||||||
|
color: #656c7a
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 1.5;
|
||||||
|
word-wrap: break-word;
|
||||||
|
overflow: hidden;
|
||||||
|
color: #2a2e2e
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body code {
|
||||||
|
padding: .2em .4em;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 85%;
|
||||||
|
background: #f5f5f5;
|
||||||
|
color: inherit;
|
||||||
|
border-radius: 3px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body pre {
|
||||||
|
padding: .5em;
|
||||||
|
overflow: auto;
|
||||||
|
font-size: 85%;
|
||||||
|
line-height: 1.45;
|
||||||
|
border-radius: 3px;
|
||||||
|
background: #f5f5f5;
|
||||||
|
margin: .5em 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body blockquote {
|
||||||
|
padding: 0 .8em;
|
||||||
|
margin: .5em 0;
|
||||||
|
color: #6a737d;
|
||||||
|
border-left: .25em solid #dfe2e5
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body p:last-child {
|
||||||
|
margin: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||||
|
margin-left: 30px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img {
|
||||||
|
width: 38px;
|
||||||
|
height: 38px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-load-more {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 400;
|
||||||
|
display: block;
|
||||||
|
text-align: center;
|
||||||
|
padding: 11px 14px;
|
||||||
|
margin: 0 0 24px;
|
||||||
|
background: #687a86;
|
||||||
|
color: #fff;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-load-more:hover {
|
||||||
|
opacity: .8
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs footer {
|
||||||
|
text-align: right;
|
||||||
|
line-height: 1.5;
|
||||||
|
padding-top: 10px;
|
||||||
|
padding-right: 10px;
|
||||||
|
border-top: 2px solid #e7e9ee;
|
||||||
|
margin-top: 12px;
|
||||||
|
font-weight: 700;
|
||||||
|
font-size: 16px;
|
||||||
|
color: #555
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-disqus-logo {
|
||||||
|
background-image: url(https://c.disquscdn.com/next/embed/assets/img/sprite.654110a9206fd22f08cca0798e34a65e.png);
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
display: inline-block;
|
||||||
|
background-size: 86px 40.5px;
|
||||||
|
height: 16.5px;
|
||||||
|
width: 86px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order {
|
||||||
|
display: flex;
|
||||||
|
float: right;
|
||||||
|
align-items: center;
|
||||||
|
margin-top: 10px;
|
||||||
|
margin-bottom: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order-radio {
|
||||||
|
display: none
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
|
||||||
|
color: #fff;
|
||||||
|
background-color: #888
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order-label {
|
||||||
|
display: block;
|
||||||
|
height: 20px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-right: 10px;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 0 5px;
|
||||||
|
background-color: #dcdcdc;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs p.dsqjs-has-more {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
margin-left: 48px;
|
||||||
|
font-size: 13px;
|
||||||
|
line-height: 15px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn {
|
||||||
|
color: #656c7a;
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||||
|
margin-left:48px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
|
||||||
|
margin-right: 12px
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-list .dsqjs-post-item {
|
||||||
|
margin-bottom: 20px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 1024px) {
|
||||||
|
#dsqjs .dsqjs-post-list.dsqjs-children>li {
|
||||||
|
margin-left:60px
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-scheme="light"] {
|
||||||
|
#dsqjs .dsqjs-disqus-logo {
|
||||||
|
background-position: 0 -7px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
:root[data-scheme="dark"] {
|
||||||
|
#dsqjs {
|
||||||
|
--t-s: rgba(255,255,255,0.9);
|
||||||
|
--alt: #3e4b5e;
|
||||||
|
--link-hover: #47a2e0;
|
||||||
|
--hover-bg: #3e4b5e;
|
||||||
|
--tag: #3e4b5e;
|
||||||
|
--border: #435266;
|
||||||
|
--pre: #3c495b;
|
||||||
|
--c-bg: #2f3947;
|
||||||
|
--code: #c3c7cb;
|
||||||
|
--kbd: #4e5f77;
|
||||||
|
--hl: #abb2bf;
|
||||||
|
--hlc: #808895;
|
||||||
|
--hlk: #c678dd;
|
||||||
|
--hln: #e06c75;
|
||||||
|
--hll: #56b6c2;
|
||||||
|
--hls: #98c379;
|
||||||
|
--hlt: #e6c07b;
|
||||||
|
--hlv: #d19a66;
|
||||||
|
--bg: #181c27;
|
||||||
|
--main: #252d38;
|
||||||
|
--t: rgba(255,255,255,0.86);
|
||||||
|
--t-l: rgba(255,255,255,0.66);
|
||||||
|
--logo: #fff;
|
||||||
|
--link: #38a3fd;
|
||||||
|
--title: rgba(255,255,255,0.92);
|
||||||
|
--fab: #364151;
|
||||||
|
--shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
#disqus_thread {
|
||||||
|
color: var(--body-text-color)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs #dsqjs-msg {
|
||||||
|
color: var(--t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs a {
|
||||||
|
color:var(--link)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs a:focus,#dsqjs a:hover {
|
||||||
|
color: var(--link-hover)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-disqus-logo {
|
||||||
|
background-position: 0 -24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-nav,#dsqjs footer {
|
||||||
|
border-color: var(--hlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-load-more,#dsqjs .dsqjs-load-more:hover,#dsqjs .dsqjs-nav-tab,#dsqjs .dsqjs-no-comment,#dsqjs .dsqjs-post-content {
|
||||||
|
color: var(--t)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order-label {
|
||||||
|
background-color: var(--hlc)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
|
||||||
|
background-color: var(--kbd)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-tab-active>span:after {
|
||||||
|
background-color: #2e9fff
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-footer,#dsqjs .dsqjs-meta {
|
||||||
|
color: var(--t-l)
|
||||||
|
}
|
||||||
|
|
||||||
|
#dsqjs .dsqjs-post-body blockquote {
|
||||||
|
border-color: var(--border)
|
||||||
|
}
|
||||||
|
}
|
||||||
199
themes/stack/assets/scss/partials/cookies.scss
Normal file
@ -0,0 +1,199 @@
|
|||||||
|
.cookie-banner {
|
||||||
|
position: fixed;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
z-index: 9999;
|
||||||
|
padding: var(--container-padding);
|
||||||
|
pointer-events: none;
|
||||||
|
|
||||||
|
&[aria-hidden="true"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-banner__content {
|
||||||
|
max-width: 900px;
|
||||||
|
margin: 0 auto;
|
||||||
|
background: var(--card-background);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
padding: var(--card-padding);
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
pointer-events: auto;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
flex-direction: row;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-banner__text {
|
||||||
|
flex: 1;
|
||||||
|
min-width: 200px;
|
||||||
|
|
||||||
|
strong {
|
||||||
|
display: block;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.6;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-banner__actions {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-banner__settings-link {
|
||||||
|
width: auto;
|
||||||
|
align-self: flex-start;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 5px;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
margin-left: auto;
|
||||||
|
align-self: center;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-btn {
|
||||||
|
padding: 10px 20px;
|
||||||
|
border-radius: var(--tag-border-radius);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 0.2s ease;
|
||||||
|
border: none;
|
||||||
|
|
||||||
|
&--primary {
|
||||||
|
background: var(--accent-color);
|
||||||
|
color: var(--accent-color-text);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--accent-color-darker);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--secondary {
|
||||||
|
background: transparent;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
border: 1.5px solid var(--card-separator-color);
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: var(--card-background-selected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cookie settings panel
|
||||||
|
.cookie-settings {
|
||||||
|
padding-top: 20px;
|
||||||
|
border-top: 1.5px solid var(--card-separator-color);
|
||||||
|
|
||||||
|
&[aria-hidden="true"] {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3 {
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin: 0 0 var(--spacing-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.cookie-category {
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
border-bottom: 1px solid var(--card-separator-color);
|
||||||
|
|
||||||
|
&:last-of-type {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
input[type="checkbox"] {
|
||||||
|
width: 18px;
|
||||||
|
height: 18px;
|
||||||
|
accent-color: var(--accent-color);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
strong {
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
margin: 8px 0 0 28px;
|
||||||
|
line-height: 1.5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Footer link for reopening settings
|
||||||
|
.cookie-settings-link {
|
||||||
|
display: inline-block;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-size: 1.2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
color: var(--accent-color);
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Comments placeholder when consent not given
|
||||||
|
.consent-placeholder {
|
||||||
|
@include card-style;
|
||||||
|
padding: var(--card-padding);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin: 0 0 var(--spacing-md) 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
30
themes/stack/assets/scss/partials/footer.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
footer.site-footer {
|
||||||
|
padding: var(--spacing-lg) 0 var(--section-separation) 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
line-height: 1.75;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
height: 3px;
|
||||||
|
width: 50px;
|
||||||
|
background: var(--body-text-color);
|
||||||
|
margin-bottom: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyright {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: bold;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.powerby {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-weight: normal;
|
||||||
|
font-size: 1.2rem;
|
||||||
|
|
||||||
|
a {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
434
themes/stack/assets/scss/partials/highlight/common.scss
Normal file
@ -0,0 +1,434 @@
|
|||||||
|
/* Background */
|
||||||
|
.chroma {
|
||||||
|
color: $color;
|
||||||
|
background-color: $background-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Other */
|
||||||
|
.chroma .x {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error */
|
||||||
|
.chroma .err {
|
||||||
|
color: $error-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LineTableTD */
|
||||||
|
.chroma .lntd {
|
||||||
|
vertical-align: top;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LineTable */
|
||||||
|
.chroma .lntable {
|
||||||
|
border-spacing: 0;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
width: 100%;
|
||||||
|
display: block;
|
||||||
|
|
||||||
|
> tbody {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
> tr {
|
||||||
|
display: flex;
|
||||||
|
width: 100%;
|
||||||
|
> td:last-child {
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LineHighlight */
|
||||||
|
.chroma .hl {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
background-color: #ffffcc;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LineNumbersTable */
|
||||||
|
.chroma .lnt {
|
||||||
|
margin-right: 0;
|
||||||
|
user-select: none;
|
||||||
|
padding: 0 0.4em 0 0.4em;
|
||||||
|
color: #7f7f7f;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Optimise code block selection experience */
|
||||||
|
span.line {
|
||||||
|
margin-left: 0.4em;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LineNumbers */
|
||||||
|
.chroma .ln {
|
||||||
|
margin-right: 0.4em;
|
||||||
|
padding: 0 0.4em 0 0.4em;
|
||||||
|
color: #7f7f7f;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Keyword */
|
||||||
|
.chroma .k {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordConstant */
|
||||||
|
.chroma .kc {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordDeclaration */
|
||||||
|
.chroma .kd {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordNamespace */
|
||||||
|
.chroma .kn {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordPseudo */
|
||||||
|
.chroma .kp {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordReserved */
|
||||||
|
.chroma .kr {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* KeywordType */
|
||||||
|
.chroma .kt {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Name */
|
||||||
|
.chroma .n {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameAttribute */
|
||||||
|
.chroma .na {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameBuiltin */
|
||||||
|
.chroma .nb {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameBuiltinPseudo */
|
||||||
|
.chroma .bp {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameClass */
|
||||||
|
.chroma .nc {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameConstant */
|
||||||
|
.chroma .no {
|
||||||
|
color: $keyword-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameDecorator */
|
||||||
|
.chroma .nd {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameEntity */
|
||||||
|
.chroma .ni {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameException */
|
||||||
|
.chroma .ne {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameFunction */
|
||||||
|
.chroma .nf {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameFunctionMagic */
|
||||||
|
.chroma .fm {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameLabel */
|
||||||
|
.chroma .nl {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameNamespace */
|
||||||
|
.chroma .nn {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameOther */
|
||||||
|
.chroma .nx {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameProperty */
|
||||||
|
.chroma .py {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameTag */
|
||||||
|
.chroma .nt {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameVariable */
|
||||||
|
.chroma .nv {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameVariableClass */
|
||||||
|
.chroma .vc {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameVariableGlobal */
|
||||||
|
.chroma .vg {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameVariableInstance */
|
||||||
|
.chroma .vi {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* NameVariableMagic */
|
||||||
|
.chroma .vm {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Literal */
|
||||||
|
.chroma .l {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralDate */
|
||||||
|
.chroma .ld {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralString */
|
||||||
|
.chroma .s {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringAffix */
|
||||||
|
.chroma .sa {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringBacktick */
|
||||||
|
.chroma .sb {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringChar */
|
||||||
|
.chroma .sc {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringDelimiter */
|
||||||
|
.chroma .dl {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringDoc */
|
||||||
|
.chroma .sd {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringDouble */
|
||||||
|
.chroma .s2 {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringEscape */
|
||||||
|
.chroma .se {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringHeredoc */
|
||||||
|
.chroma .sh {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringInterpol */
|
||||||
|
.chroma .si {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringOther */
|
||||||
|
.chroma .sx {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringRegex */
|
||||||
|
.chroma .sr {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringSingle */
|
||||||
|
.chroma .s1 {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralStringSymbol */
|
||||||
|
.chroma .ss {
|
||||||
|
color: $literal-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumber */
|
||||||
|
.chroma .m {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberBin */
|
||||||
|
.chroma .mb {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberFloat */
|
||||||
|
.chroma .mf {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberHex */
|
||||||
|
.chroma .mh {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberInteger */
|
||||||
|
.chroma .mi {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberIntegerLong */
|
||||||
|
.chroma .il {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* LiteralNumberOct */
|
||||||
|
.chroma .mo {
|
||||||
|
color: #ae81ff;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Operator */
|
||||||
|
.chroma .o {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* OperatorWord */
|
||||||
|
.chroma .ow {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Punctuation */
|
||||||
|
.chroma .p {
|
||||||
|
color: $text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Comment */
|
||||||
|
.chroma .c {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentHashbang */
|
||||||
|
.chroma .ch {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentMultiline */
|
||||||
|
.chroma .cm {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentSingle */
|
||||||
|
.chroma .c1 {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentSpecial */
|
||||||
|
.chroma .cs {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentPreproc */
|
||||||
|
.chroma .cp {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* CommentPreprocFile */
|
||||||
|
.chroma .cpf {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Generic */
|
||||||
|
.chroma .g {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericDeleted */
|
||||||
|
.chroma .gd {
|
||||||
|
color: #f92672;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericEmph */
|
||||||
|
.chroma .ge {
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericError */
|
||||||
|
.chroma .gr {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericHeading */
|
||||||
|
.chroma .gh {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericInserted */
|
||||||
|
.chroma .gi {
|
||||||
|
color: $name-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericOutput */
|
||||||
|
.chroma .go {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericPrompt */
|
||||||
|
.chroma .gp {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericStrong */
|
||||||
|
.chroma .gs {
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericSubheading */
|
||||||
|
.chroma .gu {
|
||||||
|
color: #75715e;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericTraceback */
|
||||||
|
.chroma .gt {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* GenericUnderline */
|
||||||
|
.chroma .gl {
|
||||||
|
}
|
||||||
|
|
||||||
|
/* TextWhitespace */
|
||||||
|
.chroma .w {
|
||||||
|
}
|
||||||
14
themes/stack/assets/scss/partials/highlight/dark.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Style: monokai
|
||||||
|
* https://xyproto.github.io/splash/docs/monokai.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
$color: #f8f8f2;
|
||||||
|
$background-color: #272822;
|
||||||
|
$error-color: #bb0064;
|
||||||
|
$keyword-color: #66d9ef;
|
||||||
|
$text-color: $color;
|
||||||
|
$name-color: #a6e22e;
|
||||||
|
$literal-color: #e6db74;
|
||||||
|
|
||||||
|
@import "common.scss";
|
||||||
14
themes/stack/assets/scss/partials/highlight/light.scss
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
/*
|
||||||
|
* Style: monokailight
|
||||||
|
* https://xyproto.github.io/splash/docs/monokailight.html
|
||||||
|
*/
|
||||||
|
|
||||||
|
$color: #272822;
|
||||||
|
$background-color: #fafafa;
|
||||||
|
$error-color: #960050;
|
||||||
|
$keyword-color: #00a8c8;
|
||||||
|
$text-color: #111111;
|
||||||
|
$name-color: #75af00;
|
||||||
|
$literal-color: #d88200;
|
||||||
|
|
||||||
|
@import "common.scss";
|
||||||
6
themes/stack/assets/scss/partials/layout/404.scss
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
.not-found-card {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
padding: var(--card-padding);
|
||||||
|
}
|
||||||
486
themes/stack/assets/scss/partials/layout/article.scss
Normal file
@ -0,0 +1,486 @@
|
|||||||
|
.article-page {
|
||||||
|
&.hide-sidebar-sm .left-sidebar {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
display: inherit;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.main-article {
|
||||||
|
background: var(--card-background);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.article-header {
|
||||||
|
.article-image {
|
||||||
|
img {
|
||||||
|
height: auto;
|
||||||
|
width: 100%;
|
||||||
|
max-height: 50vh;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
padding: var(--card-padding);
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
margin: var(--card-padding) 0;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
|
||||||
|
.footnotes {
|
||||||
|
font-family: var(--base-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
img {
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-footer {
|
||||||
|
margin: var(--card-padding);
|
||||||
|
gap: var(--spacing-2xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.toc-nav {
|
||||||
|
overflow-x: auto;
|
||||||
|
max-height: 75vh;
|
||||||
|
|
||||||
|
ol,
|
||||||
|
ul {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
ol {
|
||||||
|
list-style-type: none;
|
||||||
|
counter-reset: item;
|
||||||
|
|
||||||
|
li a:first-of-type::before {
|
||||||
|
counter-increment: item;
|
||||||
|
content: counters(item, ".") ". ";
|
||||||
|
font-weight: bold;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&>ul {
|
||||||
|
padding: 0 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
margin: var(--spacing-md) 0 var(--spacing-md) var(--spacing-lg);
|
||||||
|
padding: var(--spacing-xs);
|
||||||
|
|
||||||
|
&>ol,
|
||||||
|
&>ul {
|
||||||
|
margin-top: var(--spacing-sm);
|
||||||
|
padding-left: var(--spacing-sm);
|
||||||
|
margin-bottom: calc(var(--spacing-xs) * -1);
|
||||||
|
|
||||||
|
&>li:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
li.active-class>a {
|
||||||
|
border-left: var(--heading-border-size) solid var(--accent-color);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
ul li.active-class>a {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
@function repeat($str, $n) {
|
||||||
|
$result: "";
|
||||||
|
|
||||||
|
@for $_ from 0 to $n {
|
||||||
|
$result: $result + $str;
|
||||||
|
}
|
||||||
|
|
||||||
|
@return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Support up to 6 levels of indentation for lists in ToCs
|
||||||
|
@for $i from 0 to 5 {
|
||||||
|
&>ul #{repeat("> li > ul", $i)}>li.active-class>a {
|
||||||
|
$n: 25 + $i * 35;
|
||||||
|
margin-left: calc(-#{$n}px - 1em);
|
||||||
|
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
|
||||||
|
}
|
||||||
|
|
||||||
|
&>ol #{repeat("> li > ol", $i)}>li.active-class>a {
|
||||||
|
$n: 9 + $i * 35;
|
||||||
|
margin-left: calc(-#{$n}px - 1em);
|
||||||
|
padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget--toc,
|
||||||
|
.article-toc {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.widget--toc {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-toc {
|
||||||
|
padding: 0 var(--card-padding);
|
||||||
|
margin-top: var(--spacing-lg);
|
||||||
|
|
||||||
|
@include respond(lg) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
details {
|
||||||
|
background-color: var(--card-background);
|
||||||
|
border-radius: var(--card-border-radius);
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
|
||||||
|
&[open] {
|
||||||
|
.article-toc-title {
|
||||||
|
svg {
|
||||||
|
transform: rotate(90deg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
summary {
|
||||||
|
list-style: none;
|
||||||
|
padding: var(--spacing-md) var(--spacing-lg);
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
&::-webkit-details-marker {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-toc-title {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
text-transform: uppercase;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
font-weight: bold;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
stroke-width: 1.33;
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-content {
|
||||||
|
font-family: var(--article-font-family);
|
||||||
|
font-size: var(--article-font-size);
|
||||||
|
padding: 0 var(--card-padding);
|
||||||
|
line-height: var(--article-line-height);
|
||||||
|
|
||||||
|
&>p {
|
||||||
|
margin: 1.5em 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1,
|
||||||
|
h2,
|
||||||
|
h3,
|
||||||
|
h4,
|
||||||
|
h5,
|
||||||
|
h6 {
|
||||||
|
margin-inline-start: calc((var(--card-padding)) * -1);
|
||||||
|
padding-inline-start: calc(var(--card-padding) - var(--heading-border-size));
|
||||||
|
border-inline-start: var(--heading-border-size) solid var(--accent-color);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
a.header-anchor {
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
opacity: 0;
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
width: var(--card-padding);
|
||||||
|
text-align: center;
|
||||||
|
color: var(--accent-color);
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
content: "#";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus {
|
||||||
|
a.header-anchor {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
figure {
|
||||||
|
text-align: center;
|
||||||
|
|
||||||
|
figcaption {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote {
|
||||||
|
position: relative;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color);
|
||||||
|
padding: var(--spacing-md) calc(var(--card-padding) - var(--blockquote-border-size));
|
||||||
|
background-color: var(--blockquote-background-color);
|
||||||
|
|
||||||
|
.cite {
|
||||||
|
display: block;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 0.75em;
|
||||||
|
|
||||||
|
a {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
blockquote.alert {
|
||||||
|
.alert-header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 0.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.alert-body {
|
||||||
|
> :last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$alert-types: note, tip, important, warning, caution;
|
||||||
|
|
||||||
|
@each $type in $alert-types {
|
||||||
|
&.alert-#{$type} {
|
||||||
|
border-color: var(--alert-#{$type}-color);
|
||||||
|
background-color: var(--alert-#{$type}-background);
|
||||||
|
|
||||||
|
.alert-title {
|
||||||
|
color: var(--alert-#{$type}-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hr {
|
||||||
|
width: 100px;
|
||||||
|
margin: 40px auto;
|
||||||
|
background: var(--card-text-color-tertiary);
|
||||||
|
height: 2px;
|
||||||
|
border: 0;
|
||||||
|
opacity: 0.55;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: var(--code-text-color);
|
||||||
|
background-color: var(--code-background-color);
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: var(--tag-border-radius);
|
||||||
|
font-family: var(--code-font-family);
|
||||||
|
}
|
||||||
|
|
||||||
|
a,
|
||||||
|
code {
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gallery {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
justify-content: center;
|
||||||
|
margin: 1.5em 0;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
|
||||||
|
figure {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
background-color: var(--pre-background-color);
|
||||||
|
color: var(--pre-text-color);
|
||||||
|
font-family: var(--code-font-family);
|
||||||
|
line-height: 1.428571429;
|
||||||
|
word-break: break-all;
|
||||||
|
padding: var(--card-padding);
|
||||||
|
|
||||||
|
// keep Codeblocks LTR
|
||||||
|
[dir="rtl"] & {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
code {
|
||||||
|
color: unset;
|
||||||
|
border: none;
|
||||||
|
background: none;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.highlight {
|
||||||
|
background-color: var(--pre-background-color);
|
||||||
|
padding: var(--card-padding);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
pre {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
.copyCodeButton {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// keep Codeblocks LTR
|
||||||
|
[dir="rtl"] & {
|
||||||
|
direction: ltr;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.copyCodeButton {
|
||||||
|
position: absolute;
|
||||||
|
top: calc(var(--card-padding));
|
||||||
|
right: calc(var(--card-padding));
|
||||||
|
background: var(--card-background);
|
||||||
|
border: none;
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
border-radius: var(--tag-border-radius);
|
||||||
|
padding: 8px 16px;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
opacity: 0;
|
||||||
|
transition: opacity 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
padding: 0 var(--card-padding);
|
||||||
|
overflow-x: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
table {
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
border-spacing: 0;
|
||||||
|
margin-bottom: 1.5em;
|
||||||
|
font-size: 0.96em;
|
||||||
|
}
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
text-align: left;
|
||||||
|
padding: 4px 8px 4px 10px;
|
||||||
|
border: 1px solid var(--table-border-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
vertical-align: top;
|
||||||
|
}
|
||||||
|
|
||||||
|
tr:nth-child(even) {
|
||||||
|
background-color: var(--tr-even-background-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
.twitter-tweet {
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.video-wrapper {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
height: 0;
|
||||||
|
padding-bottom: 56.25%;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&>iframe,
|
||||||
|
&>video {
|
||||||
|
position: absolute;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.gitlab-embed-snippets {
|
||||||
|
margin: 0 !important;
|
||||||
|
|
||||||
|
.file-holder.snippet-file-content {
|
||||||
|
margin-block-end: 0 !important;
|
||||||
|
margin-block-start: 0 !important;
|
||||||
|
margin-left: calc((var(--card-padding)) * -1) !important;
|
||||||
|
margin-right: calc((var(--card-padding)) * -1) !important;
|
||||||
|
padding: 0 var(--card-padding) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Negative margins
|
||||||
|
blockquote,
|
||||||
|
figure,
|
||||||
|
.highlight,
|
||||||
|
pre,
|
||||||
|
.gallery,
|
||||||
|
.video-wrapper,
|
||||||
|
.table-wrapper,
|
||||||
|
.s_video_simple {
|
||||||
|
margin-left: calc((var(--card-padding)) * -1);
|
||||||
|
margin-right: calc((var(--card-padding)) * -1);
|
||||||
|
width: calc(100% + var(--card-padding) * 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Make long KaTeX equations scrollable in the x-axis
|
||||||
|
.katex-display>.katex {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
border: 1px solid var(--kbd-border-color);
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 0.9em;
|
||||||
|
line-height: 1;
|
||||||
|
padding: 2px 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
}
|
||||||
157
themes/stack/assets/scss/partials/layout/list.scss
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
.section-card {
|
||||||
|
@include card-style;
|
||||||
|
padding: var(--small-card-padding);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
|
||||||
|
--separation: 15px;
|
||||||
|
|
||||||
|
.section-term {
|
||||||
|
font-size: 2.2rem;
|
||||||
|
margin: 0;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-description {
|
||||||
|
font-weight: normal;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-details {
|
||||||
|
flex-grow: 1;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-image {
|
||||||
|
img {
|
||||||
|
width: 60px;
|
||||||
|
height: 60px;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.section-count {
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
margin: 0;
|
||||||
|
font-weight: bold;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@property --left-edge-fade-size {
|
||||||
|
syntax: "<length>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 0px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@property --right-edge-fade-size {
|
||||||
|
syntax: "<length>";
|
||||||
|
inherits: false;
|
||||||
|
initial-value: 64px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subsection-list {
|
||||||
|
--shadow-overlap-x: calc(var(--shadow-l2-blur) + 4px);
|
||||||
|
--shadow-overlap-y: calc(var(--shadow-l2-blur) + var(--shadow-l2-y) + 4px);
|
||||||
|
--edge-fade-size: 64px;
|
||||||
|
--left-edge-fade-size: 0px;
|
||||||
|
--right-edge-fade-size: var(--edge-fade-size);
|
||||||
|
|
||||||
|
overflow-x: auto;
|
||||||
|
|
||||||
|
/// Limit the horizontal overlap to the container padding to prevent page overflow on mobile.
|
||||||
|
/// On desktop, it still allows the full shadow to be visible.
|
||||||
|
--overlap-x: min(var(--shadow-overlap-x), var(--container-padding));
|
||||||
|
|
||||||
|
/// These values are computed from the shadow of the article-list--tile (which uses shadow-l1 and shadow-l2)
|
||||||
|
/// so that the shadow is not cut off, while staying within the viewport on small screens.
|
||||||
|
/// Clamp both sides to prevent page-level horizontal overflow.
|
||||||
|
margin-left: calc(var(--overlap-x) * -1);
|
||||||
|
margin-right: calc(var(--overlap-x) * -1);
|
||||||
|
margin-bottom: calc(var(--shadow-overlap-y) * -1);
|
||||||
|
|
||||||
|
/// Smooth fade on both horizontal edges to avoid hard cuts where shadows are clamped.
|
||||||
|
--subsection-mask-image: linear-gradient(
|
||||||
|
to right,
|
||||||
|
transparent 0%,
|
||||||
|
rgb(0 0 0 / 0.06) calc(var(--left-edge-fade-size) * 0.12),
|
||||||
|
rgb(0 0 0 / 0.2) calc(var(--left-edge-fade-size) * 0.3),
|
||||||
|
rgb(0 0 0 / 0.45) calc(var(--left-edge-fade-size) * 0.52),
|
||||||
|
rgb(0 0 0 / 0.72) calc(var(--left-edge-fade-size) * 0.76),
|
||||||
|
black var(--left-edge-fade-size),
|
||||||
|
black calc(100% - var(--right-edge-fade-size)),
|
||||||
|
rgb(0 0 0 / 0.72) calc(100% - (var(--right-edge-fade-size) * 0.76)),
|
||||||
|
rgb(0 0 0 / 0.45) calc(100% - (var(--right-edge-fade-size) * 0.52)),
|
||||||
|
rgb(0 0 0 / 0.2) calc(100% - (var(--right-edge-fade-size) * 0.3)),
|
||||||
|
rgb(0 0 0 / 0.06) calc(100% - (var(--right-edge-fade-size) * 0.12)),
|
||||||
|
transparent 100%
|
||||||
|
);
|
||||||
|
-webkit-mask-image: var(--subsection-mask-image);
|
||||||
|
mask-image: var(--subsection-mask-image);
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
margin-left: calc(var(--overlap-x) * -1);
|
||||||
|
margin-right: calc(var(--overlap-x) * -1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-list--tile {
|
||||||
|
display: flex;
|
||||||
|
padding: var(--spacing-xs) var(--overlap-x) var(--shadow-overlap-y) var(--overlap-x);
|
||||||
|
width: max-content;
|
||||||
|
gap: var(--spacing-lg);
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
padding: var(--spacing-xs) var(--overlap-x) var(--shadow-overlap-y) var(--overlap-x);
|
||||||
|
}
|
||||||
|
|
||||||
|
article {
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.article-title {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-details {
|
||||||
|
padding: var(--spacing-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Only apply scroll-driven edge fades in browsers that support scroll timelines.
|
||||||
|
// Browsers without support (e.g. Firefox) fall back to the static right-edge fade
|
||||||
|
// defined by the explicit CSS variable declarations above.
|
||||||
|
@supports (animation-timeline: scroll(self inline)) {
|
||||||
|
.subsection-list {
|
||||||
|
animation: subsection-left-fade linear both, subsection-right-fade linear both;
|
||||||
|
animation-timeline: scroll(self inline);
|
||||||
|
animation-range: 1px 24px, 0% 100%;
|
||||||
|
animation-timing-function: ease-out, linear;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subsection-left-fade {
|
||||||
|
from {
|
||||||
|
--left-edge-fade-size: 0px;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
--left-edge-fade-size: var(--edge-fade-size);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes subsection-right-fade {
|
||||||
|
0%,
|
||||||
|
92% {
|
||||||
|
--right-edge-fade-size: var(--edge-fade-size);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
--right-edge-fade-size: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
87
themes/stack/assets/scss/partials/layout/search.scss
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
.search-form {
|
||||||
|
position: relative;
|
||||||
|
--button-size: 80px;
|
||||||
|
|
||||||
|
&.widget {
|
||||||
|
--button-size: 60px;
|
||||||
|
|
||||||
|
label {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
top: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
padding: var(--spacing-2xl) var(--spacing-lg) var(--spacing-md) var(--spacing-lg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
p {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
label {
|
||||||
|
position: absolute;
|
||||||
|
top: var(--spacing-md);
|
||||||
|
inset-inline-start: var(--spacing-lg);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
}
|
||||||
|
|
||||||
|
input {
|
||||||
|
padding: 40px 20px 20px;
|
||||||
|
@include card-style;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
width: 100%;
|
||||||
|
border: 0;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
font-size: 1.8rem;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
button {
|
||||||
|
position: absolute;
|
||||||
|
inset-inline-end: 0;
|
||||||
|
top: 0;
|
||||||
|
height: 100%;
|
||||||
|
width: var(--button-size);
|
||||||
|
cursor: pointer;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
|
||||||
|
padding: 0 10px;
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: 0;
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke-width: 2;
|
||||||
|
color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
stroke-width: 1.33;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
.search-result {
|
||||||
|
&.hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
231
themes/stack/assets/scss/partials/menu.scss
Normal file
@ -0,0 +1,231 @@
|
|||||||
|
/*!
|
||||||
|
* Hamburgers
|
||||||
|
* @description Tasty CSS-animated hamburgers
|
||||||
|
* @author Jonathan Suh @jonsuh
|
||||||
|
* @site https://jonsuh.com/hamburgers
|
||||||
|
* @link https://github.com/jonsuh/hamburgers
|
||||||
|
*/
|
||||||
|
|
||||||
|
.hamburger {
|
||||||
|
padding-top: var(--spacing-sm);
|
||||||
|
display: inline-block;
|
||||||
|
cursor: pointer;
|
||||||
|
transition-property: opacity, filter;
|
||||||
|
transition-duration: 0.15s;
|
||||||
|
transition-timing-function: linear;
|
||||||
|
font: inherit;
|
||||||
|
color: inherit;
|
||||||
|
text-transform: none;
|
||||||
|
background-color: transparent;
|
||||||
|
border: 0;
|
||||||
|
margin: 0;
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger:hover {
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-box {
|
||||||
|
width: 30px;
|
||||||
|
height: 24px;
|
||||||
|
display: inline-block;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-inner {
|
||||||
|
display: block;
|
||||||
|
top: 50%;
|
||||||
|
margin-top: -2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-inner,
|
||||||
|
.hamburger-inner::before,
|
||||||
|
.hamburger-inner::after {
|
||||||
|
width: 30px;
|
||||||
|
height: 2px;
|
||||||
|
background-color: var(--card-text-color-main);
|
||||||
|
border-radius: 4px;
|
||||||
|
position: absolute;
|
||||||
|
transition-property: transform;
|
||||||
|
transition-duration: 0.15s;
|
||||||
|
transition-timing-function: ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-inner::before,
|
||||||
|
.hamburger-inner::after {
|
||||||
|
content: "";
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-inner::before {
|
||||||
|
top: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger-inner::after {
|
||||||
|
bottom: -10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin .hamburger-inner {
|
||||||
|
transition-duration: 0.22s;
|
||||||
|
transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin .hamburger-inner::before {
|
||||||
|
transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin .hamburger-inner::after {
|
||||||
|
transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin.is-active .hamburger-inner {
|
||||||
|
transform: rotate(225deg);
|
||||||
|
transition-delay: 0.12s;
|
||||||
|
transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin.is-active .hamburger-inner::before {
|
||||||
|
top: 0;
|
||||||
|
opacity: 0;
|
||||||
|
transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.hamburger--spin.is-active .hamburger-inner::after {
|
||||||
|
bottom: 0;
|
||||||
|
transform: rotate(-90deg);
|
||||||
|
transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#toggle-menu {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
z-index: 2;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
[dir="rtl"] & {
|
||||||
|
left: 0;
|
||||||
|
right: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
outline: none;
|
||||||
|
|
||||||
|
&.is-active {
|
||||||
|
|
||||||
|
.hamburger-inner,
|
||||||
|
.hamburger-inner::before,
|
||||||
|
.hamburger-inner::after {
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Menu style */
|
||||||
|
#main-menu {
|
||||||
|
list-style: none;
|
||||||
|
overflow-y: auto;
|
||||||
|
flex-grow: 1;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
background-color: var(--card-background);
|
||||||
|
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
display: none;
|
||||||
|
margin: 0 calc(var(--container-padding) * -1);
|
||||||
|
|
||||||
|
padding: var(--spacing-2xl);
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
padding: 15px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&,
|
||||||
|
.menu-bottom-section ol {
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-2xl);
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
gap: var(--spacing-xl);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.show {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
align-items: flex-end;
|
||||||
|
display: flex;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0;
|
||||||
|
box-shadow: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
li {
|
||||||
|
position: relative;
|
||||||
|
vertical-align: middle;
|
||||||
|
padding: 0;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
stroke: currentColor;
|
||||||
|
stroke-width: 1.33;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
height: 100%;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
gap: var(--menu-icon-separation);
|
||||||
|
}
|
||||||
|
|
||||||
|
span {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
a {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&.menu-bottom-section {
|
||||||
|
margin-top: auto;
|
||||||
|
|
||||||
|
ol {
|
||||||
|
display: flex;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.menu-social {
|
||||||
|
list-style: none;
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
stroke: var(--body-text-color);
|
||||||
|
stroke-width: 1.33;
|
||||||
|
}
|
||||||
|
}
|
||||||
259
themes/stack/assets/scss/partials/pagination.scss
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
.pagination {
|
||||||
|
display: flex;
|
||||||
|
@include card-style;
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
.hide-on-mobile,
|
||||||
|
.pagination-link {
|
||||||
|
display: none;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.page-link {
|
||||||
|
padding: var(--spacing-sm) 0;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
background: none;
|
||||||
|
font: inherit;
|
||||||
|
border-radius: 0;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1 1 auto;
|
||||||
|
min-width: 36px;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
text-decoration: none;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
min-width: 48px;
|
||||||
|
padding: var(--spacing-md) 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:first-child,
|
||||||
|
&:last-child {
|
||||||
|
flex: 0 0 var(--spacing-3xl);
|
||||||
|
padding: var(--spacing-sm);
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
flex: 0 0 48px;
|
||||||
|
padding: var(--spacing-md);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover:not(.current):not(.disabled) {
|
||||||
|
background-color: var(--card-background-selected);
|
||||||
|
opacity: 0.8;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.current {
|
||||||
|
background-color: var(--card-background-selected);
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
opacity: 0.3;
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
color: var(--card-text-color-secondary);
|
||||||
|
}
|
||||||
|
|
||||||
|
svg {
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.pagination-jump-trigger {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#pagination-jump-dialog {
|
||||||
|
border: none;
|
||||||
|
@include card-style;
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
color: var(--body-text-color);
|
||||||
|
margin: auto; // Center dialog
|
||||||
|
width: calc(100% - (var(--container-padding) * 2));
|
||||||
|
max-width: 320px;
|
||||||
|
padding: var(--card-padding);
|
||||||
|
|
||||||
|
&::backdrop {
|
||||||
|
background-color: rgba(0, 0, 0, 0.4);
|
||||||
|
opacity: 0;
|
||||||
|
animation: backdrop-fade-in 0.3s ease forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[open] {
|
||||||
|
animation: scale-up 0.3s cubic-bezier(0.175, 0.885, 0.32, 1.275) forwards;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.closing {
|
||||||
|
animation: scale-down 0.2s ease forwards;
|
||||||
|
|
||||||
|
&::backdrop {
|
||||||
|
animation: backdrop-fade-out 0.2s ease forwards;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-jump-form {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-md);
|
||||||
|
|
||||||
|
header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
margin-bottom: var(--spacing-xs);
|
||||||
|
|
||||||
|
label {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.subtitle {
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-jump-input-group {
|
||||||
|
display: flex;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
align-items: stretch;
|
||||||
|
|
||||||
|
input[type="number"] {
|
||||||
|
flex: 1;
|
||||||
|
width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
padding: 0 var(--spacing-md);
|
||||||
|
height: 44px; // Explicit height for perfect alignment
|
||||||
|
border: 1.5px solid var(--card-separator-color);
|
||||||
|
border-radius: 6px;
|
||||||
|
background-color: var(--body-background);
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 1.6rem;
|
||||||
|
outline: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
appearance: none;
|
||||||
|
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
/* Hide spinner */
|
||||||
|
-moz-appearance: textfield;
|
||||||
|
&::-webkit-outer-spin-button,
|
||||||
|
&::-webkit-inner-spin-button {
|
||||||
|
-webkit-appearance: none;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-visible {
|
||||||
|
outline: none;
|
||||||
|
border-color: var(--accent-color);
|
||||||
|
box-shadow: 0 0 0 3px color-mix(in srgb, var(--accent-color) 20%, transparent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-primary {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 0 var(--spacing-xl);
|
||||||
|
height: 44px; // Match input height
|
||||||
|
background-color: var(--accent-color);
|
||||||
|
color: var(--accent-color-text);
|
||||||
|
border: none;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 1.5rem;
|
||||||
|
font-weight: bold;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s ease, box-shadow 0.2s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: var(--accent-color-darker);
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active {
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.pagination-jump-hint {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: flex-start;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
font-size: 1.3rem;
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
margin-top: calc(var(--spacing-xs) * -1);
|
||||||
|
|
||||||
|
kbd {
|
||||||
|
background-color: var(--body-background);
|
||||||
|
border: 1px solid var(--card-separator-color);
|
||||||
|
border-radius: 4px;
|
||||||
|
padding: 3px 6px;
|
||||||
|
font-family: var(--base-font-family);
|
||||||
|
font-size: 1.1rem;
|
||||||
|
box-shadow: 0 2px 0 var(--card-separator-color);
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-weight: 600;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-up {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.9);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scale-down {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backdrop-fade-in {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes backdrop-fade-out {
|
||||||
|
from {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
228
themes/stack/assets/scss/partials/sidebar.scss
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
.sidebar {
|
||||||
|
&.sticky {
|
||||||
|
@include respond(md) {
|
||||||
|
position: sticky;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.left-sidebar {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex-shrink: 0;
|
||||||
|
align-self: stretch;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
gap: var(--sidebar-element-separation);
|
||||||
|
}
|
||||||
|
|
||||||
|
margin-bottom: calc(-1 * var(--sidebar-element-separation));
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&> :not(:last-child) {
|
||||||
|
margin-bottom: var(--sidebar-element-separation);
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
max-width: none;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
--sidebar-avatar-size: 100px;
|
||||||
|
--sidebar-element-separation: var(--spacing-lg);
|
||||||
|
--emoji-size: 40px;
|
||||||
|
--emoji-font-size: 20px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
width: auto;
|
||||||
|
padding-top: var(--main-top-padding);
|
||||||
|
padding-bottom: var(--main-top-padding);
|
||||||
|
max-height: 100vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(2xl) {
|
||||||
|
--sidebar-avatar-size: 120px;
|
||||||
|
--sidebar-element-separation: var(--spacing-xl);
|
||||||
|
--emoji-size: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.sticky {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.compact {
|
||||||
|
--sidebar-avatar-size: 80px;
|
||||||
|
--emoji-size: 30px;
|
||||||
|
--emoji-font-size: 15px;
|
||||||
|
|
||||||
|
header {
|
||||||
|
@include respond(lg) {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-meta {
|
||||||
|
gap: var(--spacing-xs);
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
@include respond(2xl) {
|
||||||
|
font-size: 1.75rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-description {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.right-sidebar {
|
||||||
|
width: 100%;
|
||||||
|
display: none;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--widget-separation);
|
||||||
|
|
||||||
|
&.sticky {
|
||||||
|
top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(lg) {
|
||||||
|
padding-top: var(--main-top-padding);
|
||||||
|
padding-bottom: var(--main-top-padding);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.sidebar header {
|
||||||
|
z-index: 1;
|
||||||
|
transition: box-shadow 0.5s ease;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--sidebar-element-separation);
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-avatar {
|
||||||
|
position: relative;
|
||||||
|
margin: 0;
|
||||||
|
width: var(--sidebar-avatar-size);
|
||||||
|
height: var(--sidebar-avatar-size);
|
||||||
|
flex-shrink: 0;
|
||||||
|
|
||||||
|
.site-logo {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: var(--shadow-l1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.emoji {
|
||||||
|
position: absolute;
|
||||||
|
width: var(--emoji-size);
|
||||||
|
height: var(--emoji-size);
|
||||||
|
line-height: var(--emoji-size);
|
||||||
|
border-radius: 100%;
|
||||||
|
bottom: 0;
|
||||||
|
right: 0;
|
||||||
|
text-align: center;
|
||||||
|
font-size: var(--emoji-font-size);
|
||||||
|
background-color: var(--card-background);
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-meta {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-name {
|
||||||
|
color: var(--accent-color);
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.6rem;
|
||||||
|
|
||||||
|
@include respond(2xl) {
|
||||||
|
font-size: 1.8rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.site-description {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
font-weight: normal;
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.4rem;
|
||||||
|
|
||||||
|
@include respond(2xl) {
|
||||||
|
font-size: 1.6rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="dark"] {
|
||||||
|
#dark-mode-toggle {
|
||||||
|
color: var(--accent-color);
|
||||||
|
font-weight: 700;
|
||||||
|
|
||||||
|
.icon-tabler-toggle-left {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-tabler-toggle-right {
|
||||||
|
display: unset;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#dark-mode-toggle {
|
||||||
|
margin-top: auto;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
cursor: pointer;
|
||||||
|
gap: var(--menu-icon-separation);
|
||||||
|
|
||||||
|
.icon-tabler-toggle-right {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#i18n-switch {
|
||||||
|
color: var(--body-text-color);
|
||||||
|
display: inline-flex;
|
||||||
|
align-content: center;
|
||||||
|
gap: var(--menu-icon-separation);
|
||||||
|
|
||||||
|
select {
|
||||||
|
border: 0;
|
||||||
|
background: none !important;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
padding: 0 !important;
|
||||||
|
cursor: pointer;
|
||||||
|
font-family: inherit;
|
||||||
|
font-size: inherit;
|
||||||
|
appearance: none;
|
||||||
|
-webkit-appearance: none;
|
||||||
|
-moz-appearance: none;
|
||||||
|
|
||||||
|
&::-ms-expand {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
option {
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
background-color: var(--card-background);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
64
themes/stack/assets/scss/partials/widgets.scss
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
.widget {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
|
||||||
|
.widget-icon {
|
||||||
|
svg {
|
||||||
|
width: var(--widget-icon-size);
|
||||||
|
height: var(--widget-icon-size);
|
||||||
|
stroke-width: 1.6;
|
||||||
|
color: var(--body-text-color);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Tag cloud widget */
|
||||||
|
.tagCloud {
|
||||||
|
.tagCloud-tags {
|
||||||
|
display: flex;
|
||||||
|
flex-wrap: wrap;
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
|
||||||
|
a {
|
||||||
|
@include card-style;
|
||||||
|
border-radius: var(--tag-border-radius);
|
||||||
|
padding: 8px 20px;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-size: 1.4rem;
|
||||||
|
transition: box-shadow 0.3s ease;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: var(--shadow-l2);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Archives widget */
|
||||||
|
.widget.archives {
|
||||||
|
.widget-archive--list {
|
||||||
|
@include card-style;
|
||||||
|
}
|
||||||
|
|
||||||
|
.archives-year {
|
||||||
|
&:not(:last-of-type) {
|
||||||
|
border-bottom: 1.5px solid var(--card-separator-color);
|
||||||
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
font-size: 1.4rem;
|
||||||
|
padding: 18px 25px;
|
||||||
|
display: flex;
|
||||||
|
|
||||||
|
span.year {
|
||||||
|
flex: 1;
|
||||||
|
color: var(--card-text-color-main);
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
span.count {
|
||||||
|
color: var(--card-text-color-tertiary);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
30
themes/stack/assets/scss/style.scss
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
/*!
|
||||||
|
* Hugo Theme Stack
|
||||||
|
*
|
||||||
|
* @author: Jimmy Cai
|
||||||
|
* @website: https://jimmycai.com
|
||||||
|
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
||||||
|
*/
|
||||||
|
|
||||||
|
@import "breakpoints.scss";
|
||||||
|
@import "variables.scss";
|
||||||
|
@import "mixins.scss";
|
||||||
|
@import "grid.scss";
|
||||||
|
|
||||||
|
@import "external/normalize.scss";
|
||||||
|
|
||||||
|
@import "partials/menu.scss";
|
||||||
|
@import "partials/article.scss";
|
||||||
|
@import "partials/widgets.scss";
|
||||||
|
@import "partials/footer.scss";
|
||||||
|
@import "partials/pagination.scss";
|
||||||
|
@import "partials/sidebar.scss";
|
||||||
|
@import "partials/base.scss";
|
||||||
|
@import "partials/layout/article.scss";
|
||||||
|
@import "partials/layout/list.scss";
|
||||||
|
@import "partials/layout/404.scss";
|
||||||
|
@import "partials/layout/search.scss";
|
||||||
|
@import "partials/cookies.scss";
|
||||||
|
|
||||||
|
@import "general.scss";
|
||||||
|
@import "custom.scss";
|
||||||
228
themes/stack/assets/scss/variables.scss
Normal file
@ -0,0 +1,228 @@
|
|||||||
|
/*
|
||||||
|
* Global style
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--main-top-padding: 35px;
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
--main-top-padding: 50px;
|
||||||
|
}
|
||||||
|
|
||||||
|
--body-background: #f5f5fa;
|
||||||
|
|
||||||
|
--accent-color: #34495e;
|
||||||
|
--accent-color-darker: #2c3e50;
|
||||||
|
--accent-color-text: #fff;
|
||||||
|
--body-text-color: #707070;
|
||||||
|
|
||||||
|
--tag-border-radius: 4px;
|
||||||
|
|
||||||
|
--section-separation: 40px;
|
||||||
|
|
||||||
|
&[data-scheme="dark"] {
|
||||||
|
--body-background: #303030;
|
||||||
|
--accent-color: #ecf0f1;
|
||||||
|
--accent-color-darker: #bdc3c7;
|
||||||
|
--accent-color-text: #000;
|
||||||
|
--body-text-color: rgba(255, 255, 255, 0.7);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Global font family
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--sys-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue";
|
||||||
|
--base-font-family: "Lato", var(--sys-font-family), sans-serif;
|
||||||
|
--code-font-family: Menlo, Monaco, Consolas, "Courier New", monospace;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Card style
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--card-background: #fff;
|
||||||
|
--card-background-selected: #eaeaea;
|
||||||
|
|
||||||
|
--card-text-color-main: #000;
|
||||||
|
--card-text-color-secondary: #747474;
|
||||||
|
--card-text-color-tertiary: #767676;
|
||||||
|
--card-separator-color: rgba(218, 218, 218, 0.5);
|
||||||
|
|
||||||
|
--card-border-radius: 10px;
|
||||||
|
|
||||||
|
--card-padding: 20px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--card-padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
--card-padding: 30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
--small-card-padding: 25px 20px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--small-card-padding: 25px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-scheme="dark"] {
|
||||||
|
--card-background: #424242;
|
||||||
|
--card-background-selected: rgba(255, 255, 255, 0.16);
|
||||||
|
--card-text-color-main: rgba(255, 255, 255, 0.9);
|
||||||
|
--card-text-color-secondary: rgba(255, 255, 255, 0.7);
|
||||||
|
--card-text-color-tertiary: rgba(255, 255, 255, 0.5);
|
||||||
|
--card-separator-color: rgba(255, 255, 255, 0.12);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Article content font settings
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--article-font-family: var(--base-font-family);
|
||||||
|
--article-font-size: 1.6rem;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--article-font-size: 1.7rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
--article-line-height: 1.85;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Article content style
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--blockquote-border-size: 4px;
|
||||||
|
--blockquote-background-color: rgb(248 248 248);
|
||||||
|
|
||||||
|
--heading-border-size: 4px;
|
||||||
|
|
||||||
|
--alert-note-color: #4da3ff;
|
||||||
|
--alert-note-background: rgba(77, 163, 255, 0.08);
|
||||||
|
--alert-tip-color: #3fb950;
|
||||||
|
--alert-tip-background: rgba(63, 185, 80, 0.08);
|
||||||
|
--alert-important-color: #8457d9;
|
||||||
|
--alert-important-background: rgba(130, 80, 223, 0.08);
|
||||||
|
--alert-warning-color: #d29922;
|
||||||
|
--alert-warning-background: rgba(210, 153, 34, 0.1);
|
||||||
|
--alert-caution-color: #f85149;
|
||||||
|
--alert-caution-background: rgba(248, 81, 73, 0.1);
|
||||||
|
|
||||||
|
--link-background-color: 189, 195, 199;
|
||||||
|
--link-background-opacity: 0.5;
|
||||||
|
--link-background-opacity-hover: 0.7;
|
||||||
|
|
||||||
|
--code-background-color: rgba(0, 0, 0, 0.12);
|
||||||
|
--code-text-color: #808080;
|
||||||
|
|
||||||
|
--table-border-color: #dadada;
|
||||||
|
--tr-even-background-color: #efefee;
|
||||||
|
|
||||||
|
--kbd-border-color: #dadada;
|
||||||
|
|
||||||
|
&[data-scheme="dark"] {
|
||||||
|
--code-background-color: #272822;
|
||||||
|
--code-text-color: rgba(255, 255, 255, 0.9);
|
||||||
|
|
||||||
|
--table-border-color: #717171;
|
||||||
|
--tr-even-background-color: #545454;
|
||||||
|
|
||||||
|
--blockquote-background-color: rgb(75 75 75);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Shadow style
|
||||||
|
* Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker
|
||||||
|
*/
|
||||||
|
:root {
|
||||||
|
--shadow-l1-y: 4px;
|
||||||
|
--shadow-l1-blur: 8px;
|
||||||
|
--shadow-l2-y: 10px;
|
||||||
|
--shadow-l2-blur: 20px;
|
||||||
|
|
||||||
|
--shadow-l1: 0px var(--shadow-l1-y) var(--shadow-l1-blur) rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||||
|
--shadow-l2: 0px var(--shadow-l2-y) var(--shadow-l2-blur) rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="light"] {
|
||||||
|
--pre-text-color: #272822;
|
||||||
|
--pre-background-color: #fafafa;
|
||||||
|
@import "partials/highlight/light.scss";
|
||||||
|
}
|
||||||
|
|
||||||
|
[data-scheme="dark"] {
|
||||||
|
--pre-text-color: #f8f8f2;
|
||||||
|
--pre-background-color: #272822;
|
||||||
|
@import "partials/highlight/dark.scss";
|
||||||
|
}
|
||||||
|
|
||||||
|
:root {
|
||||||
|
--menu-icon-separation: 40px;
|
||||||
|
--container-padding: 15px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--container-padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
--widget-separation: var(--section-separation);
|
||||||
|
|
||||||
|
/* Spacing tokens */
|
||||||
|
--spacing-xs: 5px;
|
||||||
|
--spacing-sm: 10px;
|
||||||
|
--spacing-md: 15px;
|
||||||
|
--spacing-lg: 20px;
|
||||||
|
--spacing-xl: 25px;
|
||||||
|
--spacing-2xl: 30px;
|
||||||
|
--spacing-3xl: 40px;
|
||||||
|
|
||||||
|
/* Article image height variables */
|
||||||
|
--article-image-height: 150px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--article-image-height: 200px;
|
||||||
|
}
|
||||||
|
|
||||||
|
@include respond(xl) {
|
||||||
|
--article-image-height: 250px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mermaid component variables */
|
||||||
|
--mermaid-toolbar-btn-bg: rgba(255, 255, 255, 0.95);
|
||||||
|
--mermaid-toolbar-btn-text: #333;
|
||||||
|
--mermaid-modal-bg: rgba(0, 0, 0, 0.95);
|
||||||
|
--mermaid-modal-content-bg: #fff;
|
||||||
|
--mermaid-modal-btn-bg: #fff;
|
||||||
|
--mermaid-modal-btn-hover: #ddd;
|
||||||
|
|
||||||
|
&[data-scheme="dark"] {
|
||||||
|
--mermaid-toolbar-btn-bg: rgba(0, 0, 0, 0.85);
|
||||||
|
--mermaid-toolbar-btn-text: #eee;
|
||||||
|
--mermaid-modal-content-bg: #1e1e1e;
|
||||||
|
--mermaid-modal-btn-bg: #333;
|
||||||
|
--mermaid-modal-btn-hover: #444;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Article compact image size */
|
||||||
|
--article-compact-image-size: 50px;
|
||||||
|
|
||||||
|
@include respond(md) {
|
||||||
|
--article-compact-image-size: 60px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Widget icon size */
|
||||||
|
--widget-icon-size: 32px;
|
||||||
|
|
||||||
|
/* Article list tile variables */
|
||||||
|
--article-tile-width: 250px;
|
||||||
|
--article-tile-height: 150px;
|
||||||
|
--article-tile-overlay-bg: rgba(0, 0, 0, 0.25);
|
||||||
|
--article-tile-text-color: #fff;
|
||||||
|
|
||||||
|
&[data-scheme="dark"] {
|
||||||
|
--article-tile-overlay-bg: rgba(0, 0, 0, 0.45);
|
||||||
|
}
|
||||||
|
}
|
||||||
41
themes/stack/assets/ts/code-copy.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import * as params from '@params';
|
||||||
|
|
||||||
|
export function setupCodeCopy() {
|
||||||
|
/**
|
||||||
|
* Add copy button to code block
|
||||||
|
*/
|
||||||
|
const highlights = document.querySelectorAll('.article-content div.highlight');
|
||||||
|
const copyText = params.codeblock.copy,
|
||||||
|
copiedText = params.codeblock.copied;
|
||||||
|
|
||||||
|
if (!navigator.clipboard) {
|
||||||
|
/// Clipboard API is only supported in secure contexts (HTTPS)
|
||||||
|
console.warn('Clipboard API not supported, copy button will not work.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
highlights.forEach(highlight => {
|
||||||
|
const copyButton = document.createElement('button');
|
||||||
|
copyButton.innerHTML = copyText;
|
||||||
|
copyButton.classList.add('copyCodeButton');
|
||||||
|
highlight.appendChild(copyButton);
|
||||||
|
|
||||||
|
const codeBlock = highlight.querySelector('code[data-lang]');
|
||||||
|
if (!codeBlock) return;
|
||||||
|
|
||||||
|
copyButton.addEventListener('click', () => {
|
||||||
|
navigator.clipboard.writeText(codeBlock.textContent)
|
||||||
|
.then(() => {
|
||||||
|
copyButton.textContent = copiedText;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
copyButton.textContent = copyText;
|
||||||
|
}, 1000);
|
||||||
|
})
|
||||||
|
.catch(err => {
|
||||||
|
alert(err)
|
||||||
|
console.log('Something went wrong', err);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
};
|
||||||
92
themes/stack/assets/ts/colorScheme.ts
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
type colorScheme = 'light' | 'dark' | 'auto';
|
||||||
|
|
||||||
|
class StackColorScheme {
|
||||||
|
private localStorageKey = 'StackColorScheme';
|
||||||
|
private currentScheme: colorScheme;
|
||||||
|
private systemPreferScheme: colorScheme;
|
||||||
|
|
||||||
|
constructor(toggleEl: HTMLElement) {
|
||||||
|
this.bindMatchMedia();
|
||||||
|
this.currentScheme = this.getSavedScheme();
|
||||||
|
if (window.matchMedia('(prefers-color-scheme: dark)').matches === true)
|
||||||
|
this.systemPreferScheme = 'dark'
|
||||||
|
else
|
||||||
|
this.systemPreferScheme = 'light';
|
||||||
|
|
||||||
|
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
|
||||||
|
|
||||||
|
if (toggleEl)
|
||||||
|
this.bindClick(toggleEl);
|
||||||
|
|
||||||
|
if (document.body.style.transition == '')
|
||||||
|
document.body.style.setProperty('transition', 'background-color .3s ease');
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveScheme() {
|
||||||
|
localStorage.setItem(this.localStorageKey, this.currentScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindClick(toggleEl: HTMLElement) {
|
||||||
|
toggleEl.addEventListener('click', (e) => {
|
||||||
|
if (this.isDark()) {
|
||||||
|
/// Disable dark mode
|
||||||
|
this.currentScheme = 'light';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.currentScheme = 'dark';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.setBodyClass();
|
||||||
|
|
||||||
|
if (this.currentScheme == this.systemPreferScheme) {
|
||||||
|
/// Set to auto
|
||||||
|
this.currentScheme = 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.saveScheme();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private isDark() {
|
||||||
|
return (this.currentScheme == 'dark' || this.currentScheme == 'auto' && this.systemPreferScheme == 'dark');
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatchEvent(colorScheme: colorScheme) {
|
||||||
|
const event = new CustomEvent('onColorSchemeChange', {
|
||||||
|
detail: colorScheme
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
}
|
||||||
|
|
||||||
|
private setBodyClass() {
|
||||||
|
if (this.isDark()) {
|
||||||
|
document.documentElement.dataset.scheme = 'dark';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
document.documentElement.dataset.scheme = 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
this.dispatchEvent(document.documentElement.dataset.scheme as colorScheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private getSavedScheme(): colorScheme {
|
||||||
|
const savedScheme = localStorage.getItem(this.localStorageKey);
|
||||||
|
|
||||||
|
if (savedScheme == 'light' || savedScheme == 'dark' || savedScheme == 'auto') return savedScheme;
|
||||||
|
else return 'auto';
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindMatchMedia() {
|
||||||
|
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
|
||||||
|
if (e.matches) {
|
||||||
|
this.systemPreferScheme = 'dark';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.systemPreferScheme = 'light';
|
||||||
|
}
|
||||||
|
this.setBodyClass();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default StackColorScheme;
|
||||||
141
themes/stack/assets/ts/commentsConsent.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
/*
|
||||||
|
* Consent-gated comments bootstrap.
|
||||||
|
*
|
||||||
|
* This module controls whether third-party comments are rendered based on
|
||||||
|
* functional cookie consent, and injects template scripts in sequence to
|
||||||
|
* avoid eager or duplicate script execution.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Consent event payload used by the comments gate.
|
||||||
|
* It mirrors cookie consent state shape but keeps optional fields for safety.
|
||||||
|
*/
|
||||||
|
interface CommentsConsentState {
|
||||||
|
necessary?: boolean;
|
||||||
|
analytics?: boolean;
|
||||||
|
functional?: boolean;
|
||||||
|
timestamp?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resolve functional consent with layered fallback:
|
||||||
|
* 1) explicit event detail, 2) CookieConsent public API, 3) dataset mirror.
|
||||||
|
*/
|
||||||
|
const hasFunctionalConsent = (consentDetail?: CommentsConsentState | null): boolean => {
|
||||||
|
const cookieConsent = (window as Window & {
|
||||||
|
cookieConsent?: {
|
||||||
|
hasConsent?: (category: 'necessary' | 'analytics' | 'functional') => boolean;
|
||||||
|
};
|
||||||
|
}).cookieConsent;
|
||||||
|
|
||||||
|
return (
|
||||||
|
consentDetail?.functional ??
|
||||||
|
cookieConsent?.hasConsent?.('functional') ??
|
||||||
|
document.documentElement.dataset.consentFunctional === 'true'
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
interface DeferredScript {
|
||||||
|
placeholder: Comment;
|
||||||
|
script: HTMLScriptElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
const prepareDeferredScripts = (fragment: DocumentFragment): DeferredScript[] => {
|
||||||
|
return Array.from(fragment.querySelectorAll('script')).map(script => {
|
||||||
|
const placeholder = document.createComment('comments-script-placeholder');
|
||||||
|
script.replaceWith(placeholder);
|
||||||
|
return { placeholder, script };
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const activateDeferredScripts = async (scripts: DeferredScript[]): Promise<void> => {
|
||||||
|
// Inject scripts sequentially so third-party embeds can rely on execution order.
|
||||||
|
for (const { placeholder, script } of scripts) {
|
||||||
|
if (!placeholder.parentNode) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const newScript = document.createElement('script');
|
||||||
|
Array.from(script.attributes).forEach(attr => {
|
||||||
|
newScript.setAttribute(attr.name, attr.value);
|
||||||
|
});
|
||||||
|
|
||||||
|
if (script.textContent) {
|
||||||
|
newScript.text = script.textContent;
|
||||||
|
}
|
||||||
|
|
||||||
|
let done: Promise<void> = Promise.resolve();
|
||||||
|
if (newScript.src) {
|
||||||
|
done = new Promise<void>(resolve => {
|
||||||
|
newScript.onload = newScript.onerror = () => resolve();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
placeholder.replaceWith(newScript);
|
||||||
|
await done;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const initCommentsConsent = (): void => {
|
||||||
|
// Main entry: gate comments rendering by functional cookie consent.
|
||||||
|
const placeholder = document.getElementById('comments-consent-placeholder') as HTMLElement | null;
|
||||||
|
const container = document.getElementById('comments-container') as HTMLElement | null;
|
||||||
|
const template = document.getElementById('comments-template') as HTMLTemplateElement | null;
|
||||||
|
|
||||||
|
if (!placeholder || !container || !template) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let commentsLoaded = false;
|
||||||
|
let commentsLoading = false;
|
||||||
|
|
||||||
|
const showComments = async (): Promise<void> => {
|
||||||
|
placeholder.style.display = 'none';
|
||||||
|
container.style.display = 'block';
|
||||||
|
|
||||||
|
if (commentsLoaded || commentsLoading) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Prevent duplicate template hydration while scripts are still loading.
|
||||||
|
commentsLoading = true;
|
||||||
|
|
||||||
|
try {
|
||||||
|
const clone = template.content.cloneNode(true) as DocumentFragment;
|
||||||
|
const scripts = prepareDeferredScripts(clone);
|
||||||
|
|
||||||
|
container.appendChild(clone);
|
||||||
|
await activateDeferredScripts(scripts);
|
||||||
|
commentsLoaded = true;
|
||||||
|
} finally {
|
||||||
|
commentsLoading = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const hideComments = (): void => {
|
||||||
|
placeholder.style.display = 'block';
|
||||||
|
container.style.display = 'none';
|
||||||
|
};
|
||||||
|
|
||||||
|
window.addEventListener('onCookieConsentChange', (event: Event) => {
|
||||||
|
// Cross-module contract: this event is dispatched by cookies.ts.
|
||||||
|
const customEvent = event as CustomEvent<CommentsConsentState | null>;
|
||||||
|
if (hasFunctionalConsent(customEvent.detail)) {
|
||||||
|
showComments();
|
||||||
|
} else {
|
||||||
|
hideComments();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasFunctionalConsent()) {
|
||||||
|
showComments();
|
||||||
|
} else {
|
||||||
|
hideComments();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', initCommentsConsent, { once: true });
|
||||||
|
} else {
|
||||||
|
initCommentsConsent();
|
||||||
|
}
|
||||||
222
themes/stack/assets/ts/cookies.ts
Normal file
@ -0,0 +1,222 @@
|
|||||||
|
interface ConsentState {
|
||||||
|
necessary: boolean;
|
||||||
|
analytics: boolean;
|
||||||
|
functional: boolean;
|
||||||
|
timestamp: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
class CookieConsent {
|
||||||
|
private static COOKIE_NAME = 'cookie_consent';
|
||||||
|
private static COOKIE_DAYS = 365;
|
||||||
|
|
||||||
|
private state: ConsentState | null = null;
|
||||||
|
private banner: HTMLElement | null = null;
|
||||||
|
private settingsPanel: HTMLElement | null = null;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.banner = document.getElementById('cookie-consent-banner');
|
||||||
|
this.settingsPanel = document.getElementById('cookie-settings-panel');
|
||||||
|
|
||||||
|
this.state = this.loadState();
|
||||||
|
|
||||||
|
if (!this.state && this.banner) {
|
||||||
|
this.showBanner();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindEvents();
|
||||||
|
this.dispatchConsentEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadState(): ConsentState | null {
|
||||||
|
const cookie = document.cookie
|
||||||
|
.split('; ')
|
||||||
|
.find(row => row.startsWith(CookieConsent.COOKIE_NAME + '='));
|
||||||
|
|
||||||
|
if (!cookie) return null;
|
||||||
|
|
||||||
|
try {
|
||||||
|
return JSON.parse(decodeURIComponent(cookie.split('=')[1]));
|
||||||
|
} catch {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveState(): void {
|
||||||
|
if (!this.state) return;
|
||||||
|
|
||||||
|
const expires = new Date();
|
||||||
|
expires.setDate(expires.getDate() + CookieConsent.COOKIE_DAYS);
|
||||||
|
|
||||||
|
document.cookie = `${CookieConsent.COOKIE_NAME}=${encodeURIComponent(JSON.stringify(this.state))}; expires=${expires.toUTCString()}; path=/; SameSite=Lax`;
|
||||||
|
}
|
||||||
|
|
||||||
|
private showBanner(): void {
|
||||||
|
if (this.banner) {
|
||||||
|
this.banner.removeAttribute('aria-hidden');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideBanner(): void {
|
||||||
|
if (this.banner) {
|
||||||
|
// Blur any focused element inside the banner before hiding
|
||||||
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
|
if (activeElement && this.banner.contains(activeElement)) {
|
||||||
|
activeElement.blur();
|
||||||
|
}
|
||||||
|
this.banner.setAttribute('aria-hidden', 'true');
|
||||||
|
}
|
||||||
|
this.hideSettings();
|
||||||
|
}
|
||||||
|
|
||||||
|
private showSettings(): void {
|
||||||
|
if (this.settingsPanel) {
|
||||||
|
this.settingsPanel.removeAttribute('aria-hidden');
|
||||||
|
|
||||||
|
// Restore checkbox states from current state or defaults
|
||||||
|
const checkboxes = this.settingsPanel.querySelectorAll('input[data-cookie-category]');
|
||||||
|
checkboxes.forEach((cb) => {
|
||||||
|
const input = cb as HTMLInputElement;
|
||||||
|
const category = input.dataset.cookieCategory as keyof ConsentState;
|
||||||
|
if (category && this.state && typeof this.state[category] === 'boolean') {
|
||||||
|
input.checked = this.state[category] as boolean;
|
||||||
|
} else {
|
||||||
|
input.checked = false;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private hideSettings(): void {
|
||||||
|
if (this.settingsPanel) {
|
||||||
|
// Blur any focused element inside the settings panel before hiding
|
||||||
|
const activeElement = document.activeElement as HTMLElement;
|
||||||
|
if (activeElement && this.settingsPanel.contains(activeElement)) {
|
||||||
|
activeElement.blur();
|
||||||
|
}
|
||||||
|
this.settingsPanel.setAttribute('aria-hidden', 'true');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindEvents(): void {
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const action = target.dataset.cookieAction;
|
||||||
|
|
||||||
|
if (!action) return;
|
||||||
|
|
||||||
|
switch (action) {
|
||||||
|
case 'accept':
|
||||||
|
this.acceptAll();
|
||||||
|
break;
|
||||||
|
case 'deny':
|
||||||
|
this.denyAll();
|
||||||
|
break;
|
||||||
|
case 'settings':
|
||||||
|
this.showSettings();
|
||||||
|
break;
|
||||||
|
case 'save':
|
||||||
|
this.saveSettings();
|
||||||
|
break;
|
||||||
|
case 'cancel':
|
||||||
|
this.hideSettings();
|
||||||
|
break;
|
||||||
|
case 'reopen':
|
||||||
|
this.showBanner();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private acceptAll(): void {
|
||||||
|
this.state = {
|
||||||
|
necessary: true,
|
||||||
|
analytics: true,
|
||||||
|
functional: true,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
this.saveState();
|
||||||
|
this.hideBanner();
|
||||||
|
this.dispatchConsentEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private denyAll(): void {
|
||||||
|
this.state = {
|
||||||
|
necessary: true,
|
||||||
|
analytics: false,
|
||||||
|
functional: false,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
this.saveState();
|
||||||
|
this.hideBanner();
|
||||||
|
this.dispatchConsentEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private saveSettings(): void {
|
||||||
|
const checkboxes = document.querySelectorAll('input[data-cookie-category]');
|
||||||
|
|
||||||
|
this.state = {
|
||||||
|
necessary: true,
|
||||||
|
analytics: false,
|
||||||
|
functional: false,
|
||||||
|
timestamp: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
checkboxes.forEach((cb) => {
|
||||||
|
const input = cb as HTMLInputElement;
|
||||||
|
const category = input.dataset.cookieCategory as keyof ConsentState;
|
||||||
|
if (category && category in this.state!) {
|
||||||
|
(this.state as any)[category] = input.checked;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.saveState();
|
||||||
|
this.hideBanner();
|
||||||
|
this.dispatchConsentEvent();
|
||||||
|
}
|
||||||
|
|
||||||
|
private dispatchConsentEvent(): void {
|
||||||
|
// Cross-module event consumed by consent-gated features (for example commentsConsent.ts).
|
||||||
|
const event = new CustomEvent('onCookieConsentChange', {
|
||||||
|
detail: this.state
|
||||||
|
});
|
||||||
|
window.dispatchEvent(event);
|
||||||
|
|
||||||
|
// Set data attributes on document for CSS-based control
|
||||||
|
if (this.state) {
|
||||||
|
document.documentElement.dataset.consentAnalytics = String(this.state.analytics);
|
||||||
|
document.documentElement.dataset.consentFunctional = String(this.state.functional);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Public API
|
||||||
|
public hasConsent(category: keyof Omit<ConsentState, 'timestamp'>): boolean {
|
||||||
|
if (!this.state) return false;
|
||||||
|
return this.state[category] ?? false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public getState(): ConsentState | null {
|
||||||
|
return this.state;
|
||||||
|
}
|
||||||
|
|
||||||
|
public reopenBanner(): void {
|
||||||
|
this.showBanner();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Export for module usage
|
||||||
|
export default CookieConsent;
|
||||||
|
|
||||||
|
// Initialize when DOM is ready and expose globally
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
cookieConsent: CookieConsent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (document.readyState === 'loading') {
|
||||||
|
document.addEventListener('DOMContentLoaded', () => {
|
||||||
|
window.cookieConsent = new CookieConsent();
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
window.cookieConsent = new CookieConsent();
|
||||||
|
}
|
||||||
34
themes/stack/assets/ts/createElement.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/**
|
||||||
|
* createElement
|
||||||
|
* Edited from:
|
||||||
|
* @link https://stackoverflow.com/a/42405694
|
||||||
|
*/
|
||||||
|
function createElement(tag, attrs, children) {
|
||||||
|
var element = document.createElement(tag);
|
||||||
|
|
||||||
|
for (let name in attrs) {
|
||||||
|
if (name && attrs.hasOwnProperty(name)) {
|
||||||
|
let value = attrs[name];
|
||||||
|
|
||||||
|
if (name == "dangerouslySetInnerHTML") {
|
||||||
|
element.innerHTML = value.__html;
|
||||||
|
}
|
||||||
|
else if (value === true) {
|
||||||
|
element.setAttribute(name, name);
|
||||||
|
} else if (value !== false && value != null) {
|
||||||
|
element.setAttribute(name, value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (let i = 2; i < arguments.length; i++) {
|
||||||
|
let child = arguments[i];
|
||||||
|
if (child) {
|
||||||
|
element.appendChild(
|
||||||
|
child.nodeType == null ?
|
||||||
|
document.createTextNode(child.toString()) : child);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return element;
|
||||||
|
}
|
||||||
|
|
||||||
|
export default createElement;
|
||||||
110
themes/stack/assets/ts/gallery.ts
Normal file
@ -0,0 +1,110 @@
|
|||||||
|
const wrap = (figures: HTMLElement[]) => {
|
||||||
|
const galleryContainer = document.createElement('div');
|
||||||
|
galleryContainer.className = 'gallery';
|
||||||
|
|
||||||
|
const parentNode = figures[0].parentNode!,
|
||||||
|
first = figures[0];
|
||||||
|
|
||||||
|
parentNode.insertBefore(galleryContainer, first)
|
||||||
|
|
||||||
|
for (const figure of figures) {
|
||||||
|
galleryContainer.appendChild(figure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const unescapeHtml = (html: string) => {
|
||||||
|
/// Replace special HTML entities with their corresponding characters
|
||||||
|
return html.replace(/&/g, '&')
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/"/g, '"')
|
||||||
|
.replace(/'/g, "'");
|
||||||
|
}
|
||||||
|
|
||||||
|
export default (container: HTMLElement) => {
|
||||||
|
/// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
|
||||||
|
/// because it can not detect whether image is being wrapped by a link or not
|
||||||
|
/// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
|
||||||
|
const images = container.querySelectorAll('img.gallery-image') as NodeListOf<HTMLImageElement>;
|
||||||
|
for (const img of Array.from(images)) {
|
||||||
|
/// Images are wrapped with figure tag if the paragraph has only images without texts
|
||||||
|
/// This is done to allow inline images within paragraphs
|
||||||
|
const paragraph = img.closest('p');
|
||||||
|
|
||||||
|
if (!paragraph || !container.contains(paragraph)) continue;
|
||||||
|
|
||||||
|
if (paragraph.textContent.trim() == '') {
|
||||||
|
/// Once we insert figcaption, this check no longer works
|
||||||
|
/// So we add a class to paragraph to mark it
|
||||||
|
paragraph.classList.add('no-text');
|
||||||
|
}
|
||||||
|
|
||||||
|
let isNewLineImage = paragraph.classList.contains('no-text');
|
||||||
|
if (!isNewLineImage) continue;
|
||||||
|
|
||||||
|
const hasLink = img.parentElement!.tagName == 'A';
|
||||||
|
|
||||||
|
let el: HTMLElement = img;
|
||||||
|
/// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
|
||||||
|
const figure = document.createElement('figure');
|
||||||
|
figure.classList.add('gallery-image');
|
||||||
|
figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
|
||||||
|
figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
|
||||||
|
|
||||||
|
if (hasLink) {
|
||||||
|
/// Wrap <a> if it exists
|
||||||
|
el = img.parentElement!;
|
||||||
|
el.classList.add('image-link');
|
||||||
|
el.setAttribute('data-pswp-width', img.getAttribute('width')!);
|
||||||
|
el.setAttribute('data-pswp-height', img.getAttribute('height')!);
|
||||||
|
} else {
|
||||||
|
const a = document.createElement('a');
|
||||||
|
a.href = img.src;
|
||||||
|
a.setAttribute('class', 'image-link');
|
||||||
|
a.setAttribute('target', '_blank');
|
||||||
|
a.setAttribute('data-pswp-width', img.getAttribute('width')!);
|
||||||
|
a.setAttribute('data-pswp-height', img.getAttribute('height')!);
|
||||||
|
img.parentNode!.insertBefore(a, img);
|
||||||
|
a.appendChild(img);
|
||||||
|
el = a;
|
||||||
|
}
|
||||||
|
|
||||||
|
el.parentElement!.insertBefore(figure, el);
|
||||||
|
figure.appendChild(el);
|
||||||
|
|
||||||
|
/// Add figcaption if it exists
|
||||||
|
let caption = img.getAttribute('alt');
|
||||||
|
if (img.getAttribute('data-title-escaped')) {
|
||||||
|
caption = unescapeHtml(img.getAttribute('data-title-escaped')!);
|
||||||
|
}
|
||||||
|
if (caption) {
|
||||||
|
const figcaption = document.createElement('figcaption');
|
||||||
|
figcaption.innerHTML = caption;
|
||||||
|
figure.appendChild(figcaption);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const figuresEl = container.querySelectorAll('figure.gallery-image') as NodeListOf<HTMLElement>;
|
||||||
|
|
||||||
|
let currentGallery: HTMLElement[] = [];
|
||||||
|
|
||||||
|
for (const figure of Array.from(figuresEl)) {
|
||||||
|
if (!currentGallery.length) {
|
||||||
|
/// First iteration
|
||||||
|
currentGallery = [figure];
|
||||||
|
}
|
||||||
|
else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
|
||||||
|
/// Adjacent figures
|
||||||
|
currentGallery.push(figure);
|
||||||
|
}
|
||||||
|
else if (currentGallery.length) {
|
||||||
|
/// End gallery
|
||||||
|
wrap(currentGallery);
|
||||||
|
currentGallery = [figure];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (currentGallery.length > 0) {
|
||||||
|
wrap(currentGallery);
|
||||||
|
}
|
||||||
|
};
|
||||||
50
themes/stack/assets/ts/main.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
/*!
|
||||||
|
* Hugo Theme Stack
|
||||||
|
*
|
||||||
|
* @author: Jimmy Cai
|
||||||
|
* @website: https://jimmycai.com
|
||||||
|
* @link: https://github.com/CaiJimmy/hugo-theme-stack
|
||||||
|
*/
|
||||||
|
import menu from './menu';
|
||||||
|
import createElement from './createElement';
|
||||||
|
import StackColorScheme from './colorScheme';
|
||||||
|
import { setupScrollspy } from './scrollspy';
|
||||||
|
import { setupSmoothAnchors } from './smoothAnchors';
|
||||||
|
import { setupPaginationJump } from './pagination';
|
||||||
|
import { setupCodeCopy } from './code-copy';
|
||||||
|
|
||||||
|
let Stack = {
|
||||||
|
init: () => {
|
||||||
|
/**
|
||||||
|
* Bind menu event
|
||||||
|
*/
|
||||||
|
menu();
|
||||||
|
|
||||||
|
const articleContent = document.querySelector('.article-content') as HTMLElement;
|
||||||
|
if (articleContent) {
|
||||||
|
setupSmoothAnchors();
|
||||||
|
setupScrollspy();
|
||||||
|
setupCodeCopy();
|
||||||
|
}
|
||||||
|
|
||||||
|
setupPaginationJump();
|
||||||
|
|
||||||
|
new StackColorScheme(document.getElementById('dark-mode-toggle')!);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
setTimeout(function () {
|
||||||
|
Stack.init();
|
||||||
|
}, 0);
|
||||||
|
})
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
createElement: any;
|
||||||
|
Stack: any
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.Stack = Stack;
|
||||||
|
window.createElement = createElement;
|
||||||
83
themes/stack/assets/ts/menu.ts
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
/**
|
||||||
|
* Slide up/down
|
||||||
|
* Code from https://dev.to/bmsvieira/vanilla-js-slidedown-up-4dkn
|
||||||
|
* @param target
|
||||||
|
* @param duration
|
||||||
|
*/
|
||||||
|
let slideUp = (target: HTMLElement, duration = 500) => {
|
||||||
|
target.classList.add('transiting');
|
||||||
|
target.style.transitionProperty = 'height, margin, padding';
|
||||||
|
target.style.transitionDuration = duration + 'ms';
|
||||||
|
///target.style.boxSizing = 'border-box';
|
||||||
|
target.style.height = target.offsetHeight + 'px';
|
||||||
|
target.offsetHeight;
|
||||||
|
target.style.overflow = 'hidden';
|
||||||
|
target.style.height = "0";
|
||||||
|
target.style.paddingTop = "0";
|
||||||
|
target.style.paddingBottom = "0";
|
||||||
|
target.style.marginTop = "0";
|
||||||
|
target.style.marginBottom = "0";
|
||||||
|
window.setTimeout(() => {
|
||||||
|
target.classList.remove('show')
|
||||||
|
target.style.removeProperty('height');
|
||||||
|
target.style.removeProperty('padding-top');
|
||||||
|
target.style.removeProperty('padding-bottom');
|
||||||
|
target.style.removeProperty('margin-top');
|
||||||
|
target.style.removeProperty('margin-bottom');
|
||||||
|
target.style.removeProperty('overflow');
|
||||||
|
target.style.removeProperty('transition-duration');
|
||||||
|
target.style.removeProperty('transition-property');
|
||||||
|
target.classList.remove('transiting');
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slideDown = (target: HTMLElement, duration = 500) => {
|
||||||
|
target.classList.add('transiting');
|
||||||
|
target.style.removeProperty('display');
|
||||||
|
|
||||||
|
target.classList.add('show');
|
||||||
|
|
||||||
|
let height = target.offsetHeight;
|
||||||
|
target.style.overflow = 'hidden';
|
||||||
|
target.style.height = "0";
|
||||||
|
target.style.paddingTop = "0";
|
||||||
|
target.style.paddingBottom = "0";
|
||||||
|
target.style.marginTop = "0";
|
||||||
|
target.style.marginBottom = "0";
|
||||||
|
target.offsetHeight;
|
||||||
|
///target.style.boxSizing = 'border-box';
|
||||||
|
target.style.transitionProperty = "height, margin, padding";
|
||||||
|
target.style.transitionDuration = duration + 'ms';
|
||||||
|
target.style.height = height + 'px';
|
||||||
|
target.style.removeProperty('padding-top');
|
||||||
|
target.style.removeProperty('padding-bottom');
|
||||||
|
target.style.removeProperty('margin-top');
|
||||||
|
target.style.removeProperty('margin-bottom');
|
||||||
|
window.setTimeout(() => {
|
||||||
|
target.style.removeProperty('height');
|
||||||
|
target.style.removeProperty('overflow');
|
||||||
|
target.style.removeProperty('transition-duration');
|
||||||
|
target.style.removeProperty('transition-property');
|
||||||
|
target.classList.remove('transiting');
|
||||||
|
}, duration);
|
||||||
|
}
|
||||||
|
|
||||||
|
let slideToggle = (target, duration = 500) => {
|
||||||
|
if (window.getComputedStyle(target).display === 'none') {
|
||||||
|
return slideDown(target, duration);
|
||||||
|
} else {
|
||||||
|
return slideUp(target, duration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function () {
|
||||||
|
const toggleMenu = document.getElementById('toggle-menu');
|
||||||
|
if (toggleMenu) {
|
||||||
|
toggleMenu.addEventListener('click', () => {
|
||||||
|
if (document.getElementById('main-menu').classList.contains('transiting')) return;
|
||||||
|
document.body.classList.toggle('show-menu');
|
||||||
|
slideToggle(document.getElementById('main-menu'), 300);
|
||||||
|
toggleMenu.classList.toggle('is-active');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
235
themes/stack/assets/ts/mermaid.ts
Normal file
@ -0,0 +1,235 @@
|
|||||||
|
declare const mermaid: {
|
||||||
|
initialize(config: Record<string, any>): void;
|
||||||
|
run(options: { nodes: HTMLElement[] }): Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
interface MermaidConfig {
|
||||||
|
transparentBackground?: boolean;
|
||||||
|
lightTheme?: string;
|
||||||
|
darkTheme?: string;
|
||||||
|
lightThemeVariables?: Record<string, any>;
|
||||||
|
darkThemeVariables?: Record<string, any>;
|
||||||
|
securityLevel?: string;
|
||||||
|
look?: string;
|
||||||
|
htmlLabels?: boolean;
|
||||||
|
maxTextSize?: number;
|
||||||
|
maxEdges?: number;
|
||||||
|
fontSize?: number;
|
||||||
|
fontFamily?: string;
|
||||||
|
curve?: string;
|
||||||
|
logLevel?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
type Scheme = 'light' | 'dark';
|
||||||
|
|
||||||
|
const PANZOOM_CDN = 'https://cdn.jsdelivr.net/npm/panzoom@9.4.3/+esm';
|
||||||
|
|
||||||
|
function getScheme(): Scheme {
|
||||||
|
return document.documentElement.dataset.scheme === 'dark' ? 'dark' : 'light';
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildThemeConfig(cfg: MermaidConfig, scheme: Scheme) {
|
||||||
|
const isLight = scheme === 'light';
|
||||||
|
const theme = isLight ? (cfg.lightTheme ?? 'default') : (cfg.darkTheme ?? 'dark');
|
||||||
|
const vars = isLight ? (cfg.lightThemeVariables ?? {}) : (cfg.darkThemeVariables ?? {});
|
||||||
|
return {
|
||||||
|
theme,
|
||||||
|
themeVariables: { ...vars, ...(cfg.transparentBackground ? { background: 'transparent' } : {}) },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function buildBaseConfig(cfg: MermaidConfig): Record<string, any> {
|
||||||
|
const base: Record<string, any> = {
|
||||||
|
startOnLoad: false,
|
||||||
|
securityLevel: cfg.securityLevel ?? 'strict',
|
||||||
|
look: cfg.look ?? 'classic',
|
||||||
|
flowchart: { htmlLabels: cfg.htmlLabels ?? true, useMaxWidth: true },
|
||||||
|
gantt: { useWidth: 800 },
|
||||||
|
};
|
||||||
|
const optional: (keyof MermaidConfig)[] = ['maxTextSize', 'maxEdges', 'fontSize', 'fontFamily', 'curve', 'logLevel'];
|
||||||
|
for (const key of optional) {
|
||||||
|
if (cfg[key] != null) base[key] = cfg[key];
|
||||||
|
}
|
||||||
|
return base;
|
||||||
|
}
|
||||||
|
|
||||||
|
function initWithTheme(
|
||||||
|
scheme: Scheme,
|
||||||
|
themes: Record<Scheme, ReturnType<typeof buildThemeConfig>>,
|
||||||
|
baseConfig: Record<string, any>,
|
||||||
|
) {
|
||||||
|
const { theme, themeVariables } = themes[scheme];
|
||||||
|
mermaid.initialize({
|
||||||
|
...baseConfig,
|
||||||
|
theme,
|
||||||
|
...(Object.keys(themeVariables).length && { themeVariables }),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function renderOffscreen(sources: string[]): Promise<string[]> {
|
||||||
|
const container = document.createElement('div');
|
||||||
|
container.className = 'mermaid-offscreen';
|
||||||
|
document.body.appendChild(container);
|
||||||
|
const nodes = sources.map(src => {
|
||||||
|
const n = document.createElement('pre');
|
||||||
|
n.innerHTML = src;
|
||||||
|
container.appendChild(n);
|
||||||
|
return n;
|
||||||
|
});
|
||||||
|
await mermaid.run({ nodes });
|
||||||
|
const results = nodes.map(n => n.innerHTML);
|
||||||
|
container.remove();
|
||||||
|
return results;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupWrappers(elements: NodeListOf<HTMLElement>) {
|
||||||
|
elements.forEach((el, idx) => {
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'mermaid-wrapper';
|
||||||
|
el.parentNode!.insertBefore(wrapper, el);
|
||||||
|
wrapper.appendChild(el);
|
||||||
|
wrapper.insertAdjacentHTML(
|
||||||
|
'beforeend',
|
||||||
|
`<div class="mermaid-toolbar"><button data-idx="${idx}" title="Open fullscreen with pan/zoom">⛶ Expand</button></div>`,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupModal(elements: NodeListOf<HTMLElement>) {
|
||||||
|
const modal = document.getElementById('mermaid-modal')!;
|
||||||
|
const modalBody = document.getElementById('mermaid-modal-body')!;
|
||||||
|
const modalContent = document.getElementById('mermaid-modal-content')!;
|
||||||
|
let pzInstance: any = null;
|
||||||
|
let panzoom: any = null;
|
||||||
|
|
||||||
|
const loadPanzoom = async () => {
|
||||||
|
if (!panzoom) {
|
||||||
|
const url = PANZOOM_CDN;
|
||||||
|
panzoom = (await import(url)).default;
|
||||||
|
}
|
||||||
|
return panzoom;
|
||||||
|
};
|
||||||
|
|
||||||
|
const fitToScreen = () => {
|
||||||
|
const wrapper = modalContent.querySelector('.mermaid-panzoom-container') as HTMLElement | null;
|
||||||
|
if (!pzInstance || !wrapper) return;
|
||||||
|
const w = +(wrapper.dataset.nativeWidth ?? 0);
|
||||||
|
const h = +(wrapper.dataset.nativeHeight ?? 0);
|
||||||
|
const rect = modalContent.getBoundingClientRect();
|
||||||
|
const scale = Math.min((rect.width - 60) / w, (rect.height - 60) / h);
|
||||||
|
pzInstance.zoomAbs(0, 0, scale);
|
||||||
|
pzInstance.moveTo((rect.width - w * scale) / 2, (rect.height - h * scale) / 2);
|
||||||
|
};
|
||||||
|
|
||||||
|
const closeModal = () => {
|
||||||
|
modal.classList.remove('active');
|
||||||
|
document.body.style.overflow = '';
|
||||||
|
pzInstance?.dispose();
|
||||||
|
pzInstance = null;
|
||||||
|
modalContent.innerHTML = '';
|
||||||
|
};
|
||||||
|
|
||||||
|
const openModal = async (idx: number) => {
|
||||||
|
const svg = elements[idx].querySelector('svg');
|
||||||
|
if (!svg) return;
|
||||||
|
|
||||||
|
const svgClone = svg.cloneNode(true) as SVGElement;
|
||||||
|
const viewBox = svg.getAttribute('viewBox');
|
||||||
|
const [w, h] = viewBox
|
||||||
|
? viewBox.split(/[\s,]+/).slice(2).map(Number)
|
||||||
|
: [svg.getBoundingClientRect().width || 800, svg.getBoundingClientRect().height || 600];
|
||||||
|
svgClone.setAttribute('width', String(w));
|
||||||
|
svgClone.setAttribute('height', String(h));
|
||||||
|
|
||||||
|
const wrapper = document.createElement('div');
|
||||||
|
wrapper.className = 'mermaid-panzoom-container';
|
||||||
|
wrapper.dataset.nativeWidth = String(w);
|
||||||
|
wrapper.dataset.nativeHeight = String(h);
|
||||||
|
wrapper.appendChild(svgClone);
|
||||||
|
|
||||||
|
modalContent.innerHTML = '';
|
||||||
|
modalContent.appendChild(wrapper);
|
||||||
|
modal.classList.add('active');
|
||||||
|
document.body.style.overflow = 'hidden';
|
||||||
|
|
||||||
|
const pz = await loadPanzoom();
|
||||||
|
setTimeout(() => {
|
||||||
|
pzInstance = pz(wrapper, { maxZoom: 10, minZoom: 0.05, bounds: false });
|
||||||
|
fitToScreen();
|
||||||
|
wrapper.classList.add('ready');
|
||||||
|
}, 50);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Event delegation
|
||||||
|
document.addEventListener('click', (e) => {
|
||||||
|
const target = e.target as HTMLElement;
|
||||||
|
const toolbarBtn = target.closest('.mermaid-toolbar button') as HTMLElement | null;
|
||||||
|
if (toolbarBtn) return openModal(+(toolbarBtn.dataset.idx!));
|
||||||
|
|
||||||
|
const zoomBtn = target.closest('.mermaid-modal-controls button') as HTMLElement | null;
|
||||||
|
if (zoomBtn && pzInstance) {
|
||||||
|
const z = zoomBtn.dataset.zoom;
|
||||||
|
const rect = modalBody.getBoundingClientRect();
|
||||||
|
if (z === 'fit') fitToScreen();
|
||||||
|
else if (z === '0') { pzInstance.moveTo(0, 0); pzInstance.zoomAbs(0, 0, 1); }
|
||||||
|
else pzInstance.smoothZoom(rect.width / 2, rect.height / 2, z === '1' ? 1.5 : 0.67);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('mermaid-modal-close')!.addEventListener('click', closeModal);
|
||||||
|
modalBody.addEventListener('click', (e) => { if (e.target === modalBody) closeModal(); });
|
||||||
|
document.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Escape' && modal.classList.contains('active')) closeModal();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function initMermaidPage(config: MermaidConfig) {
|
||||||
|
const elements = document.querySelectorAll('.mermaid') as NodeListOf<HTMLElement>;
|
||||||
|
if (!elements.length) return;
|
||||||
|
|
||||||
|
const sources = Array.from(elements).map(el => el.innerHTML);
|
||||||
|
const perDiagramTransparent = sources.map(src => /%%\s*transparent\s*%%/i.test(src));
|
||||||
|
const cache: Record<Scheme, string[]> = { light: [], dark: [] };
|
||||||
|
|
||||||
|
const themes = {
|
||||||
|
light: buildThemeConfig(config, 'light'),
|
||||||
|
dark: buildThemeConfig(config, 'dark'),
|
||||||
|
};
|
||||||
|
const baseConfig = buildBaseConfig(config);
|
||||||
|
|
||||||
|
const applyTransparency = (el: HTMLElement, i: number) => {
|
||||||
|
if (perDiagramTransparent[i]) el.querySelector('svg')?.style.setProperty('background', 'transparent');
|
||||||
|
};
|
||||||
|
|
||||||
|
setupWrappers(elements);
|
||||||
|
setupModal(elements);
|
||||||
|
|
||||||
|
// Initial render
|
||||||
|
const scheme = getScheme();
|
||||||
|
initWithTheme(scheme, themes, baseConfig);
|
||||||
|
await mermaid.run({ nodes: Array.from(elements) });
|
||||||
|
elements.forEach((el, i) => {
|
||||||
|
el.style.visibility = '';
|
||||||
|
cache[scheme][i] = el.innerHTML;
|
||||||
|
applyTransparency(el, i);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Pre-render alternate theme during idle time
|
||||||
|
const alt: Scheme = scheme === 'dark' ? 'light' : 'dark';
|
||||||
|
const idle = window.requestIdleCallback ?? ((fn: IdleRequestCallback) => setTimeout(fn, 1000));
|
||||||
|
idle(() => {
|
||||||
|
if (cache[alt].length) return;
|
||||||
|
initWithTheme(alt, themes, baseConfig);
|
||||||
|
renderOffscreen(sources).then(results => { cache[alt] = results; });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Swap cached diagrams on theme change
|
||||||
|
window.addEventListener('onColorSchemeChange', async () => {
|
||||||
|
const newScheme = getScheme();
|
||||||
|
if (!cache[newScheme].length) {
|
||||||
|
initWithTheme(newScheme, themes, baseConfig);
|
||||||
|
cache[newScheme] = await renderOffscreen(sources);
|
||||||
|
}
|
||||||
|
elements.forEach((el, i) => { el.innerHTML = cache[newScheme][i]; applyTransparency(el, i); });
|
||||||
|
});
|
||||||
|
}
|
||||||
104
themes/stack/assets/ts/pagination.ts
Normal file
@ -0,0 +1,104 @@
|
|||||||
|
export function setupPaginationJump() {
|
||||||
|
const triggers = document.querySelectorAll<HTMLButtonElement>('.pagination-jump-trigger');
|
||||||
|
const dialog = document.getElementById('pagination-jump-dialog') as HTMLDialogElement;
|
||||||
|
|
||||||
|
if (!dialog || triggers.length === 0) return;
|
||||||
|
|
||||||
|
const nav = document.querySelector('.pagination') as HTMLElement;
|
||||||
|
const input = document.getElementById('pagination-jump-input') as HTMLInputElement;
|
||||||
|
const form = dialog.querySelector('.pagination-jump-form') as HTMLFormElement;
|
||||||
|
const supportsDialog = typeof dialog.showModal === 'function' && typeof dialog.close === 'function';
|
||||||
|
let lastFocusedElement: HTMLElement | null = null;
|
||||||
|
|
||||||
|
if (!supportsDialog || !nav || !input || !form) return;
|
||||||
|
|
||||||
|
const closeDialog = (): void => {
|
||||||
|
if (dialog.classList.contains('closing')) return;
|
||||||
|
dialog.classList.add('closing');
|
||||||
|
dialog.addEventListener(
|
||||||
|
'animationend',
|
||||||
|
() => {
|
||||||
|
dialog.classList.remove('closing');
|
||||||
|
dialog.close();
|
||||||
|
if (lastFocusedElement?.isConnected) {
|
||||||
|
lastFocusedElement.focus();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{ once: true }
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Open dialog when triggers are clicked
|
||||||
|
triggers.forEach((trigger) => {
|
||||||
|
trigger.addEventListener('click', () => {
|
||||||
|
const activeElement = document.activeElement;
|
||||||
|
lastFocusedElement = activeElement instanceof HTMLElement ? activeElement : trigger;
|
||||||
|
dialog.showModal();
|
||||||
|
input.value = '';
|
||||||
|
input.focus();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle ESC key closing the dialog
|
||||||
|
dialog.addEventListener('cancel', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
closeDialog();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Close dialog when clicking backdrop
|
||||||
|
dialog.addEventListener('click', (e) => {
|
||||||
|
const rect = dialog.getBoundingClientRect();
|
||||||
|
const isInDialog =
|
||||||
|
rect.top <= e.clientY &&
|
||||||
|
e.clientY <= rect.top + rect.height &&
|
||||||
|
rect.left <= e.clientX &&
|
||||||
|
e.clientX <= rect.left + rect.width;
|
||||||
|
if (!isInDialog) {
|
||||||
|
closeDialog();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Allow Enter key to trigger validation and submit
|
||||||
|
input.addEventListener('keydown', (e) => {
|
||||||
|
if (e.key === 'Enter') {
|
||||||
|
e.preventDefault();
|
||||||
|
if (form.reportValidity()) {
|
||||||
|
form.dispatchEvent(new Event('submit', { cancelable: true, bubbles: true }));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle form submission
|
||||||
|
form.addEventListener('submit', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
|
||||||
|
if (!form.checkValidity()) {
|
||||||
|
form.reportValidity();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const targetPage = parseInt(input.value);
|
||||||
|
if (isNaN(targetPage) || targetPage < 1) return;
|
||||||
|
|
||||||
|
const totalPages = parseInt(nav.dataset.total || '0');
|
||||||
|
if (targetPage > totalPages) return;
|
||||||
|
|
||||||
|
const firstUrl = nav.dataset.firstUrl || '';
|
||||||
|
const formatUrl = nav.dataset.formatUrl || '';
|
||||||
|
|
||||||
|
let targetUrl = '';
|
||||||
|
if (targetPage === 1) {
|
||||||
|
targetUrl = firstUrl;
|
||||||
|
} else {
|
||||||
|
// formatUrl is the URL for page 2. E.g., /tags/page/2/ or /page/2/
|
||||||
|
// Replace the '2' before the trailing slash or .html with the target page number
|
||||||
|
targetUrl = formatUrl.replace(/2([^\d]*)$/, `${targetPage}$1`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (targetUrl) {
|
||||||
|
window.location.href = targetUrl;
|
||||||
|
}
|
||||||
|
|
||||||
|
closeDialog();
|
||||||
|
});
|
||||||
|
}
|
||||||
140
themes/stack/assets/ts/scrollspy.ts
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed.
|
||||||
|
|
||||||
|
// Inspired from https://gomakethings.com/debouncing-your-javascript-events/
|
||||||
|
function debounced(func: Function) {
|
||||||
|
let timeout;
|
||||||
|
return () => {
|
||||||
|
if (timeout) {
|
||||||
|
window.cancelAnimationFrame(timeout);
|
||||||
|
}
|
||||||
|
|
||||||
|
timeout = window.requestAnimationFrame(() => func());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]";
|
||||||
|
const tocQuery = "#TableOfContents";
|
||||||
|
const navigationQuery = "#TableOfContents li";
|
||||||
|
const activeClass = "active-class";
|
||||||
|
|
||||||
|
function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) {
|
||||||
|
let textHeight = tocElement.querySelector("a").offsetHeight;
|
||||||
|
let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
|
||||||
|
if (scrollTop < 0) {
|
||||||
|
scrollTop = 0;
|
||||||
|
}
|
||||||
|
scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" });
|
||||||
|
}
|
||||||
|
|
||||||
|
type IdToElementMap = { [key: string]: HTMLElement };
|
||||||
|
|
||||||
|
function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap {
|
||||||
|
const sectionLinkRef: IdToElementMap = {};
|
||||||
|
navigation.forEach((navigationElement: HTMLElement) => {
|
||||||
|
const link = navigationElement.querySelector("a");
|
||||||
|
if (link) {
|
||||||
|
const href = link.getAttribute("href");
|
||||||
|
if (href.startsWith("#")) {
|
||||||
|
sectionLinkRef[href.slice(1)] = navigationElement;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return sectionLinkRef;
|
||||||
|
}
|
||||||
|
|
||||||
|
function computeOffsets(headers: NodeListOf<Element>) {
|
||||||
|
let sectionsOffsets = [];
|
||||||
|
headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
|
||||||
|
sectionsOffsets.sort((a, b) => a.offset - b.offset);
|
||||||
|
return sectionsOffsets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function setupScrollspy() {
|
||||||
|
let headers = document.querySelectorAll(headersQuery);
|
||||||
|
if (!headers) {
|
||||||
|
console.warn("No header matched query", headers);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined;
|
||||||
|
if (!scrollableNavigation) {
|
||||||
|
console.warn("No toc matched query", tocQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let navigation = document.querySelectorAll(navigationQuery);
|
||||||
|
if (!navigation) {
|
||||||
|
console.warn("No navigation matched query", navigationQuery);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sectionsOffsets = computeOffsets(headers);
|
||||||
|
|
||||||
|
// We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC,
|
||||||
|
// we would scroll their view, which is not optimal usability-wise.
|
||||||
|
let tocHovered: boolean = false;
|
||||||
|
scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true));
|
||||||
|
scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false));
|
||||||
|
|
||||||
|
let activeSectionLink: Element;
|
||||||
|
|
||||||
|
let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation);
|
||||||
|
|
||||||
|
function scrollHandler() {
|
||||||
|
let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
|
||||||
|
|
||||||
|
let newActiveSection: HTMLElement | undefined;
|
||||||
|
|
||||||
|
// Find the section that is currently active.
|
||||||
|
// It is possible for no section to be active, so newActiveSection may be undefined.
|
||||||
|
sectionsOffsets.forEach((section) => {
|
||||||
|
if (scrollPosition >= section.offset - 20) {
|
||||||
|
newActiveSection = document.getElementById(section.id);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Find the link for the active section. Once again, there are a few edge cases:
|
||||||
|
// - No active section = no link => undefined
|
||||||
|
// - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined
|
||||||
|
let newActiveSectionLink: HTMLElement | undefined
|
||||||
|
if (newActiveSection) {
|
||||||
|
newActiveSectionLink = idToNavigationElement[newActiveSection.id];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newActiveSection && !newActiveSectionLink) {
|
||||||
|
// The active section does not have a link in the ToC, so we can't scroll to it.
|
||||||
|
console.debug("No link found for section", newActiveSection);
|
||||||
|
} else if (newActiveSectionLink !== activeSectionLink) {
|
||||||
|
if (activeSectionLink)
|
||||||
|
activeSectionLink.classList.remove(activeClass);
|
||||||
|
if (newActiveSectionLink) {
|
||||||
|
newActiveSectionLink.classList.add(activeClass);
|
||||||
|
if (!tocHovered) {
|
||||||
|
// Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
|
||||||
|
scrollToTocElement(newActiveSectionLink, scrollableNavigation);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
activeSectionLink = newActiveSectionLink;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("scroll", debounced(scrollHandler));
|
||||||
|
|
||||||
|
// Resizing may cause the offset values to change: recompute them.
|
||||||
|
function resizeHandler() {
|
||||||
|
sectionsOffsets = computeOffsets(headers);
|
||||||
|
scrollHandler();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Use ResizeObserver to detect changes in the size of .article-content
|
||||||
|
const articleContent = document.querySelector(".article-content");
|
||||||
|
if (articleContent) {
|
||||||
|
const resizeObserver = new ResizeObserver(debounced(resizeHandler));
|
||||||
|
resizeObserver.observe(articleContent);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener("resize", debounced(resizeHandler));
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setupScrollspy };
|
||||||
348
themes/stack/assets/ts/search.tsx
Normal file
@ -0,0 +1,348 @@
|
|||||||
|
interface pageData {
|
||||||
|
title: string,
|
||||||
|
date: string,
|
||||||
|
permalink: string,
|
||||||
|
content: string,
|
||||||
|
image?: string,
|
||||||
|
preview: string,
|
||||||
|
matchCount: number
|
||||||
|
}
|
||||||
|
|
||||||
|
interface match {
|
||||||
|
start: number,
|
||||||
|
end: number
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Escape HTML tags as HTML entities
|
||||||
|
* Edited from:
|
||||||
|
* @link https://stackoverflow.com/a/5499821
|
||||||
|
*/
|
||||||
|
const tagsToReplace = {
|
||||||
|
'&': '&',
|
||||||
|
'<': '<',
|
||||||
|
'>': '>',
|
||||||
|
'"': '"',
|
||||||
|
'…': '…'
|
||||||
|
};
|
||||||
|
|
||||||
|
function replaceTag(tag: string) {
|
||||||
|
return (tagsToReplace as Record<string, string>)[tag] || tag;
|
||||||
|
}
|
||||||
|
|
||||||
|
function replaceHTMLEnt(str: string) {
|
||||||
|
return str.replace(/[&<>"]/g, replaceTag);
|
||||||
|
}
|
||||||
|
|
||||||
|
function escapeRegExp(string: string) {
|
||||||
|
return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
|
||||||
|
}
|
||||||
|
|
||||||
|
class Search {
|
||||||
|
private data: pageData[];
|
||||||
|
private form: HTMLFormElement;
|
||||||
|
private input: HTMLInputElement;
|
||||||
|
private list: HTMLDivElement;
|
||||||
|
private resultTitle: HTMLHeadElement;
|
||||||
|
private resultTitleTemplate: string;
|
||||||
|
private container: HTMLDivElement;
|
||||||
|
|
||||||
|
constructor({ form, input, list, resultTitle, resultTitleTemplate }: {
|
||||||
|
form: HTMLFormElement,
|
||||||
|
input: HTMLInputElement,
|
||||||
|
list: HTMLDivElement,
|
||||||
|
resultTitle: HTMLHeadingElement,
|
||||||
|
resultTitleTemplate: string
|
||||||
|
}) {
|
||||||
|
this.form = form;
|
||||||
|
this.input = input;
|
||||||
|
this.list = list;
|
||||||
|
this.resultTitle = resultTitle;
|
||||||
|
this.resultTitleTemplate = resultTitleTemplate;
|
||||||
|
this.container = list.parentElement as HTMLDivElement;
|
||||||
|
|
||||||
|
/// Check if there's already value in the search input
|
||||||
|
if (this.input.value.trim() !== '') {
|
||||||
|
this.doSearch(this.input.value.split(' '));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.handleQueryString();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.bindQueryStringChange();
|
||||||
|
this.bindSearchForm();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Processes search matches
|
||||||
|
* @param str original text
|
||||||
|
* @param matches array of matches
|
||||||
|
* @param ellipsis whether to add ellipsis to the end of each match
|
||||||
|
* @param charLimit max length of preview string
|
||||||
|
* @param offset how many characters before and after the match to include in preview
|
||||||
|
* @returns preview string
|
||||||
|
*/
|
||||||
|
private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string {
|
||||||
|
matches.sort((a, b) => {
|
||||||
|
return a.start - b.start;
|
||||||
|
});
|
||||||
|
|
||||||
|
let i = 0,
|
||||||
|
lastIndex = 0,
|
||||||
|
charCount = 0;
|
||||||
|
|
||||||
|
const resultArray: string[] = [];
|
||||||
|
|
||||||
|
while (i < matches.length) {
|
||||||
|
const item = matches[i];
|
||||||
|
|
||||||
|
/// item.start >= lastIndex (equal only for the first iteration)
|
||||||
|
/// because of the while loop that comes after, iterating over variable j
|
||||||
|
|
||||||
|
if (ellipsis && item.start - offset > lastIndex) {
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `);
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`);
|
||||||
|
charCount += offset * 2;
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/// If the match is too close to the end of last match, don't add ellipsis
|
||||||
|
resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start)));
|
||||||
|
charCount += item.start - lastIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
let j = i + 1,
|
||||||
|
end = item.end;
|
||||||
|
|
||||||
|
/// Include as many matches as possible
|
||||||
|
/// [item.start, end] is the range of the match
|
||||||
|
while (j < matches.length && matches[j].start <= end) {
|
||||||
|
end = Math.max(matches[j].end, end);
|
||||||
|
++j;
|
||||||
|
}
|
||||||
|
|
||||||
|
resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`);
|
||||||
|
charCount += end - item.start;
|
||||||
|
|
||||||
|
i = j;
|
||||||
|
lastIndex = end;
|
||||||
|
|
||||||
|
if (ellipsis && charCount > charLimit) break;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add the rest of the string
|
||||||
|
if (lastIndex < str.length) {
|
||||||
|
let end = str.length;
|
||||||
|
if (ellipsis) end = Math.min(end, lastIndex + offset);
|
||||||
|
|
||||||
|
resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`);
|
||||||
|
|
||||||
|
if (ellipsis && end != str.length) {
|
||||||
|
resultArray.push(` [...]`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return resultArray.join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
private async searchKeywords(keywords: string[]) {
|
||||||
|
const rawData = await this.getData();
|
||||||
|
const results: pageData[] = [];
|
||||||
|
|
||||||
|
const regex = new RegExp(keywords.filter((v, index, arr) => {
|
||||||
|
arr[index] = escapeRegExp(v);
|
||||||
|
return v.trim() !== '';
|
||||||
|
}).join('|'), 'gi');
|
||||||
|
|
||||||
|
for (const item of rawData) {
|
||||||
|
const titleMatches: match[] = [],
|
||||||
|
contentMatches: match[] = [];
|
||||||
|
|
||||||
|
let result = {
|
||||||
|
...item,
|
||||||
|
preview: '',
|
||||||
|
matchCount: 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const contentMatchAll = item.content.matchAll(regex);
|
||||||
|
for (const match of Array.from(contentMatchAll)) {
|
||||||
|
contentMatches.push({
|
||||||
|
start: match.index,
|
||||||
|
end: match.index + match[0].length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const titleMatchAll = item.title.matchAll(regex);
|
||||||
|
for (const match of Array.from(titleMatchAll)) {
|
||||||
|
titleMatches.push({
|
||||||
|
start: match.index,
|
||||||
|
end: match.index + match[0].length
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (titleMatches.length > 0) result.title = Search.processMatches(result.title, titleMatches, false);
|
||||||
|
if (contentMatches.length > 0) {
|
||||||
|
result.preview = Search.processMatches(result.content, contentMatches);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
/// If there are no matches in the content, use the first 140 characters as preview
|
||||||
|
result.preview = replaceHTMLEnt(result.content.substring(0, 140));
|
||||||
|
}
|
||||||
|
|
||||||
|
result.matchCount = titleMatches.length + contentMatches.length;
|
||||||
|
if (result.matchCount > 0) results.push(result);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result with more matches appears first
|
||||||
|
return results.sort((a, b) => {
|
||||||
|
return b.matchCount - a.matchCount;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private async doSearch(keywords: string[]) {
|
||||||
|
const startTime = performance.now();
|
||||||
|
|
||||||
|
const results = await this.searchKeywords(keywords);
|
||||||
|
this.clear();
|
||||||
|
|
||||||
|
for (const item of results) {
|
||||||
|
this.list.append(Search.render(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
const endTime = performance.now();
|
||||||
|
|
||||||
|
this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1));
|
||||||
|
|
||||||
|
this.container?.classList.remove('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
private generateResultTitle(resultLen: number, time: string) {
|
||||||
|
return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen.toString()).replace("#TIME_SECONDS", time);
|
||||||
|
}
|
||||||
|
|
||||||
|
public async getData() {
|
||||||
|
if (!this.data) {
|
||||||
|
/// Not fetched yet
|
||||||
|
const jsonURL = this.form.dataset.json as string;
|
||||||
|
this.data = await fetch(jsonURL).then(res => res.json());
|
||||||
|
const parser = new DOMParser();
|
||||||
|
|
||||||
|
for (const item of this.data) {
|
||||||
|
item.content = parser.parseFromString(item.content, 'text/html').body.innerText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return this.data;
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindSearchForm() {
|
||||||
|
let lastSearch = '';
|
||||||
|
|
||||||
|
const eventHandler = (e: Event) => {
|
||||||
|
e.preventDefault();
|
||||||
|
const keywords = this.input.value.trim();
|
||||||
|
|
||||||
|
Search.updateQueryString(keywords, true);
|
||||||
|
|
||||||
|
if (keywords === '') {
|
||||||
|
lastSearch = '';
|
||||||
|
return this.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (lastSearch === keywords) return;
|
||||||
|
lastSearch = keywords;
|
||||||
|
|
||||||
|
this.doSearch(keywords.split(' '));
|
||||||
|
}
|
||||||
|
|
||||||
|
this.input.addEventListener('input', eventHandler);
|
||||||
|
this.input.addEventListener('compositionend', eventHandler);
|
||||||
|
}
|
||||||
|
|
||||||
|
private clear() {
|
||||||
|
this.list.innerHTML = '';
|
||||||
|
this.resultTitle.innerText = '';
|
||||||
|
this.container.classList.add('hidden');
|
||||||
|
}
|
||||||
|
|
||||||
|
private bindQueryStringChange() {
|
||||||
|
window.addEventListener('popstate', (e) => {
|
||||||
|
this.handleQueryString()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
private handleQueryString() {
|
||||||
|
const pageURL = new URL(window.location.toString());
|
||||||
|
const keywords = pageURL.searchParams.get('keyword') || '';
|
||||||
|
this.input.value = keywords;
|
||||||
|
|
||||||
|
if (keywords) {
|
||||||
|
this.doSearch(keywords.split(' '));
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.clear()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static updateQueryString(keywords: string, replaceState = false) {
|
||||||
|
const pageURL = new URL(window.location.toString());
|
||||||
|
|
||||||
|
if (keywords === '') {
|
||||||
|
pageURL.searchParams.delete('keyword')
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
pageURL.searchParams.set('keyword', keywords);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (replaceState) {
|
||||||
|
window.history.replaceState('', '', pageURL.toString());
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
window.history.pushState('', '', pageURL.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static render(item: pageData) {
|
||||||
|
return (
|
||||||
|
<article>
|
||||||
|
<a href={item.permalink}>
|
||||||
|
<div class="article-details">
|
||||||
|
<h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
|
||||||
|
<section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
|
||||||
|
</div>
|
||||||
|
{item.image &&
|
||||||
|
<div class="article-image">
|
||||||
|
<img src={item.image} loading="lazy" />
|
||||||
|
</div>
|
||||||
|
}
|
||||||
|
</a>
|
||||||
|
</article>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
declare global {
|
||||||
|
interface Window {
|
||||||
|
searchResultTitleTemplate: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.addEventListener('load', () => {
|
||||||
|
setTimeout(function () {
|
||||||
|
const searchForm = document.querySelector('.search-form') as HTMLFormElement,
|
||||||
|
searchInput = searchForm?.querySelector('input') as HTMLInputElement,
|
||||||
|
searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
|
||||||
|
searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
|
||||||
|
|
||||||
|
if (!searchForm || !searchInput || !searchResultList || !searchResultTitle) return;
|
||||||
|
|
||||||
|
new Search({
|
||||||
|
form: searchForm,
|
||||||
|
input: searchInput,
|
||||||
|
list: searchResultList,
|
||||||
|
resultTitle: searchResultTitle,
|
||||||
|
resultTitleTemplate: window.searchResultTitleTemplate
|
||||||
|
});
|
||||||
|
}, 0);
|
||||||
|
})
|
||||||
|
|
||||||
|
export default Search;
|
||||||
37
themes/stack/assets/ts/smoothAnchors.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
// Implements smooth scrolling when clicking on an anchor link.
|
||||||
|
// This is required instead of using modern CSS because Chromium does not currently support scrolling
|
||||||
|
// one element with scrollTo while another element is scrolled because of a click on a link. This would
|
||||||
|
// thus not work with the ToC scrollspy and e.g. footnotes.
|
||||||
|
|
||||||
|
// Here are additional links about this issue:
|
||||||
|
// - https://stackoverflow.com/questions/49318497/google-chrome-simultaneously-smooth-scrollintoview-with-more-elements-doesn
|
||||||
|
// - https://stackoverflow.com/questions/57214373/scrollintoview-using-smooth-function-on-multiple-elements-in-chrome
|
||||||
|
// - https://bugs.chromium.org/p/chromium/issues/detail?id=833617
|
||||||
|
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1043933
|
||||||
|
// - https://bugs.chromium.org/p/chromium/issues/detail?id=1121151
|
||||||
|
|
||||||
|
const anchorLinksQuery = "a[href]";
|
||||||
|
|
||||||
|
function setupSmoothAnchors() {
|
||||||
|
document.querySelectorAll(anchorLinksQuery).forEach(aElement => {
|
||||||
|
let href = aElement.getAttribute("href");
|
||||||
|
if (!href.startsWith("#")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
aElement.addEventListener("click", clickEvent => {
|
||||||
|
clickEvent.preventDefault();
|
||||||
|
|
||||||
|
const targetId = decodeURI(aElement.getAttribute("href").substring(1)),
|
||||||
|
target = document.getElementById(targetId) as HTMLElement,
|
||||||
|
offset = target.getBoundingClientRect().top - document.documentElement.getBoundingClientRect().top;
|
||||||
|
|
||||||
|
window.history.pushState({}, "", aElement.getAttribute("href"));
|
||||||
|
scrollTo({
|
||||||
|
top: offset,
|
||||||
|
behavior: "smooth"
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
export { setupSmoothAnchors };
|
||||||
3
themes/stack/config/_default/module.toml
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
[hugoVersion]
|
||||||
|
extended = true
|
||||||
|
min = "0.157.0"
|
||||||
202
themes/stack/config/_default/params.toml
Normal file
@ -0,0 +1,202 @@
|
|||||||
|
mainSections = ["post"]
|
||||||
|
rssFullContent = true
|
||||||
|
# Accepted values: "default", "lastmod"
|
||||||
|
# default = see https://gohugo.io/quick-reference/glossary/#default-sort-order
|
||||||
|
# lastmod = sort by last modified date, in descending order
|
||||||
|
SortBy = "default"
|
||||||
|
|
||||||
|
[footer]
|
||||||
|
since = 2026
|
||||||
|
customText = ""
|
||||||
|
|
||||||
|
[dateFormat]
|
||||||
|
published = ":date_full"
|
||||||
|
lastUpdated = ":date_full"
|
||||||
|
|
||||||
|
[sidebar]
|
||||||
|
compact = false
|
||||||
|
avatar = ""
|
||||||
|
|
||||||
|
[article]
|
||||||
|
headingAnchor = false
|
||||||
|
math = false
|
||||||
|
toc = true
|
||||||
|
readingTime = true
|
||||||
|
|
||||||
|
[article.list]
|
||||||
|
# Showing article tags in the list view (e.g. homepage, section pages)
|
||||||
|
showTags = false
|
||||||
|
|
||||||
|
[article.license]
|
||||||
|
enabled = false
|
||||||
|
default = "Licensed under CC BY-NC-SA 4.0"
|
||||||
|
|
||||||
|
# Mermaid diagram configuration
|
||||||
|
# Diagrams are only loaded when mermaid code blocks are present
|
||||||
|
# Theme auto-switches based on site light/dark mode
|
||||||
|
[article.mermaid]
|
||||||
|
# Visual style: classic or handDrawn (sketch style)
|
||||||
|
look = "classic"
|
||||||
|
# Theme for light mode: default, neutral, dark, forest, base, null
|
||||||
|
lightTheme = "default"
|
||||||
|
# Theme for dark mode: default, neutral, dark, forest, base, null
|
||||||
|
darkTheme = "dark"
|
||||||
|
# Custom theme variables for light mode (only works with lightTheme: base)
|
||||||
|
# lightThemeVariables = { primaryColor = "#ff0000" }
|
||||||
|
# Custom theme variables for dark mode (only works with darkTheme: base)
|
||||||
|
# darkThemeVariables = { primaryColor = "#00ff00" }
|
||||||
|
# Security level: strict (default), loose, antiscript, sandbox
|
||||||
|
# Set to "loose" to enable HTML labels like <br/>
|
||||||
|
securityLevel = "strict"
|
||||||
|
# Enable HTML labels in diagrams (requires securityLevel: loose)
|
||||||
|
htmlLabels = true
|
||||||
|
# Make diagram backgrounds transparent (default: false)
|
||||||
|
# Can also be set per-diagram with %%transparent%% directive
|
||||||
|
transparentBackground = false
|
||||||
|
|
||||||
|
[article.alertIcon]
|
||||||
|
# Alert icons for different alert types (note, tip, important, warning, caution)
|
||||||
|
note = "📝"
|
||||||
|
tip = "💡"
|
||||||
|
important = "📌"
|
||||||
|
warning = "⚠️"
|
||||||
|
caution = "🚨"
|
||||||
|
|
||||||
|
[widgets]
|
||||||
|
homepage = []
|
||||||
|
page = []
|
||||||
|
|
||||||
|
[opengraph.twitter]
|
||||||
|
site = ""
|
||||||
|
card = "summary_large_image"
|
||||||
|
|
||||||
|
[colorScheme]
|
||||||
|
toggle = true
|
||||||
|
default = "auto"
|
||||||
|
|
||||||
|
[imageProcessing]
|
||||||
|
autoOrient = false
|
||||||
|
|
||||||
|
[imageProcessing.external]
|
||||||
|
timeout = "5s"
|
||||||
|
|
||||||
|
[imageProcessing.content]
|
||||||
|
widths = [800, 1600, 2400]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
[imageProcessing.thumbnail]
|
||||||
|
enabled = true
|
||||||
|
|
||||||
|
# GDPR Cookie Consent Configuration
|
||||||
|
# When enabled, analytics and functional cookies require user consent
|
||||||
|
[cookies]
|
||||||
|
enabled = false
|
||||||
|
showSettings = true
|
||||||
|
|
||||||
|
[cookies.categories]
|
||||||
|
analytics = true
|
||||||
|
functional = true
|
||||||
|
|
||||||
|
[comments]
|
||||||
|
enabled = true
|
||||||
|
provider = "disqus"
|
||||||
|
|
||||||
|
[comments.artalk]
|
||||||
|
serverUrl = ""
|
||||||
|
site = ""
|
||||||
|
|
||||||
|
[comments.disqusjs]
|
||||||
|
shortname = ""
|
||||||
|
apiUrl = ""
|
||||||
|
apiKey = ""
|
||||||
|
admin = ""
|
||||||
|
adminLabel = ""
|
||||||
|
|
||||||
|
[comments.utterances]
|
||||||
|
repo = ""
|
||||||
|
issueTerm = "pathname"
|
||||||
|
label = ""
|
||||||
|
|
||||||
|
[comments.beaudar]
|
||||||
|
repo = ""
|
||||||
|
issueTerm = "pathname"
|
||||||
|
label = ""
|
||||||
|
theme = ""
|
||||||
|
|
||||||
|
[comments.remark42]
|
||||||
|
host = ""
|
||||||
|
site = ""
|
||||||
|
locale = "en"
|
||||||
|
max_shown_comments = 15
|
||||||
|
show_email_subscription = true
|
||||||
|
|
||||||
|
[comments.vssue]
|
||||||
|
platform = ""
|
||||||
|
owner = ""
|
||||||
|
repo = ""
|
||||||
|
clientId = ""
|
||||||
|
clientSecret = ""
|
||||||
|
autoCreateIssue = false
|
||||||
|
|
||||||
|
# Waline client configuration see: https://waline.js.org/en/reference/component.html
|
||||||
|
[comments.waline]
|
||||||
|
serverURL = ""
|
||||||
|
lang = ""
|
||||||
|
pageview = ""
|
||||||
|
emoji = ["https://unpkg.com/@waline/emojis@1.0.1/weibo"]
|
||||||
|
requiredMeta = ["name", "email", "url"]
|
||||||
|
|
||||||
|
[comments.waline.locale]
|
||||||
|
admin = "Admin"
|
||||||
|
placeholder = ""
|
||||||
|
|
||||||
|
[comments.twikoo]
|
||||||
|
envId = ""
|
||||||
|
region = ""
|
||||||
|
path = ""
|
||||||
|
lang = ""
|
||||||
|
|
||||||
|
# See https://cactus.chat/docs/reference/web-client/#configuration for description of the various options
|
||||||
|
[comments.cactus]
|
||||||
|
defaultHomeserverUrl = "https://matrix.cactus.chat:8448"
|
||||||
|
serverName = "cactus.chat"
|
||||||
|
siteName = "" # You must insert a unique identifier here matching the one you registered (See https://cactus.chat/docs/getting-started/quick-start/#register-your-site)
|
||||||
|
|
||||||
|
[comments.giscus]
|
||||||
|
repo = ""
|
||||||
|
repoID = ""
|
||||||
|
category = ""
|
||||||
|
categoryID = ""
|
||||||
|
mapping = "title"
|
||||||
|
lightTheme = "light"
|
||||||
|
darkTheme = "dark_dimmed"
|
||||||
|
reactionsEnabled = 1
|
||||||
|
emitMetadata = 0
|
||||||
|
inputPosition = "top"
|
||||||
|
lang = "en"
|
||||||
|
strict = 0
|
||||||
|
loading = "lazy"
|
||||||
|
|
||||||
|
[comments.gitalk]
|
||||||
|
owner = ""
|
||||||
|
admin = ""
|
||||||
|
repo = ""
|
||||||
|
clientID = ""
|
||||||
|
clientSecret = ""
|
||||||
|
proxy = ""
|
||||||
|
|
||||||
|
[comments.cusdis]
|
||||||
|
host = "https://cusdis.com"
|
||||||
|
id = ""
|
||||||
|
|
||||||
|
# Comentario client configuration
|
||||||
|
[comments.comentario]
|
||||||
|
url = ""
|
||||||
|
autoInit = false
|
||||||
|
autoNonInteractiveSso = false
|
||||||
|
cssOverride = ""
|
||||||
|
lang = ""
|
||||||
|
liveUpdate = false
|
||||||
|
maxLevel = 0
|
||||||
|
noFonts = false
|
||||||
|
theme = ""
|
||||||
19
themes/stack/data/external.toml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
KaTeX = [
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css", integrity = "sha384-n8MVd4RsNIU0tAv4ct0nTaAbDJwPJzDEaqSD1odI+WdtXRGWt2kTvGFasHpSy3SV", type = "style" },
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js", integrity = "sha384-XjKyOOlGwcjNTAIQHIpgOno0Hl1YQqzUOEleOLALmuqehneUG+vnGctmUb0ZY0l8", type = "script", defer = true },
|
||||||
|
{ src = "https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js", integrity = "sha384-+VBxd3r6XgURycqtZ117nYw44OOcIax56Z4dCRWbxyPt0Koah1uHoK0o4+/RRE05", type = "script", defer = true },
|
||||||
|
]
|
||||||
|
|
||||||
|
Cactus = [
|
||||||
|
{ src = "https://latest.cactus.chat/cactus.js", type = "script" },
|
||||||
|
{ src = "https://latest.cactus.chat/style.css", type = "style" },
|
||||||
|
]
|
||||||
|
|
||||||
|
[PhotoSwipe]
|
||||||
|
Style = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.css"
|
||||||
|
Core = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe.esm.min.js"
|
||||||
|
Lightbox = "https://cdn.jsdelivr.net/npm/photoswipe@5.4.4/dist/photoswipe-lightbox.esm.min.js"
|
||||||
|
|
||||||
|
[Artalk]
|
||||||
|
Style = "https://cdn.jsdelivr.net/npm/artalk@2.9.1/dist/Artalk.css"
|
||||||
|
Script = "https://cdn.jsdelivr.net/npm/artalk@2.9.1/+esm"
|
||||||
30
themes/stack/demo/.gitignore
vendored
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
# Compiled Object files, Static and Dynamic libs (Shared Objects)
|
||||||
|
*.o
|
||||||
|
*.a
|
||||||
|
*.so
|
||||||
|
|
||||||
|
# Folders
|
||||||
|
_obj
|
||||||
|
_test
|
||||||
|
|
||||||
|
# Architecture specific extensions/prefixes
|
||||||
|
*.[568vq]
|
||||||
|
[568vq].out
|
||||||
|
|
||||||
|
*.cgo1.go
|
||||||
|
*.cgo2.c
|
||||||
|
_cgo_defun.c
|
||||||
|
_cgo_gotypes.go
|
||||||
|
_cgo_export.*
|
||||||
|
|
||||||
|
_testmain.go
|
||||||
|
|
||||||
|
*.exe
|
||||||
|
*.test
|
||||||
|
|
||||||
|
public
|
||||||
|
themes
|
||||||
|
resources
|
||||||
|
|
||||||
|
assets/jsconfig.json
|
||||||
|
.DS_Store
|
||||||
21
themes/stack/demo/LICENSE
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2014 Steve Francia
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
3
themes/stack/demo/README.md
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
This is an example site used for solely for testing purposes. **It is not meant to be used as a template for your site**.
|
||||||
|
|
||||||
|
To create a new site, please use the starter template: [CaiJimmy/hugo-theme-stack-starter](https://github.com/CaiJimmy/hugo-theme-stack-starter/)
|
||||||
BIN
themes/stack/demo/assets/img/avatar.png
Normal file
|
After Width: | Height: | Size: 373 B |
22
themes/stack/demo/config/_default/hugo.toml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
baseURL = "https://demo.stack.cai.im/"
|
||||||
|
locale = "en-us"
|
||||||
|
title = "Hugo Theme Stack"
|
||||||
|
# Theme i18n support
|
||||||
|
# See i18n folder for available languages
|
||||||
|
defaultContentLanguage = "en"
|
||||||
|
# Set hasCJKLanguage to true if DefaultContentLanguage is a CJK language
|
||||||
|
# This will make .Summary and .WordCount behave correctly
|
||||||
|
hasCJKLanguage = false
|
||||||
|
|
||||||
|
[[module.imports]]
|
||||||
|
path = "github.com/CaiJimmy/hugo-theme-stack/v3"
|
||||||
|
|
||||||
|
[pagination]
|
||||||
|
pagerSize = 3
|
||||||
|
|
||||||
|
[permalinks]
|
||||||
|
post = "/p/:slug/"
|
||||||
|
page = "/:slug/"
|
||||||
|
|
||||||
|
[services.disqus]
|
||||||
|
shortname = "hugo-theme-stack"
|
||||||
31
themes/stack/demo/config/_default/languages.toml
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
[en]
|
||||||
|
label = "English"
|
||||||
|
title = "Hugo Theme Stack"
|
||||||
|
weight = 1
|
||||||
|
|
||||||
|
[en.params.sidebar]
|
||||||
|
subtitle = "Card-style Hugo theme designed for bloggers"
|
||||||
|
|
||||||
|
[zh]
|
||||||
|
label = "简体中文"
|
||||||
|
title = "Hugo 主题 Stack"
|
||||||
|
weight = 2
|
||||||
|
|
||||||
|
[zh.params.sidebar]
|
||||||
|
subtitle = "为博客设计的卡片式 Hugo 主题"
|
||||||
|
|
||||||
|
[zh-hant-tw]
|
||||||
|
label = "正體中文"
|
||||||
|
title = "Hugo 主題 Stack"
|
||||||
|
weight = 3
|
||||||
|
|
||||||
|
[zh-hant-tw.params.sidebar]
|
||||||
|
subtitle = "為博客設計的卡片式 Hugo 主題"
|
||||||
|
|
||||||
|
[ja]
|
||||||
|
label = "日本語"
|
||||||
|
title = "Hugo Theme Stack"
|
||||||
|
weight = 4
|
||||||
|
|
||||||
|
[ja.params.sidebar]
|
||||||
|
subtitle = "ブログ用のカードスタイルの Hugo テーマ"
|
||||||
26
themes/stack/demo/config/_default/markup.toml
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
[goldmark]
|
||||||
|
[goldmark.extensions]
|
||||||
|
[goldmark.extensions.passthrough]
|
||||||
|
enable = true
|
||||||
|
|
||||||
|
[goldmark.extensions.passthrough.delimiters]
|
||||||
|
block = [["\\[", "\\]"], ["$$", "$$"]]
|
||||||
|
inline = [["\\(", "\\)"]]
|
||||||
|
|
||||||
|
[goldmark.renderer]
|
||||||
|
## Set to true if you have HTML content inside Markdown
|
||||||
|
unsafe = true
|
||||||
|
|
||||||
|
[tableOfContents]
|
||||||
|
endLevel = 4
|
||||||
|
ordered = true
|
||||||
|
startLevel = 2
|
||||||
|
|
||||||
|
[highlight]
|
||||||
|
noClasses = false
|
||||||
|
codeFences = true
|
||||||
|
guessSyntax = true
|
||||||
|
lineNoStart = 1
|
||||||
|
lineNos = true
|
||||||
|
lineNumbersInTable = true
|
||||||
|
tabWidth = 4
|
||||||
15
themes/stack/demo/config/_default/menu.toml
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
[[social]]
|
||||||
|
identifier = "github"
|
||||||
|
name = "GitHub"
|
||||||
|
url = "https://github.com/CaiJimmy/hugo-theme-stack"
|
||||||
|
|
||||||
|
[social.params]
|
||||||
|
icon = "brand-github"
|
||||||
|
|
||||||
|
[[social]]
|
||||||
|
identifier = "twitter"
|
||||||
|
name = "Twitter"
|
||||||
|
url = "https://twitter.com"
|
||||||
|
|
||||||
|
[social.params]
|
||||||
|
icon = "brand-twitter"
|
||||||
39
themes/stack/demo/config/_default/params.toml
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
mainSections = ["post"]
|
||||||
|
rssFullContent = true
|
||||||
|
favicon = "img/avatar.png"
|
||||||
|
|
||||||
|
[footer]
|
||||||
|
since = 2020
|
||||||
|
|
||||||
|
[sidebar]
|
||||||
|
emoji = "✏️"
|
||||||
|
subtitle = "Lorem ipsum dolor sit amet, consectetur adipiscing elit."
|
||||||
|
avatar = "img/avatar.png"
|
||||||
|
|
||||||
|
[article]
|
||||||
|
[article.license]
|
||||||
|
enabled = true
|
||||||
|
default = "Licensed under [CC BY-NC-SA 4.0](https://creativecommons.org/licenses/by-nc-sa/4.0/)"
|
||||||
|
|
||||||
|
[widgets]
|
||||||
|
homepage = [
|
||||||
|
{ type = "search" },
|
||||||
|
{ type = "archives", params = { limit = 5 } },
|
||||||
|
{ type = "categories", params = { limit = 10 } },
|
||||||
|
{ type = "tag-cloud", params = { limit = 10 } },
|
||||||
|
]
|
||||||
|
page = [{ type = "toc" }]
|
||||||
|
|
||||||
|
[comments]
|
||||||
|
# See complete configuration in config/_default/params.toml
|
||||||
|
enabled = true
|
||||||
|
provider = "disqus"
|
||||||
|
|
||||||
|
# GDPR Cookie Consent Configuration
|
||||||
|
# When enabled, analytics and functional cookies require user consent
|
||||||
|
[cookies]
|
||||||
|
enabled = false
|
||||||
|
|
||||||
|
[cookies.categories]
|
||||||
|
analytics = true
|
||||||
|
functional = true
|
||||||
7
themes/stack/demo/config/_default/related.toml
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
includeNewer = true
|
||||||
|
threshold = 60
|
||||||
|
toLower = false
|
||||||
|
indices = [
|
||||||
|
{ name = "tags", weight = 100 },
|
||||||
|
{ name = "categories", weight = 200 },
|
||||||
|
]
|
||||||
8
themes/stack/demo/content/_index.ja.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
name: ホーム
|
||||||
|
weight: -100
|
||||||
|
params:
|
||||||
|
icon: home
|
||||||
|
---
|
||||||
8
themes/stack/demo/content/_index.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
name: Home
|
||||||
|
weight: -100
|
||||||
|
params:
|
||||||
|
icon: home
|
||||||
|
---
|
||||||
8
themes/stack/demo/content/_index.zh-hant-tw.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
name: 主頁
|
||||||
|
weight: -100
|
||||||
|
params:
|
||||||
|
icon: home
|
||||||
|
---
|
||||||
8
themes/stack/demo/content/_index.zh.md
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
---
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
name: 主页
|
||||||
|
weight: -100
|
||||||
|
params:
|
||||||
|
icon: home
|
||||||
|
---
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: ドキュメント
|
||||||
|
description: "Stack テーマの使用方法に関する投稿"
|
||||||
|
slug: "documentation"
|
||||||
|
image: "hutomo-abrianto-l2jk-uxb1BY-unsplash.jpg"
|
||||||
|
style:
|
||||||
|
background: "#2a9d8f"
|
||||||
|
color: "#fff"
|
||||||
|
---
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: Documentation
|
||||||
|
description: "Posts about how to use Stack theme"
|
||||||
|
slug: "documentation"
|
||||||
|
image: "hutomo-abrianto-l2jk-uxb1BY-unsplash.jpg"
|
||||||
|
style:
|
||||||
|
background: "#2a9d8f"
|
||||||
|
color: "#fff"
|
||||||
|
---
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: 文檔
|
||||||
|
description: "關於如何使用 Stack 主題的文章"
|
||||||
|
slug: "documentation"
|
||||||
|
image: "hutomo-abrianto-l2jk-uxb1BY-unsplash.jpg"
|
||||||
|
style:
|
||||||
|
background: "#2a9d8f"
|
||||||
|
color: "#fff"
|
||||||
|
---
|
||||||
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
title: 文档
|
||||||
|
description: "关于如何使用 Stack 主题的文章"
|
||||||
|
slug: "documentation"
|
||||||
|
image: "hutomo-abrianto-l2jk-uxb1BY-unsplash.jpg"
|
||||||
|
style:
|
||||||
|
background: "#2a9d8f"
|
||||||
|
color: "#fff"
|
||||||
|
---
|
||||||
|
After Width: | Height: | Size: 52 KiB |
46
themes/stack/demo/content/page/about/index.ja.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: このサイトについて
|
||||||
|
description: このサイトとその著者についてのすべて。
|
||||||
|
date: 2026-01-26
|
||||||
|
lastmod: 2026-01-26
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: -90
|
||||||
|
params:
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
|
|
||||||
|
## 誰のためのページですか?
|
||||||
|
|
||||||
|
これは**サンプル詳細**ページです。あなた自身、あなたのブログ、またはあなたのプロジェクトを紹介するために使用できます。
|
||||||
|
|
||||||
|
## 著者について
|
||||||
|
|
||||||
|
こんにちは!私はブログとオープンソースに情熱を注いでいる愛好家です。新しい技術を探索し、その経験をコミュニティと共有することが大好きです。
|
||||||
|
|
||||||
|
### 私の歩み
|
||||||
|
|
||||||
|
10年以上前にWeb開発の旅を始めました。それ以来、個人のブログから大規模なエンタープライズアプリケーションまで、数多くのプロジェクトに携わってきました。
|
||||||
|
|
||||||
|
### スキル
|
||||||
|
|
||||||
|
- **フロントエンド**: HTML, CSS, JavaScript, React, Vue
|
||||||
|
- **バックエンド**: Node.js, Python, Go
|
||||||
|
- **ツール**: Git, Docker, Hugo, Neovim
|
||||||
|
|
||||||
|
## お問い合わせ
|
||||||
|
|
||||||
|
質問がある場合や、単にあいさつをしたい場合は、お気軽にご連絡ください!
|
||||||
|
|
||||||
|
- **メール**: [hello@example.com](mailto:hello@example.com)
|
||||||
|
- **Twitter**: [@example](https://twitter.com/example)
|
||||||
|
- **GitHub**: [example](https://github.com/example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> "Logic will get you from A to B. Imagination will take you everywhere."
|
||||||
|
> — *Albert Einstein*
|
||||||
|
|
||||||
|
---
|
||||||
|
*このページは AI によって生成されました。*
|
||||||
|
|
||||||
46
themes/stack/demo/content/page/about/index.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: About
|
||||||
|
description: Everything you need to know about this site and its author.
|
||||||
|
date: 2026-01-26
|
||||||
|
lastmod: 2026-01-26
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: -90
|
||||||
|
params:
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
|
|
||||||
|
## Who is this for?
|
||||||
|
|
||||||
|
This is a sample **About** page. You can use it to introduce yourself, your blog, or your project.
|
||||||
|
|
||||||
|
## About the Author
|
||||||
|
|
||||||
|
Hello! I'm a passionate blogger and open-source enthusiast. I love exploring new technologies and sharing my experiences with the community.
|
||||||
|
|
||||||
|
### My Journey
|
||||||
|
|
||||||
|
I started my journey in web development over 10 years ago. Since then, I've worked on numerous projects ranging from small personal blogs to large-scale enterprise applications.
|
||||||
|
|
||||||
|
### Skills
|
||||||
|
|
||||||
|
- **Front-end**: HTML, CSS, JavaScript, React, Vue
|
||||||
|
- **Back-end**: Node.js, Python, Go
|
||||||
|
- **Tools**: Git, Docker, Hugo, Neovim
|
||||||
|
|
||||||
|
## Contact Me
|
||||||
|
|
||||||
|
Feel free to reach out if you have any questions or just want to say hi!
|
||||||
|
|
||||||
|
- **Email**: [hello@example.com](mailto:hello@example.com)
|
||||||
|
- **Twitter**: [@example](https://twitter.com/example)
|
||||||
|
- **GitHub**: [example](https://github.com/example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> "Logic will get you from A to B. Imagination will take you everywhere."
|
||||||
|
> — *Albert Einstein*
|
||||||
|
|
||||||
|
---
|
||||||
|
*This page was generated by AI.*
|
||||||
|
|
||||||
46
themes/stack/demo/content/page/about/index.zh-hant-tw.md
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
---
|
||||||
|
title: 關於
|
||||||
|
description: 關於本站及其作者的一切。
|
||||||
|
date: 2026-01-26
|
||||||
|
lastmod: 2026-01-26
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: -90
|
||||||
|
params:
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
|
|
||||||
|
## 這是給誰看的?
|
||||||
|
|
||||||
|
這是一個示例**關於**頁面。你可以用它來介紹你自己、你的博客或你的項目。
|
||||||
|
|
||||||
|
## 關於作者
|
||||||
|
|
||||||
|
你好!我是一個熱衷於博客和開源的愛好者。我喜歡探索新技術並向社區分享我的經驗。
|
||||||
|
|
||||||
|
### 我的旅程
|
||||||
|
|
||||||
|
我在 10 多年前開始了我的 Web 開發之旅。自那以後,我參與了許多項目,從個人博客到大規模企業應用。
|
||||||
|
|
||||||
|
### 技能
|
||||||
|
|
||||||
|
- **前端**: HTML, CSS, JavaScript, React, Vue
|
||||||
|
- **後端**: Node.js, Python, Go
|
||||||
|
- **工具**: Git, Docker, Hugo, Neovim
|
||||||
|
|
||||||
|
## 聯繫我
|
||||||
|
|
||||||
|
如果你有任何問題或只是想打個招呼,請隨時聯繫我!
|
||||||
|
|
||||||
|
- **電子郵件**: [hello@example.com](mailto:hello@example.com)
|
||||||
|
- **Twitter**: [@example](https://twitter.com/example)
|
||||||
|
- **GitHub**: [example](https://github.com/example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> "Logic will get you from A to B. Imagination will take you everywhere."
|
||||||
|
> — *Albert Einstein*
|
||||||
|
|
||||||
|
---
|
||||||
|
*此頁面由 AI 生成。*
|
||||||
|
|
||||||
45
themes/stack/demo/content/page/about/index.zh.md
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
---
|
||||||
|
title: 关于
|
||||||
|
description: 关于本站及其作者的一切。
|
||||||
|
date: 2026-01-26
|
||||||
|
lastmod: 2026-01-26
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: -90
|
||||||
|
params:
|
||||||
|
icon: user
|
||||||
|
---
|
||||||
|
|
||||||
|
## 这是给谁看的?
|
||||||
|
|
||||||
|
这是一个示例**关于**页面。你可以用它来介绍你自己、你的博客或你的项目。
|
||||||
|
|
||||||
|
## 关于作者
|
||||||
|
|
||||||
|
你好!我是一个热衷于博客和开源的爱好者。我喜欢探索新技术并向社区分享我的经验。
|
||||||
|
|
||||||
|
### 我的旅程
|
||||||
|
|
||||||
|
我在 10 多年前开始了我的 Web 开发之旅。自那以后,我参与了许多项目,从个人博客到大规模企业应用。
|
||||||
|
|
||||||
|
### 技能
|
||||||
|
|
||||||
|
- **前端**: HTML, CSS, JavaScript, React, Vue
|
||||||
|
- **后端**: Node.js, Python, Go
|
||||||
|
- **工具**: Git, Docker, Hugo, Neovim
|
||||||
|
|
||||||
|
## 联系我
|
||||||
|
|
||||||
|
如果你有任何问题或只是想打个招呼,请随时联系我!
|
||||||
|
|
||||||
|
- **电子邮件**: [hello@example.com](mailto:hello@example.com)
|
||||||
|
- **Twitter**: [@example](https://twitter.com/example)
|
||||||
|
- **GitHub**: [example](https://github.com/example)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
> "Logic will get you from A to B. Imagination will take you everywhere."
|
||||||
|
> — *Albert Einstein*
|
||||||
|
|
||||||
|
---
|
||||||
|
*此页面由 AI 生成。*
|
||||||
11
themes/stack/demo/content/page/archives/index.ja.md
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
---
|
||||||
|
title: "アーカイブ"
|
||||||
|
date: 2019-05-28
|
||||||
|
layout: "archives"
|
||||||
|
slug: "archives"
|
||||||
|
menu:
|
||||||
|
main:
|
||||||
|
weight: -70
|
||||||
|
params:
|
||||||
|
icon: archives
|
||||||
|
---
|
||||||