Jared hace 3 años
padre
commit
7afaa8257a
Se han modificado 100 ficheros con 7395 adiciones y 0 borrados
  1. 31 0
      themes/hugo-theme-stack/.devcontainer/Dockerfile
  2. 45 0
      themes/hugo-theme-stack/.devcontainer/devcontainer.json
  3. 674 0
      themes/hugo-theme-stack/LICENSE
  4. 88 0
      themes/hugo-theme-stack/README.md
  5. 7 0
      themes/hugo-theme-stack/archetypes/categories.md
  6. 11 0
      themes/hugo-theme-stack/archetypes/default.md
  7. 5 0
      themes/hugo-theme-stack/archetypes/tags.md
  8. 8 0
      themes/hugo-theme-stack/assets/icons/archives.svg
  9. 6 0
      themes/hugo-theme-stack/assets/icons/arrow-back.svg
  10. 6 0
      themes/hugo-theme-stack/assets/icons/back.svg
  11. 6 0
      themes/hugo-theme-stack/assets/icons/brand-github.svg
  12. 6 0
      themes/hugo-theme-stack/assets/icons/brand-twitter.svg
  13. 9 0
      themes/hugo-theme-stack/assets/icons/categories.svg
  14. 7 0
      themes/hugo-theme-stack/assets/icons/clock.svg
  15. 7 0
      themes/hugo-theme-stack/assets/icons/copyright.svg
  16. 9 0
      themes/hugo-theme-stack/assets/icons/date.svg
  17. 9 0
      themes/hugo-theme-stack/assets/icons/hash.svg
  18. 8 0
      themes/hugo-theme-stack/assets/icons/home.svg
  19. 6 0
      themes/hugo-theme-stack/assets/icons/infinity.svg
  20. 10 0
      themes/hugo-theme-stack/assets/icons/language.svg
  21. 7 0
      themes/hugo-theme-stack/assets/icons/link.svg
  22. 7 0
      themes/hugo-theme-stack/assets/icons/messages.svg
  23. 8 0
      themes/hugo-theme-stack/assets/icons/rss.svg
  24. 7 0
      themes/hugo-theme-stack/assets/icons/search.svg
  25. 7 0
      themes/hugo-theme-stack/assets/icons/tag.svg
  26. 7 0
      themes/hugo-theme-stack/assets/icons/toggle-left.svg
  27. 7 0
      themes/hugo-theme-stack/assets/icons/toggle-right.svg
  28. 7 0
      themes/hugo-theme-stack/assets/icons/user.svg
  29. BIN
      themes/hugo-theme-stack/assets/img/avatar.png
  30. 10 0
      themes/hugo-theme-stack/assets/jsconfig.json
  31. 17 0
      themes/hugo-theme-stack/assets/scss/breakpoints.scss
  32. 1 0
      themes/hugo-theme-stack/assets/scss/custom.scss
  33. 349 0
      themes/hugo-theme-stack/assets/scss/external/normalize.scss
  34. 98 0
      themes/hugo-theme-stack/assets/scss/grid.scss
  35. 267 0
      themes/hugo-theme-stack/assets/scss/partials/article.scss
  36. 38 0
      themes/hugo-theme-stack/assets/scss/partials/base.scss
  37. 394 0
      themes/hugo-theme-stack/assets/scss/partials/comments/disqusjs.scss
  38. 30 0
      themes/hugo-theme-stack/assets/scss/partials/footer.scss
  39. 384 0
      themes/hugo-theme-stack/assets/scss/partials/highlight/dark.scss
  40. 409 0
      themes/hugo-theme-stack/assets/scss/partials/highlight/light.scss
  41. 6 0
      themes/hugo-theme-stack/assets/scss/partials/layout/404.scss
  42. 389 0
      themes/hugo-theme-stack/assets/scss/partials/layout/article.scss
  43. 71 0
      themes/hugo-theme-stack/assets/scss/partials/layout/list.scss
  44. 82 0
      themes/hugo-theme-stack/assets/scss/partials/layout/search.scss
  45. 227 0
      themes/hugo-theme-stack/assets/scss/partials/menu.scss
  46. 21 0
      themes/hugo-theme-stack/assets/scss/partials/pagination.scss
  47. 198 0
      themes/hugo-theme-stack/assets/scss/partials/sidebar.scss
  48. 67 0
      themes/hugo-theme-stack/assets/scss/partials/widgets.scss
  49. 59 0
      themes/hugo-theme-stack/assets/scss/style.scss
  50. 165 0
      themes/hugo-theme-stack/assets/scss/variables.scss
  51. 63 0
      themes/hugo-theme-stack/assets/ts/color.ts
  52. 88 0
      themes/hugo-theme-stack/assets/ts/colorScheme.ts
  53. 34 0
      themes/hugo-theme-stack/assets/ts/createElement.ts
  54. 186 0
      themes/hugo-theme-stack/assets/ts/gallery.ts
  55. 112 0
      themes/hugo-theme-stack/assets/ts/main.ts
  56. 83 0
      themes/hugo-theme-stack/assets/ts/menu.ts
  57. 131 0
      themes/hugo-theme-stack/assets/ts/scrollspy.ts
  58. 325 0
      themes/hugo-theme-stack/assets/ts/search.tsx
  59. 34 0
      themes/hugo-theme-stack/assets/ts/smoothAnchors.ts
  60. 141 0
      themes/hugo-theme-stack/config.yaml
  61. 46 0
      themes/hugo-theme-stack/data/external.yaml
  62. 1 0
      themes/hugo-theme-stack/debug.sh
  63. 3 0
      themes/hugo-theme-stack/go.mod
  64. 70 0
      themes/hugo-theme-stack/i18n/ar.yaml
  65. 73 0
      themes/hugo-theme-stack/i18n/ca.yaml
  66. 70 0
      themes/hugo-theme-stack/i18n/de.yaml
  67. 70 0
      themes/hugo-theme-stack/i18n/el.yaml
  68. 73 0
      themes/hugo-theme-stack/i18n/en.yaml
  69. 73 0
      themes/hugo-theme-stack/i18n/es.yaml
  70. 69 0
      themes/hugo-theme-stack/i18n/fr.yaml
  71. 73 0
      themes/hugo-theme-stack/i18n/id.yaml
  72. 69 0
      themes/hugo-theme-stack/i18n/it.yaml
  73. 60 0
      themes/hugo-theme-stack/i18n/ja.yaml
  74. 68 0
      themes/hugo-theme-stack/i18n/ko.yaml
  75. 53 0
      themes/hugo-theme-stack/i18n/nl.yaml
  76. 73 0
      themes/hugo-theme-stack/i18n/pl.yaml
  77. 64 0
      themes/hugo-theme-stack/i18n/pt-br.yaml
  78. 63 0
      themes/hugo-theme-stack/i18n/ru.yaml
  79. 70 0
      themes/hugo-theme-stack/i18n/th.yaml
  80. 53 0
      themes/hugo-theme-stack/i18n/tr.yaml
  81. 71 0
      themes/hugo-theme-stack/i18n/uk.yaml
  82. 60 0
      themes/hugo-theme-stack/i18n/zh-cn.yaml
  83. 73 0
      themes/hugo-theme-stack/i18n/zh-hk.yaml
  84. 49 0
      themes/hugo-theme-stack/i18n/zh-tw.yaml
  85. BIN
      themes/hugo-theme-stack/images/screenshot.png
  86. BIN
      themes/hugo-theme-stack/images/tn.png
  87. 7 0
      themes/hugo-theme-stack/layouts/404.html
  88. 41 0
      themes/hugo-theme-stack/layouts/_default/_markup/render-image.html
  89. 3 0
      themes/hugo-theme-stack/layouts/_default/_markup/render-link.html
  90. 35 0
      themes/hugo-theme-stack/layouts/_default/archives.html
  91. 28 0
      themes/hugo-theme-stack/layouts/_default/baseof.html
  92. 85 0
      themes/hugo-theme-stack/layouts/_default/list.html
  93. 48 0
      themes/hugo-theme-stack/layouts/_default/rss.xml
  94. 46 0
      themes/hugo-theme-stack/layouts/_default/single.html
  95. 19 0
      themes/hugo-theme-stack/layouts/index.html
  96. 33 0
      themes/hugo-theme-stack/layouts/page/search.html
  97. 26 0
      themes/hugo-theme-stack/layouts/page/search.json
  98. 40 0
      themes/hugo-theme-stack/layouts/partials/article-list/compact.html
  99. 4 0
      themes/hugo-theme-stack/layouts/partials/article-list/default.html
  100. 39 0
      themes/hugo-theme-stack/layouts/partials/article-list/tile.html

+ 31 - 0
themes/hugo-theme-stack/.devcontainer/Dockerfile

@@ -0,0 +1,31 @@
+# Update the NODE_VERSION arg in docker-compose.yml to pick a Node version: 10, 12, 14
+ARG NODE_VERSION=14
+FROM mcr.microsoft.com/vscode/devcontainers/javascript-node:0-${NODE_VERSION}
+
+# VARIANT can be either 'hugo' for the standard version or 'hugo_extended' for the extended version.
+ARG VARIANT=hugo
+# VERSION can be either 'latest' or a specific version number
+ARG VERSION=latest
+
+# Download Hugo
+RUN apt-get update && apt-get install -y ca-certificates openssl git curl && \
+    rm -rf /var/lib/apt/lists/* && \
+    case ${VERSION} in \
+    latest) \
+    export VERSION=$(curl -s https://api.github.com/repos/gohugoio/hugo/releases/latest | grep "tag_name" | awk '{print substr($2, 3, length($2)-4)}') ;;\
+    esac && \
+    echo ${VERSION} && \
+    wget -O ${VERSION}.tar.gz https://github.com/gohugoio/hugo/releases/download/v${VERSION}/${VARIANT}_${VERSION}_Linux-64bit.tar.gz && \
+    tar xf ${VERSION}.tar.gz && \
+    mv hugo /usr/bin/hugo
+
+# Hugo dev server port
+EXPOSE 1313
+
+# [Optional] Uncomment this section to install additional OS packages you may want.
+#
+# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
+#     && apt-get -y install --no-install-recommends <your-package-list-here>
+
+# [Optional] Uncomment if you want to install more global node packages
+# RUN sudo -u node npm install -g <your-package-list-here>

+ 45 - 0
themes/hugo-theme-stack/.devcontainer/devcontainer.json

@@ -0,0 +1,45 @@
+// For format details, see https://aka.ms/devcontainer.json. For config options, see the README at:
+// https://github.com/microsoft/vscode-dev-containers/tree/v0.202.3/containers/hugo
+{
+	"name": "Hugo (Community)",
+	"build": {
+		"dockerfile": "Dockerfile",
+		"args": {
+			// Update VARIANT to pick hugo variant.
+			// Example variants: hugo, hugo_extended
+			// Rebuild the container if it already exists to update.
+			"VARIANT": "hugo_extended",
+			// Update VERSION to pick a specific hugo version.
+			// Example versions: latest, 0.73.0, 0,71.1
+			// Rebuild the container if it already exists to update.
+			"VERSION": "latest",
+			// Update NODE_VERSION to pick the Node.js version: 12, 14
+			"NODE_VERSION": "14",
+		}
+	},
+
+	// Set *default* container specific settings.json values on container create.
+	"settings": {
+		"html.format.templating": true,
+	},
+	
+	// Add the IDs of extensions you want installed when the container is created.
+	"extensions": [
+		"bungcip.better-toml",
+		"davidanson.vscode-markdownlint"
+	],
+
+	// Use 'forwardPorts' to make a list of ports inside the container available locally.
+	"forwardPorts": [
+		1313
+	],
+
+	// Use 'postCreateCommand' to run commands after the container is created.
+	// "postCreateCommand": "uname -a",
+
+	// Comment out connect as root instead. More info: https://aka.ms/vscode-remote/containers/non-root.
+	"remoteUser": "node",
+	"features": {
+		"golang": "latest"
+	}
+}

+ 674 - 0
themes/hugo-theme-stack/LICENSE

@@ -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>.

+ 88 - 0
themes/hugo-theme-stack/README.md

@@ -0,0 +1,88 @@
+![Stack](https://i.imgur.com/cCiHOGS.jpg)
+# Hugo Theme Stack
+
+> 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
+
+[Example Site](https://demo.stack.jimmycai.com/)
+
+[![Netlify Status](https://api.netlify.com/api/v1/badges/a2d2807a-a905-4bcb-97da-8da8d847da3d/deploy-status)](https://app.netlify.com/sites/hugo-theme-stack/deploys)
+
+## Documentation
+
+[Documentation](https://docs.stack.jimmycai.com/) | [中文文档](https://docs.stack.jimmycai.com/zh/)
+
+## Introduction
+
+Stack is a simple card-style Hugo theme designed for bloggers, some of its features are:
+
+- Responsive images support
+- Lazy load images
+- Dark mode
+- Local search
+- [PhotoSwipe](https://photoswipe.com/) integration
+- Archive page template
+- Full native JavaScript, no jQuery or any other frameworks are used
+- No CSS framework, keep it simple and minimal
+- Properly cropped thumbnails
+- Subsection support
+- Table of contents
+- Multilingual mode and RTL support
+
+## Requirements
+
+It's necessary to use **Hugo Extended ≥ 0.87.0**.
+
+## Installation
+
+* Route 1: Clone / Download this repository to `themes` folder
+* Route 2: Turn your site into a hugo module and add this theme as a module dependency
+
+ Edit your site config following `exampleSite/config.yaml`.
+
+*Note: Remove `config.toml` if there is one in the site folder.*
+
+Check [documentation](https://docs.stack.jimmycai.com/) for more details.
+
+## 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🙏.
+
+## Sponsoring
+
+If you like this theme, give it a star, and consider supporting its development:
+
+[![ko-fi](https://www.ko-fi.com/img/githubbutton_sm.svg)](https://ko-fi.com/C0C530AXX)
+
+Your support is greatly appreciated :)
+
+## Thanks to
+
+| Project | Description | Licence |
+| ------- | ----------- | ------- |
+| [PhotoSwipe](https://photoswipe.com/) | For the lightbox effect | [MIT](https://github.com/dimsemenov/PhotoSwipe/blob/master/LICENSE) |
+| [Normalize.css](https://github.com/necolas/normalize.css) | - | [MIT](https://github.com/necolas/normalize.css/blob/master/LICENSE.md) |
+| [Node Vibrant](https://github.com/Vibrant-Colors/node-vibrant) | To extract the color from images | [MIT](https://github.com/Vibrant-Colors/node-vibrant/blob/master/LICENSE.md)
+| [Tabler icons](https://github.com/tabler/tabler-icons) | Default menu icons | [MIT](https://github.com/tabler/tabler-icons/blob/master/LICENSE) |
+| [jonsuh/hamburgers](https://github.com/jonsuh/hamburgers) | Hamburger icon of menu | [MIT](https://github.com/jonsuh/hamburgers/blob/master/LICENSE) |
+| [lepture/yue.css](https://github.com/lepture/yue.css) | Part of it is used for styling article content | MIT |
+| [Typlog](https://typlog.com/) | Where the markdown gallery syntax is borrowed from | The author gave me the permission | 
+| [Pure CSS implementation of Google Photos / 500px image layout](https://github.com/xieranmaya/blog/issues/6) | Used for image gallery | - |
+
+### References
+
+Some references that I took while building this theme:
+
+| Project | Licence|
+| ------- | ------|
+| [artchen/hexo-theme-element](https://github.com/artchen/hexo-theme-element) | [MIT](https://github.com/artchen/hexo-theme-element/blob/master/LICENSE) |
+| [MunifTanjim/minimo](https://github.com/MunifTanjim/minimo) | [MIT](https://github.com/MunifTanjim/minimo/blob/master/LICENSE) |

+ 7 - 0
themes/hugo-theme-stack/archetypes/categories.md

@@ -0,0 +1,7 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+image: 
+style:
+    background: "#2a9d8f"
+    color: "#fff"
+---

+ 11 - 0
themes/hugo-theme-stack/archetypes/default.md

@@ -0,0 +1,11 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+description: 
+date: {{ .Date }}
+image: 
+math: 
+license: 
+hidden: false
+comments: true
+draft: true
+---

+ 5 - 0
themes/hugo-theme-stack/archetypes/tags.md

@@ -0,0 +1,5 @@
+---
+title: "{{ replace .Name "-" " " | title }}"
+description: 
+image: 
+---

+ 8 - 0
themes/hugo-theme-stack/assets/icons/archives.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-archive" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <rect x="3" y="4" width="18" height="4" rx="2" />
+  <path d="M5 8v10a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-10" />
+  <line x1="10" y1="12" x2="14" y2="12" />
+</svg>
+
+

+ 6 - 0
themes/hugo-theme-stack/assets/icons/arrow-back.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-back" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" />
+</svg>
+
+

+ 6 - 0
themes/hugo-theme-stack/assets/icons/back.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-chevron-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <polyline points="15 6 9 12 15 18" />
+</svg>
+
+

+ 6 - 0
themes/hugo-theme-stack/assets/icons/brand-github.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-github" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
+  <path d="M9 19c-4.3 1.4 -4.3 -2.5 -6 -3m12 5v-3.5c0 -1 .1 -1.4 -.5 -2c2.8 -.3 5.5 -1.4 5.5 -6a4.6 4.6 0 0 0 -1.3 -3.2a4.2 4.2 0 0 0 -.1 -3.2s-1.1 -.3 -3.5 1.3a12.3 12.3 0 0 0 -6.2 0c-2.4 -1.6 -3.5 -1.3 -3.5 -1.3a4.2 4.2 0 0 0 -.1 3.2a4.6 4.6 0 0 0 -1.3 3.2c0 4.6 2.7 5.7 5.5 6c-.6 .6 -.6 1.2 -.5 2v3.5" />
+</svg>
+
+

+ 6 - 0
themes/hugo-theme-stack/assets/icons/brand-twitter.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-brand-twitter" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
+  <path d="M22 4.01c-1 .49 -1.98 .689 -3 .99c-1.121 -1.265 -2.783 -1.335 -4.38 -.737s-2.643 2.06 -2.62 3.737v1c-3.245 .083 -6.135 -1.395 -8 -4c0 0 -4.182 7.433 4 11c-1.872 1.247 -3.739 2.088 -6 2c3.308 1.803 6.913 2.423 10.034 1.517c3.58 -1.04 6.522 -3.723 7.651 -7.742a13.84 13.84 0 0 0 .497 -3.753c-.002 -.249 1.51 -2.772 1.818 -4.013z" />
+</svg>
+
+

+ 9 - 0
themes/hugo-theme-stack/assets/icons/categories.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <line x1="5" y1="9" x2="19" y2="9" />
+  <line x1="5" y1="15" x2="19" y2="15" />
+  <line x1="11" y1="4" x2="7" y2="20" />
+  <line x1="17" y1="4" x2="13" y2="20" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/clock.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-clock" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="12" cy="12" r="9" />
+  <polyline points="12 7 12 12 15 15" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/copyright.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-copyright" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="12" cy="12" r="9" />
+  <path d="M14.5 9a3.5 4 0 1 0 0 6" />
+</svg>
+
+

+ 9 - 0
themes/hugo-theme-stack/assets/icons/date.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-calendar-time" width="56" height="56" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M11.795 21h-6.795a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v4" />
+  <circle cx="18" cy="18" r="4" />
+  <path d="M15 3v4" />
+  <path d="M7 3v4" />
+  <path d="M3 11h16" />
+  <path d="M18 16.496v1.504l1 1" />
+</svg>

+ 9 - 0
themes/hugo-theme-stack/assets/icons/hash.svg

@@ -0,0 +1,9 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-hash" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <line x1="5" y1="9" x2="19" y2="9" />
+  <line x1="5" y1="15" x2="19" y2="15" />
+  <line x1="11" y1="4" x2="7" y2="20" />
+  <line x1="17" y1="4" x2="13" y2="20" />
+</svg>
+
+

+ 8 - 0
themes/hugo-theme-stack/assets/icons/home.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-home" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <polyline points="5 12 3 12 12 3 21 12 19 12" />
+  <path d="M5 12v7a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-7" />
+  <path d="M9 21v-6a2 2 0 0 1 2 -2h2a2 2 0 0 1 2 2v6" />
+</svg>
+
+

+ 6 - 0
themes/hugo-theme-stack/assets/icons/infinity.svg

@@ -0,0 +1,6 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-infinity" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M9.828 9.172a4 4 0 1 0 0 5.656 a10 10 0 0 0 2.172 -2.828a10 10 0 0 1 2.172 -2.828 a4 4 0 1 1 0 5.656a10 10 0 0 1 -2.172 -2.828a10 10 0 0 0 -2.172 -2.828" />
+</svg>
+
+

+ 10 - 0
themes/hugo-theme-stack/assets/icons/language.svg

@@ -0,0 +1,10 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-language" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z" fill="none"/>
+  <path d="M4 5h7" />
+  <path d="M9 3v2c0 4.418 -2.239 8 -5 8" />
+  <path d="M5 9c-.003 2.144 2.952 3.908 6.7 4" />
+  <path d="M12 20l4 -9l4 9" />
+  <path d="M19.1 18h-6.2" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/link.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-link" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M10 14a3.5 3.5 0 0 0 5 0l4 -4a3.5 3.5 0 0 0 -5 -5l-.5 .5" />
+  <path d="M14 10a3.5 3.5 0 0 0 -5 0l-4 4a3.5 3.5 0 0 0 5 5l.5 -.5" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/messages.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-messages" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M21 14l-3 -3h-7a1 1 0 0 1 -1 -1v-6a1 1 0 0 1 1 -1h9a1 1 0 0 1 1 1v10" />
+  <path d="M14 15v2a1 1 0 0 1 -1 1h-7l-3 3v-10a1 1 0 0 1 1 -1h2" />
+</svg>
+
+

+ 8 - 0
themes/hugo-theme-stack/assets/icons/rss.svg

@@ -0,0 +1,8 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rss" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="5" cy="19" r="1" />
+  <path d="M4 4a16 16 0 0 1 16 16" />
+  <path d="M4 11a9 9 0 0 1 9 9" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/search.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="10" cy="10" r="7" />
+  <line x1="21" y1="21" x2="15" y2="15" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/tag.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-tag" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <path d="M11 3L20 12a1.5 1.5 0 0 1 0 2L14 20a1.5 1.5 0 0 1 -2 0L3 11v-4a4 4 0 0 1 4 -4h4" />
+  <circle cx="9" cy="9" r="2" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/toggle-left.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-left" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="8" cy="12" r="2" />
+  <rect x="2" y="6" width="20" height="12" rx="6" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/toggle-right.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-toggle-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="16" cy="12" r="2" />
+  <rect x="2" y="6" width="20" height="12" rx="6" />
+</svg>
+
+

+ 7 - 0
themes/hugo-theme-stack/assets/icons/user.svg

@@ -0,0 +1,7 @@
+<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-user" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
+  <path stroke="none" d="M0 0h24v24H0z"/>
+  <circle cx="12" cy="7" r="4" />
+  <path d="M6 21v-2a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v2" />
+</svg>
+
+

BIN
themes/hugo-theme-stack/assets/img/avatar.png


+ 10 - 0
themes/hugo-theme-stack/assets/jsconfig.json

@@ -0,0 +1,10 @@
+{
+ "compilerOptions": {
+  "baseUrl": ".",
+  "paths": {
+   "*": [
+    "*"
+   ]
+  }
+ }
+}

+ 17 - 0
themes/hugo-theme-stack/assets/scss/breakpoints.scss

@@ -0,0 +1,17 @@
+$breakpoints: (
+    sm: 640px,
+    md: 768px,
+    lg: 1024px,
+    xl: 1280px,
+    2xl: 1536px,
+);
+
+@mixin respond($breakpoint) {
+    @if not map-has-key($breakpoints, $breakpoint) {
+        @warn "'#{$breakpoint}' is not a valid breakpoint";
+    } @else {
+        @media (min-width: map-get($breakpoints, $breakpoint)) {
+            @content;
+        }
+    }
+}

+ 1 - 0
themes/hugo-theme-stack/assets/scss/custom.scss

@@ -0,0 +1 @@
+/* Place your custom SCSS in HUGO_SITE_FOLDER/assets/scss/custom.scss */

