+
+{{ end }}
{{ if not .Params.hideReply }}
{{ with .Site.Params.author.email }}
diff --git a/static/upvote.js b/static/upvote.js
new file mode 100644
index 0000000..5abf255
--- /dev/null
+++ b/static/upvote.js
@@ -0,0 +1,37 @@
+(async function () {
+ // Define the slug
+ const slug = location.pathname.replace(/^\/|\/$/g, "").replaceAll("/", "-");
+ const API = "http://localhost:3000";
+
+ const btn = document.getElementById("upvote-btn");
+ const count = document.getElementById("upvote-count");
+
+ // On load fetch curent vote count and whether the button should be clicked on or off
+ const res = await fetch(`${API}/posts/${slug}/votes`, {
+ credentials: "include",
+ });
+ const data = await res.json();
+
+ count.textContent = data.vote_count;
+ if (data.voted) btn.setAttribute("voted", true);
+
+ btn.addEventListener("click", async () => {
+ const alreadyVoted = btn.hasAttribute("voted");
+
+ const method = alreadyVoted ? "DELETE" : "POST";
+ const r = await fetch(`${API}/posts/${slug}/vote`, {
+ method,
+ credentials: "include",
+ });
+
+ if (r.ok) {
+ const updated = await fetch(`${API}/posts/${slug}/votes`, {
+ credentials: "include",
+ });
+ const d = await updated.json();
+ count.textContent = d.vote_count;
+ if (d.voted) btn.setAttribute("voted", false);
+ else btn.removeAttribute("voted");
+ }
+ });
+})();
From d8834d83b781837986c9f678a8bfdfd0b79287e7 Mon Sep 17 00:00:00 2001
From: Alex Selimov
Date: Fri, 20 Mar 2026 00:01:45 -0400
Subject: [PATCH 2/9] Template api url
---
layouts/_default/single.html | 1 +
static/upvote.js | 2 +-
2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index 7a5fd7b..9b68f55 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -25,6 +25,7 @@
—
+
{{ end }}
{{ if not .Params.hideReply }}
diff --git a/static/upvote.js b/static/upvote.js
index 5abf255..131da67 100644
--- a/static/upvote.js
+++ b/static/upvote.js
@@ -1,7 +1,7 @@
(async function () {
// Define the slug
const slug = location.pathname.replace(/^\/|\/$/g, "").replaceAll("/", "-");
- const API = "http://localhost:3000";
+ const API = window.UPVOTE_API;
const btn = document.getElementById("upvote-btn");
const count = document.getElementById("upvote-count");
From 460fe6a820299d0f41064bae0504ea380eaa74cd Mon Sep 17 00:00:00 2001
From: Alex Selimov
Date: Fri, 20 Mar 2026 08:45:33 -0400
Subject: [PATCH 3/9] Fix button color swapping
---
assets/auto-theme.css | 4 ++++
assets/herman.css | 2 +-
2 files changed, 5 insertions(+), 1 deletion(-)
diff --git a/assets/auto-theme.css b/assets/auto-theme.css
index dce83e8..ee3f9a6 100644
--- a/assets/auto-theme.css
+++ b/assets/auto-theme.css
@@ -104,6 +104,10 @@ li {
}
@media (prefers-color-scheme: light) {
+ #upvote-btn[voted] {
+ color: #61ABDA;
+ }
+
body {
background-color: #ffffff;
color: #1f2328;
diff --git a/assets/herman.css b/assets/herman.css
index 8a5ca66..277fba2 100644
--- a/assets/herman.css
+++ b/assets/herman.css
@@ -203,7 +203,7 @@ td {
background: none;
border: none;
cursor: pointer;
- color: var(--color-light);
+ color: currentColor;
font-size: calc(var(--size) * 4);
line-height: 0.8;
padding: 0;
From 41c217030c8f93fb71d2c14e7a6800e0e25a526c Mon Sep 17 00:00:00 2001
From: Alex Selimov
Date: Fri, 20 Mar 2026 09:00:56 -0400
Subject: [PATCH 4/9] Update README and exampleSite
---
README.md | 21 ++++++++++++++++++---
exampleSite/hugo.toml | 11 +++++++++++
theme.toml | 28 ++++++++++++++--------------
3 files changed, 43 insertions(+), 17 deletions(-)
diff --git a/README.md b/README.md
index a084e50..47ede3d 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,6 @@
-# ᕦʕ •ᴥ•ʔᕤ Bear Cub
+# ᕦʕ •ᴥ•ʔᕤ Voting Bear Cub
-[](https://github.com/clente/hugo-bearcub/actions/workflows/gh-pages.yml)
-[](https://github.com/clente/hugo-bearcub/blob/main/LICENSE)
+[](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main/LICENSE)
## Overview
@@ -9,6 +8,22 @@
Blog](https://bearblog.dev) and [Hugo Bear
Blog](https://github.com/janraasch/hugo-bearblog).
+This is a fork of [Bear Cub](https://github.com/clente/hugo-bearcub) with the
+following additions:
+
+- **Anonymous post upvoting**: each post can display an upvote button backed by
+ a configurable API endpoint. Votes are tracked anonymously via cookies so
+ readers can toggle their vote without creating an account. Enable with
+ `upvotes = true` and set `upvoteApi` to your API base URL.
+- **Redesigned header**: the site header now uses a mesh SVG background for a
+ more distinctive look.
+- **GitHub light/dark color scheme**: the syntax highlighting palette has been
+ replaced with colors matching the GitHub light and dark themes.
+- **Herman CSS upvote styles**: upvote button styles are bundled into the Herman
+ CSS variant so the button integrates cleanly with the existing theme.
+- **Configurable index post count**: use `postsToShowOnIndex` in `[params]` to
+ control how many posts appear on the home page (defaults to 3).
+
**Bear Cub** takes care of speed and optimization, so you can focus on writing
good content. It is free, multilingual, optimized for search engines,
no-nonsense, responsive, light, and fast. Really fast.
diff --git a/exampleSite/hugo.toml b/exampleSite/hugo.toml
index 04690c0..939c13d 100644
--- a/exampleSite/hugo.toml
+++ b/exampleSite/hugo.toml
@@ -75,6 +75,17 @@ enableRobotsTXT = true
# information see layouts/partials/social_card.html
generateSocialCard = true
+ # The number of posts to show on the index page. Defaults to 3 if not set.
+ postsToShowOnIndex = 3
+
+ # Enable upvoting on posts. When set to true, each post will display an upvote
+ # button that calls the API endpoint specified by `upvoteApi`.
+ upvotes = false
+
+ # The base URL of the upvote API. Only used when `upvotes = true`.
+ # The API is expected to handle GET and POST requests at {upvoteApi}/{post-path}.
+ upvoteApi = "https://example.com/api"
+
# Social media. Delete any item you aren't using to make sure it won't show up
# in your website's metadata.
[params.social]
diff --git a/theme.toml b/theme.toml
index 602a351..7f6b154 100644
--- a/theme.toml
+++ b/theme.toml
@@ -1,29 +1,29 @@
-name = "Bear Cub"
+name = "Voting Bear Cub"
license = "MIT"
licenselink = "https://github.com/clente/hugo-bearcub/blob/master/LICENSE"
-description = "A lightweight Hugo theme based on Bear Blog and Hugo Bear Blog. It is free, multilingual, optimized for search engines, no-nonsense, responsive, light, and fast. Really fast."
+description = """A lightweight Hugo theme based on Bear Blog and Hugo Bear Blog. It is free,
+multilingual, optimized for search engines, no-nonsense, responsive, light, and fast. Really fast.
+This version adds an upvote button that connects to an
+https://forge.alexselimov.com/aselimov/upvoters server to handle anonymous upvotes.
+"""
# The home page of the theme, where the source can be found.
-homepage = "https://github.com/clente/hugo-bearcub"
-
-# If you have a running demo of the theme.
-demosite = "https://clente.github.io/hugo-bearcub"
+homepage = "https://forge.alexselimov.com/aselimov/hugo-bearcub"
tags = ["blog", "responsive", "minimal", "personal", "dark", "multilingual"]
features = ["favicon", "seo", "no javascript", "rss", "social cards"]
# If the theme has a single author
[author]
- name = "Caio Lente"
- homepage = "https://lente.dev"
+name = "Alex Selimov"
+homepage = "https://alexselimov.com"
-# If porting an existing theme
[original]
- author = "Jan Raasch"
- homepage = "https://www.janraasch.com"
- repo = "https://github.com/janraasch/hugo-bearblog"
+name = "Caio Lente"
+homepage = "https://lente.dev"
+repo = "https://github.com/clente/hugo-bearcub"
# Hugo versions the theme supports
[module]
- [module.hugoVersion]
- min = "0.90"
+[module.hugoVersion]
+min = "0.90"
From 665da9bec6e9ab22792552d356556e6f76f5e4fb Mon Sep 17 00:00:00 2001
From: Alex Selimov
Date: Fri, 20 Mar 2026 09:06:40 -0400
Subject: [PATCH 5/9] Small README updates
---
README.md | 11 ++++++-----
1 file changed, 6 insertions(+), 5 deletions(-)
diff --git a/README.md b/README.md
index 47ede3d..faf5e82 100644
--- a/README.md
+++ b/README.md
@@ -1,13 +1,9 @@
-# ᕦʕ •ᴥ•ʔᕤ Voting Bear Cub
+# ʕ •ᴥ•ʔつ🗳️ Voting Bear Cub
[](https://forge.alexselimov.com/aselimov/hugo-bearcub/src/branch/main/LICENSE)
## Overview
-🐻 A lightweight [Hugo](https://gohugo.io/) theme based on [Bear
-Blog](https://bearblog.dev) and [Hugo Bear
-Blog](https://github.com/janraasch/hugo-bearblog).
-
This is a fork of [Bear Cub](https://github.com/clente/hugo-bearcub) with the
following additions:
@@ -44,6 +40,11 @@ To finish off, append a line to the site configuration file:
echo 'theme = "hugo-bearcub"' >> hugo.toml
```
+### Anonymous voting
+
+To easily setup the anonymous voting you need to run an [upvoters](https://forge.alexselimov.com/aselimov/upvoters) server.
+You should be able to ship your own solution as well.
+
## Features
Like [Bear Blog](https://bearblog.dev), this theme:
From ecceb7afa9e0973ffc98336474eefe2544b60629 Mon Sep 17 00:00:00 2001
From: Alex Selimov
Date: Fri, 20 Mar 2026 13:00:23 -0400
Subject: [PATCH 6/9] Add option to hide upvotes
---
layouts/_default/single.html | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/layouts/_default/single.html b/layouts/_default/single.html
index 9b68f55..d9a1118 100644
--- a/layouts/_default/single.html
+++ b/layouts/_default/single.html
@@ -18,7 +18,7 @@
#{{ lower .LinkTitle }}
{{ end }}
-{{ if .Site.Params.upvotes }}
+{{ if and .Site.Params.upvotes (not .Params.hideUpvotes) }}