+
+ Latest
+
+
+
+
+
+
-
Thoughts
-
- {thoughts.length}
-
+
Thoughts
+
+ {thoughts.length}
+
+
+
-
-
- {/* Text content */}
- {Content && }
+
+
+
+ {/* Text content */}
+ {Content && }
- {/* Images grid */}
- {latest.data.images && latest.data.images.length > 0 && (
-
- {latest.data.images.map((img: any, index: number) => (
-
- ))}
-
- )}
-
+ {/* Images grid */}
+ {latest.data.images && latest.data.images.length > 0 && (
+
+ {latest.data.images.map((img: any, index: number) => (
+
+ ))}
+
+ )}
+
+
{/* Time & tags below content */}
@@ -58,15 +81,7 @@ const Content = latest ? (await render(latest)).Content : null
)}
-
-
)}
+
+{hasPreviewImages &&
}
diff --git a/src/components/icons/Bot.astro b/src/components/icons/Bot.astro
new file mode 100644
index 0000000..8beb936
--- /dev/null
+++ b/src/components/icons/Bot.astro
@@ -0,0 +1,25 @@
+---
+interface Props {
+ class?: string
+}
+const { class: className = '' } = Astro.props
+---
+
+
diff --git a/src/components/icons/Lightbulb.astro b/src/components/icons/Lightbulb.astro
new file mode 100644
index 0000000..610426b
--- /dev/null
+++ b/src/components/icons/Lightbulb.astro
@@ -0,0 +1,25 @@
+---
+interface Props {
+ class?: string
+}
+const { class: className = '' } = Astro.props
+---
+
+
diff --git a/src/components/icons/Sparkles.astro b/src/components/icons/Sparkles.astro
new file mode 100644
index 0000000..aab47dd
--- /dev/null
+++ b/src/components/icons/Sparkles.astro
@@ -0,0 +1,26 @@
+---
+interface Props {
+ class?: string
+}
+const { class: className = '' } = Astro.props
+---
+
+
diff --git a/src/content/posts/2025/create-watermark-for-web.mdx b/src/content/posts/2024/create-watermark-for-web.mdx
similarity index 99%
rename from src/content/posts/2025/create-watermark-for-web.mdx
rename to src/content/posts/2024/create-watermark-for-web.mdx
index 3929e6d..5211c3a 100644
--- a/src/content/posts/2025/create-watermark-for-web.mdx
+++ b/src/content/posts/2024/create-watermark-for-web.mdx
@@ -1,7 +1,6 @@
---
title: 给 Web 端网站添加水印
description: 通过 canvas 给 Web 端网站一个透明度很高的水印,然后通过 ffmpeg 调整曲线是的水印显示出来
-slug: 'create-watermark-for-web'
tags: ["rails", "watermark"]
date: 2024-06-03
---
diff --git a/src/content/posts/2025/docker-guide.mdx b/src/content/posts/2024/docker-guide.mdx
similarity index 99%
rename from src/content/posts/2025/docker-guide.mdx
rename to src/content/posts/2024/docker-guide.mdx
index 360fde6..c5beb7f 100644
--- a/src/content/posts/2025/docker-guide.mdx
+++ b/src/content/posts/2024/docker-guide.mdx
@@ -1,7 +1,6 @@
---
title: Docker 入门指南
description: Docker 入门指南
-slug: 'docker-guide'
image:
url: 'https://docs.astro.build/assets/full-logo-light.png'
alt: 'The full Astro logo.'
diff --git a/src/content/posts/2025/docker-sync-image-to-private-registry.mdx b/src/content/posts/2024/docker-sync-image-to-private-registry.mdx
similarity index 94%
rename from src/content/posts/2025/docker-sync-image-to-private-registry.mdx
rename to src/content/posts/2024/docker-sync-image-to-private-registry.mdx
index 9017f78..8504c72 100644
--- a/src/content/posts/2025/docker-sync-image-to-private-registry.mdx
+++ b/src/content/posts/2024/docker-sync-image-to-private-registry.mdx
@@ -2,7 +2,6 @@
draft: true
title: 将 Docker 镜像异步推送到私有仓库
description: 通过 GitHub Actions 将 Docker 镜像异步推送到私有仓库
-slug: 'docker-async-image-to-private-registry'
tags: ["docker", "docker registry"]
date: 2024-11-06
---
diff --git a/src/content/posts/2025/explore-rails-with-vue.mdx b/src/content/posts/2024/explore-rails-with-vue.mdx
similarity index 89%
rename from src/content/posts/2025/explore-rails-with-vue.mdx
rename to src/content/posts/2024/explore-rails-with-vue.mdx
index 7598ea6..b65a002 100644
--- a/src/content/posts/2025/explore-rails-with-vue.mdx
+++ b/src/content/posts/2024/explore-rails-with-vue.mdx
@@ -2,7 +2,6 @@
draft: true
title: 探索 Rails 与 Vue 的结合
description: 探索 Rails 与 Vue 的结合
-slug: 'explore-rails-with-vue'
tags: ["docker", "SASS"]
date: 2024-09-25
---
diff --git a/src/content/posts/2025/hi.mdx b/src/content/posts/2024/hi.mdx
similarity index 98%
rename from src/content/posts/2025/hi.mdx
rename to src/content/posts/2024/hi.mdx
index a24e18c..2315bfe 100644
--- a/src/content/posts/2025/hi.mdx
+++ b/src/content/posts/2024/hi.mdx
@@ -1,7 +1,6 @@
---
title: 'Hi, Astro!'
description: 'xxx'
-slug: 'hi'
image:
url: 'https://docs.astro.build/assets/full-logo.png'
alt: 'The full Astro logo.'
diff --git a/src/content/posts/2025/setup-email-for-your-application.mdx b/src/content/posts/2024/setup-email-for-your-application.mdx
similarity index 97%
rename from src/content/posts/2025/setup-email-for-your-application.mdx
rename to src/content/posts/2024/setup-email-for-your-application.mdx
index acc026e..7ee33b7 100644
--- a/src/content/posts/2025/setup-email-for-your-application.mdx
+++ b/src/content/posts/2024/setup-email-for-your-application.mdx
@@ -1,7 +1,6 @@
---
title: 应用程序邮箱的设置
description: 通过腾讯企业邮箱设置,实现域名绑定和邮箱管理,并进行测试。
-slug: 'setup-email-for-your-application'
tags: ["email", "SASS"]
date: 2024-08-27
---
diff --git a/src/content/posts/2025/setup-mac-mini.md b/src/content/posts/2024/setup-mac-mini.md
similarity index 98%
rename from src/content/posts/2025/setup-mac-mini.md
rename to src/content/posts/2024/setup-mac-mini.md
index 21d57b2..3bfcabf 100644
--- a/src/content/posts/2025/setup-mac-mini.md
+++ b/src/content/posts/2024/setup-mac-mini.md
@@ -1,7 +1,6 @@
---
title: 'Setup Mac Mini'
description: 'WIP'
-slug: 'setup-mac-mini'
tags: ["mac", "mac mini"]
date: 2024-12-09
---
diff --git a/src/content/posts/2025/webchat-bot.mdx b/src/content/posts/2024/webchat-bot.mdx
similarity index 99%
rename from src/content/posts/2025/webchat-bot.mdx
rename to src/content/posts/2024/webchat-bot.mdx
index f1b97a8..2bd3348 100644
--- a/src/content/posts/2025/webchat-bot.mdx
+++ b/src/content/posts/2024/webchat-bot.mdx
@@ -1,7 +1,6 @@
---
title: 'WeChat Bot!'
description: 'Wechat bot'
-slug: 'wechat-bot'
tags: ["WeChat", "Robot"]
date: 2024-11-15
---
diff --git a/src/content/posts/2025/deepseek.md b/src/content/posts/2025/deepseek.md
index 2d4753c..9b4eca8 100644
--- a/src/content/posts/2025/deepseek.md
+++ b/src/content/posts/2025/deepseek.md
@@ -1,7 +1,6 @@
---
title: 'Hi, DeepSeek!'
description: 'An introduction to the DeepSeek large language model, including its features, use cases, and self-hosting deployment guide.'
-slug: 'deepseek'
tags: ["DeepSeek", "LLM", "AI", "Ollama"]
date: 2025-02-18
---
@@ -138,10 +137,10 @@ https://github.com/deepseek-ai/DeepSeek-R1?tab=readme-ov-file#2-model-summary
提问:根据附件 `financial_report.xlsx`, 张三的营业额是多少?
-- 上传附件 `compony_policy.docx`
+- 上传附件 `company_policy.docx`
提问:根据文档中的描述,请假流程是?带薪休假有多少天?
-提问:根据附件中 `compony_policy.docx`, 生成一句话总结公司政策。
+提问:根据附件中 `company_policy.docx`, 生成一句话总结公司政策。
### Prompt
@@ -229,10 +228,25 @@ async function main(message) {
console.log(completion.choices[0].message.content)
}
-main('DeepSeek 是什么?做一个简短的介绍,他有那些优势和那些不足?')
+main('DeepSeek 是什么?做一个简短的介绍,它有哪些优势和哪些不足?')
```
-JSON 示例?
+### JSON 请求示例
+
+如果不使用 SDK,也可以直接向 DeepSeek 的 OpenAI-compatible API 发送 JSON 请求:
+
+```bash
+curl https://api.deepseek.com/chat/completions \
+ -H "Content-Type: application/json" \
+ -H "Authorization: Bearer $DEEPSEEK_API_KEY" \
+ -d '{
+ "model": "deepseek-chat",
+ "messages": [
+ { "role": "system", "content": "You are a helpful assistant." },
+ { "role": "user", "content": "DeepSeek 是什么?" }
+ ]
+ }'
+```
## Ollama 接口调用
diff --git a/src/content/posts/2026/agent-skills.mdx b/src/content/posts/2026/agent-skills.mdx
index 72d7443..a264920 100644
--- a/src/content/posts/2026/agent-skills.mdx
+++ b/src/content/posts/2026/agent-skills.mdx
@@ -202,7 +202,7 @@ Agent Skill 是 **"你会干什么"**,而 MCP 是 **"你如何与外界沟通
- [OpenCode Skills](https://opencode.ai/docs/skills/#place-files)
- [Cursor Skills](https://cursor.com/docs/context/skills#skill-directories)
-- [Antigravity Skills](https://antigravity.google/docs/skills#where-skills-live)
+- [Antigravity Skills](https://antigravity.google/docs/skills)
### 跨平台工具:skills
diff --git a/src/content/posts/2026/app-strava.mdx b/src/content/posts/2026/app-strava.mdx
index 82e7c77..be5ac34 100644
--- a/src/content/posts/2026/app-strava.mdx
+++ b/src/content/posts/2026/app-strava.mdx
@@ -1,6 +1,6 @@
---
title: 'Strava Activities Sync'
-description: ''
+description: '用 Strava API 同步运动数据到个人网站,并借助 AI 快速完成 Workouts 卡片、详情页和 GitHub Action 自动同步。'
tags: ['strava', 'AI']
date: 2026-03-18
---
diff --git a/src/content/posts/2026/vibe-coding.mdx b/src/content/posts/2026/vibe-coding.mdx
index 2b54429..6225fe5 100644
--- a/src/content/posts/2026/vibe-coding.mdx
+++ b/src/content/posts/2026/vibe-coding.mdx
@@ -370,7 +370,7 @@ claude
"text": "
\nThe following skills are available for use with the Skill tool:\n\n- update-config: Use this skill to configure the Claude Code harness via settings.json. Automated behaviors (\"from now on when X\", \"each time X\", \"whenever X\", \"before/after X\") require hooks configured in settings.json - the harness executes these, not Claude, so memory/preferences cannot fulfill them. Also use for: permissions (\"allow X\", \"add permission\", \"move permission to\"), env vars (\"set X=Y\"), hook troubleshooting, or any changes to settings.json/settings.local.json files. Examples: \"allow npm commands\", \"add bq permission to global settings\", \"move permission to user settings\", \"set DEBUG=true\", \"when claude stops show X\". For simple settings like theme/model, use Config tool.\n- simplify: Review changed code for reuse, quality, and efficiency, then fix any issues found.\n- loop: Run a prompt or slash command on a recurring interval (e.g. /loop 5m /foo, defaults to 10m) - When the user wants to set up a recurring task, poll for status, or run something repeatedly on an interval (e.g. \"check the deploy every 5 minutes\", \"keep running /babysit-prs\"). Do NOT invoke for one-off tasks.\n- claude-api: Build apps with the Claude API or Anthropic SDK.\nTRIGGER when: code imports `anthropic`/`@anthropic-ai/sdk`/`claude_agent_sdk`, or user asks to use Claude API, Anthropic SDKs, or Agent SDK.\nDO NOT TRIGGER when: code imports `openai`/other AI SDK, general programming, or ML/data-science tasks.\n- article-extractor: Extract clean article content from URLs (blog posts, articles, tutorials) and save as readable text. Use when user wants to download, extract, or save an article/blog post from a URL without ads, navigation, or clutter.\n- system-macro: Automate desktop operations via keyboard/mouse simulation and macros on Linux and macOS. Current support wechat-app send message\n- find-skills: Helps users discover and install agent skills when they ask questions like \"how do I do X\", \"find a skill for X\", \"is there a skill that can...\", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.\n- slidev: Create and present web-based slides for developers using Markdown, Vue components, code highlighting, animations, and interactive features. Use when building technical presentations, conference talks, or teaching materials.\n- git-commit: Generate a concise, human-readable git commit message from the staged diff with a leading emoji, then run `git commit` to complete the commit. Use when the user wants to commit changes, asks for a commit message, or needs a summary of staged changes. Start by running `./scripts/git-diff.sh`, which reads the staged diff while excluding common lock files such as `pnpm-lock.yaml`, `package-lock.json`, `yarn.lock`, `bun.lockb`, `Gemfile.lock`, `uv.lock`, `composer.lock`, `go.sum`, and `Cargo.lock`.\n- gh-pages: Use when deploying a repository to GitHub Pages from the `main` branch and guiding users to complete required setup in GitHub repository Settings.\n- agent-browser: Automates browser interactions for web testing, form filling, screenshots, and data extraction. Use when the user needs to navigate websites, interact with web pages, fill forms, take screenshots, test web applications, or extract information from web pages.\n- skill-creator: Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.\n- crawl-x: Extract tweet/post text and long-form article content from X/Twitter status URLs and save it as Markdown. Use when the user provides an `x.com` or `twitter.com` status link and wants the tweet body, article body, author metadata, or media links captured locally without browser login.\n\n"
}, {
"type": "text",
- "text": "
\nAs you answer the user's questions, you can use the following context:\n# claudeMd\nCodebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.\n\nContents of /home/yule/Sides/yuler.dev/CLAUDE.md (project instructions, checked into the codebase):\n\n# Agent instructions (yuler.dev)\n\nPersonal site at https://yuler.dev — Astro 5, MDX, Tailwind CSS 4, TypeScript. Package manager: **pnpm**.\n\n## Setup & Commands\n\n- Use the Node.js version specified in `.nvmrc`.\n- Run `pnpm install` and `pnpm dev` to start the local dev server.\n- Use `pnpm create:post` to quickly scaffold a new post.\n- Run `pnpm autocorrect` to format post content files.\n- Use `pnpm export:pdf` to export PDF for a specific post.\n- Run `pnpm sync:strava:activities` to sync Strava activities to local data.\n\n## Git Commit\n\nEvery commit needs to invoke the `/git-commit` skill.\n# currentDate\nToday's date is 2026-03-27.\n\n IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.\n\n\n"
+ "text": "
\nAs you answer the user's questions, you can use the following context:\n# claudeMd\nCodebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.\n\nContents of /path/to/project/CLAUDE.md (project instructions, checked into the codebase):\n\n# Agent instructions (yuler.dev)\n\nPersonal site at https://yuler.dev — Astro 5, MDX, Tailwind CSS 4, TypeScript. Package manager: **pnpm**.\n\n## Setup & Commands\n\n- Use the Node.js version specified in `.nvmrc`.\n- Run `pnpm install` and `pnpm dev` to start the local dev server.\n- Use `pnpm create:post` to quickly scaffold a new post.\n- Run `pnpm autocorrect` to format post content files.\n- Use `pnpm export:pdf` to export PDF for a specific post.\n- Run `pnpm sync:strava:activities` to sync Strava activities to local data.\n\n## Git Commit\n\nEvery commit needs to invoke the `/git-commit` skill.\n# currentDate\nToday's date is 2026-03-27.\n\n IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task.\n\n\n"
}, {
"type": "text",
"text": "hi",
@@ -415,7 +415,7 @@ DO NOT TRIGGER when: code imports `openai`/other AI SDK, general programming, or
As you answer the user's questions, you can use the following context:
# claudeMd
Codebase and user instructions are shown below. Be sure to adhere to these instructions. IMPORTANT: These instructions OVERRIDE any default behavior and you MUST follow them exactly as written.
-Contents of /home/yule/Sides/yuler.dev/CLAUDE.md (project instructions, checked into the codebase):
+Contents of /path/to/project/CLAUDE.md (project instructions, checked into the codebase):
# Agent instructions (yuler.dev)
Personal site at https://yuler.dev — Astro 5, MDX, Tailwind CSS 4, TypeScript. Package manager: **pnpm**.
## Setup & Commands
diff --git a/src/pages/index.astro b/src/pages/index.astro
index e58447d..a06fdb9 100644
--- a/src/pages/index.astro
+++ b/src/pages/index.astro
@@ -1,4 +1,5 @@
---
+import AgentStuff from '../components/cards/AgentStuff.astro'
import Github from '../components/cards/Github.astro'
import Location from '../components/cards/Location.astro'
import Posts from '../components/cards/Posts.astro'
@@ -6,32 +7,45 @@ import Profile from '../components/cards/Profile.astro'
import Thoughts from '../components/cards/Thoughts.astro'
import Workouts from '../components/cards/Workouts.astro'
import X from '../components/cards/X.astro'
-import LightBox from '../components/LightBox.astro'
import Layout from '../layouts/Layout.astro'
---
-
-
+ {hasPreviewImages &&
}
diff --git a/src/pages/workouts/[id].astro b/src/pages/workouts/[id].astro
index 4c4d132..88930d5 100644
--- a/src/pages/workouts/[id].astro
+++ b/src/pages/workouts/[id].astro
@@ -33,7 +33,7 @@ const stats = [
{ label: 'Distance', value: formatDistanceMeters(activity.distance) },
{ label: 'Moving Time', value: formatMovingDuration(activity.moving_time, 'detail') },
{ label: 'Elapsed Time', value: formatMovingDuration(activity.elapsed_time, 'detail') },
- { label: 'Pace / Speed', value: formatPaceOrSpeed(activity.sport_type, activity.average_speed) },
+ { label: 'Pace / Speed', value: formatPaceOrSpeed(activity.sport_type, activity.average_speed, activity.distance) },
{ label: 'Max Speed', value: formatMaxSpeedKmh(activity.max_speed) },
{ label: 'Elevation Gain', value: `${activity.total_elevation_gain}m` },
...(activity.has_heartrate
diff --git a/src/pages/workouts/index.astro b/src/pages/workouts/index.astro
index a27a59e..fe8b7ff 100644
--- a/src/pages/workouts/index.astro
+++ b/src/pages/workouts/index.astro
@@ -82,7 +82,7 @@ const showEmptyRangeState = Boolean(serverFilterRange && visibleInServerRange ==
@@ -102,7 +102,7 @@ const showEmptyRangeState = Boolean(serverFilterRange && visibleInServerRange ==
|
- {formatPaceOrSpeed(activity.sport_type, activity.average_speed)}
+ {formatPaceOrSpeed(activity.sport_type, activity.average_speed, activity.distance)}
diff --git a/src/utils/workout-display.test.ts b/src/utils/workout-display.test.ts
index 1256379..cc7e8ab 100644
--- a/src/utils/workout-display.test.ts
+++ b/src/utils/workout-display.test.ts
@@ -28,6 +28,14 @@ describe('workout-display', () => {
expect(formatPaceOrSpeed('Run', avgSpeed)).toBe(`1'00"/km`)
})
+ it('does not show pace for zero-distance indoor activities', () => {
+ expect(formatPaceOrSpeed('Badminton', 1.23, 0)).toBe('Indoor')
+ })
+
+ it('does not invent pace for other zero-distance activities', () => {
+ expect(formatPaceOrSpeed('Run', 1.23, 0)).toBe('No distance')
+ })
+
it('computes activity count heat levels', () => {
expect(dailyActivityCountHeatLevel(0)).toBe(0)
expect(dailyActivityCountHeatLevel(1)).toBe(1)
diff --git a/src/utils/workout-display.ts b/src/utils/workout-display.ts
index af3e5af..31dfb97 100644
--- a/src/utils/workout-display.ts
+++ b/src/utils/workout-display.ts
@@ -67,7 +67,11 @@ export function formatDistanceMetersCompact(meters: number): string {
return `${(meters / 1000).toFixed(2)}km`
}
-export function formatPaceOrSpeed(sportType: string, avgSpeed: number): string {
+const INDOOR_ZERO_DISTANCE_SPORTS = new Set(['Badminton', 'Yoga', 'WeightTraining'])
+
+export function formatPaceOrSpeed(sportType: string, avgSpeed: number, distanceMeters?: number): string {
+ if (distanceMeters !== undefined && distanceMeters <= 0)
+ return INDOOR_ZERO_DISTANCE_SPORTS.has(sportType) ? 'Indoor' : 'No distance'
if (avgSpeed === 0)
return '-'
if (sportType === 'Ride')