+ 349 - 0
themes/hugo-theme-stack/assets/scss/external/normalize.scss

@@ -0,0 +1,349 @@
+/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */
+
+/* Document
+   ========================================================================== */
+
+/**
+ * 1. Correct the line height in all browsers.
+ * 2. Prevent adjustments of font size after orientation changes in iOS.
+ */
+
+ html {
+    line-height: 1.15; /* 1 */
+    -webkit-text-size-adjust: 100%; /* 2 */
+  }
+  
+  /* Sections
+     ========================================================================== */
+  
+  /**
+   * Remove the margin in all browsers.
+   */
+  
+  body {
+    margin: 0;
+  }
+  
+  /**
+   * Render the `main` element consistently in IE.
+   */
+  
+  main {
+    display: block;
+  }
+  
+  /**
+   * Correct the font size and margin on `h1` elements within `section` and
+   * `article` contexts in Chrome, Firefox, and Safari.
+   */
+  
+  h1 {
+    font-size: 2em;
+    margin: 0.67em 0;
+  }
+  
+  /* Grouping content
+     ========================================================================== */
+  
+  /**
+   * 1. Add the correct box sizing in Firefox.
+   * 2. Show the overflow in Edge and IE.
+   */
+  
+  hr {
+    box-sizing: content-box; /* 1 */
+    height: 0; /* 1 */
+    overflow: visible; /* 2 */
+  }
+  
+  /**
+   * 1. Correct the inheritance and scaling of font size in all browsers.
+   * 2. Correct the odd `em` font sizing in all browsers.
+   */
+  
+  pre {
+    font-family: monospace, monospace; /* 1 */
+    font-size: 1em; /* 2 */
+  }
+  
+  /* Text-level semantics
+     ========================================================================== */
+  
+  /**
+   * Remove the gray background on active links in IE 10.
+   */
+  
+  a {
+    background-color: transparent;
+  }
+  
+  /**
+   * 1. Remove the bottom border in Chrome 57-
+   * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari.
+   */
+  
+  abbr[title] {
+    border-bottom: none; /* 1 */
+    text-decoration: underline; /* 2 */
+    text-decoration: underline dotted; /* 2 */
+  }
+  
+  /**
+   * Add the correct font weight in Chrome, Edge, and Safari.
+   */
+  
+  b,
+  strong {
+    font-weight: bolder;
+  }
+  
+  /**
+   * 1. Correct the inheritance and scaling of font size in all browsers.
+   * 2. Correct the odd `em` font sizing in all browsers.
+   */
+  
+  code,
+  kbd,
+  samp {
+    font-family: monospace, monospace; /* 1 */
+    font-size: 1em; /* 2 */
+  }
+  
+  /**
+   * Add the correct font size in all browsers.
+   */
+  
+  small {
+    font-size: 80%;
+  }
+  
+  /**
+   * Prevent `sub` and `sup` elements from affecting the line height in
+   * all browsers.
+   */
+  
+  sub,
+  sup {
+    font-size: 75%;
+    line-height: 0;
+    position: relative;
+    vertical-align: baseline;
+  }
+  
+  sub {
+    bottom: -0.25em;
+  }
+  
+  sup {
+    top: -0.5em;
+  }
+  
+  /* Embedded content
+     ========================================================================== */
+  
+  /**
+   * Remove the border on images inside links in IE 10.
+   */
+  
+  img {
+    border-style: none;
+  }
+  
+  /* Forms
+     ========================================================================== */
+  
+  /**
+   * 1. Change the font styles in all browsers.
+   * 2. Remove the margin in Firefox and Safari.
+   */
+  
+  button,
+  input,
+  optgroup,
+  select,
+  textarea {
+    font-family: inherit; /* 1 */
+    font-size: 100%; /* 1 */
+    line-height: 1.15; /* 1 */
+    margin: 0; /* 2 */
+  }
+  
+  /**
+   * Show the overflow in IE.
+   * 1. Show the overflow in Edge.
+   */
+  
+  button,
+  input { /* 1 */
+    overflow: visible;
+  }
+  
+  /**
+   * Remove the inheritance of text transform in Edge, Firefox, and IE.
+   * 1. Remove the inheritance of text transform in Firefox.
+   */
+  
+  button,
+  select { /* 1 */
+    text-transform: none;
+  }
+  
+  /**
+   * Correct the inability to style clickable types in iOS and Safari.
+   */
+  
+  button,
+  [type="button"],
+  [type="reset"],
+  [type="submit"] {
+    -webkit-appearance: button;
+  }
+  
+  /**
+   * Remove the inner border and padding in Firefox.
+   */
+  
+  button::-moz-focus-inner,
+  [type="button"]::-moz-focus-inner,
+  [type="reset"]::-moz-focus-inner,
+  [type="submit"]::-moz-focus-inner {
+    border-style: none;
+    padding: 0;
+  }
+  
+  /**
+   * Restore the focus styles unset by the previous rule.
+   */
+  
+  button:-moz-focusring,
+  [type="button"]:-moz-focusring,
+  [type="reset"]:-moz-focusring,
+  [type="submit"]:-moz-focusring {
+    outline: 1px dotted ButtonText;
+  }
+  
+  /**
+   * Correct the padding in Firefox.
+   */
+  
+  fieldset {
+    padding: 0.35em 0.75em 0.625em;
+  }
+  
+  /**
+   * 1. Correct the text wrapping in Edge and IE.
+   * 2. Correct the color inheritance from `fieldset` elements in IE.
+   * 3. Remove the padding so developers are not caught out when they zero out
+   *    `fieldset` elements in all browsers.
+   */
+  
+  legend {
+    box-sizing: border-box; /* 1 */
+    color: inherit; /* 2 */
+    display: table; /* 1 */
+    max-width: 100%; /* 1 */
+    padding: 0; /* 3 */
+    white-space: normal; /* 1 */
+  }
+  
+  /**
+   * Add the correct vertical alignment in Chrome, Firefox, and Opera.
+   */
+  
+  progress {
+    vertical-align: baseline;
+  }
+  
+  /**
+   * Remove the default vertical scrollbar in IE 10+.
+   */
+  
+  textarea {
+    overflow: auto;
+  }
+  
+  /**
+   * 1. Add the correct box sizing in IE 10.
+   * 2. Remove the padding in IE 10.
+   */
+  
+  [type="checkbox"],
+  [type="radio"] {
+    box-sizing: border-box; /* 1 */
+    padding: 0; /* 2 */
+  }
+  
+  /**
+   * Correct the cursor style of increment and decrement buttons in Chrome.
+   */
+  
+  [type="number"]::-webkit-inner-spin-button,
+  [type="number"]::-webkit-outer-spin-button {
+    height: auto;
+  }
+  
+  /**
+   * 1. Correct the odd appearance in Chrome and Safari.
+   * 2. Correct the outline style in Safari.
+   */
+  
+  [type="search"] {
+    -webkit-appearance: textfield; /* 1 */
+    outline-offset: -2px; /* 2 */
+  }
+  
+  /**
+   * Remove the inner padding in Chrome and Safari on macOS.
+   */
+  
+  [type="search"]::-webkit-search-decoration {
+    -webkit-appearance: none;
+  }
+  
+  /**
+   * 1. Correct the inability to style clickable types in iOS and Safari.
+   * 2. Change font properties to `inherit` in Safari.
+   */
+  
+  ::-webkit-file-upload-button {
+    -webkit-appearance: button; /* 1 */
+    font: inherit; /* 2 */
+  }
+  
+  /* Interactive
+     ========================================================================== */
+  
+  /*
+   * Add the correct display in Edge, IE 10+, and Firefox.
+   */
+  
+  details {
+    display: block;
+  }
+  
+  /*
+   * Add the correct display in all browsers.
+   */
+  
+  summary {
+    display: list-item;
+  }
+  
+  /* Misc
+     ========================================================================== */
+  
+  /**
+   * Add the correct display in IE 10+.
+   */
+  
+  template {
+    display: none;
+  }
+  
+  /**
+   * Add the correct display in IE 10.
+   */
+  
+  [hidden] {
+    display: none;
+  }

+ 98 - 0
themes/hugo-theme-stack/assets/scss/grid.scss

@@ -0,0 +1,98 @@
+.container {
+    margin-left: auto;
+    margin-right: auto;
+
+    .left-sidebar {
+        max-width: var(--left-sidebar-max-width);
+    }
+
+    .right-sidebar {
+        max-width: var(--right-sidebar-max-width);
+
+        /// Display right sidebar when min-width: lg
+        @include respond(lg) {
+            display: flex;
+        }
+    }
+
+    &.extended {
+        @include respond(md) {
+            max-width: 1024px;
+            --left-sidebar-max-width: 25%;
+            --right-sidebar-max-width: 30%;
+        }
+
+        @include respond(lg) {
+            max-width: 1280px;
+            --left-sidebar-max-width: 20%;
+            --right-sidebar-max-width: 30%;
+        }
+
+        @include respond(xl) {
+            max-width: 1536px;
+            --left-sidebar-max-width: 15%;
+            --right-sidebar-max-width: 25%;
+        }
+    }
+
+    &.compact {
+        @include respond(md) {
+            --left-sidebar-max-width: 25%;
+            max-width: 768px;
+        }
+
+        @include respond(lg) {
+            max-width: 1024px;
+            --left-sidebar-max-width: 20%;
+        }
+
+        @include respond(xl) {
+            max-width: 1280px;
+        }
+    }
+}
+
+.flex {
+    display: flex;
+    flex-direction: row;
+
+    &.column {
+        flex-direction: column;
+    }
+
+    &.on-phone--column {
+        flex-direction: column;
+        @include respond(md) {
+            flex-direction: unset;
+        }
+    }
+
+    .full-width {
+        width: 100%;
+    }
+}
+
+main.main {
+    min-width: 0;
+    max-width: 100%;
+    flex-grow: 1;
+    display: flex;
+    flex-direction: column;
+    gap: var(--section-separation);
+
+    @include respond(md) {
+        padding-top: var(--main-top-padding);
+    }
+}
+
+.main-container {
+    min-height: 100vh;
+    align-items: flex-start;
+    padding: 0 15px;
+    gap: var(--section-separation);
+    padding-top: var(--main-top-padding);
+    
+    @include respond(md) {
+        padding: 0 20px;
+    }
+}

+ 267 - 0
themes/hugo-theme-stack/assets/scss/partials/article.scss

@@ -0,0 +1,267 @@
+/* Default article style */
+.article-list {
+    display: flex;
+    flex-direction: column;
+    gap: var(--section-separation);
+
+    article {
+        display: flex;
+        flex-direction: column;
+        background-color: var(--card-background);
+        box-shadow: var(--shadow-l1);
+        border-radius: var(--card-border-radius);
+        overflow: hidden;
+
+        transition: box-shadow 0.3s ease;
+
+        &:hover {
+            box-shadow: var(--shadow-l2);
+        }
+
+        .article-image {
+            img {
+                width: 100%;
+                height: 150px;
+                object-fit: cover;
+
+                @include respond(md) {
+                    height: 200px;
+                }
+
+                @include respond(xl) {
+                    height: 250px;
+                }
+            }
+        }
+
+        @for $i from 1 through length($defaultTagBackgrounds) {
+            &:nth-child(#{length($defaultTagBackgrounds)}n + #{$i}) {
+                .article-category a {
+                    background: nth($defaultTagBackgrounds, $i);
+                    color: nth($defaultTagColors, $i);
+                }
+            }
+        }
+    }
+}
+
+.article-details {
+    display: flex;
+    flex-direction: column;
+    justify-content: center;
+    padding: var(--card-padding);
+    gap: 15px;
+}
+
+.article-title {
+    font-weight: 600;
+    margin: 0;
+    color: var(--card-text-color-main);
+    font-size: 2.2rem;
+
+    @include respond(xl) {
+        font-size: 2.4rem;
+    }
+
+    a {
+        color: var(--card-text-color-main);
+
+        &:hover {
+            color: var(--card-text-color-main);
+        }
+    }
+}
+
+.article-subtitle {
+    font-weight: normal;
+    color: var(--card-text-color-secondary);
+    line-height: 1.5;
+    margin: 0;
+    font-size: 1.75rem;
+    @include respond(xl) {
+        font-size: 2rem;
+    }
+}
+
+.article-title-wrapper {
+    display: flex;
+    flex-direction: column;
+    gap: 8px;
+}
+
+.article-time,
+.article-translations {
+    display: flex;
+    align-items: center;
+    color: var(--card-text-color-tertiary);
+    gap: 15px;
+    flex-wrap: wrap;
+
+    svg {
+        vertical-align: middle;
+        width: 20px;
+        height: 20px;
+        stroke-width: 1.33;
+    }
+
+    time,
+    a {
+        font-size: 1.4rem;
+        color: var(--card-text-color-tertiary);
+    }
+
+    & > div {
+        display: inline-flex;
+        align-items: center;
+        gap: 15px;
+    }
+}
+
+.article-category,
+.article-tags {
+    display: flex;
+    gap: 10px;
+
+    a {
+        color: var(--accent-color-text);
+        background-color: var(--accent-color);
+        padding: 8px 16px;
+        border-radius: var(--tag-border-radius);
+        display: inline-block;
+        font-size: 1.4rem;
+        transition: background-color 0.5s ease;
+
+        &:hover {
+            color: var(--accent-color-text);
+            background-color: var(--accent-color-darker);
+        }
+    }
+}
+
+/* Compact style article list */
+.article-list--compact {
+    border-radius: var(--card-border-radius);
+    box-shadow: var(--shadow-l1);
+    background-color: var(--card-background);
+    --image-size: 50px;
+
+    @include respond(md) {
+        --image-size: 60px;
+    }
+
+    article {
+        & > a {
+            display: flex;
+            align-items: center;
+            padding: var(--small-card-padding);
+            gap: 15px;
+        }
+
+        &:not(:last-of-type) {
+            border-bottom: 1.5px solid var(--card-separator-color);
+        }
+
+        .article-details {
+            flex-grow: 1;
+            padding: 0;
+            min-height: var(--image-size);
+            gap: 10px;
+        }
+
+        .article-title {
+            margin: 0;
+            font-size: 1.6rem;
+
+            @include respond(md) {
+                font-size: 1.8rem;
+            }
+        }
+
+        .article-image {
+            img {
+                width: var(--image-size);
+                height: var(--image-size);
+                object-fit: cover;
+            }
+        }
+
+        .article-time {
+            font-size: 1.4rem;
+        }
+
+        .article-preview {
+            font-size: 1.4rem;
+            color: var(--card-text-color-tertiary);
+            margin-top: 10px;
+            line-height: 1.5;
+        }
+    }
+}
+
+/* Tile style article list */
+.article-list--tile {
+    article {
+        border-radius: var(--card-border-radius);
+        overflow: hidden;
+        position: relative;
+        height: 350px;
+        width: 250px;
+        box-shadow: var(--shadow-l1);
+        transition: box-shadow 0.3s ease;
+        background-color: var(--card-background);
+
+        &:hover {
+            box-shadow: var(--shadow-l2);
+        }
+
+        &.has-image {
+            .article-details {
+                background-color: rgba(#000, 0.25);
+            }
+
+            .article-title {
+                color: #fff;
+            }
+        }
+
+        .article-image {
+            position: absolute;
+            top: 0;
+            left: 0;
+            width: 100%;
+            height: 100%;
+
+            img {
+                width: 100%;
+                height: 100%;
+                object-fit: cover;
+            }
+        }
+
+        .article-details {
+            border-radius: var(--card-border-radius);
+            position: relative;
+            height: 100%;
+            width: 100%;
+            display: flex;
+            flex-direction: column;
+            justify-content: flex-end;
+            z-index: 2;
+            padding: 15px;
+
+            @include respond(sm) {
+                padding: 20px;
+            }
+        }
+
+        .article-title {
+            font-size: 2rem;
+            font-weight: 500;
+            color: var(--card-text-color-main);
+
+            @include respond(sm) {
+                font-size: 2.2rem;
+            }
+        }
+    }
+}

+ 38 - 0
themes/hugo-theme-stack/assets/scss/partials/base.scss

@@ -0,0 +1,38 @@
+html {
+    font-size: 62.5%;
+    overflow-y: scroll;
+}
+
+* {
+    box-sizing: border-box;
+}
+
+body {
+    background: var(--body-background);
+    margin: 0;
+    font-family: var(--base-font-family);
+    font-size: 1.6rem;
+    -webkit-font-smoothing: antialiased;
+    -moz-osx-font-smoothing: grayscale;
+}
+
+/* scrollbar styles for Firefox */
+* {
+    scrollbar-width: auto;
+    scrollbar-color: var(--scrollbar-thumb) transparent;
+}
+/**/
+
+/* scrollbar styles for Chromium */
+::-webkit-scrollbar {
+    height: auto;
+}
+
+::-webkit-scrollbar-thumb {
+    background-color: var(--scrollbar-thumb);
+}
+
+::-webkit-scrollbar-track {
+    background-color: transparent;
+}
+/**/

+ 394 - 0
themes/hugo-theme-stack/assets/scss/partials/comments/disqusjs.scss

@@ -0,0 +1,394 @@
+.disqus-container {
+    background-color: var(--card-background);
+    border-radius: var(--card-border-radius);
+    box-shadow: var(--shadow-l1);
+    padding: var(--card-padding);
+}
+
+#dsqjs * {
+    margin: 0;
+    padding: 0
+}
+
+#dsqjs a {
+    text-decoration: none;
+    color: #076dd0
+}
+
+#dsqjs .dsqjs-hide {
+    display: none!important
+}
+
+#dsqjs .dsqjs-disabled {
+    cursor: not-allowed;
+    opacity: .5
+}
+
+#dsqjs #dsqjs-msg {
+    text-align: center;
+    margin-top: 4px;
+    margin-bottom: 4px;
+    font-size: 14px
+}
+
+#dsqjs #dsqjs-msg .dsqjs-msg-btn {
+    cursor: pointer
+}
+
+#dsqjs .dsqjs-bullet {
+    line-height: 1.4;
+    margin: 0 2px
+}
+
+#dsqjs .dsqjs-bullet:after {
+    color: #c2c6cc;
+    content: "·";
+    font-weight: 700
+}
+
+#dsqjs .dsqjs-clearfix:after,#dsqjs .dsqjs-clearfix:before {
+    display: table;
+    content: "";
+    line-height: 0;
+    clear: both
+}
+
+#dsqjs .dsqjs-nav {
+    position: relative;
+    margin: 0 0 20px;
+    border-bottom: 2px solid #e7e9ee
+}
+
+#dsqjs ol,#dsqjs ul {
+    list-style: none;
+    list-style-type: none
+}
+
+#dsqjs .dsqjs-no-comment {
+    text-align: center;
+    font-size: 16px;
+    line-height: 1.5;
+    word-wrap: break-word;
+    overflow: hidden;
+    color: #2a2e2e;
+    margin-bottom: 6px
+}
+
+#dsqjs .dsqjs-nav-tab {
+    float: left;
+    text-transform: capitalize;
+    font-size: 15px;
+    padding: 12px 8px;
+    color: #656c7a;
+    display: block;
+    margin: 0 15px 0 0;
+    font-weight: 700;
+    line-height: 1;
+    position: relative;
+    transition: all .2s ease-in-out
+}
+
+#dsqjs .dsqjs-nav-tab:last-child {
+    margin: 0
+}
+
+#dsqjs .dsqjs-tab-active {
+    color: #2a2e2e
+}
+
+#dsqjs .dsqjs-tab-active>span:after {
+    content: " ";
+    display: block;
+    height: 2px;
+    background-color: #076dd0!important;
+    position: absolute;
+    bottom: -5px;
+    left: 0;
+    right: 0
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-item {
+    position: relative;
+    margin-bottom: 16px
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-avatar {
+    float: left;
+    margin-right: 10px;
+    position: relative;
+    background: #dbdfe4;
+    padding: 0;
+    display: block;
+    border-radius: 4px
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-avatar img {
+    width: 44px;
+    height: 44px;
+    display: block;
+    border-radius: 4px
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-header {
+    line-height: 1;
+    font-size: 14px;
+    margin-bottom: 3px
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-post-author {
+    color: #656c7a;
+    font-weight: 700
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-admin-badge {
+    color: #fff;
+    background: #687a86;
+    padding: 1px 3px;
+    margin-left: 4px;
+    font-size: 12px;
+    line-height: 1;
+    font-weight: 700;
+    border-radius: 3px;
+    display: inline-block;
+    position: relative;
+    top: -1px;
+    left: 1px
+}
+
+#dsqjs .dsqjs-post-list .dsqjs-post-header .dsqjs-meta {
+    display: inline-block;
+    font-size: 12px;
+    color: #656c7a
+}
+
+#dsqjs .dsqjs-post-body {
+    font-size: 15px;
+    line-height: 1.5;
+    word-wrap: break-word;
+    overflow: hidden;
+    color: #2a2e2e
+}
+
+#dsqjs .dsqjs-post-body code {
+    padding: .2em .4em;
+    margin: 0;
+    font-size: 85%;
+    background: #f5f5f5;
+    color: inherit;
+    border-radius: 3px
+}
+
+#dsqjs .dsqjs-post-body pre {
+    padding: .5em;
+    overflow: auto;
+    font-size: 85%;
+    line-height: 1.45;
+    border-radius: 3px;
+    background: #f5f5f5;
+    margin: .5em 0
+}
+
+#dsqjs .dsqjs-post-body blockquote {
+    padding: 0 .8em;
+    margin: .5em 0;
+    color: #6a737d;
+    border-left: .25em solid #dfe2e5
+}
+
+#dsqjs .dsqjs-post-body p:last-child {
+    margin: 0
+}
+
+#dsqjs .dsqjs-post-list.dsqjs-children>li {
+    margin-left: 30px
+}
+
+#dsqjs .dsqjs-post-list.dsqjs-children .dsqjs-post-avatar img {
+    width: 38px;
+    height: 38px
+}
+
+#dsqjs .dsqjs-load-more {
+    font-size: 14px;
+    font-weight: 400;
+    display: block;
+    text-align: center;
+    padding: 11px 14px;
+    margin: 0 0 24px;
+    background: #687a86;
+    color: #fff;
+    cursor: pointer
+}
+
+#dsqjs .dsqjs-load-more:hover {
+    opacity: .8
+}
+
+#dsqjs footer {
+    text-align: right;
+    line-height: 1.5;
+    padding-top: 10px;
+    padding-right: 10px;
+    border-top: 2px solid #e7e9ee;
+    margin-top: 12px;
+    font-weight: 700;
+    font-size: 16px;
+    color: #555
+}
+
+#dsqjs .dsqjs-disqus-logo {
+    background-image: url(https://c.disquscdn.com/next/embed/assets/img/sprite.654110a9206fd22f08cca0798e34a65e.png);
+    background-repeat: no-repeat;
+    display: inline-block;
+    background-size: 86px 40.5px;
+    height: 16.5px;
+    width: 86px;
+}
+
+#dsqjs .dsqjs-order {
+    display: flex;
+    float: right;
+    align-items: center;
+    margin-top: 10px;
+    margin-bottom: 12px
+}
+
+#dsqjs .dsqjs-order-radio {
+    display: none
+}
+
+#dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
+    color: #fff;
+    background-color: #888
+}
+
+#dsqjs .dsqjs-order-label {
+    display: block;
+    height: 20px;
+    line-height: 20px;
+    margin-right: 10px;
+    font-size: 12px;
+    border-radius: 2px;
+    padding: 0 5px;
+    background-color: #dcdcdc;
+    cursor: pointer
+}
+
+#dsqjs p.dsqjs-has-more {
+    margin-bottom: 24px;
+    margin-left: 48px;
+    font-size: 13px;
+    line-height: 15px
+}
+
+#dsqjs p.dsqjs-has-more a.dsqjs-has-more-btn {
+    color: #656c7a;
+    text-decoration: underline;
+    cursor: pointer
+}
+
+@media (min-width: 768px) {
+    #dsqjs .dsqjs-post-list.dsqjs-children>li {
+        margin-left:48px
+    }
+
+    #dsqjs .dsqjs-post-list .dsqjs-post-avatar {
+        margin-right: 12px
+    }
+
+    #dsqjs .dsqjs-post-list .dsqjs-post-item {
+        margin-bottom: 20px
+    }
+}
+
+@media (min-width: 1024px) {
+    #dsqjs .dsqjs-post-list.dsqjs-children>li {
+        margin-left:60px
+    }
+}
+
+:root[data-scheme="light"]  {
+    #dsqjs .dsqjs-disqus-logo {
+        background-position: 0 -7px;
+    }
+}
+
+:root[data-scheme="dark"]  {
+    #dsqjs {
+        --t-s: rgba(255,255,255,0.9);
+        --alt: #3e4b5e;
+        --link-hover: #47a2e0;
+        --hover-bg: #3e4b5e;
+        --tag: #3e4b5e;
+        --border: #435266;
+        --pre: #3c495b;
+        --c-bg: #2f3947;
+        --code: #c3c7cb;
+        --kbd: #4e5f77;
+        --hl: #abb2bf;
+        --hlc: #808895;
+        --hlk: #c678dd;
+        --hln: #e06c75;
+        --hll: #56b6c2;
+        --hls: #98c379;
+        --hlt: #e6c07b;
+        --hlv: #d19a66;
+        --bg: #181c27;
+        --main: #252d38;
+        --t: rgba(255,255,255,0.86);
+        --t-l: rgba(255,255,255,0.66);
+        --logo: #fff;
+        --link: #38a3fd;
+        --title: rgba(255,255,255,0.92);
+        --fab: #364151;
+        --shadow: none;
+    }
+
+    #disqus_thread {
+        color: var(--body-text-color)
+    }
+
+    #dsqjs #dsqjs-msg {
+        color: var(--t)
+    }
+
+    #dsqjs a {
+        color:var(--link)
+    }
+
+    #dsqjs a:focus,#dsqjs a:hover {
+        color: var(--link-hover)
+    }
+
+    #dsqjs .dsqjs-disqus-logo {
+        background-position: 0 -24px;
+    }
+
+    #dsqjs .dsqjs-nav,#dsqjs footer {
+        border-color: var(--hlc)
+    }
+
+    #dsqjs .dsqjs-load-more,#dsqjs .dsqjs-load-more:hover,#dsqjs .dsqjs-nav-tab,#dsqjs .dsqjs-no-comment,#dsqjs .dsqjs-post-content {
+        color: var(--t)
+    }
+
+    #dsqjs .dsqjs-order-label {
+        background-color: var(--hlc)
+    }
+
+    #dsqjs .dsqjs-order-radio:checked+.dsqjs-order-label {
+        background-color: var(--kbd)
+    }
+
+    #dsqjs .dsqjs-tab-active>span:after {
+        background-color: #2e9fff
+    }
+
+    #dsqjs .dsqjs-footer,#dsqjs .dsqjs-meta {
+        color: var(--t-l)
+    }
+
+    #dsqjs .dsqjs-post-body blockquote {
+        border-color: var(--border)
+    }
+}

