diff --git a/content/blog/2024-10-11_example_post.md b/content/blog/2024-10-11_example_post.md
index d82b8bf..8f33df4 100644
--- a/content/blog/2024-10-11_example_post.md
+++ b/content/blog/2024-10-11_example_post.md
@@ -158,3 +158,5 @@ You can also create `
` horizontal rule tags using `---` in markdown, like so
---
But these should be used sparingly, if at all.
+
+You can also use emojis inline from the hackclub slack like this :yay:! This is just done by writing `:emoji:` and it gets progressively enhanced with a bit of js as long as the emoji is in cachet!
diff --git a/sass/css/_emoji-inline.scss b/sass/css/_emoji-inline.scss
new file mode 100644
index 0000000..09988f5
--- /dev/null
+++ b/sass/css/_emoji-inline.scss
@@ -0,0 +1,32 @@
+.emoji-inline--wrapper {
+ vertical-align: baseline;
+ height: auto;
+ position: relative;
+ overflow: visible;
+ vertical-align: top;
+ object-fit: contain;
+ align-items: center;
+ display: inline-flex;
+ overflow: hidden;
+ width: 22px;
+ height: 22px;
+}
+
+.emoji-inline--wrapper img {
+ object-fit: contain;
+ position: absolute;
+ top: 50%;
+ overflow: hidden;
+ width: 22px !important;
+ height: 22px !important;
+ margin-top: -11px;
+ margin-left: 0 !important;
+ margin-right: 0 !important;
+ margin-bottom: 0 !important;
+ border: none !important;
+ border-radius: 0 !important;
+ padding: 0 !important;
+ opacity: 1 !important;
+ max-width: 22px !important;
+ display: inline !important;
+}
diff --git a/sass/css/main.scss b/sass/css/main.scss
index 304b64c..15f20a8 100644
--- a/sass/css/main.scss
+++ b/sass/css/main.scss
@@ -6,3 +6,4 @@
@use "copy-button";
@use "view-transitions";
+@use "emoji-inline";
diff --git a/static/js/emoji-replace.js b/static/js/emoji-replace.js
new file mode 100644
index 0000000..6f4c7d2
--- /dev/null
+++ b/static/js/emoji-replace.js
@@ -0,0 +1,65 @@
+document.addEventListener("DOMContentLoaded", () => {
+ const content = document.querySelector("main");
+ if (!content) return;
+
+ const walker = document.createTreeWalker(content, NodeFilter.SHOW_TEXT, {
+ acceptNode: (node) => {
+ // Skip code blocks, pre tags, and script/style tags
+ let parent = node.parentElement;
+ while (parent) {
+ const tag = parent.tagName.toLowerCase();
+ if (
+ tag === "code" ||
+ tag === "pre" ||
+ tag === "script" ||
+ tag === "style"
+ ) {
+ return NodeFilter.FILTER_REJECT;
+ }
+ parent = parent.parentElement;
+ }
+ return NodeFilter.FILTER_ACCEPT;
+ },
+ });
+
+ const nodesToReplace = [];
+ while (walker.nextNode()) {
+ const node = walker.currentNode;
+ if (/:[\w-]+:/.test(node.textContent)) {
+ nodesToReplace.push(node);
+ }
+ }
+
+ nodesToReplace.forEach((node) => {
+ const frag = document.createDocumentFragment();
+ const parts = node.textContent.split(/(:[\w-]+:)/);
+
+ parts.forEach((part) => {
+ if (/^:[\w-]+:$/.test(part)) {
+ const name = part.slice(1, -1);
+
+ const span = document.createElement("span");
+ span.className = "emoji-inline--wrapper";
+
+ const img = document.createElement("img");
+ img.src = `https://cachet.dunkirk.sh/emojis/${name}/r`;
+ img.alt = part;
+ img.className = "emoji-inline";
+ img.loading = "lazy";
+ img.setAttribute("aria-label", `${name} emoji`);
+
+ // Fallback: if image fails to load, show original text
+ img.onerror = () => {
+ span.replaceWith(document.createTextNode(part));
+ };
+
+ span.appendChild(img);
+ frag.appendChild(span);
+ } else if (part) {
+ frag.appendChild(document.createTextNode(part));
+ }
+ });
+
+ node.replaceWith(frag);
+ });
+});
diff --git a/templates/head.html b/templates/head.html
index 0a074bf..61b1339 100644
--- a/templates/head.html
+++ b/templates/head.html
@@ -86,6 +86,13 @@ base64=true) %}
defer
>
+{% set emojiJsHash = get_hash(path="js/emoji-replace.js", sha_type=256,
+base64=true) %}
+
+