+ 30 - 0
themes/hugo-theme-stack/assets/scss/partials/footer.scss

@@ -0,0 +1,30 @@
+footer.site-footer {
+    padding: 20px 0 var(--section-separation) 0;
+    font-size: 1.4rem;
+    line-height: 1.75;
+
+    &:before {
+        content: "";
+        display: block;
+        height: 3px;
+        width: 50px;
+        background: var(--body-text-color);
+        margin-bottom: 20px;
+    }
+
+    .copyright {
+        color: var(--accent-color);
+        font-weight: bold;
+        margin-bottom: 5px;
+    }
+
+    .powerby {
+        color: var(--body-text-color);
+        font-weight: normal;
+        font-size: 1.2rem;
+
+        a {
+            color: var(--body-text-color);
+        }
+    }
+}

+ 384 - 0
themes/hugo-theme-stack/assets/scss/partials/highlight/dark.scss

@@ -0,0 +1,384 @@
+/*
+*   Style: monokai
+*   https://xyproto.github.io/splash/docs/monokai.html
+*/
+
+/* Background */
+.chroma {
+    color: #f8f8f2;
+    background-color: #272822
+}
+
+/* Other */
+.chroma .x {}
+
+/* Error */
+.chroma .err {
+    color: #bb0064;
+}
+
+/* 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: auto;
+    overflow: auto;
+    display: block;
+}
+
+/* LineHighlight */
+.chroma .hl {
+    display: block;
+    width: 100%;
+    background-color: #ffffcc
+}
+
+/* LineNumbersTable */
+.chroma .lnt {
+    margin-right: 0.4em;
+    padding: 0 0.4em 0 0.4em;
+    color: #7f7f7f
+}
+
+/* LineNumbers */
+.chroma .ln {
+    margin-right: 0.4em;
+    padding: 0 0.4em 0 0.4em;
+    color: #7f7f7f
+}
+
+/* Keyword */
+.chroma .k {
+    color: #66d9ef
+}
+
+/* KeywordConstant */
+.chroma .kc {
+    color: #66d9ef
+}
+
+/* KeywordDeclaration */
+.chroma .kd {
+    color: #66d9ef
+}
+
+/* KeywordNamespace */
+.chroma .kn {
+    color: #f92672
+}
+
+/* KeywordPseudo */
+.chroma .kp {
+    color: #66d9ef
+}
+
+/* KeywordReserved */
+.chroma .kr {
+    color: #66d9ef
+}
+
+/* KeywordType */
+.chroma .kt {
+    color: #66d9ef
+}
+
+/* Name */
+.chroma .n {}
+
+/* NameAttribute */
+.chroma .na {
+    color: #a6e22e
+}
+
+/* NameBuiltin */
+.chroma .nb {}
+
+/* NameBuiltinPseudo */
+.chroma .bp {}
+
+/* NameClass */
+.chroma .nc {
+    color: #a6e22e
+}
+
+/* NameConstant */
+.chroma .no {
+    color: #66d9ef
+}
+
+/* NameDecorator */
+.chroma .nd {
+    color: #a6e22e
+}
+
+/* NameEntity */
+.chroma .ni {}
+
+/* NameException */
+.chroma .ne {
+    color: #a6e22e
+}
+
+/* NameFunction */
+.chroma .nf {
+    color: #a6e22e
+}
+
+/* NameFunctionMagic */
+.chroma .fm {}
+
+/* NameLabel */
+.chroma .nl {}
+
+/* NameNamespace */
+.chroma .nn {}
+
+/* NameOther */
+.chroma .nx {
+    color: #a6e22e
+}
+
+/* NameProperty */
+.chroma .py {}
+
+/* NameTag */
+.chroma .nt {
+    color: #f92672
+}
+
+/* NameVariable */
+.chroma .nv {}
+
+/* NameVariableClass */
+.chroma .vc {}
+
+/* NameVariableGlobal */
+.chroma .vg {}
+
+/* NameVariableInstance */
+.chroma .vi {}
+
+/* NameVariableMagic */
+.chroma .vm {}
+
+/* Literal */
+.chroma .l {
+    color: #ae81ff
+}
+
+/* LiteralDate */
+.chroma .ld {
+    color: #e6db74
+}
+
+/* LiteralString */
+.chroma .s {
+    color: #e6db74
+}
+
+/* LiteralStringAffix */
+.chroma .sa {
+    color: #e6db74
+}
+
+/* LiteralStringBacktick */
+.chroma .sb {
+    color: #e6db74
+}
+
+/* LiteralStringChar */
+.chroma .sc {
+    color: #e6db74
+}
+
+/* LiteralStringDelimiter */
+.chroma .dl {
+    color: #e6db74
+}
+
+/* LiteralStringDoc */
+.chroma .sd {
+    color: #e6db74
+}
+
+/* LiteralStringDouble */
+.chroma .s2 {
+    color: #e6db74
+}
+
+/* LiteralStringEscape */
+.chroma .se {
+    color: #ae81ff
+}
+
+/* LiteralStringHeredoc */
+.chroma .sh {
+    color: #e6db74
+}
+
+/* LiteralStringInterpol */
+.chroma .si {
+    color: #e6db74
+}
+
+/* LiteralStringOther */
+.chroma .sx {
+    color: #e6db74
+}
+
+/* LiteralStringRegex */
+.chroma .sr {
+    color: #e6db74
+}
+
+/* LiteralStringSingle */
+.chroma .s1 {
+    color: #e6db74
+}
+
+/* LiteralStringSymbol */
+.chroma .ss {
+    color: #e6db74
+}
+
+/* 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 {}
+
+/* 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: #a6e22e
+}
+
+/* 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 {}

+ 409 - 0
themes/hugo-theme-stack/assets/scss/partials/highlight/light.scss

@@ -0,0 +1,409 @@
+/*
+*   Style: monokailight
+*   https://xyproto.github.io/splash/docs/monokailight.html
+*/
+
+/* Background */
+.chroma {
+    color: #272822;
+    background-color: #fafafa;
+}
+
+/* Other */
+.chroma .x {
+}
+
+/* Error */
+.chroma .err {
+    color: #960050;
+}
+
+/* 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: auto;
+    overflow: auto;
+    display: block;
+}
+
+/* LineHighlight */
+.chroma .hl {
+    display: block;
+    width: 100%;
+    background-color: #ffffcc;
+}
+
+/* LineNumbersTable */
+.chroma .lnt {
+    margin-right: 0.4em;
+    padding: 0 0.4em 0 0.4em;
+    color: #7f7f7f;
+}
+
+/* LineNumbers */
+.chroma .ln {
+    margin-right: 0.4em;
+    padding: 0 0.4em 0 0.4em;
+    color: #7f7f7f;
+}
+
+/* Keyword */
+.chroma .k {
+    color: #00a8c8;
+}
+
+/* KeywordConstant */
+.chroma .kc {
+    color: #00a8c8;
+}
+
+/* KeywordDeclaration */
+.chroma .kd {
+    color: #00a8c8;
+}
+
+/* KeywordNamespace */
+.chroma .kn {
+    color: #f92672;
+}
+
+/* KeywordPseudo */
+.chroma .kp {
+    color: #00a8c8;
+}
+
+/* KeywordReserved */
+.chroma .kr {
+    color: #00a8c8;
+}
+
+/* KeywordType */
+.chroma .kt {
+    color: #00a8c8;
+}
+
+/* Name */
+.chroma .n {
+    color: #111111;
+}
+
+/* NameAttribute */
+.chroma .na {
+    color: #75af00;
+}
+
+/* NameBuiltin */
+.chroma .nb {
+    color: #111111;
+}
+
+/* NameBuiltinPseudo */
+.chroma .bp {
+    color: #111111;
+}
+
+/* NameClass */
+.chroma .nc {
+    color: #75af00;
+}
+
+/* NameConstant */
+.chroma .no {
+    color: #00a8c8;
+}
+
+/* NameDecorator */
+.chroma .nd {
+    color: #75af00;
+}
+
+/* NameEntity */
+.chroma .ni {
+    color: #111111;
+}
+
+/* NameException */
+.chroma .ne {
+    color: #75af00;
+}
+
+/* NameFunction */
+.chroma .nf {
+    color: #75af00;
+}
+
+/* NameFunctionMagic */
+.chroma .fm {
+    color: #111111;
+}
+
+/* NameLabel */
+.chroma .nl {
+    color: #111111;
+}
+
+/* NameNamespace */
+.chroma .nn {
+    color: #111111;
+}
+
+/* NameOther */
+.chroma .nx {
+    color: #75af00;
+}
+
+/* NameProperty */
+.chroma .py {
+    color: #111111;
+}
+
+/* NameTag */
+.chroma .nt {
+    color: #f92672;
+}
+
+/* NameVariable */
+.chroma .nv {
+    color: #111111;
+}
+
+/* NameVariableClass */
+.chroma .vc {
+    color: #111111;
+}
+
+/* NameVariableGlobal */
+.chroma .vg {
+    color: #111111;
+}
+
+/* NameVariableInstance */
+.chroma .vi {
+    color: #111111;
+}
+
+/* NameVariableMagic */
+.chroma .vm {
+    color: #111111;
+}
+
+/* Literal */
+.chroma .l {
+    color: #ae81ff;
+}
+
+/* LiteralDate */
+.chroma .ld {
+    color: #d88200;
+}
+
+/* LiteralString */
+.chroma .s {
+    color: #d88200;
+}
+
+/* LiteralStringAffix */
+.chroma .sa {
+    color: #d88200;
+}
+
+/* LiteralStringBacktick */
+.chroma .sb {
+    color: #d88200;
+}
+
+/* LiteralStringChar */
+.chroma .sc {
+    color: #d88200;
+}
+
+/* LiteralStringDelimiter */
+.chroma .dl {
+    color: #d88200;
+}
+
+/* LiteralStringDoc */
+.chroma .sd {
+    color: #d88200;
+}
+
+/* LiteralStringDouble */
+.chroma .s2 {
+    color: #d88200;
+}
+
+/* LiteralStringEscape */
+.chroma .se {
+    color: #8045ff;
+}
+
+/* LiteralStringHeredoc */
+.chroma .sh {
+    color: #d88200;
+}
+
+/* LiteralStringInterpol */
+.chroma .si {
+    color: #d88200;
+}
+
+/* LiteralStringOther */
+.chroma .sx {
+    color: #d88200;
+}
+
+/* LiteralStringRegex */
+.chroma .sr {
+    color: #d88200;
+}
+
+/* LiteralStringSingle */
+.chroma .s1 {
+    color: #d88200;
+}
+
+/* LiteralStringSymbol */
+.chroma .ss {
+    color: #d88200;
+}
+
+/* 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: #111111;
+}
+
+/* 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: #7ca727;
+}
+/* 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 {
+}

+ 6 - 0
themes/hugo-theme-stack/assets/scss/partials/layout/404.scss

@@ -0,0 +1,6 @@
+.not-found-card {
+    background-color: var(--card-background);
+    box-shadow: var(--shadow-l1);
+    border-radius: var(--card-border-radius);
+    padding: var(--card-padding);
+}

+ 389 - 0
themes/hugo-theme-stack/assets/scss/partials/layout/article.scss

@@ -0,0 +1,389 @@
+.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);
+
+            img {
+                max-width: 100%;
+                height: auto;
+            }
+        }
+
+        .article-footer {
+            margin: var(--card-padding);
+            margin-top: 0;
+
+            section:not(:first-child) {
+                margin-top: var(--card-padding);
+            }
+
+            section {
+                color: var(--card-text-color-tertiary);
+                text-transform: uppercase;
+                display: flex;
+                align-items: center;
+                font-size: 1.4rem;
+                gap: 15px;
+
+                svg {
+                    width: 20px;
+                    height: 20px;
+                    stroke-width: 1.33;
+                }
+            }
+
+            .article-tags {
+                flex-wrap: wrap;
+                text-transform: unset;
+            }
+
+            .article-copyright,
+            .article-lastmod {
+                a {
+                    color: var(--body-text-color);
+                }
+            }
+        }
+    }
+}
+
+.widget--toc {
+    background-color: var(--card-background);
+    border-radius: var(--card-border-radius);
+    box-shadow: var(--shadow-l1);
+    display: flex;
+    flex-direction: column;
+    color: var(--card-text-color-main);
+    overflow: hidden;
+
+    ::-webkit-scrollbar-thumb {
+        background-color: var(--card-separator-color);
+    }
+
+    #TableOfContents {
+        overflow-x: auto;
+        max-height: 75vh;
+
+        ol,
+        ul {
+            margin: 0;
+            padding: 0;
+        }
+
+        ol {
+            list-style-type: none;
+            counter-reset: item;
+
+            li a::before {
+                counter-increment: item;
+                content: counters(item, ".") ". ";
+                font-weight: bold;
+                margin-right: 5px;
+            }
+        }
+
+        & > ul {
+            padding: 0 1em;
+        }
+
+        li {
+            margin: 15px 0 15px 20px;
+            padding: 5px;
+
+            & > ol,
+            & > ul {
+                margin-top: 10px;
+                padding-left: 10px;
+                margin-bottom: -5px;
+
+                & > li:last-child {
+                    margin-bottom: 0;
+                }
+            }
+        }
+        li.active-class > a {
+            border-left: var(--heading-border-size) solid var(--accent-color);
+            font-weight: bold;
+        }
+
+        ul li.active-class > a {
+            display: block;
+        }
+
+        @function repeat($str, $n) {
+            $result: "";
+            @for $_ from 0 to $n {
+                $result: $result + $str;
+            }
+            @return $result;
+        }
+
+        // Support up to 6 levels of indentation for lists in ToCs
+        @for $i from 0 to 5 {
+            & > ul #{repeat("> li > ul", $i)} > li.active-class > a {
+                $n: 25 + $i * 35;
+                margin-left: calc(-#{$n}px - 1em);
+                padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
+            }
+
+            & > ol #{repeat("> li > ol", $i)} > li.active-class > a {
+                $n: 9 + $i * 35;
+                margin-left: calc(-#{$n}px - 1em);
+                padding-left: calc(#{$n}px + 1em - var(--heading-border-size));
+                display: block;
+            }
+        }
+    }
+}
+
+.related-contents {
+    overflow-x: auto;
+    padding-bottom: 15px;
+
+    & > .flex {
+        float: left;
+    }
+
+    article {
+        margin-right: 15px;
+        flex-shrink: 0;
+        overflow: hidden;
+        width: 250px;
+        height: 150px;
+
+        .article-title {
+            font-size: 1.8rem;
+            margin: 0;
+        }
+
+        &.has-image {
+            .article-details {
+                padding: 20px;
+                background: linear-gradient(0deg, rgba(0, 0, 0, 0.25) 0%, rgba(0, 0, 0, 0.75) 100%);
+            }
+        }
+    }
+}
+
+.article-content {
+    font-family: var(--article-font-family);
+    font-size: var(--article-font-size);
+    padding: 0 var(--card-padding);
+    line-height: var(--article-line-height);
+
+    & > p {
+        margin: 1.5em 0;
+    }
+
+    h1,
+    h2,
+    h3,
+    h4,
+    h5,
+    h6 {
+        margin-inline-start: calc((var(--card-padding)) * -1);
+        padding-inline-start: calc(var(--card-padding) - var(--heading-border-size));
+        border-inline-start: var(--heading-border-size) solid var(--accent-color);
+    }
+
+    figure {
+        text-align: center;
+
+        figcaption {
+            font-size: 1.4rem;
+            color: var(--card-text-color-secondary);
+        }
+    }
+
+    blockquote {
+        position: relative;
+        margin: 1.5em 0;
+        border-inline-start: var(--blockquote-border-size) solid var(--card-separator-color);
+        padding: 15px calc(var(--card-padding) - var(--blockquote-border-size));
+        background-color: var(--blockquote-background-color);
+    }
+
+    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);
+    }
+
+    .gallery {
+        position: relative;
+        display: flex;
+        flex-direction: row;
+        justify-content: center;
+        margin: 1.5em 0;
+        gap: 10px;
+
+        figure {
+            margin: 0;
+        }
+    }
+
+    pre {
+        overflow-x: auto;
+        display: block;
+        background-color: var(--pre-background-color);
+        color: var(--pre-text-color);
+        font-family: var(--code-font-family);
+        line-height: 1.428571429;
+        word-break: break-all;
+        padding: var(--card-padding);
+        //  keep Codeblocks LTR
+        [dir="rtl"] & {
+            direction: ltr;
+        }
+        code {
+            color: unset;
+            border: none;
+            background: none;
+            padding: 0;
+        }
+    }
+
+    .highlight {
+        background-color: var(--pre-background-color);
+        padding: var(--card-padding);
+        position: relative;
+
+        &:hover {
+            .copyCodeButton {
+                opacity: 1;
+            }
+        }
+        // keep Codeblocks LTR
+        [dir="rtl"] & {
+            direction: ltr;
+        }
+        pre {
+            margin: initial;
+            padding: 0;
+            margin: 0;
+            width: auto;
+        }
+    }
+
+    .copyCodeButton {
+        position: absolute;
+        top: calc(var(--card-padding));
+        right: calc(var(--card-padding));
+        background: var(--card-background);
+        border: none;
+        box-shadow: var(--shadow-l2);
+        border-radius: var(--tag-border-radius);
+        padding: 8px 16px;
+        color: var(--card-text-color-main);
+        cursor: pointer;
+        font-size: 14px;
+        opacity: 0;
+        transition: opacity 0.3s ease;
+    }
+
+    .table-wrapper {
+        padding: 0 var(--card-padding);
+        overflow-x: auto;
+        display: block;
+    }
+
+    table {
+        width: 100%;
+        border-collapse: collapse;
+        border-spacing: 0;
+        margin-bottom: 1.5em;
+        font-size: 0.96em;
+    }
+
+    th,
+    td {
+        text-align: left;
+        padding: 4px 8px 4px 10px;
+        border: 1px solid var(--table-border-color);
+    }
+
+    td {
+        vertical-align: top;
+    }
+
+    tr:nth-child(even) {
+        background-color: var(--tr-even-background-color);
+    }
+
+    .twitter-tweet {
+        color: var(--card-text-color-main);
+    }
+
+    .video-wrapper {
+        position: relative;
+        width: 100%;
+        height: 0;
+        padding-bottom: 56.25%;
+        overflow: hidden;
+
+        & > iframe,
+        & > video {
+            position: absolute;
+            width: 100%;
+            height: 100%;
+            left: 0;
+            top: 0;
+            border: 0;
+        }
+    }
+
+    /// 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);
+    }
+}

+ 71 - 0
themes/hugo-theme-stack/assets/scss/partials/layout/list.scss

@@ -0,0 +1,71 @@
+.section-card {
+    border-radius: var(--card-border-radius);
+    background-color: var(--card-background);
+    padding: var(--small-card-padding);
+    box-shadow: var(--shadow-l1);
+    display: flex;
+    align-items: center;
+    gap: 20px;
+
+    --separation: 15px;
+
+    .section-term {
+        font-size: 2.2rem;
+        margin: 0;
+        color: var(--card-text-color-main);
+    }
+
+    .section-description {
+        font-weight: normal;
+        color: var(--card-text-color-secondary);
+        font-size: 1.6rem;
+        margin: 0;
+    }
+
+    .section-details {
+        flex-grow: 1;
+        display: flex;
+        flex-direction: column;
+        gap: 8px;
+    }
+
+    .section-image {
+        img {
+            width: 60px;
+            height: 60px;
+        }
+    }
+
+    .section-count {
+        color: var(--card-text-color-tertiary);
+        font-size: 1.4rem;
+        margin: 0;
+        font-weight: bold;
+        text-transform: uppercase;
+    }
+}
+
+.subsection-list {
+    overflow-x: auto;
+
+    .article-list--tile {
+        display: flex;
+        padding-bottom: 15px;
+
+        article {
+            width: 250px;
+            height: 150px;
+            margin-right: 20px;
+            flex-shrink: 0;
+
+            .article-title {
+                margin: 0;
+                font-size: 1.8rem;
+            }
+
+            .article-details {
+                padding: 20px;
+            }
+        }
+    }
+}

+ 82 - 0
themes/hugo-theme-stack/assets/scss/partials/layout/search.scss

@@ -0,0 +1,82 @@
+.search-form {
+    position: relative;
+    --button-size: 80px;
+
+    &.widget {
+        --button-size: 60px;
+
+        label {
+            font-size: 1.3rem;
+            top: 10px;
+        }
+
+        input {
+            font-size: 1.5rem;
+            padding: 30px 20px 15px 20px;
+        }
+    }
+
+    p {
+        position: relative;
+        margin: 0;
+    }
+
+    label {
+        position: absolute;
+        top: 15px;
+        inset-inline-start: 20px;
+        font-size: 1.4rem;
+        color: var(--card-text-color-tertiary);
+    }
+
+    input {
+        padding: 40px 20px 20px;
+        border-radius: var(--card-border-radius);
+        background-color: var(--card-background);
+        box-shadow: var(--shadow-l1);
+        color: var(--card-text-color-main);
+        width: 100%;
+        border: 0;
+        -webkit-appearance: none;
+
+        transition: box-shadow 0.3s ease;
+
+        font-size: 1.8rem;
+
+        &:focus {
+            outline: 0;
+            box-shadow: var(--shadow-l2);
+        }
+    }
+
+    button {
+        position: absolute;
+        inset-inline-end: 0;
+        top: 0;
+        height: 100%;
+        width: var(--button-size);
+        cursor: pointer;
+        background-color: transparent;
+        border: 0;
+
+        padding: 0 10px;
+
+        &:focus {
+            outline: 0;
+
+            svg {
+                stroke-width: 2;
+                color: var(--accent-color);
+            }
+        }
+
+        svg {
+            color: var(--card-text-color-secondary);
+            stroke-width: 1.33;
+            transition: all 0.3s ease;
+            width: 20px;
+            height: 20px;
+        }
+    }
+
+}

+ 227 - 0
themes/hugo-theme-stack/assets/scss/partials/menu.scss

@@ -0,0 +1,227 @@
+/*!
+ * Hamburgers
+ * @description Tasty CSS-animated hamburgers
+ * @author Jonathan Suh @jonsuh
+ * @site https://jonsuh.com/hamburgers
+ * @link https://github.com/jonsuh/hamburgers
+ */
+
+.hamburger {
+    padding-top: 10px;
+    display: inline-block;
+    cursor: pointer;
+    transition-property: opacity, filter;
+    transition-duration: 0.15s;
+    transition-timing-function: linear;
+    font: inherit;
+    color: inherit;
+    text-transform: none;
+    background-color: transparent;
+    border: 0;
+    margin: 0;
+    overflow: visible;
+}
+.hamburger:hover {
+    opacity: 0.7;
+}
+.hamburger.is-active:hover {
+    opacity: 0.7;
+}
+.hamburger.is-active .hamburger-inner,
+.hamburger.is-active .hamburger-inner::before,
+.hamburger.is-active .hamburger-inner::after {
+    background-color: #000;
+}
+
+.hamburger-box {
+    width: 30px;
+    height: 24px;
+    display: inline-block;
+    position: relative;
+}
+
+.hamburger-inner {
+    display: block;
+    top: 50%;
+    margin-top: -2px;
+}
+
+.hamburger-inner,
+.hamburger-inner::before,
+.hamburger-inner::after {
+    width: 30px;
+    height: 2px;
+    background-color: var(--card-text-color-main);
+    border-radius: 4px;
+    position: absolute;
+    transition-property: transform;
+    transition-duration: 0.15s;
+    transition-timing-function: ease;
+}
+.hamburger-inner::before,
+.hamburger-inner::after {
+    content: "";
+    display: block;
+}
+.hamburger-inner::before {
+    top: -10px;
+}
+.hamburger-inner::after {
+    bottom: -10px;
+}
+
+.hamburger--spin .hamburger-inner {
+    transition-duration: 0.22s;
+    transition-timing-function: cubic-bezier(0.55, 0.055, 0.675, 0.19);
+}
+.hamburger--spin .hamburger-inner::before {
+    transition: top 0.1s 0.25s ease-in, opacity 0.1s ease-in;
+}
+.hamburger--spin .hamburger-inner::after {
+    transition: bottom 0.1s 0.25s ease-in, transform 0.22s cubic-bezier(0.55, 0.055, 0.675, 0.19);
+}
+
+.hamburger--spin.is-active .hamburger-inner {
+    transform: rotate(225deg);
+    transition-delay: 0.12s;
+    transition-timing-function: cubic-bezier(0.215, 0.61, 0.355, 1);
+}
+.hamburger--spin.is-active .hamburger-inner::before {
+    top: 0;
+    opacity: 0;
+    transition: top 0.1s ease-out, opacity 0.1s 0.12s ease-out;
+}
+.hamburger--spin.is-active .hamburger-inner::after {
+    bottom: 0;
+    transform: rotate(-90deg);
+    transition: bottom 0.1s ease-out, transform 0.22s 0.12s cubic-bezier(0.215, 0.61, 0.355, 1);
+}
+
+#toggle-menu {
+    background: none;
+    border: none;
+    position: absolute;
+    right: 0;
+    top: 0;
+    z-index: 2;
+    cursor: pointer;
+
+    [dir="rtl"] & {
+        left: 0;
+        right: auto;
+    }
+
+    @include respond(md) {
+        display: none;
+    }
+
+    outline: none;
+
+    &.is-active {
+        .hamburger-inner,
+        .hamburger-inner::before,
+        .hamburger-inner::after {
+            background-color: var(--accent-color);
+        }
+    }
+}
+
+/* Menu style */
+.menu {
+    padding-left: 0;
+    list-style: none;
+    flex-direction: column;
+    overflow-y: auto;
+    flex-grow: 1;
+    font-size: 1.4rem;
+    background-color: var(--card-background);
+
+    box-shadow: var(--shadow-l1);
+    display: none;
+    margin: 0 calc(var(--container-padding) * -1);
+
+    padding: 30px 30px;
+    @include respond(xl) {
+        padding: 15px 0;
+    }
+
+    &,
+    .menu-bottom-section {
+        gap: 30px;
+        @include respond(xl) {
+            gap: 25px;
+        }
+    }
+
+    &.show {
+        display: flex;
+    }
+
+    @include respond(md) {
+        align-items: flex-end;
+        display: flex;
+        background-color: transparent;
+        padding: 0;
+        box-shadow: none;
+        margin: 0;
+    }
+
+    li {
+        position: relative;
+        vertical-align: middle;
+        padding: 0;
+
+        @include respond(md) {
+            width: 100%;
+        }
+
+        svg {
+            stroke-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;
+        display: flex;
+        flex-direction: column;
+        width: 100%;
+    }
+}
+
+.social-menu {
+    list-style: none;
+    padding: 0;
+    margin: 0;
+    display: flex;
+    flex-direction: row;
+    gap: 10px;
+
+    svg {
+        width: 24px;
+        height: 24px;
+        stroke: var(--body-text-color);
+        stroke-width: 1.33;
+    }
+}

+ 21 - 0
themes/hugo-theme-stack/assets/scss/partials/pagination.scss

@@ -0,0 +1,21 @@
+.pagination {
+    display: flex;
+    background-color: var(--card-background);
+    box-shadow: var(--shadow-l1);
+    border-radius: var(--card-border-radius);
+    overflow: hidden;
+    flex-wrap: wrap;
+
+    .page-link {
+        padding: 16px 32px;
+        display: inline-flex;
+
+        &.current {
+            font-weight: bold;
+            background-color: var(--card-background-selected);
+            color: var(--card-text-color-main);
+        }
+
+        color: var(--card-text-color-secondary);
+    }
+}

+ 198 - 0
themes/hugo-theme-stack/assets/scss/partials/sidebar.scss

@@ -0,0 +1,198 @@
+.sidebar {
+    &.sticky {
+        @include respond(md) {
+            position: sticky;
+        }
+    }
+}
+
+.left-sidebar {
+    display: flex;
+    flex-direction: column;
+    flex-shrink: 0;
+    align-self: stretch;
+    gap: var(--sidebar-element-separation);
+    max-width: none;
+    width: 100%;
+    position: relative;
+
+    --sidebar-avatar-size: 100px;
+    --sidebar-element-separation: 20px;
+    --emoji-size: 40px;
+    --emoji-font-size: 20px;
+
+    @include respond(md) {
+        width: auto;
+        padding-top: var(--main-top-padding);
+        padding-bottom: var(--main-top-padding);
+        max-height: 100vh;
+    }
+
+    @include respond(2xl) {
+        --sidebar-avatar-size: 120px;
+        --sidebar-element-separation: 25px;
+        --emoji-size: 40px;
+    }
+
+    &.sticky {
+        top: 0;
+    }
+
+    &.compact {
+        --sidebar-avatar-size: 80px;
+        --emoji-size: 30px;
+        --emoji-font-size: 15px;
+
+        header {
+            @include respond(lg) {
+                flex-direction: row;
+            }
+
+            .site-meta {
+                gap: 5px;
+            }
+
+            .site-name {
+                font-size: 1.4rem;
+
+                @include respond(2xl) {
+                    font-size: 1.75rem;
+                }
+            }
+
+            .site-description {
+                font-size: 1.4rem;
+            }
+        }
+    }
+}
+
+.right-sidebar {
+    width: 100%;
+    display: none;
+    flex-direction: column;
+    gap: var(--widget-separation);
+
+    &.sticky {
+        top: 0;
+    }
+
+    @include respond(lg) {
+        padding-top: var(--main-top-padding);
+    }
+}
+
+.sidebar header {
+    z-index: 1;
+    transition: box-shadow 0.5s ease;
+    display: flex;
+    flex-direction: column;
+    gap: var(--sidebar-element-separation);
+
+    @include respond(md) {
+        padding: 0;
+    }
+
+    .site-avatar {
+        position: relative;
+        margin: 0;
+        width: var(--sidebar-avatar-size);
+        height: var(--sidebar-avatar-size);
+        flex-shrink: 0;
+
+        .site-logo {
+            width: 100%;
+            height: 100%;
+            border-radius: 100%;
+            box-shadow: var(--shadow-l1);
+        }
+
+        .emoji {
+            position: absolute;
+            width: var(--emoji-size);
+            height: var(--emoji-size);
+            line-height: var(--emoji-size);
+            border-radius: 100%;
+            bottom: 0;
+            right: 0;
+            text-align: center;
+            font-size: var(--emoji-font-size);
+            background-color: var(--card-background);
+            box-shadow: var(--shadow-l2);
+        }
+    }
+
+    .site-meta {
+        display: flex;
+        flex-direction: column;
+        gap: 10px;
+        justify-content: center;
+    }
+
+    .site-name {
+        color: var(--accent-color);
+        margin: 0;
+        font-size: 1.6rem;
+
+        @include respond(2xl) {
+            font-size: 1.8rem;
+        }
+    }
+
+    .site-description {
+        color: var(--body-text-color);
+        font-weight: normal;
+        margin: 0;
+        font-size: 1.4rem;
+
+        @include respond(2xl) {
+            font-size: 1.6rem;
+        }
+    }
+}
+
+[data-scheme="dark"] {
+    #dark-mode-toggle {
+        color: var(--accent-color);
+        font-weight: 700;
+
+        .icon-tabler-toggle-left {
+            display: none;
+        }
+
+        .icon-tabler-toggle-right {
+            display: unset;
+        }
+    }
+}
+
+#dark-mode-toggle {
+    margin-top: auto;
+    color: var(--body-text-color);
+    display: flex;
+    align-items: center;
+    cursor: pointer;
+    gap: var(--menu-icon-separation);
+
+    .icon-tabler-toggle-right {
+        display: none;
+    }
+}
+
+#i18n-switch {
+    color: var(--body-text-color);
+    display: inline-flex;
+    align-content: center;
+    gap: var(--menu-icon-separation);
+
+    select {
+        border: 0;
+        background-color: transparent;
+        color: var(--body-text-color);
+
+        option {
+            color: var(--card-text-color-main);
+            background-color: var(--card-background);
+        }
+    }
+}

+ 67 - 0
themes/hugo-theme-stack/assets/scss/partials/widgets.scss

@@ -0,0 +1,67 @@
+.widget {
+    display: flex;
+    flex-direction: column;
+
+    .widget-icon {
+        svg {
+            width: 32px;
+            height: 32px;
+            stroke-width: 1.6;
+            color: var(--body-text-color);
+        }
+    }
+}
+
+/* Tag cloud widget */
+.tagCloud {
+    .tagCloud-tags {
+        display: flex;
+        flex-wrap: wrap;
+        gap: 10px;
+
+        a {
+            background: var(--card-background);
+            box-shadow: var(--shadow-l1);
+            border-radius: var(--tag-border-radius);
+            padding: 8px 20px;
+            color: var(--card-text-color-main);
+            font-size: 1.4rem;
+            transition: box-shadow 0.3s ease;
+
+            &:hover {
+                box-shadow: var(--shadow-l2);
+            }
+        }
+    }
+}
+
+/* Archives widget */
+.widget.archives {
+    .widget-archive--list {
+        border-radius: var(--card-border-radius);
+        box-shadow: var(--shadow-l1);
+        background-color: var(--card-background);
+    }
+
+    .archives-year {
+        &:not(:last-of-type) {
+            border-bottom: 1.5px solid var(--card-separator-color);
+        }
+
+        a {
+            font-size: 1.4rem;
+            padding: 18px 25px;
+            display: flex;
+
+            span.year {
+                flex: 1;
+                color: var(--card-text-color-main);
+                font-weight: bold;
+            }
+
+            span.count {
+                color: var(--card-text-color-tertiary);
+            }
+        }
+    }
+}

+ 59 - 0
themes/hugo-theme-stack/assets/scss/style.scss

@@ -0,0 +1,59 @@
+/*!
+*   Hugo Theme Stack
+*
+*   @author: Jimmy Cai
+*   @website: https://jimmycai.com
+*   @link: https://github.com/CaiJimmy/hugo-theme-stack
+*/
+
+@import "breakpoints.scss";
+@import "variables.scss";
+@import "grid.scss";
+
+@import "external/normalize.scss";
+
+@import "partials/menu.scss";
+@import "partials/article.scss";
+@import "partials/widgets.scss";
+@import "partials/footer.scss";
+@import "partials/pagination.scss";
+@import "partials/sidebar.scss";
+@import "partials/base.scss";
+@import "partials/layout/article.scss";
+@import "partials/layout/list.scss";
+@import "partials/layout/404.scss";
+@import "partials/layout/search.scss";
+
+@import "custom.scss";
+
+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 -10px 0px rgba(var(--link-background-color), var(--link-background-opacity-hover)) inset;
+        }
+    }
+}
+
+.section-title {
+    text-transform: uppercase;
+    margin-top: 0;
+    margin-bottom: 10px;
+    display: block;
+    font-size: 1.6rem;
+    font-weight: bold;
+    color: var(--body-text-color);
+
+    a {
+        color: var(--body-text-color);
+    }
+}

+ 165 - 0
themes/hugo-theme-stack/assets/scss/variables.scss

@@ -0,0 +1,165 @@
+$defaultTagBackgrounds: #8ea885, #df7988, #0177b8, #ffb900, #6b69d6;
+$defaultTagColors: #fff, #fff, #fff, #fff, #fff;
+
+/*
+*   Global style
+*/
+:root {
+    --main-top-padding: 35px;
+
+    @include respond(xl) {
+        --main-top-padding: 50px;
+    }
+
+    --body-background: #f5f5fa;
+
+    --accent-color: #34495e;
+    --accent-color-darker: #2c3e50;
+    --accent-color-text: #fff;
+    --body-text-color: #bababa;
+
+    --tag-border-radius: 4px;
+
+    --section-separation: 40px;
+
+    --scrollbar-thumb: hsl(0, 0%, 85%);
+    --scrollbar-track: var(--body-background);
+
+    &[data-scheme="dark"] {
+        --body-background: #303030;
+        --accent-color: #ecf0f1;
+        --accent-color-darker: #bdc3c7;
+        --accent-color-text: #000;
+        --body-text-color: rgba(255, 255, 255, 0.7);
+        --scrollbar-thumb: hsl(0, 0%, 40%);
+        --scrollbar-track: var(--body-background);
+    }
+}
+
+/**
+*   Global font family
+*/
+:root {
+    --sys-font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Droid Sans", "Helvetica Neue";
+    --zh-font-family: "PingFang SC", "Hiragino Sans GB", "Droid Sans Fallback", "Microsoft YaHei";
+
+    --base-font-family: "Lato", var(--sys-font-family), var(--zh-font-family), sans-serif;
+    --code-font-family: Menlo, Monaco, Consolas, "Courier New", 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: #bababa;
+    --card-separator-color: rgba(218, 218, 218, 0.5);
+
+    --card-border-radius: 10px;
+
+    --card-padding: 20px;
+
+    @include respond(md) {
+        --card-padding: 25px;
+    }
+
+    @include respond(xl) {
+        --card-padding: 30px;
+    }
+
+    --small-card-padding: 25px 20px;
+
+    @include respond(md) {
+        --small-card-padding: 25px;
+    }
+
+    &[data-scheme="dark"] {
+        --card-background: #424242;
+        --card-background-selected: rgba(255, 255, 255, 0.16);
+        --card-text-color-main: rgba(255, 255, 255, 0.9);
+        --card-text-color-secondary: rgba(255, 255, 255, 0.7);
+        --card-text-color-tertiary: rgba(255, 255, 255, 0.5);
+        --card-separator-color: rgba(255, 255, 255, 0.12);
+    }
+}
+
+/**
+*   Article content font settings
+*/
+:root {
+    --article-font-family: var(--base-font-family);
+    --article-font-size: 1.6rem;
+
+    @include respond(md) {
+        --article-font-size: 1.7rem;
+    }
+
+    --article-line-height: 1.85;
+}
+
+/*
+*   Article content style
+*/
+:root {
+    --blockquote-border-size: 4px;
+    --blockquote-background-color: rgb(248 248 248);
+
+    --heading-border-size: 4px;
+
+    --link-background-color: 189, 195, 199;
+    --link-background-opacity: 0.5;
+    --link-background-opacity-hover: 0.7;
+
+    --pre-background-color: #272822;
+    --pre-text-color: #f8f8f2;
+
+    --code-background-color: rgba(0, 0, 0, 0.12);
+    --code-text-color: #808080;
+
+    --table-border-color: #dadada;
+    --tr-even-background-color: #efefee;
+
+    &[data-scheme="dark"] {
+        --code-background-color: #272822;
+        --code-text-color: rgba(255, 255, 255, 0.9);
+
+        --table-border-color: #717171;
+        --tr-even-background-color: #545454;
+
+        --blockquote-background-color: rgb(75 75 75);
+    }
+}
+
+/*
+*   Shadow style
+*   Thanks to https://www.figma.com/community/plugin/744987207861965946/Shadow-picker
+*/
+:root {
+    --shadow-l1: 0px 4px 8px rgba(0, 0, 0, 0.04), 0px 0px 2px rgba(0, 0, 0, 0.06), 0px 0px 1px rgba(0, 0, 0, 0.04);
+    --shadow-l2: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
+    --shadow-l3: 0px 10px 20px rgba(0, 0, 0, 0.04), 0px 2px 6px rgba(0, 0, 0, 0.04), 0px 0px 1px rgba(0, 0, 0, 0.04);
+    --shadow-l4: 0px 24px 32px rgba(0, 0, 0, 0.04), 0px 16px 24px rgba(0, 0, 0, 0.04), 0px 4px 8px rgba(0, 0, 0, 0.04),
+        0px 0px 1px rgba(0, 0, 0, 0.04);
+}
+
+[data-scheme="light"] {
+    --pre-text-color: #272822;
+    --pre-background-color: #fafafa;
+    @import "partials/highlight/light.scss";
+}
+
+[data-scheme="dark"] {
+    --pre-text-color: #f8f8f2;
+    --pre-background-color: #272822;
+    @import "partials/highlight/dark.scss";
+}
+
+:root {
+    --menu-icon-separation: 40px;
+    --container-padding: 15px;
+    --widget-separation: var(--section-separation);
+}

+ 63 - 0
themes/hugo-theme-stack/assets/ts/color.ts

@@ -0,0 +1,63 @@
+interface colorScheme {
+    hash: string,                        /// Regenerate color scheme when the image hash is changed
+    DarkMuted: {
+        hex: string,
+        rgb: Number[],
+        bodyTextColor: string
+    },
+    Vibrant: {
+        hex: string,
+        rgb: Number[],
+        bodyTextColor: string
+    }
+}
+
+let colorsCache: { [key: string]: colorScheme } = {};
+
+if (localStorage.hasOwnProperty('StackColorsCache')) {
+    try {
+        colorsCache = JSON.parse(localStorage.getItem('StackColorsCache'));
+    }
+    catch (e) {
+        colorsCache = {};
+    }
+}
+
+async function getColor(key: string, hash: string, imageURL: string) {
+    if (!key) {
+        /**
+         * If no key is provided, do not cache the result
+         */
+        return await Vibrant.from(imageURL).getPalette();
+    }
+
+    if (!colorsCache.hasOwnProperty(key) || colorsCache[key].hash !== hash) {
+        /**
+         * If key is provided, but not found in cache, or the hash mismatches => Regenerate color scheme
+         */
+        const palette = await Vibrant.from(imageURL).getPalette();
+
+        colorsCache[key] = {
+            hash: hash,
+            Vibrant: {
+                hex: palette.Vibrant.hex,
+                rgb: palette.Vibrant.rgb,
+                bodyTextColor: palette.Vibrant.bodyTextColor
+            },
+            DarkMuted: {
+                hex: palette.DarkMuted.hex,
+                rgb: palette.DarkMuted.rgb,
+                bodyTextColor: palette.DarkMuted.bodyTextColor
+            }
+        }
+
+        /* Save the result in localStorage */
+        localStorage.setItem('StackColorsCache', JSON.stringify(colorsCache));
+    }
+
+    return colorsCache[key];
+}
+
+export {
+    getColor
+}

+ 88 - 0
themes/hugo-theme-stack/assets/ts/colorScheme.ts

@@ -0,0 +1,88 @@
+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();
+
+        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;

+ 34 - 0
themes/hugo-theme-stack/assets/ts/createElement.ts

@@ -0,0 +1,34 @@
+/**
+ * createElement
+ * Edited from:
+ * @link https://stackoverflow.com/a/42405694
+ */
+function createElement(tag, attrs, children) {
+    var element = document.createElement(tag);
+
+    for (let name in attrs) {
+        if (name && attrs.hasOwnProperty(name)) {
+            let value = attrs[name];
+
+            if (name == "dangerouslySetInnerHTML") {
+                element.innerHTML = value.__html;
+            }
+            else if (value === true) {
+                element.setAttribute(name, name);
+            } else if (value !== false && value != null) {
+                element.setAttribute(name, value.toString());
+            }
+        }
+    }
+    for (let i = 2; i < arguments.length; i++) {
+        let child = arguments[i];
+        if (child) {
+            element.appendChild(
+                child.nodeType == null ?
+                    document.createTextNode(child.toString()) : child);
+        }
+    }
+    return element;
+}
+
+export default createElement;

+ 186 - 0
themes/hugo-theme-stack/assets/ts/gallery.ts

@@ -0,0 +1,186 @@
+declare global {
+    interface Window {
+        PhotoSwipe: any;
+        PhotoSwipeUI_Default: any
+    }
+}
+
+interface PhotoSwipeItem {
+    w: number;
+    h: number;
+    src: string;
+    msrc: string;
+    title?: string;
+    el: HTMLElement;
+}
+
+class StackGallery {
+    private galleryUID: number;
+    private items: PhotoSwipeItem[] = [];
+
+    constructor(container: HTMLElement, galleryUID = 1) {
+        if (window.PhotoSwipe == undefined || window.PhotoSwipeUI_Default == undefined) {
+            console.error("PhotoSwipe lib not loaded.");
+            return;
+        }
+
+        this.galleryUID = galleryUID;
+
+        StackGallery.createGallery(container);
+        this.loadItems(container);
+        this.bindClick();
+    }
+
+    private loadItems(container: HTMLElement) {
+        this.items = [];
+
+        const figures = container.querySelectorAll('figure.gallery-image');
+
+        for (const el of figures) {
+            const figcaption = el.querySelector('figcaption'),
+                img = el.querySelector('img');
+
+            let aux: PhotoSwipeItem = {
+                w: parseInt(img.getAttribute('width')),
+                h: parseInt(img.getAttribute('height')),
+                src: img.src,
+                msrc: img.getAttribute('data-thumb') || img.src,
+                el: el
+            }
+
+            if (figcaption) {
+                aux.title = figcaption.innerHTML;
+            }
+
+            this.items.push(aux);
+        }
+    }
+
+    public static createGallery(container: HTMLElement) {
+        /// The process of wrapping image with figure tag is done using JavaScript instead of only Hugo markdown render hook
+        /// because it can not detect whether image is being wrapped by a link or not
+        /// and it lead to a invalid HTML construction (<a><figure><img></figure></a>)
+
+        const images = container.querySelectorAll('img.gallery-image');
+        for (const img of Array.from(images)) {
+            /// Images are wrapped with figure tag if the paragraph has only images without texts
+            /// This is done to allow inline images within paragraphs
+            const paragraph = img.closest('p');
+
+            if (!paragraph || !container.contains(paragraph)) continue;
+
+            if (paragraph.textContent.trim() == '') {
+                /// Once we insert figcaption, this check no longer works
+                /// So we add a class to paragraph to mark it
+                paragraph.classList.add('no-text');
+            }
+
+            let isNewLineImage = paragraph.classList.contains('no-text');
+            if (!isNewLineImage) continue;
+
+            const hasLink = img.parentElement.tagName == 'A';
+
+            let el: HTMLElement = img;
+            /// Wrap image with figure tag, with flex-grow and flex-basis values extracted from img's data attributes
+            const figure = document.createElement('figure');
+            figure.style.setProperty('flex-grow', img.getAttribute('data-flex-grow') || '1');
+            figure.style.setProperty('flex-basis', img.getAttribute('data-flex-basis') || '0');
+            if (hasLink) {
+                /// Wrap <a> if it exists
+                el = img.parentElement;
+            }
+            el.parentElement.insertBefore(figure, el);
+            figure.appendChild(el);
+
+            /// Add figcaption if it exists
+            if (img.hasAttribute('alt')) {
+                const figcaption = document.createElement('figcaption');
+                figcaption.innerText = img.getAttribute('alt');
+                figure.appendChild(figcaption);
+            }
+
+            /// Wrap img tag with <a> tag if image was not wrapped by <a> tag
+            if (!hasLink) {
+                figure.className = 'gallery-image';
+
+                const a = document.createElement('a');
+                a.href = img.src;
+                a.setAttribute('target', '_blank');
+                img.parentNode.insertBefore(a, img);
+                a.appendChild(img);
+            }
+        }
+
+        const figuresEl = container.querySelectorAll('figure.gallery-image');
+
+        let currentGallery = [];
+
+        for (const figure of figuresEl) {
+            if (!currentGallery.length) {
+                /// First iteration
+                currentGallery = [figure];
+            }
+            else if (figure.previousElementSibling === currentGallery[currentGallery.length - 1]) {
+                /// Adjacent figures
+                currentGallery.push(figure);
+            }
+            else if (currentGallery.length) {
+                /// End gallery
+                StackGallery.wrap(currentGallery);
+                currentGallery = [figure];
+            }
+        }
+
+        if (currentGallery.length > 0) {
+            StackGallery.wrap(currentGallery);
+        }
+    }
+
+    /**
+     * Wrap adjacent figure tags with div.gallery
+     * @param figures 
+     */
+    public static wrap(figures: HTMLElement[]) {
+        const galleryContainer = document.createElement('div');
+        galleryContainer.className = 'gallery';
+
+        const parentNode = figures[0].parentNode,
+            first = figures[0];
+
+        parentNode.insertBefore(galleryContainer, first)
+
+        for (const figure of figures) {
+            galleryContainer.appendChild(figure);
+        }
+    }
+
+    public open(index: number) {
+        const pswp = document.querySelector('.pswp') as HTMLDivElement;
+        const ps = new window.PhotoSwipe(pswp, window.PhotoSwipeUI_Default, this.items, {
+            index: index,
+            galleryUID: this.galleryUID,
+            getThumbBoundsFn: (index) => {
+                const thumbnail = this.items[index].el.getElementsByTagName('img')[0],
+                    pageYScroll = window.pageYOffset || document.documentElement.scrollTop,
+                    rect = thumbnail.getBoundingClientRect();
+
+                return { x: rect.left, y: rect.top + pageYScroll, w: rect.width };
+            }
+        });
+
+        ps.init();
+    }
+
+    private bindClick() {
+        for (const [index, item] of this.items.entries()) {
+            const a = item.el.querySelector('a');
+
+            a.addEventListener('click', (e) => {
+                e.preventDefault();
+                this.open(index);
+            })
+        }
+    }
+}
+
+export default StackGallery;

+ 112 - 0
themes/hugo-theme-stack/assets/ts/main.ts

@@ -0,0 +1,112 @@
+/*!
+*   Hugo Theme Stack
+*
+*   @author: Jimmy Cai
+*   @website: https://jimmycai.com
+*   @link: https://github.com/CaiJimmy/hugo-theme-stack
+*/
+import StackGallery from "ts/gallery";
+import { getColor } from 'ts/color';
+import menu from 'ts/menu';
+import createElement from 'ts/createElement';
+import StackColorScheme from 'ts/colorScheme';
+import { setupScrollspy } from 'ts/scrollspy';
+import { setupSmoothAnchors } from "ts/smoothAnchors";
+
+let Stack = {
+    init: () => {
+        /**
+         * Bind menu event
+         */
+        menu();
+
+        const articleContent = document.querySelector('.article-content') as HTMLElement;
+        if (articleContent) {
+            new StackGallery(articleContent);
+            setupSmoothAnchors();
+            setupScrollspy();
+        }
+
+        /**
+         * Add linear gradient background to tile style article
+         */
+        const articleTile = document.querySelector('.article-list--tile');
+        if (articleTile) {
+            let observer = new IntersectionObserver(async (entries, observer) => {
+                entries.forEach(entry => {
+                    if (!entry.isIntersecting) return;
+                    observer.unobserve(entry.target);
+
+                    const articles = entry.target.querySelectorAll('article.has-image');
+                    articles.forEach(async articles => {
+                        const image = articles.querySelector('img'),
+                            imageURL = image.src,
+                            key = image.getAttribute('data-key'),
+                            hash = image.getAttribute('data-hash'),
+                            articleDetails: HTMLDivElement = articles.querySelector('.article-details');
+
+                        const colors = await getColor(key, hash, imageURL);
+
+                        articleDetails.style.background = `
+                        linear-gradient(0deg, 
+                            rgba(${colors.DarkMuted.rgb[0]}, ${colors.DarkMuted.rgb[1]}, ${colors.DarkMuted.rgb[2]}, 0.5) 0%, 
+                            rgba(${colors.Vibrant.rgb[0]}, ${colors.Vibrant.rgb[1]}, ${colors.Vibrant.rgb[2]}, 0.75) 100%)`;
+                    })
+                })
+            });
+
+            observer.observe(articleTile)
+        }
+
+
+        /**
+         * Add copy button to code block
+        */
+        const highlights = document.querySelectorAll('.article-content div.highlight');
+        const copyText = `Copy`,
+            copiedText = `Copied!`;
+
+        highlights.forEach(highlight => {
+            const copyButton = document.createElement('button');
+            copyButton.innerHTML = copyText;
+            copyButton.classList.add('copyCodeButton');
+            highlight.appendChild(copyButton);
+
+            const codeBlock = highlight.querySelector('code[data-lang]');
+            if (!codeBlock) return;
+
+            copyButton.addEventListener('click', () => {
+                navigator.clipboard.writeText(codeBlock.textContent)
+                    .then(() => {
+                        copyButton.textContent = copiedText;
+
+                        setTimeout(() => {
+                            copyButton.textContent = copyText;
+                        }, 1000);
+                    })
+                    .catch(err => {
+                        alert(err)
+                        console.log('Something went wrong', err);
+                    });
+            });
+        });
+
+        new StackColorScheme(document.getElementById('dark-mode-toggle'));
+    }
+}
+
+window.addEventListener('load', () => {
+    setTimeout(function () {
+        Stack.init();
+    }, 0);
+})
+
+declare global {
+    interface Window {
+        createElement: any;
+        Stack: any
+    }
+}
+
+window.Stack = Stack;
+window.createElement = createElement;

+ 83 - 0
themes/hugo-theme-stack/assets/ts/menu.ts

@@ -0,0 +1,83 @@
+/**
+ * Slide up/down
+ * Code from https://dev.to/bmsvieira/vanilla-js-slidedown-up-4dkn
+ * @param target 
+ * @param duration 
+ */
+let slideUp = (target: HTMLElement, duration = 500) => {
+    target.classList.add('transiting');
+    target.style.transitionProperty = 'height, margin, padding';
+    target.style.transitionDuration = duration + 'ms';
+    ///target.style.boxSizing = 'border-box';
+    target.style.height = target.offsetHeight + 'px';
+    target.offsetHeight;
+    target.style.overflow = 'hidden';
+    target.style.height = "0";
+    target.style.paddingTop = "0";
+    target.style.paddingBottom = "0";
+    target.style.marginTop = "0";
+    target.style.marginBottom = "0";
+    window.setTimeout(() => {
+        target.classList.remove('show')
+        target.style.removeProperty('height');
+        target.style.removeProperty('padding-top');
+        target.style.removeProperty('padding-bottom');
+        target.style.removeProperty('margin-top');
+        target.style.removeProperty('margin-bottom');
+        target.style.removeProperty('overflow');
+        target.style.removeProperty('transition-duration');
+        target.style.removeProperty('transition-property');
+        target.classList.remove('transiting');
+    }, duration);
+}
+
+let slideDown = (target: HTMLElement, duration = 500) => {
+    target.classList.add('transiting');
+    target.style.removeProperty('display');
+
+    target.classList.add('show');
+
+    let height = target.offsetHeight;
+    target.style.overflow = 'hidden';
+    target.style.height = "0";
+    target.style.paddingTop = "0";
+    target.style.paddingBottom = "0";
+    target.style.marginTop = "0";
+    target.style.marginBottom = "0";
+    target.offsetHeight;
+    ///target.style.boxSizing = 'border-box';
+    target.style.transitionProperty = "height, margin, padding";
+    target.style.transitionDuration = duration + 'ms';
+    target.style.height = height + 'px';
+    target.style.removeProperty('padding-top');
+    target.style.removeProperty('padding-bottom');
+    target.style.removeProperty('margin-top');
+    target.style.removeProperty('margin-bottom');
+    window.setTimeout(() => {
+        target.style.removeProperty('height');
+        target.style.removeProperty('overflow');
+        target.style.removeProperty('transition-duration');
+        target.style.removeProperty('transition-property');
+        target.classList.remove('transiting');
+    }, duration);
+}
+
+let slideToggle = (target, duration = 500) => {
+    if (window.getComputedStyle(target).display === 'none') {
+        return slideDown(target, duration);
+    } else {
+        return slideUp(target, duration);
+    }
+}
+
+export default function () {
+    const toggleMenu = document.getElementById('toggle-menu');
+    if (toggleMenu) {
+        toggleMenu.addEventListener('click', () => {
+            if (document.getElementById('main-menu').classList.contains('transiting')) return;
+            document.body.classList.toggle('show-menu');
+            slideToggle(document.getElementById('main-menu'), 300);
+            toggleMenu.classList.toggle('is-active');
+        });
+    }
+}

+ 131 - 0
themes/hugo-theme-stack/assets/ts/scrollspy.ts

@@ -0,0 +1,131 @@
+// Implements a scroll spy system for the ToC, displaying the current section with an indicator and scrolling to it when needed.
+
+// Inspired from https://gomakethings.com/debouncing-your-javascript-events/
+function debounced(func: Function) {
+    let timeout;
+    return () => {
+        if (timeout) {
+            window.cancelAnimationFrame(timeout);
+        }
+
+        timeout = window.requestAnimationFrame(() => func());
+    }
+}
+
+const headersQuery = ".article-content h1[id], .article-content h2[id], .article-content h3[id], .article-content h4[id], .article-content h5[id], .article-content h6[id]";
+const tocQuery = "#TableOfContents";
+const navigationQuery = "#TableOfContents li";
+const activeClass = "active-class";
+
+function scrollToTocElement(tocElement: HTMLElement, scrollableNavigation: HTMLElement) {
+    let textHeight = tocElement.querySelector("a").offsetHeight;
+    let scrollTop = tocElement.offsetTop - scrollableNavigation.offsetHeight / 2 + textHeight / 2 - scrollableNavigation.offsetTop;
+    if (scrollTop < 0) {
+        scrollTop = 0;
+    }
+    scrollableNavigation.scrollTo({ top: scrollTop, behavior: "smooth" });
+}
+
+type IdToElementMap = { [key: string]: HTMLElement };
+
+function buildIdToNavigationElementMap(navigation: NodeListOf<Element>): IdToElementMap {
+    const sectionLinkRef: IdToElementMap = {};
+    navigation.forEach((navigationElement: HTMLElement) => {
+        const link = navigationElement.querySelector("a");
+        const href = link.getAttribute("href");
+        if (href.startsWith("#")) {
+            sectionLinkRef[href.slice(1)] = navigationElement;
+        }
+    });
+
+    return sectionLinkRef;
+}
+
+function computeOffsets(headers: NodeListOf<Element>) {
+    let sectionsOffsets = [];
+    headers.forEach((header: HTMLElement) => { sectionsOffsets.push({ id: header.id, offset: header.offsetTop }) });
+    sectionsOffsets.sort((a, b) => a.offset - b.offset);
+    return sectionsOffsets;
+}
+
+function setupScrollspy() {
+    let headers = document.querySelectorAll(headersQuery);
+    if (!headers) {
+        console.warn("No header matched query", headers);
+        return;
+    }
+
+    let scrollableNavigation = document.querySelector(tocQuery) as HTMLElement | undefined;
+    if (!scrollableNavigation) {
+        console.warn("No toc matched query", tocQuery);
+        return;
+    }
+
+    let navigation = document.querySelectorAll(navigationQuery);
+    if (!navigation) {
+        console.warn("No navigation matched query", navigationQuery);
+        return;
+    }
+
+    let sectionsOffsets = computeOffsets(headers);
+
+    // We need to avoid scrolling when the user is actively interacting with the ToC. Otherwise, if the user clicks on a link in the ToC,
+    // we would scroll their view, which is not optimal usability-wise.
+    let tocHovered: boolean = false;
+    scrollableNavigation.addEventListener("mouseenter", debounced(() => tocHovered = true));
+    scrollableNavigation.addEventListener("mouseleave", debounced(() => tocHovered = false));
+
+    let activeSectionLink: Element;
+
+    let idToNavigationElement: IdToElementMap = buildIdToNavigationElementMap(navigation);
+
+    function scrollHandler() {
+        let scrollPosition = document.documentElement.scrollTop || document.body.scrollTop;
+
+        let newActiveSection: HTMLElement | undefined;
+
+        // Find the section that is currently active.
+        // It is possible for no section to be active, so newActiveSection may be undefined.
+        sectionsOffsets.forEach((section) => {
+            if (scrollPosition >= section.offset - 20) {
+                newActiveSection = document.getElementById(section.id);
+            }
+        });
+
+        // Find the link for the active section. Once again, there are a few edge cases:
+        // - No active section = no link => undefined
+        // - No active section but the link does not exist in toc (e.g. because it is outside of the applicable ToC levels) => undefined
+        let newActiveSectionLink: HTMLElement | undefined
+        if (newActiveSection) {
+            newActiveSectionLink = idToNavigationElement[newActiveSection.id];
+        }
+
+        if (newActiveSection && !newActiveSectionLink) {
+            // The active section does not have a link in the ToC, so we can't scroll to it.
+            console.debug("No link found for section", newActiveSection);
+        } else if (newActiveSectionLink !== activeSectionLink) {
+            if (activeSectionLink)
+                activeSectionLink.classList.remove(activeClass);
+            if (newActiveSectionLink) {
+                newActiveSectionLink.classList.add(activeClass);
+                if (!tocHovered) {
+                    // Scroll so that newActiveSectionLink is in the middle of scrollableNavigation, except when it's from a manual click (hence the tocHovered check)
+                    scrollToTocElement(newActiveSectionLink, scrollableNavigation);
+                }
+            }
+            activeSectionLink = newActiveSectionLink;
+        }
+    }
+
+    window.addEventListener("scroll", debounced(scrollHandler));
+    
+    // Resizing may cause the offset values to change: recompute them.
+    function resizeHandler() {
+        sectionsOffsets = computeOffsets(headers);
+        scrollHandler();
+    }
+
+    window.addEventListener("resize", debounced(resizeHandler));
+}
+
+export { setupScrollspy };

+ 325 - 0
themes/hugo-theme-stack/assets/ts/search.tsx

@@ -0,0 +1,325 @@
+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 = {
+    '&': '&amp;',
+    '<': '&lt;',
+    '>': '&gt;',
+    '"': '&quot;',
+    '…': '&hellip;'
+};
+
+function replaceTag(tag) {
+    return tagsToReplace[tag] || tag;
+}
+
+function replaceHTMLEnt(str) {
+    return str.replace(/[&<>"]/g, replaceTag);
+}
+
+function escapeRegExp(string) {
+    return string.replace(/[.*+\-?^${}()|[\]\\]/g, '\\$&');
+}
+
+class Search {
+    private data: pageData[];
+    private form: HTMLFormElement;
+    private input: HTMLInputElement;
+    private list: HTMLDivElement;
+    private resultTitle: HTMLHeadElement;
+    private resultTitleTemplate: string;
+
+    constructor({ form, input, list, resultTitle, resultTitleTemplate }) {
+        this.form = form;
+        this.input = input;
+        this.list = list;
+        this.resultTitle = resultTitle;
+        this.resultTitleTemplate = resultTitleTemplate;
+
+        this.handleQueryString();
+        this.bindQueryStringChange();
+        this.bindSearchForm();
+    }
+
+    /**
+     * Processes search matches
+     * @param str original text
+     * @param matches array of matches
+     * @param ellipsis whether to add ellipsis to the end of each match
+     * @param charLimit max length of preview string
+     * @param offset how many characters before and after the match to include in preview
+     * @returns preview string
+     */
+    private static processMatches(str: string, matches: match[], ellipsis: boolean = true, charLimit = 140, offset = 20): string {
+        matches.sort((a, b) => {
+            return a.start - b.start;
+        });
+
+        let i = 0,
+            lastIndex = 0,
+            charCount = 0;
+
+        const resultArray: string[] = [];
+
+        while (i < matches.length) {
+            const item = matches[i];
+
+            /// item.start >= lastIndex (equal only for the first iteration)
+            /// because of the while loop that comes after, iterating over variable j
+
+            if (ellipsis && item.start - offset > lastIndex) {
+                resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, lastIndex + offset))} [...] `);
+                resultArray.push(`${replaceHTMLEnt(str.substring(item.start - offset, item.start))}`);
+                charCount += offset * 2;
+            }
+            else {
+                /// If the match is too close to the end of last match, don't add ellipsis
+                resultArray.push(replaceHTMLEnt(str.substring(lastIndex, item.start)));
+                charCount += item.start - lastIndex;
+            }
+
+            let j = i + 1,
+                end = item.end;
+
+            /// Include as many matches as possible
+            /// [item.start, end] is the range of the match
+            while (j < matches.length && matches[j].start <= end) {
+                end = Math.max(matches[j].end, end);
+                ++j;
+            }
+
+            resultArray.push(`<mark>${replaceHTMLEnt(str.substring(item.start, end))}</mark>`);
+            charCount += end - item.start;
+
+            i = j;
+            lastIndex = end;
+
+            if (ellipsis && charCount > charLimit) break;
+        }
+
+        /// Add the rest of the string
+        if (lastIndex < str.length) {
+            let end = str.length;
+            if (ellipsis) end = Math.min(end, lastIndex + offset);
+
+            resultArray.push(`${replaceHTMLEnt(str.substring(lastIndex, end))}`);
+
+            if (ellipsis && end != str.length) {
+                resultArray.push(` [...]`);
+            }
+        }
+
+        return resultArray.join('');
+    }
+
+    private async searchKeywords(keywords: string[]) {
+        const rawData = await this.getData();
+        const results: pageData[] = [];
+
+        const regex = new RegExp(keywords.filter((v, index, arr) => {
+            arr[index] = escapeRegExp(v);
+            return v.trim() !== '';
+        }).join('|'), 'gi');
+
+        for (const item of rawData) {
+            const titleMatches: match[] = [],
+                contentMatches: match[] = [];
+
+            let result = {
+                ...item,
+                preview: '',
+                matchCount: 0
+            }
+
+            const contentMatchAll = item.content.matchAll(regex);
+            for (const match of Array.from(contentMatchAll)) {
+                contentMatches.push({
+                    start: match.index,
+                    end: match.index + match[0].length
+                });
+            }
+
+            const titleMatchAll = item.title.matchAll(regex);
+            for (const match of Array.from(titleMatchAll)) {
+                titleMatches.push({
+                    start: match.index,
+                    end: match.index + match[0].length
+                });
+            }
+
+            if (titleMatches.length > 0) result.title = Search.processMatches(result.title, titleMatches, false);
+            if (contentMatches.length > 0) {
+                result.preview = Search.processMatches(result.content, contentMatches);
+            }
+            else {
+                /// If there are no matches in the content, use the first 140 characters as preview
+                result.preview = replaceHTMLEnt(result.content.substring(0, 140));
+            }
+
+            result.matchCount = titleMatches.length + contentMatches.length;
+            if (result.matchCount > 0) results.push(result);
+        }
+
+        /// Result with more matches appears first
+        return results.sort((a, b) => {
+            return b.matchCount - a.matchCount;
+        });
+    }
+
+    private async doSearch(keywords: string[]) {
+        const startTime = performance.now();
+
+        const results = await this.searchKeywords(keywords);
+        this.clear();
+
+        for (const item of results) {
+            this.list.append(Search.render(item));
+        }
+
+        const endTime = performance.now();
+
+        this.resultTitle.innerText = this.generateResultTitle(results.length, ((endTime - startTime) / 1000).toPrecision(1));
+    }
+
+    private generateResultTitle(resultLen, time) {
+        return this.resultTitleTemplate.replace("#PAGES_COUNT", resultLen).replace("#TIME_SECONDS", time);
+    }
+
+    public async getData() {
+        if (!this.data) {
+            /// Not fetched yet
+            const jsonURL = this.form.dataset.json;
+            this.data = await fetch(jsonURL).then(res => res.json());
+            const parser = new DOMParser();
+
+            for (const item of this.data) {
+                item.content = parser.parseFromString(item.content, 'text/html').body.innerText;
+            }
+        }
+
+        return this.data;
+    }
+
+    private bindSearchForm() {
+        let lastSearch = '';
+
+        const eventHandler = (e) => {
+            e.preventDefault();
+            const keywords = this.input.value.trim();
+
+            Search.updateQueryString(keywords, true);
+
+            if (keywords === '') {
+                return this.clear();
+            }
+
+            if (lastSearch === keywords) return;
+            lastSearch = keywords;
+
+            this.doSearch(keywords.split(' '));
+        }
+
+        this.input.addEventListener('input', eventHandler);
+        this.input.addEventListener('compositionend', eventHandler);
+    }
+
+    private clear() {
+        this.list.innerHTML = '';
+        this.resultTitle.innerText = '';
+    }
+
+    private bindQueryStringChange() {
+        window.addEventListener('popstate', (e) => {
+            this.handleQueryString()
+        })
+    }
+
+    private handleQueryString() {
+        const pageURL = new URL(window.location.toString());
+        const keywords = pageURL.searchParams.get('keyword');
+        this.input.value = keywords;
+
+        if (keywords) {
+            this.doSearch(keywords.split(' '));
+        }
+        else {
+            this.clear()
+        }
+    }
+
+    private static updateQueryString(keywords: string, replaceState = false) {
+        const pageURL = new URL(window.location.toString());
+
+        if (keywords === '') {
+            pageURL.searchParams.delete('keyword')
+        }
+        else {
+            pageURL.searchParams.set('keyword', keywords);
+        }
+
+        if (replaceState) {
+            window.history.replaceState('', '', pageURL.toString());
+        }
+        else {
+            window.history.pushState('', '', pageURL.toString());
+        }
+    }
+
+    public static render(item: pageData) {
+        return <article>
+            <a href={item.permalink}>
+                <div class="article-details">
+                    <h2 class="article-title" dangerouslySetInnerHTML={{ __html: item.title }}></h2>
+                    <section class="article-preview" dangerouslySetInnerHTML={{ __html: item.preview }}></section>
+                </div>
+                {item.image &&
+                    <div class="article-image">
+                        <img src={item.image} loading="lazy" />
+                    </div>
+                }
+            </a>
+        </article>;
+    }
+}
+
+declare global {
+    interface Window {
+        searchResultTitleTemplate: string;
+    }
+}
+
+window.addEventListener('load', () => {
+    setTimeout(function () {
+        const searchForm = document.querySelector('.search-form') as HTMLFormElement,
+            searchInput = searchForm.querySelector('input') as HTMLInputElement,
+            searchResultList = document.querySelector('.search-result--list') as HTMLDivElement,
+            searchResultTitle = document.querySelector('.search-result--title') as HTMLHeadingElement;
+
+        new Search({
+            form: searchForm,
+            input: searchInput,
+            list: searchResultList,
+            resultTitle: searchResultTitle,
+            resultTitleTemplate: window.searchResultTitleTemplate
+        });
+    }, 0);
+})
+
+export default Search;

+ 34 - 0
themes/hugo-theme-stack/assets/ts/smoothAnchors.ts

@@ -0,0 +1,34 @@
+// 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();
+
+            let targetId = aElement.getAttribute("href").substring(1);
+            // The replace done on ':' is here for footnotes, as this character would otherwise interfere when used as a CSS selector.
+            let target = document.querySelector(`#${targetId.replace(":", "\\:")}`) as HTMLElement;
+
+            window.history.pushState({}, "", aElement.getAttribute("href"));
+            scrollTo({ top: target.offsetTop, behavior: "smooth" });
+        });
+    });
+}
+
+export { setupSmoothAnchors };

+ 141 - 0
themes/hugo-theme-stack/config.yaml

@@ -0,0 +1,141 @@
+module:
+    hugoVersion:
+        extended: true
+        min: "0.87.0"
+
+params:
+    mainSections:
+        - post
+    featuredImageField: image
+    rssFullContent: true
+    favicon:
+
+    footer:
+        since:
+        customText:
+
+    dateFormat:
+        published: Jan 02, 2006
+        lastUpdated: Jan 02, 2006 15:04 MST
+
+    sidebar:
+        compact: false
+        emoji:
+        subtitle:
+        avatar:
+            enabled: true
+            local: true
+            src: img/avatar.png
+
+    article:
+        math: false
+        toc: true
+        readingTime: true
+        license:
+            enabled: false
+            default: Licensed under CC BY-NC-SA 4.0
+
+    comments:
+        enabled: false
+        provider: disqus
+
+        disqusjs:
+            shortname:
+            apiUrl:
+            apiKey:
+            admin:
+            adminLabel:
+
+        utterances:
+            repo:
+            issueTerm: pathname
+            label:
+
+        remark42:
+            host:
+            site:
+            locale:
+
+        vssue:
+            platform:
+            owner:
+            repo:
+            clientId:
+            clientSecret:
+            autoCreateIssue: false
+
+        # Waline client configuration see: https://waline.js.org/en/reference/client.html
+        waline:
+            serverURL:
+            lang:
+            visitor:
+            avatar:
+            emoji:
+                - https://cdn.jsdelivr.net/gh/walinejs/emojis/weibo
+            requiredMeta:
+                - name
+                - email
+                - url
+            placeholder:
+            locale:
+                admin: Admin
+
+        twikoo:
+            envId:
+            region:
+            path:
+            lang:
+
+        giscus:
+            repo:
+            repoID:
+            category:
+            categoryID:
+            mapping:
+            lightTheme:
+            darkTheme:
+            reactionsEnabled: 1
+            emitMetadata: 0
+            lang:
+
+        gitalk:
+            owner:
+            admin:
+            repo:
+            clientID:
+            clientSecret:
+
+        cusdis:
+            host:
+            id:
+
+    widgets:
+        homepage: []
+        page: []
+
+    opengraph:
+        twitter:
+            # Your Twitter username
+            site:
+
+            # Available values: summary, summary_large_image
+            card: summary_large_image
+
+    defaultImage:
+        opengraph:
+            enabled: false
+            local: false
+            src:
+
+    colorScheme:
+        # Display toggle
+        toggle: true
+
+        # Available values: auto, light, dark
+        default: auto
+
+    imageProcessing:
+        cover:
+            enabled: true
+        content:
+            enabled: true

+ 46 - 0
themes/hugo-theme-stack/data/external.yaml

@@ -0,0 +1,46 @@
+Vibrant:
+    - src: https://cdn.jsdelivr.net/npm/node-vibrant@3.1.5/dist/vibrant.min.js
+      integrity: sha256-5NovOZc4iwiAWTYIFiIM7DxKUXKWvpVEuMEPLzcm5/g=
+      type: script
+
+PhotoSwipe:
+    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.min.js
+      integrity: sha256-ePwmChbbvXbsO02lbM3HoHbSHTHFAeChekF1xKJdleo=
+      type: script
+      defer: true
+
+    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe-ui-default.min.js
+      integrity: sha256-UKkzOn/w1mBxRmLLGrSeyB4e1xbrp4xylgAWb3M42pU=
+      type: script
+      defer: true
+
+    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/default-skin/default-skin.css
+      integrity: sha256-c0uckgykQ9v5k+IqViZOZKc47Jn7KQil4/MP3ySA3F8=
+      type: style
+
+    - src: https://cdn.jsdelivr.net/npm/photoswipe@4.1.3/dist/photoswipe.css
+      integrity: sha256-SBLU4vv6CA6lHsZ1XyTdhyjJxCjPif/TRkjnsyGAGnE=
+      type: style
+
+KaTeX:
+    - src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.css
+      integrity: sha384-RZU/ijkSsFbcmivfdRBQDtwuwVqK7GMOw6IMvKyeWL2K5UAlyp6WonmB8m7Jd0Hn
+      type: style
+
+    - src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/katex.min.js
+      integrity: sha384-pK1WpvzWVBQiP0/GjnvRxV4mOb0oxFuyRxJlk6vVw146n3egcN5C925NCP7a7BY8
+      type: script
+      defer: true
+
+    - src: https://cdn.jsdelivr.net/npm/katex@0.13.13/dist/contrib/auto-render.min.js
+      integrity: sha384-vZTG03m+2yp6N6BNi5iM4rW4oIwk5DfcNdFfxkk9ZWpDriOkXX8voJBFrAO7MpVl
+      type: script
+      defer: true
+
+Cactus:
+    - src: https://latest.cactus.chat/cactus.js
+      integrity:
+      type: script
+    - src: https://latest.cactus.chat/style.css
+      integrity:
+      type: style

+ 1 - 0
themes/hugo-theme-stack/debug.sh

@@ -0,0 +1 @@
+cd exampleSite && hugo server --gc --themesDir=../..

+ 3 - 0
themes/hugo-theme-stack/go.mod

@@ -0,0 +1,3 @@
+module github.com/CaiJimmy/hugo-theme-stack/v3
+
+go 1.17

+ 70 - 0
themes/hugo-theme-stack/i18n/ar.yaml

@@ -0,0 +1,70 @@
+toggleMenu:
+    other: اخفي القائمة
+
+darkMode:
+    other: الوضع الداكن
+
+list:
+    page:
+        one: "{{ .Count }} صفحه"
+        other: "{{ .Count }} صفحات"
+
+    section:
+        other: قسم
+
+    subsection:
+        one: قسم فرعي
+        other: اقسام فرعية
+
+article:
+    back:
+        other: خلف
+
+    tableOfContents:
+        other: جدول المحتويات
+
+    relatedContents:
+        other: محتوى مشابهه
+        
+    lastUpdatedOn:
+        other: التعديل الاخير
+
+    readingTime:
+        one: "تُقرأ خلال دقيقة"
+        other: "تُقرأ خلال {{ .Count }} دقائق"
+
+notFound:
+    title:
+        other: غير موجود
+
+    subtitle:
+        other: تعذر العثور على الصفحة المطلوبة.
+
+widget:
+    archives:
+        title:
+            other: الارشيفات
+
+        more:
+            other: اكثر
+
+    tagCloud:
+        title:
+            other: وسوم
+
+search:
+    title:
+        other: بحث
+
+    placeholder:
+        other: اكتب...
+
+    resultTitle:
+        other: "#PAGES_COUNT نتيجة (#TIME_SECONDS ثواني)"
+
+footer:
+    builtWith:
+        other: "مبني بستخدام {{ .Generator }}"
+        
+    designedBy:
+        other:  "قالب {{ .Theme }} مصمم من {{ .DesignedBy }}"

+ 73 - 0
themes/hugo-theme-stack/i18n/ca.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: Toggle Menu
+
+darkMode:
+    other: Mode fosc
+
+list:
+    page:
+        one: "{{ .Count }} pàgina"
+        other: "{{ .Count }} pàgines"
+
+    section:
+        other: Secció
+
+    subsection:
+        one: Subsecció
+        other: Subseccions
+
+article:
+    back:
+        other: Tornar
+
+    tableOfContents:
+        other: Taula de contingut
+
+    relatedContents:
+        other: Continguts relacionats
+
+    lastUpdatedOn:
+        other: Última vegada actualitzat
+
+    readingTime:
+        one: "{{ .Count }} minut a llegir"
+        other: "{{ .Count }} minuts a llegir"
+
+notFound:
+    title:
+        other: No Trobat
+
+    subtitle:
+        other: Aquesta pàgina no existeix
+
+widget:
+    archives:
+        title:
+            other: Arxiu
+
+        more:
+            other: Més
+
+    tagCloud:
+        title:
+            other: Etiquetes
+    categoriesCloud:
+        title:
+            other: Categories
+
+search:
+    title:
+        other: Cerca
+
+    placeholder:
+        other: Tecleja alguna cosa...
+
+    resultTitle:
+        other: "#PAGES_COUNT pàgines en (#TIME_SECONDS segons)"
+
+footer:
+    builtWith:
+        other: Creat amb {{ .Generator }}
+
+    designedBy:
+        other: Tema {{ .Theme }} dissenyat per {{ .DesignedBy }}

+ 70 - 0
themes/hugo-theme-stack/i18n/de.yaml

@@ -0,0 +1,70 @@
+toggleMenu:
+    other: Menü umschalten
+
+darkMode:
+    other: Dunkler Modus
+
+list:
+    page:
+        one: "{{ .Count }} Seite"
+        other: "{{ .Count }} Seiten"
+
+    section:
+        other: Abschnitt
+
+    subsection:
+        one: Unterabschnitt
+        other: Unterabschnitte
+
+article:
+    back:
+        other: Zurück
+
+    tableOfContents:
+        other: Inhaltsverzeichnis
+
+    relatedContents:
+        other: Verwandte Inhalte
+
+    lastUpdatedOn:
+        other: Zuletzt aktualisiert am
+
+    readingTime:
+        one: "{{ .Count }} Minute Lesezeit"
+        other: "{{ .Count }} Minuten Lesezeit"
+
+notFound:
+    title:
+        other: Seite nicht gefunden
+
+    subtitle:
+        other: Diese Seite existiert nicht
+
+widget:
+    archives:
+        title:
+            other: Archiv
+
+        more:
+            other: Weitere
+
+    tagCloud:
+        title:
+            other: Schlagwörter
+
+search:
+    title:
+        other: Suche
+
+    placeholder:
+        other: Etwas tippen...
+
+    resultTitle:
+        other: "#PAGES_COUNT Seiten (#TIME_SECONDS Sekunden)"
+
+footer:
+    builtWith:
+        other: Erstellt mit {{ .Generator }}
+
+    designedBy:
+        other: Theme {{ .Theme }} gestaltet von {{ .DesignedBy }}

+ 70 - 0
themes/hugo-theme-stack/i18n/el.yaml

@@ -0,0 +1,70 @@
+toggleMenu:
+    other: Εναλλαγή Μενού
+
+darkMode:
+    other: Σκοτεινό θέμα
+
+list:
+    page:
+        one: "{{ .Count }} σελιδα"
+        other: "{{ .Count }} σελιδες"
+
+    section:
+        other: Ενότητα
+
+    subsection:
+        one: Υποενότητα
+        other: Υποενότητες
+
+article:
+    back:
+        other: Πισω
+
+    tableOfContents:
+        other: Πινακας περιεχομενων
+
+    relatedContents:
+        other: Σχετικο περιεχομενο
+
+    lastUpdatedOn:
+        other: Τελευταια τροποποιηση στις
+
+    readingTime:
+        one: "{{ .Count }} λεπτό ανάγνωσης"
+        ### Seems that there's no need to add 's' even if it's plural in English
+        other: "{{ .Count }} λεπτά ανάγνωσης"
+
+notFound:
+    title:
+        other: Δε βρέθηκε
+    subtitle:
+        other: Η σελίδα δε βρέθηκε.
+
+widget:
+    archives:
+        title:
+            other: Αρχειο
+
+        more:
+            other: Περισσότερα
+
+    tagCloud:
+        title:
+            other: Tags
+
+search:
+    title:
+        other: Αναζήτηση
+
+    placeholder:
+        other: Πληκτρολογήστε κάτι...
+
+    resultTitle:
+        other: "#PAGES_COUNT σελιδες (#TIME_SECONDS δευτερολεπτα)"
+
+footer:
+    builtWith:
+        other: Δημιουργήθηκε με τη χρήση {{ .Generator }}
+
+    designedBy:
+        other: Το θέμα {{ .Theme }} σχεδιάστηκε από το {{ .DesignedBy }}

+ 73 - 0
themes/hugo-theme-stack/i18n/en.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: Toggle Menu
+
+darkMode:
+    other: Dark Mode
+
+list:
+    page:
+        one: "{{ .Count }} page"
+        other: "{{ .Count }} pages"
+
+    section:
+        other: Section
+
+    subsection:
+        one: Subsection
+        other: Subsections
+
+article:
+    back:
+        other: Back
+
+    tableOfContents:
+        other: Table of contents
+
+    relatedContents:
+        other: Related contents
+
+    lastUpdatedOn:
+        other: Last updated on
+
+    readingTime:
+        one: "{{ .Count }} minute read"
+        other: "{{ .Count }} minute read"
+
+notFound:
+    title:
+        other: Not Found
+
+    subtitle:
+        other: This page does not exist
+
+widget:
+    archives:
+        title:
+            other: Archives
+
+        more:
+            other: More
+
+    tagCloud:
+        title:
+            other: Tags
+    categoriesCloud:
+        title:
+            other: Categories
+
+search:
+    title:
+        other: Search
+
+    placeholder:
+        other: Type something...
+
+    resultTitle:
+        other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
+
+footer:
+    builtWith:
+        other: Built with {{ .Generator }}
+
+    designedBy:
+        other: Theme {{ .Theme }} designed by {{ .DesignedBy }}

+ 73 - 0
themes/hugo-theme-stack/i18n/es.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: Toggle Menu
+
+darkMode:
+    other: Modo oscuro
+
+list:
+    page:
+        one: "{{ .Count }} página"
+        other: "{{ .Count }} páginas"
+
+    section:
+        other: Sección
+
+    subsection:
+        one: Subsección
+        other: Subsecciones
+
+article:
+    back:
+        other: Volver
+
+    tableOfContents:
+        other: Tabla de contenido
+
+    relatedContents:
+        other: Contenidos relacionados
+
+    lastUpdatedOn:
+        other: Última vez actualizado
+
+    readingTime:
+        one: "{{ .Count }} minuto a leer"
+        other: "{{ .Count }} minutos a leer"
+
+notFound:
+    title:
+        other: No Encontrado
+
+    subtitle:
+        other: Esta página no existe
+
+widget:
+    archives:
+        title:
+            other: Archivo
+
+        more:
+            other: Más
+
+    tagCloud:
+        title:
+            other: Etiquetas
+    categoriesCloud:
+        title:
+            other: Categorías
+
+search:
+    title:
+        other: Búsqueda
+
+    placeholder:
+        other: Teclea algo...
+
+    resultTitle:
+        other: "#PAGES_COUNT páginas en (#TIME_SECONDS segundos)"
+
+footer:
+    builtWith:
+        other: Creado con {{ .Generator }}
+
+    designedBy:
+        other: Tema {{ .Theme }} diseñado por {{ .DesignedBy }}

+ 69 - 0
themes/hugo-theme-stack/i18n/fr.yaml

@@ -0,0 +1,69 @@
+toggleMenu:
+    other: Afficher le menu
+
+darkMode:
+    other: Mode sombre
+
+list:
+    page:
+        one: "{{ .Count }} page"
+        other: "{{ .Count }} pages"
+
+    section:
+        other: Section
+
+    subsection:
+        one: Sous-section
+        other: Sous-sections
+
+article:
+    back:
+        other: Retour
+
+    tableOfContents:
+        other: Table des matières
+
+    relatedContents:
+        other: Contenus liés
+
+    lastUpdatedOn:
+        other: Dernière mise à jour le
+
+    readingTime:
+        one: "{{ .Count }} minute de lecture"
+        other: "{{ .Count }} minutes de lecture"
+
+notFound:
+    title:
+        other: Page non trouvée
+    subtitle:
+        other: Cette page n'existe pas.
+
+widget:
+    archives:
+        title:
+            other: Archives
+
+        more:
+            other: Autres
+
+    tagCloud:
+        title:
+            other: Mots clés
+
+search:
+    title:
+        other: Rechercher
+
+    placeholder:
+        other: Cherchez un article, une publication, etc.
+
+    resultTitle:
+        other: "#PAGES_COUNT pages (#TIME_SECONDS secondes)"
+
+footer:
+    builtWith:
+        other: Généré avec {{ .Generator }}
+
+    designedBy:
+        other: Thème {{ .Theme }} conçu par {{ .DesignedBy }}

+ 73 - 0
themes/hugo-theme-stack/i18n/id.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: Tampilkan Menu
+
+darkMode:
+    other: Mode Gelap
+
+list:
+    page:
+        one: "{{ .Count }} halaman"
+        other: "{{ .Count }} halaman"
+
+    section:
+        other: Bagian
+
+    subsection:
+        one: Subbagian
+        other: Subbagian
+
+article:
+    back:
+        other: Kembali
+
+    tableOfContents:
+        other: Daftar Isi
+
+    relatedContents:
+        other: Konten terkait
+
+    lastUpdatedOn:
+        other: Terakhir diperbarui pada
+
+    readingTime:
+        one: "Waktu Membaca: {{ .Count }} menit"
+        other: "Waktu Membaca: {{ .Count }} menit"
+
+notFound:
+    title:
+        other: Tidak ditemukan
+    subtitle:
+        other: Halaman yang Anda akses tidak ditemukan.
+
+widget:
+    archives:
+        title:
+            other: Arsip
+
+        more:
+            other: Lebih
+
+    tagCloud:
+        title:
+            other: Tag
+
+    categoriesCloud:
+        title:
+            other: Kategori
+
+search:
+    title:
+        other: Cari
+
+    placeholder:
+        other: Ketik sesuatu...
+
+    resultTitle:
+        other: "#PAGES_COUNT halaman (#TIME_SECONDS detik)"
+
+footer:
+    builtWith:
+        other: Dibangun dengan {{ .Generator }}
+
+    designedBy:
+        other: Tema {{ .Theme }} dirancang oleh {{ .DesignedBy }}

+ 69 - 0
themes/hugo-theme-stack/i18n/it.yaml

@@ -0,0 +1,69 @@
+toggleMenu:
+    other: Toggle Menu
+
+darkMode:
+    other: Dark Mode
+
+list:
+    page:
+        one: "{{ .Count }} pagina"
+        other: "{{ .Count }} pagine"
+
+    section:
+        other: Sezione
+
+    subsection:
+        one: Sottosezione
+        other: Sottosezioni
+
+article:
+    back:
+        other: Indietro
+
+    tableOfContents:
+        other: Indice
+
+    relatedContents:
+        other: Contenuti correlati
+
+    lastUpdatedOn:
+        other: Aggiornato il
+
+    readingTime:
+        one: "{{ .Count }} min per leggere"
+        other: "{{ .Count }} min per leggere"
+
+notFound:
+    title:
+        other: Non trovato
+    subtitle:
+        other: Questa pagina non esiste.
+
+widget:
+    archives:
+        title:
+            other: Archivi
+
+        more:
+            other: Di più
+
+    tagCloud:
+        title:
+            other: Tags
+
+search:
+    title:
+        other: Cerca
+
+    placeholder:
+        other: Scrivi qualcosa...
+
+    resultTitle:
+        other: "#PAGES_COUNT pagine (#TIME_SECONDS secondi)"
+
+footer:
+    builtWith:
+        other: Realizzato con {{ .Generator }}
+
+    designedBy:
+        other: Tema {{ .Theme }} realizzato da {{ .DesignedBy }}

+ 60 - 0
themes/hugo-theme-stack/i18n/ja.yaml

@@ -0,0 +1,60 @@
+toggleMenu:
+    other: メニューを開く・閉じる
+
+darkMode:
+    other: ダークモード
+
+article:
+    back:
+        other: 前のページ
+
+    tableOfContents:
+        other: 目次
+
+    relatedContents:
+        other: 関連するコンテンツ
+
+    lastUpdatedOn:
+        other: 最終更新
+
+    readingTime:
+        other: "読了時間: {{ .Count }}分"
+
+notFound:
+    title:
+        other: 404 Not Found
+    subtitle:
+        other: 指定されたページは存在しません。
+
+widget:
+    archives:
+        title:
+            other: アーカイブ
+
+        more:
+            other: さらに見る
+
+    tagCloud:
+        title:
+            other: タグ
+
+    categoriesCloud:
+        title:
+            other: カテゴリ
+
+search:
+    title:
+        other: 検索
+
+    placeholder:
+        other: 入力...
+
+    resultTitle:
+        other: "#PAGES_COUNT 件 (#TIME_SECONDS 秒)"
+
+footer:
+    builtWith:
+        other: Built with {{ .Generator }}
+
+    designedBy:
+        other: テーマ {{ .Theme }} は {{ .DesignedBy }} によって設計されています。

+ 68 - 0
themes/hugo-theme-stack/i18n/ko.yaml

@@ -0,0 +1,68 @@
+toggleMenu:
+    other: 메뉴 여닫기
+
+darkMode:
+    other: 다크 모드
+
+list:
+    page:
+        one: "{{ .Count }} 페이지"
+        other: "{{ .Count }} 페이지"
+
+    section:
+        other: 섹션
+
+    subsection:
+        one: 서브섹션
+        other: 서브섹션
+
+article:
+    back:
+        other: 뒤로가기
+
+    tableOfContents:
+        other: 목차
+        
+    relatedContents:
+        other: 관련 글
+        
+    lastUpdatedOn:
+        other: "마지막 수정: "
+        
+    readingTime:
+        one: "{{ .Count }} 분 정도"
+        other: "{{ .Count }} 분 정도"
+
+notFound:
+    title:
+        other: 찾을 수 없음
+        
+    subtitle:
+        other: 페이지를 찾을 수 없습니다.
+
+widget:
+    archives:
+        title:
+            other: 보관함
+        more:
+            other: 더보기
+            
+    tagCloud:
+        title:
+            other: 태그
+
+search:
+    title:
+        other: 검색
+        
+    placeholder:
+        other: 검색어를 입력하세요...
+        
+    resultTitle:
+        other: "#PAGES_COUNT 페이지 (#TIME_SECONDS 초)"
+
+footer:
+    builtWith:
+        other: "{{ .Generator }}로 만듦"
+    designedBy:
+        other: "{{ .DesignedBy }}의 {{ .Theme }} 테마 사용 중"

+ 53 - 0
themes/hugo-theme-stack/i18n/nl.yaml

@@ -0,0 +1,53 @@
+toggleMenu:
+    other: Open Menu
+
+darkMode:
+    other: Donkere modus
+
+list:
+    page:
+        one: "{{ .Count }} pagina"
+        other: "{{ .Count }} pagina's"
+
+    section:
+        other: Sectie
+
+    subsection:
+        one: Subsectie
+        other: Subsecties
+
+article:
+    relatedContents:
+        other: Gerelateerde inhoud
+    lastUpdatedOn:
+        other: Laatst bijgewerkt op
+
+notFound:
+    title:
+        other: Niet gevonden
+    subtitle:
+        other: Deze pagina bestaat niet.
+
+widget:
+    archives:
+        title:
+            other: Archief
+        more:
+            other: Meer
+    tagCloud:
+        title:
+            other: Tags
+
+search:
+    title:
+        other: Zoeken
+    placeholder:
+        other: Typ iets
+    resultTitle:
+        other: "#PAGES_COUNT pagina's (#TIME_SECONDS seconden)"
+
+footer:
+    builtWith:
+        other: Gemaakt met {{ .Generator }}
+    designedBy:
+        other: Theme {{ .Theme }} ontworpen door {{ .DesignedBy }}

+ 73 - 0
themes/hugo-theme-stack/i18n/pl.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: Przełącz Menu
+
+darkMode:
+    other: Tryb ciemny
+
+list:
+    page:
+        one: "{{ .Count }} strona"
+        other: "{{ .Count }} stron"
+
+    section:
+        other: Sekcja
+
+    subsection:
+        one: Podsekcja
+        other: Podsekcje
+
+article:
+    back:
+        other: Wróć
+
+    tableOfContents:
+        other: Spis treści
+
+    relatedContents:
+        other: Powiązane artykuły
+
+    lastUpdatedOn:
+        other: Ostatnio zaktualizowany
+    
+    readingTime:
+        one: "Przeczytasz w {{ .Count }} minutę"
+        other: "Przeczytasz w {{ .Count }} minut"
+
+notFound:
+    title:
+        other: Nie znaleziono
+    subtitle:
+        other: Ta strona nie istnieje
+
+widget:
+    archives:
+        title:
+            other: Archiwum
+
+        more:
+            other: Więcej
+
+    tagCloud:
+        title:
+            other: Tagi
+    
+    categoriesCloud:
+        title:
+            other: Kategorie
+
+search:
+    title:
+        other: Szukaj
+
+    placeholder:
+        other: Wpisz coś...
+
+    resultTitle:
+        other: "#PAGES_COUNT stron (#TIME_SECONDS sekund)"
+
+footer:
+    builtWith:
+        other: Zbudowano z {{ .Generator }}
+
+    designedBy:
+        other: Motyw {{ .Theme }} zaprojektowany przez {{ .DesignedBy }}

+ 64 - 0
themes/hugo-theme-stack/i18n/pt-br.yaml

@@ -0,0 +1,64 @@
+toggleMenu:
+    other: Alternar Menu
+
+darkMode:
+    other: Modo Escuro
+
+list:
+    page:
+        one: "{{ .Count }} página"
+        other: "{{ .Count }} páginas"
+
+    section:
+        other: Seção
+
+    subsection:
+        one: Subseção
+        other: Subseções
+
+article:
+    back:
+        other: Voltar
+
+    tableOfContents:
+        other: Índice
+
+    relatedContents:
+        other: Conteúdo relacionado
+
+    lastUpdatedOn:
+        other: Última atualização em
+
+    readingTime:
+        one: "{{ .Count }} minuto de leitura"
+        other: "{{ .Count }} minutos de leitura"
+
+notFound:
+    title:
+        other: Não Encontrado
+    subtitle:
+        other: Esta página não existe.
+
+widget:
+    archives:
+        title:
+            other: Arquivos
+        more:
+            other: Mais
+    tagCloud:
+        title:
+            other: Tags
+
+search:
+    title:
+        other: Busca
+    placeholder:
+        other: Digite algo...
+    resultTitle:
+        other: "#PAGES_COUNT páginas (#TIME_SECONDS segundos)"
+
+footer:
+    builtWith:
+        other: Criado com {{ .Generator }}
+    designedBy:
+        other: Tema {{ .Theme }} desenvolvido por {{ .DesignedBy }}

+ 63 - 0
themes/hugo-theme-stack/i18n/ru.yaml

@@ -0,0 +1,63 @@
+toggleMenu:
+    other: Показать/скрыть меню
+
+darkMode:
+    other: Тёмный режим
+
+list:
+    page:
+        one: "{{ .Count }} страница"
+        few: "{{ .Count }} страницы"
+        many: "{{ .Count }} страниц"
+        other: "{{ .Count }} страниц"
+
+    section:
+        other: Раздел
+
+    subsection:
+        one: Подраздел
+        few: Подразделы
+        many: Подразделы
+        other: Подразделы
+
+article:
+    back:
+        other: Назад
+    relatedContents:
+        other: Также рекомендуем
+    lastUpdatedOn:
+        other: Обновлено
+    tableOfContents: 
+        other: Содержание
+    readingTime: 
+        other: "Время чтения: {{ .Count }} мин."
+
+notFound:
+    title:
+        other: Не найдено
+    subtitle:
+        other: Запрашиваемая страница не существует
+
+widget:
+    archives:
+        title:
+            other: Архивы
+        more:
+            other: Ещё
+    tagCloud:
+        title:
+            other: Теги
+
+search:
+    title:
+        other: Поиск
+    placeholder:
+        other: Введите что-нибудь...
+    resultTitle:
+        other: "Найдено #PAGES_COUNT страниц (за #TIME_SECONDS с.)"
+
+footer:
+    builtWith:
+        other: Создано при помощи {{ .Generator }}
+    designedBy:
+        other: Тема {{ .Theme }}, дизайн {{ .DesignedBy }}

+ 70 - 0
themes/hugo-theme-stack/i18n/th.yaml

@@ -0,0 +1,70 @@
+toggleMenu:
+    other: สลับเมนู
+
+darkMode:
+    other: ธีมมืด
+
+list:
+    page:
+        one: "{{ .Count }} หน้า"
+        other: "{{ .Count }} หน้า"
+
+    section:
+        other: หมวดหมู่
+
+    subsection:
+        one: หมวดหมู่ย่อย
+        other: หมวดหมู่ย่อยอื่นๆ
+
+article:
+    back:
+        other: กลับไป
+
+    tableOfContents:
+        other: สารบัญ
+
+    relatedContents:
+        other: เนื้อหาคล้ายคลึงกัน
+
+    lastUpdatedOn:
+        other: อัปเดตล่าสุดเมื่อ
+
+    readingTime:
+        one: "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
+        other: "น่าจะใช้เวลา {{ .Count }} นาทีในการอ่าน"
+
+notFound:
+    title:
+        other: ไม่พบหัวข้อ
+
+    subtitle:
+        other: ไม่พบหน้านี้ในระบบ
+
+widget:
+    archives:
+        title:
+            other: เนื้อหาที่เก็บถาวรแล้ว
+
+        more:
+            other: อื่นๆ นอกจากนี้
+
+    tagCloud:
+        title:
+            other: แท็ก
+
+search:
+    title:
+        other: ค้นหา
+
+    placeholder:
+        other: พิมพ์เพื่อค้นหา ...
+
+    resultTitle:
+        other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
+
+footer:
+    builtWith:
+        other: ถูกสร้างด้วย {{ .Generator }}
+
+    designedBy:
+        other: ธีม {{ .Theme }} ออกแบบโดย {{ .DesignedBy }}

+ 53 - 0
themes/hugo-theme-stack/i18n/tr.yaml

@@ -0,0 +1,53 @@
+toggleMenu:
+    other: Menüyü Gizle
+
+darkMode:
+    other: Koyu Mod
+
+list:
+    page:
+        one: "{{ .Count }} makale"
+        other: "{{ .Count }} makale"
+
+    section:
+        other: Bölüm
+
+    subsection:
+        one: Alt bölüm
+        other: Alt bölümler
+
+article:
+    relatedContents:
+        other: Alakalı içerikler
+    lastUpdatedOn:
+        other: Son güncelleme
+
+notFound:
+    title:
+        other: Bulunamadı
+    subtitle:
+        other: Aradığınız sayfa mevcut değil.
+
+widget:
+    archives:
+        title:
+            other: Arşiv
+        more:
+            other: Daha fazla
+    tagCloud:
+        title:
+            other: Etiketler
+
+search:
+    title:
+        other: Arama
+    placeholder:
+        other: Birşeyler yazın...
+    resultTitle:
+        other: "#PAGES_COUNT sayfa (#TIME_SECONDS saniye)"
+
+footer:
+    builtWith:
+        other: "{{ .Generator }} ile oluşturuldu."
+    designedBy:
+        other: "{{ .Theme }} teması {{ .DesignedBy }} tarafından tasarlandı"

+ 71 - 0
themes/hugo-theme-stack/i18n/uk.yaml

@@ -0,0 +1,71 @@
+toggleMenu:
+    other: Показати меню
+
+darkMode:
+    other: Темна тема
+
+list:
+    page:
+        one: "{{ .Count }} сторінка"
+        few: "{{ .Count }} сторінки"
+        other: "{{ .Count }} сторінок"
+
+    section:
+        other: Секція
+
+    subsection:
+        one: Підсекція
+        other: Підсекції
+
+article:
+    back:
+        other: Назад
+
+    tableOfContents:
+        other: Зміст
+
+    relatedContents:
+        other: Схожі матеріали
+
+    lastUpdatedOn:
+        other: Востаннє оновлено
+
+    readingTime:
+        one: "Час читання: {{ .Count }} хв"
+        other: "Час читання: {{ .Count }} хв"
+
+notFound:
+    title:
+        other: Не знайдено
+
+    subtitle:
+        other: Ця сторінка не існує
+
+widget:
+    archives:
+        title:
+            other: Архіви
+
+        more:
+            other: Більше
+
+    tagCloud:
+        title:
+            other: Теґи
+
+search:
+    title:
+        other: Пошук
+
+    placeholder:
+        other: Напишіть що-небудь...
+
+    resultTitle:
+        other: "#PAGES_COUNT сторінок (#TIME_SECONDS секунд)"
+
+footer:
+    builtWith:
+        other: Створено з {{ .Generator }}
+
+    designedBy:
+        other: Тема {{ .Theme }}, дизайн {{ .DesignedBy }}

+ 60 - 0
themes/hugo-theme-stack/i18n/zh-cn.yaml

@@ -0,0 +1,60 @@
+toggleMenu:
+    other: 切换菜单
+
+darkMode:
+    other: 暗色模式
+
+article:
+    back:
+        other: 返回
+
+    tableOfContents:
+        other: 目录
+
+    relatedContents:
+        other: 相关文章
+
+    lastUpdatedOn:
+        other: 最后更新于
+
+    readingTime:
+        other: "阅读时长: {{ .Count }} 分钟"
+
+notFound:
+    title:
+        other: 404 错误
+    subtitle:
+        other: 页面不存在
+
+widget:
+    archives:
+        title:
+            other: 归档
+
+        more:
+            other: 更多
+
+    tagCloud:
+        title:
+            other: 标签云
+
+    categoriesCloud:
+        title:
+            other: 分类
+
+search:
+    title:
+        other: 搜索
+
+    placeholder:
+        other: 输入关键词...
+
+    resultTitle:
+        other: "#PAGES_COUNT 个结果 (用时 #TIME_SECONDS 秒)"
+
+footer:
+    builtWith:
+        other: Built with {{ .Generator }}
+
+    designedBy:
+        other: 主题 {{ .Theme }} 由 {{ .DesignedBy }} 设计

+ 73 - 0
themes/hugo-theme-stack/i18n/zh-hk.yaml

@@ -0,0 +1,73 @@
+toggleMenu:
+    other: 切換選單
+
+darkMode:
+    other: 深色模式
+
+list:
+    page:
+        one: "第 {{ .Count }} 頁"
+        other: "第 {{ .Count }} 頁"
+
+    section:
+        other: Section
+
+    subsection:
+        one: Subsection
+        other: Subsections
+
+article:
+    back:
+        other: 返回
+
+    tableOfContents:
+        other: 目錄
+
+    relatedContents:
+        other: 相關內容
+
+    lastUpdatedOn:
+        other: 上次改過於
+
+    readingTime:
+        one: "需要 {{ .Count }} 分鐘閱讀"
+        other: "需要 {{ .Count }} 分鐘閱讀"
+
+notFound:
+    title:
+        other: Not Found
+
+    subtitle:
+        other: 頁面不存在
+
+widget:
+    archives:
+        title:
+            other: Archives
+
+        more:
+            other: 更多
+
+    tagCloud:
+        title:
+            other: Tags
+    categoriesCloud:
+        title:
+            other: Categories
+
+search:
+    title:
+        other: 搜尋
+
+    placeholder:
+        other: Type 關鍵字...
+
+    resultTitle:
+        other: "#PAGES_COUNT pages (#TIME_SECONDS seconds)"
+
+footer:
+    builtWith:
+        other: Built with {{ .Generator }}
+
+    designedBy:
+        other: 主題 {{ .Theme }} 由 {{ .DesignedBy }} 設計

+ 49 - 0
themes/hugo-theme-stack/i18n/zh-tw.yaml

@@ -0,0 +1,49 @@
+toggleMenu:
+    other: 切換選單
+
+darkMode:
+    other: 夜晚模式
+
+article:
+    back:
+        other: 返回
+
+    tableOfContents:
+        other: 目錄
+
+    relatedContents:
+        other: 相關文章
+
+    lastUpdatedOn:
+        other: 最後更新
+
+    readingTime:
+        other: "閱讀時間: {{ .Count }} 分鐘"
+
+notFound:
+    title:
+        other: 404 錯誤
+    subtitle:
+        other: 頁面不存在
+
+widget:
+    archives:
+        title:
+            other: 紀錄
+
+        more:
+            other: 更多
+
+    tagCloud:
+        title:
+            other: 標籤雲
+
+search:
+    title:
+        other: 搜尋
+
+    placeholder:
+        other: 輸入關鍵字...
+
+    resultTitle:
+        other: "#PAGES_COUNT 個結果 (用時 #TIME_SECONDS 秒)"

BIN
themes/hugo-theme-stack/images/screenshot.png


BIN
themes/hugo-theme-stack/images/tn.png


+ 7 - 0
themes/hugo-theme-stack/layouts/404.html

@@ -0,0 +1,7 @@
+{{ define "main" }}
+    <div class="not-found-card">
+        <h1 class="article-title">{{ T "notFound.title" }}</h1>
+        <h2 class="article-subtitle">{{ T "notFound.subtitle" }}</h2>
+    </div>
+    {{ partialCached "footer/footer" . }}
+{{ end }}

+ 41 - 0
themes/hugo-theme-stack/layouts/_default/_markup/render-image.html

@@ -0,0 +1,41 @@
+{{- $image := .Page.Resources.GetMatch (printf "%s" (.Destination | safeURL)) -}}
+{{- $Permalink := .Destination | relURL | safeURL -}}
+{{- $alt := .PlainText | safeHTML -}}
+{{- $Width := 0 -}}
+{{- $Height := 0 -}}
+{{- $Srcset := "" -}}
+
+{{/* SVG and external images won't work with gallery layout, because their width and height attributes are unknown */}}
+{{- $galleryImage := false -}}
+
+{{- if $image -}}
+	{{- $notSVG := ne (path.Ext .Destination) ".svg" -}}
+	{{- $Permalink = $image.RelPermalink -}}
+
+	{{- if $notSVG -}}
+		{{- $Width = $image.Width -}}
+		{{- $Height = $image.Height -}}
+		{{- $galleryImage = true -}}
+
+		{{- if (default true .Page.Site.Params.imageProcessing.content.enabled) -}}
+			{{- $small := $image.Resize `480x` -}}
+			{{- $big := $image.Resize `1024x` -}}
+			{{- $Srcset = printf `%s 480w, %s 1024w` $small.RelPermalink $big.RelPermalink -}}
+		{{- end -}}
+	{{- end -}}
+{{- end -}}
+
+<img src="{{ $Permalink }}"
+	{{ with $Width }}width="{{ . }}"{{ end }}
+	{{ with $Height }}height="{{ . }}"{{ end }}
+	{{ with $Srcset }}srcset="{{ . }}"{{ end }}
+	loading="lazy"
+	{{ with $alt }}
+		alt="{{ . }}"
+	{{ end }}
+	{{ if $galleryImage }}
+		class="gallery-image" 
+		data-flex-grow="{{ div (mul $image.Width 100) $image.Height }}"
+		data-flex-basis="{{ div (mul $image.Width 240) $image.Height }}px"
+	{{ end }}
+>

+ 3 - 0
themes/hugo-theme-stack/layouts/_default/_markup/render-link.html

@@ -0,0 +1,3 @@
+<a class="link" href="{{ .Destination | safeURL }}" {{ with .Title}} title="{{ . }}"
+    {{ end }}{{ if strings.HasPrefix .Destination "http" }} target="_blank" rel="noopener"
+    {{ end }}>{{ .Text | safeHTML }}</a>

+ 35 - 0
themes/hugo-theme-stack/layouts/_default/archives.html

@@ -0,0 +1,35 @@
+{{ define "body-class" }}template-archives{{ end }}
+{{ define "main" }}
+    <header>
+        {{- $taxonomy := $.Site.GetPage "taxonomyTerm" "categories" -}}
+        {{- $terms := $taxonomy.Pages -}}
+        {{ if $terms }}
+        <h2 class="section-title">{{ $taxonomy.Title }}</h2>
+        <div class="subsection-list">
+            <div class="article-list--tile">
+                {{ range $terms }}
+                    {{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "taxonomy") }}
+                {{ end }}
+            </div>
+        </div>
+        {{ end }}
+    </header>
+
+    {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
+    {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
+    {{ $filtered := ($pages | intersect $notHidden) }}
+
+    {{ range $filtered.GroupByDate "2006" }}
+    {{ $id := lower (replace .Key " " "-") }}
+    <div class="archives-group" id="{{ $id }}">
+        <h2 class="archives-date section-title"><a href="{{ $.RelPermalink }}#{{ $id }}">{{ .Key }}</a></h2>
+        <div class="article-list--compact">
+            {{ range .Pages }}
+                {{ partial "article-list/compact" . }}
+            {{ end }}
+        </div>
+    </div>
+    {{ end }}
+
+    {{ partialCached "footer/footer" . }}
+{{ end }}

+ 28 - 0
themes/hugo-theme-stack/layouts/_default/baseof.html

@@ -0,0 +1,28 @@
+<!DOCTYPE html>
+<html lang="{{ .Site.LanguageCode }}" dir="{{ default `ltr` .Language.LanguageDirection }}">
+    <head>
+        {{- partial "head/head.html" . -}}
+        {{- block "head" . -}}{{ end }}
+    </head>
+    <body class="{{ block `body-class` . }}{{ end }}">
+        {{- partial "head/colorScheme" . -}}
+
+        {{/* The container is wider when there's any activated widget */}}
+        {{- $hasWidget := false -}}
+        {{- range .Site.Params.widgets -}}
+            {{- if gt (len .) 0 -}}
+                {{- $hasWidget = true -}}
+            {{- end -}}
+        {{- end -}}
+        <div class="container main-container flex on-phone--column {{ if $hasWidget }}extended{{ else }}compact{{ end }}">
+            {{- block "left-sidebar" . -}}
+                {{ partial "sidebar/left.html" . }}
+            {{- end -}}
+            <main class="main full-width">
+                {{- block "main" . }}{{- end }}
+            </main>
+            {{- block "right-sidebar" . -}}{{ end }}
+        </div>
+        {{ partial "footer/include.html" . }}
+    </body>
+</html>

+ 85 - 0
themes/hugo-theme-stack/layouts/_default/list.html

@@ -0,0 +1,85 @@
+{{ define "main" }}
+    <header>
+        <h3 class="section-title">
+            {{ if eq .Parent (.GetPage "/") }}
+                {{ T "list.section" }}
+            {{ else }}
+                {{ .Parent.Title }}
+            {{ end }}
+        </h3>
+
+        <div class="section-card">
+            <div class="section-details">
+                <h3 class="section-count">{{ T "list.page" (len .Pages) }}</h3>
+                <h1 class="section-term">{{ .Title }}</h1>
+                {{ with .Params.description }}
+                    <h2 class="section-description">{{ . }}</h2>
+                {{ end }}
+            </div>
+    
+            {{- $image := partialCached "helper/image" (dict "Context" . "Type" "section") .RelPermalink "section" -}}
+            {{ if $image.exists }}
+                <div class="section-image">
+                    {{ if $image.resource }}
+                        {{- $Permalink := $image.resource.RelPermalink -}}
+                        {{- $Width := $image.resource.Width -}}
+                        {{- $Height := $image.resource.Height -}}
+    
+                        {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
+                            {{- $thumbnail := $image.resource.Fill "120x120" -}}
+                            {{- $Permalink = $thumbnail.RelPermalink -}}
+                            {{- $Width = $thumbnail.Width -}}
+                            {{- $Height = $thumbnail.Height -}}
+                        {{- end -}}
+                        
+                        <img src="{{ $Permalink }}" 
+                            width="{{ $Width }}"
+                            height="{{ $Height }}" 
+                            loading="lazy">
+                    {{ else }}
+                        <img src="{{ $image.permalink }}" loading="lazy" />
+                    {{ end }}
+                </div>
+            {{ end }}
+        </div>
+    </header>
+
+    {{- $subsections := .Sections -}}
+    {{- $pages := .Pages | complement $subsections -}}
+    
+    {{- if eq (len $pages) 0 -}}
+        {{/* If there are no normal pages, display subsections in list style, with pagination */}}
+        {{/* This happens with taxonomies like categories or tags */}}
+        {{- $pages = $subsections -}}
+        {{- $subsections = slice -}}
+    {{- end -}}
+
+    {{- with $subsections -}}
+        <aside>
+            <h2 class="section-title">{{ T "list.subsection" (len $subsections) }}</h2>
+            <div class="subsection-list">
+                <div class="article-list--tile">
+                    {{ range . }}
+                        {{ partial "article-list/tile" (dict "context" . "size" "250x150" "Type" "section") }}
+                    {{ end }}
+                </div>
+            </div>
+        </aside>
+    {{- end -}}
+    
+    {{/* List only pages that are not a subsection */}}
+    {{ $paginator := .Paginate $pages }}
+    <section class="article-list--compact">
+        {{ range $paginator.Pages }}
+            {{ partial "article-list/compact" . }}
+        {{ end }}
+    </section>
+
+    {{- partial "pagination.html" . -}}
+
+    {{ partialCached "footer/footer" . }}
+{{ end }}
+
+{{ define "right-sidebar" }}
+    {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
+{{ end }}

+ 48 - 0
themes/hugo-theme-stack/layouts/_default/rss.xml

@@ -0,0 +1,48 @@
+{{- $pctx := . -}}
+{{- if .IsHome -}}{{ $pctx = .Site }}{{- end -}}
+{{- $pages := slice -}}
+{{- if or $.IsHome $.IsSection -}}
+{{- $pages = $pctx.RegularPages -}}
+{{- else -}}
+{{- $pages = $pctx.Pages -}}
+{{- end -}}
+{{- $pages := where $pages "Params.hidden" "!=" true -}}
+{{- $limit := .Site.Config.Services.RSS.Limit -}}
+{{- if ge $limit 1 -}}
+{{- $pages = $pages | first $limit -}}
+{{- end -}}
+{{- printf "<?xml version=\"1.0\" encoding=\"utf-8\" standalone=\"yes\"?>" | safeHTML }}
+<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
+    <channel>
+        <title>{{ if eq  .Title  .Site.Title }}{{ .Site.Title }}{{ else }}{{ with .Title }}{{.}} on {{ end }}{{ .Site.Title }}{{ end }}</title>
+        <link>{{ .Permalink }}</link>
+        <description>Recent content {{ if ne  .Title  .Site.Title }}{{ with .Title }}in {{.}} {{ end }}{{ end }}on {{ .Site.Title }}</description>
+        <generator>Hugo -- gohugo.io</generator>{{ with .Site.LanguageCode }}
+        <language>{{.}}</language>{{end}}{{ with .Site.Author.email }}
+        <managingEditor>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</managingEditor>{{end}}{{ with .Site.Author.email }}
+        <webMaster>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</webMaster>{{end}}{{ with .Site.Copyright }}
+        <copyright>{{.}}</copyright>{{end}}{{ if not .Date.IsZero }}
+        <lastBuildDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</lastBuildDate>{{ end }}
+        {{- with .OutputFormats.Get "RSS" -}}
+        {{ printf "<atom:link href=%q rel=\"self\" type=%q />" .Permalink .MediaType | safeHTML }}
+        {{- end -}}
+        {{ range $pages }}
+        {{- $content := safeHTML (.Summary | html) -}}
+        {{- if .Site.Params.rssFullContent -}}
+            {{- $content = safeHTML (.Content | html) -}}
+        {{- end -}}
+        <item>
+        <title>{{ .Title }}</title>
+        <link>{{ .Permalink }}</link>
+        <pubDate>{{ .Date.Format "Mon, 02 Jan 2006 15:04:05 -0700" | safeHTML }}</pubDate>
+        {{ with .Site.Author.email }}<author>{{.}}{{ with $.Site.Author.name }} ({{.}}){{end}}</author>{{end}}
+        <guid>{{ .Permalink }}</guid>
+        <description>
+            {{- $image := partial "helper/image" (dict "Context" . "Type" "rss") -}}
+            {{- if $image.exists -}}
+                {{ "<" | html }}img src="{{ $image.permalink | absURL }}" alt="Featured image of post {{ .Title }}" {{ "/>" | html}}
+            {{- end -}}{{ $content }}</description>
+        </item>
+        {{ end }}
+    </channel>
+</rss>

+ 46 - 0
themes/hugo-theme-stack/layouts/_default/single.html

@@ -0,0 +1,46 @@
+{{ define "body-class" }}
+    article-page
+    {{/* 
+        Enable the right sidebar if
+            - Widget different from 'TOC' is enabled
+            - TOC is enabled and not empty
+    */}}
+    {{- $HasWidgetNotTOC := false -}}
+    {{- $TOCWidgetEnabled := false -}}
+    {{- range .Site.Params.widgets.page -}}
+        {{- if ne .type "toc" -}}
+            {{ $HasWidgetNotTOC = true -}}
+        {{- else -}}
+            {{ $TOCWidgetEnabled = true -}}
+        {{- end -}}
+    {{- end -}}
+
+    {{- $TOCManuallyDisabled := eq .Params.toc false -}}
+    {{- $TOCEnabled := and (not $TOCManuallyDisabled) $TOCWidgetEnabled -}}
+    {{- $hasTOC := ge (len .TableOfContents) 100 -}}
+    {{- .Scratch.Set "TOCEnabled" (and $TOCEnabled $hasTOC) -}}
+    
+    {{- .Scratch.Set "hasWidget" (or $HasWidgetNotTOC (and $TOCEnabled $hasTOC)) -}}
+{{ end }}
+
+{{ define "main" }}
+    {{ partial "article/article.html" . }}
+
+    {{ if .Params.links }}
+        {{ partial "article/components/links" . }}
+    {{ end }}
+
+    {{ partial "article/components/related-contents" . }}
+     
+    {{ if not (eq .Params.comments false) }}
+        {{ partial "comments/include" . }}
+    {{ end }}
+
+    {{ partialCached "footer/footer" . }}
+
+    {{ partialCached "article/components/photoswipe" . }}
+{{ end }}
+
+{{ define "right-sidebar" }}
+    {{ if .Scratch.Get "hasWidget" }}{{ partial "sidebar/right.html" (dict "Context" . "Scope" "page") }}{{ end}}
+{{ end }}

+ 19 - 0
themes/hugo-theme-stack/layouts/index.html

@@ -0,0 +1,19 @@
+{{ define "main" }}
+    {{ $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections }}
+    {{ $notHidden := where .Site.RegularPages "Params.hidden" "!=" true }}
+    {{ $filtered := ($pages | intersect $notHidden) }}
+    {{ $pag := .Paginate ($filtered) }}
+
+    <section class="article-list">
+        {{ range $index, $element := $pag.Pages }}
+            {{ partial "article-list/default" . }}
+        {{ end }}
+    </section>
+
+    {{- partial "pagination.html" . -}}
+    {{- partial "footer/footer" . -}}
+{{ end }}
+
+{{ define "right-sidebar" }}
+    {{ partial "sidebar/right.html" (dict "Context" . "Scope" "homepage") }}
+{{ end }}

+ 33 - 0
themes/hugo-theme-stack/layouts/page/search.html

@@ -0,0 +1,33 @@
+{{ define "body-class" }}template-search{{ end }}
+{{ define "head" }}
+    {{- with .OutputFormats.Get "json" -}} 
+        <link rel="preload" href="{{ .Permalink }}" as="fetch" crossorigin="anonymous">
+    {{- end -}}
+{{ end }}
+{{ define "main" }}
+<form action="{{ .Permalink }}" class="search-form"{{ with .OutputFormats.Get "json" -}} data-json="{{ .Permalink }}"{{- end }}>
+    <p>
+        <label>{{ T "search.title" }}</label>
+        <input name="keyword" placeholder="{{ T `search.placeholder` }}" />
+    </p>
+
+    <button title="{{ T `search.title` }}">
+        {{ partial "helper/icon" "search" }}
+    </button>
+</form>
+
+<div class="search-result">
+    <h3 class="search-result--title section-title"></h3>
+    <div class="search-result--list article-list--compact"></div>
+</div>
+
+<script>
+    window.searchResultTitleTemplate = "{{ T `search.resultTitle` }}"
+</script>
+
+{{- $opts := dict "minify" hugo.IsProduction "JSXFactory" "createElement" -}}
+{{- $searchScript := resources.Get "ts/search.tsx" | js.Build $opts -}}
+<script type="text/javascript" src="{{ $searchScript.RelPermalink }}" defer></script>
+
+{{ partialCached "footer/footer" . }}
+{{ end }}

+ 26 - 0
themes/hugo-theme-stack/layouts/page/search.json

@@ -0,0 +1,26 @@
+{{- $pages := where .Site.RegularPages "Type" "in" .Site.Params.mainSections -}}
+{{- $notHidden := where .Site.RegularPages "Params.hidden" "!=" true -}}
+{{- $filtered := ($pages | intersect $notHidden) -}}
+
+{{- $result := slice -}}
+
+{{- range $filtered -}}
+    {{- $data := dict "title" .Title "date" .Date "permalink" .Permalink "content" (.Plain) -}}
+
+    {{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}}
+    {{- if $image.exists -}}
+        {{- $imagePermalink := "" -}}
+        {{- if and $image.resource (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
+            {{- $thumbnail := $image.resource.Fill "120x120" -}}
+            {{- $imagePermalink = (absURL $thumbnail.Permalink) -}}
+        {{- else -}}
+            {{- $imagePermalink = $image.permalink -}}
+        {{- end -}}
+        
+        {{- $data = merge $data (dict "image" (absURL $imagePermalink)) -}}
+    {{- end -}}
+
+    {{- $result = $result | append $data -}}
+{{- end -}}
+
+{{ jsonify $result }}

+ 40 - 0
themes/hugo-theme-stack/layouts/partials/article-list/compact.html

@@ -0,0 +1,40 @@
+<article>
+    <a href="{{ .RelPermalink }}">
+        <div class="article-details">
+            <h2 class="article-title">
+                {{- .Title -}}
+            </h2>
+            <footer class="article-time">
+                <time datetime='{{ .Date.Format "2006-01-02T15:04:05Z07:00" }}'>
+                    {{- .Date.Format (or .Site.Params.dateFormat.published "Jan 02, 2006") -}}
+                </time>
+            </footer>
+        </div>
+
+        {{- $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" -}}
+        {{ if $image.exists }}
+            <div class="article-image">
+                {{ if $image.resource }}
+                    {{- $Permalink := $image.resource.RelPermalink -}}
+                    {{- $Width := $image.resource.Width -}}
+                    {{- $Height := $image.resource.Height -}}
+
+                    {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
+                        {{- $thumbnail := $image.resource.Fill "120x120" -}}
+                        {{- $Permalink = $thumbnail.RelPermalink -}}
+                        {{- $Width = $thumbnail.Width -}}
+                        {{- $Height = $thumbnail.Height -}}
+                    {{- end -}}
+                    
+                    <img src="{{ $Permalink }}" 
+                        width="{{ $Width }}"
+                        height="{{ $Height }}" 
+                        alt="{{ .Title }}"
+                        loading="lazy">
+                {{ else }}
+                    <img src="{{ $image.permalink }}" loading="lazy" alt="Featured image of post {{ .Title }}" />
+                {{ end }}
+            </div>
+        {{ end }}
+    </a>
+</article>

+ 4 - 0
themes/hugo-theme-stack/layouts/partials/article-list/default.html

@@ -0,0 +1,4 @@
+{{ $image := partialCached "helper/image" (dict "Context" . "Type" "articleList") .RelPermalink "articleList" }}
+<article class="{{ if $image.exists }}has-image{{ end }}">
+    {{ partial "article/components/header" . }}
+</article>

+ 39 - 0
themes/hugo-theme-stack/layouts/partials/article-list/tile.html

@@ -0,0 +1,39 @@
+{{ $image := partialCached "helper/image" (dict "Context" .context "Type" .Type) .context.RelPermalink .Type }}
+<article class="{{ if $image.exists }}has-image{{ end }}">
+    <a href="{{ .context.RelPermalink }}">
+        
+        {{ if $image.exists }}
+            <div class="article-image">
+                {{ if $image.resource }}
+                    {{- $imageRaw := $image.resource | resources.Fingerprint "md5" -}}
+                    {{- $Permalink := $imageRaw.RelPermalink -}}
+                    {{- $Width := $imageRaw.Width -}}
+                    {{- $Height := $imageRaw.Height -}}
+
+                    {{- if (default true .Page.Site.Params.imageProcessing.cover.enabled) -}}
+                        {{- $thumbnail := $imageRaw.Fill .size -}}
+                        {{- $Permalink = $thumbnail.RelPermalink -}}
+                        {{- $Width = $thumbnail.Width -}}
+                        {{- $Height = $thumbnail.Height -}}
+                    {{- end -}}
+                    
+                    <img src="{{ $Permalink }}" 
+                        width="{{ $Width }}" 
+                        height="{{ $Height }}" 
+                        loading="lazy"
+                        alt="Featured image of post {{ .context.Title }}"
+                        {{ with .context.Slug }}data-key="{{ . }}" {{ end }}
+                        data-hash="{{ $imageRaw.Data.Integrity }}">
+                {{ else }}
+                    <img src="{{ $image.permalink }}" loading="lazy" data-key="{{ .context.Slug }}" data-hash="{{ $image.permalink }}"/>
+                {{ end }}
+            </div>
+        {{ end }}
+
+        <div class="article-details">
+            <h2 class="article-title">
+                {{- .context.Title -}}
+            </h2>
+        </div>
+    </a>
+</article>

Algunos archivos no se mostraron porque demasiados archivos cambiaron en este cambio