29 Commits

Author SHA1 Message Date
4eb05cc372 Merge pull request 'merge beian' (#14) from beian into main
Reviewed-on: #14
2026-02-03 19:00:59 +08:00
jimleerx
110a2ced28 merge new code 2026-02-03 18:55:57 +08:00
jimleerx
760c70a7d5 update readme 2026-02-03 17:44:41 +08:00
jimleerx
8e5460c13a 增加readme 2026-02-03 17:42:35 +08:00
jimleerx
f849000b2a 更换头像与说明修改 2026-02-03 17:10:25 +08:00
jimleerx
4ed7091eb3 添加备案号 2026-01-23 00:26:03 +08:00
jimleerx
c5dea71f59 fix:妙妙屋描述 2026-01-22 21:06:55 +08:00
jimleerx
65db8be5f7 fix:z-index导致的元素覆盖 2026-01-22 11:24:01 +08:00
jimleerx
37db59cba5 去除旧字体 2026-01-20 18:16:16 +08:00
jimleerx
fd91f486d3 去除旧字体 2026-01-20 18:15:04 +08:00
jimleerx
1bd1e5e31a fix:z-num错误 2025-12-23 22:00:09 +08:00
jimleerx
d16b919464 调整topbar zindex 2025-12-12 15:26:32 +08:00
jimleerx
90d8d89f7b 修改项目域名,调整topbar zindex 2025-12-12 15:17:27 +08:00
jimleerx
546dde4923 统计脚本改为异步的 2025-11-12 10:38:46 +08:00
jimleerx
d72963d648 调整移动端格子顺序 2025-11-12 10:22:16 +08:00
ed5a976735 Merge pull request 'dev' (#13) from dev into main
Reviewed-on: #13
2025-11-12 00:56:16 +08:00
jimleerx
f4e7112d4c 修复断言错误 2025-11-12 00:55:22 +08:00
jimleerx
bc4155ce82 fix unuse import 2025-11-12 00:41:11 +08:00
jimleerx
f1bda537b4 隐藏光标 2025-11-12 00:39:45 +08:00
jimleerx
1b2f79f65c 增加光晕特效 2025-11-12 00:33:12 +08:00
jimleerx
62383aaf9e 给格子加上光晕特效 2025-11-12 00:18:29 +08:00
jimleerx
dcc5654603 修复改为hexagon实现后遮挡背景图的问题 2025-11-12 00:17:50 +08:00
jimleerx
cf36b71990 调整快捷方式gird的实现为代码实现hexagon,而不是图片和边距 2025-11-12 00:12:49 +08:00
jimleerx
9cda45f112 patch shikijs冲突 2025-11-12 00:12:49 +08:00
ef7ef44033 修复某些地方点击会显示光标 2025-11-11 02:00:06 +08:00
3ca63f62fb 调整sm窗口快捷方式偏移量 2025-11-11 01:38:23 +08:00
bc940a7a6e Merge pull request '修改readme说明中的首页图片' (#12) from dev into main
Reviewed-on: #12
2025-11-10 23:07:11 +08:00
6755f95e5e Merge pull request '优化整体布局,切换主题为拟物化样式' (#11) from dev into main
Reviewed-on: #11
2025-11-10 23:01:52 +08:00
351c398553 Merge pull request 'dev: 合并近期新增博客与资源调整' (#10) from dev into main
Reviewed-on: #10
2025-10-09 13:47:22 +08:00
43 changed files with 1733 additions and 388 deletions

1
.gitignore vendored
View File

@@ -1,5 +1,6 @@
# build output
dist/
dist.zip
# generated types
.astro/

View File

@@ -5,16 +5,22 @@
## dev.2ha.me
### frok by [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
[![Code License]](LICENSE.md)
[dev.2ha.me](https://dev.2ha.me) 是我在学习[Next.js](https://nextjs.org/)时练手的项目,我从[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)项目的Examples发现了[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)[dev.2ha.me](https://dev.2ha.me)和[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用了相同的架构,如[Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), 和 [shadcn/ui](https://ui.shadcn.com/)。在此基础修改了一些API展示为中国国内的平台数据, 主体框架与[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的3.0版本一致。
[![Code License]](LICENSE)
English | [简体中文](README_zh-Hans.md) | [繁體中文](README_zh-Hant.md)
[dev.2ha.me](https://dev.2ha.me) is a project I worked on while learning [Next.js](https://nextjs.org/). I discovered [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) from the Examples in the [tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog) project. Both [dev.2ha.me](https://dev.2ha.me) and [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) use the same architecture, such as [Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), and [shadcn/ui](https://ui.shadcn.com/). On this basis, I modified some APIs to display data from domestic Chinese platforms. The main framework is consistent with the 3.0 version of [enscribe.dev](https://github.com/jktrn/enscribe.dev.git).
</div>
---
## 技术栈
> [!WARNING]
This project does not use i18n.
这些是原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用到的技术栈
## Technology Stack
These are the technology stacks originally used in [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
@@ -27,9 +33,10 @@
| Deployment | [Vercel](https://vercel.com) |
> [!NOTE]
> [dev.2ha.me](https://dev.2ha.me) 是基于 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 这个项目的。如果您想参考其他样例的代码库或者使用其中的组件,可以查看[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)或者[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)项目的Examples;
> 我在[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)基础上添加了网易云音乐GiteaCalendar的支持替换了原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)背景图片,修改为我自己喜欢的风格。
[dev.2ha.me](https://dev.2ha.me) is based on the [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) project. If you want to refer to other sample code repositories or use the components within them, you can check out the Examples in [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) or the [tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog) project. I have added support for NetEase Cloud Music and GiteaCalendar on top of [enscribe.dev](https://github.com/jktrn/enscribe.dev.git), replaced the original background image of [enscribe.dev](https://github.com/jktrn/enscribe.dev.git), and modified it to my own preferred style.
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
@@ -37,31 +44,34 @@
| API | [githubCalendar](https://github.com/luckykeeper/giteaCalendar) |
---
## 许可
### 原始模板
## License
摘自许可证中的 “原始模板许可证 ”部分:
Original Template
> 本网站基于 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git),而 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 是基于 [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)的, [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)则衍生自松散的 MIT 许可项目 [trevortylerlee/astro-micro](https://github.com/trevortylerlee/astro-micro)
Extract from the "Original Template License" section of the license:
[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的许可可以查看enscribe.dev的[Licence](https://github.com/jktrn/enscribe.dev/blob/main/LICENSE.md)文件
### 站点代码
This website is based on [enscribe.dev](https://github.com/jktrn/enscribe.dev.git), which is based on [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite), and [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite) is derived from the loosely MIT-licensed project [trevortylerlee/astro-micro](https://github.com/trevortylerlee/astro-micro).
[![Code License]](LICENSE.md)
> 本许可证特别适用于对 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)(基于[jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)) 模板所做的自定义修改。它并不延伸至原始模板代码,原始模板代码仍使用其原始 MIT 许可。
The license for [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) can be viewed in the [License](https://github.com/jktrn/enscribe.dev/blob/main/LICENSE) file of enscribe.dev.
就本许可而言,“代码”是指网站的软件组件、配置、布局、样式、功能、脚本和其他功能元素,但不包括此存储库中包含的内容呈现脚本(例如 MDX、Markdown、SVG、等媒体资源文件
此存储库中的所有此类代码均根据 Apache License 2.0 获得许可:
[dev.2ha.me](https://dev.2ha.me) © 2024 Jimleerx
本文件遵循 Apache 许可证 2.0 版(简称“许可证”);您不得在未遵守该许可证的情况下使用本文件。您可以访问以下网址获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律另有规定或双方以书面形式达成一致,否则根据本许可证分发的软件均按“原样”分发,不附带任何明示或暗示的保证或条件。请参阅许可证,了解本许可证下特定语言的权限和限制规定。
### Site Code
### 非代码内容
使用的外部资源已在[dev.2ha.me](https://dev.2ha.me) 关于页面列出,资源均来自网络,如果是商业使用需得到原作者的授权。
任何未在上述代码中定义的内容,包括但不限于 MDX 博客文章、SVG 图形、个人素材、其他图片、任何格式的书面内容以及任何随附文档,均受知识共享署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0) 许可。这意味着您可以使用和分享这些内容,但必须提供适当的署名,不得将其用于商业用途,也不得分发其修改版本。更多详情,请参阅 CC BY-NC-ND 4.0 。
摘自 [Website Code License](LICENSE.md#website-code-license) 部分:
[![Code License]](LICENSE)
> This license specifically applies to custom modifications made to the [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) (based on [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)) template. It does not extend to the original template code, which remains under its original MIT license.
For the purposes of this license, "Code" refers to the software components, configuration, layout, style, functionality, scripts, and other functional elements of the website, but does not include content presentation scripts contained in this repository (such as MDX, Markdown, SVG, and other media resource files).
All such Code in this repository is licensed under the Apache License 2.0: [dev.2ha.me](https://dev.2ha.me) © 2024 iluobei
This document is subject to the Apache License, Version 2.0 (the "License"); you may not use this document except in compliance with the License. You can obtain a copy of the License at the following URL: http://www.apache.org/licenses/LICENSE-2.0
Unless otherwise required by applicable law or agreed in writing by both parties, the software distributed under this license is distributed "as is", without any express or implied warranties or conditions. Please refer to the license for specific permissions and limitations under this license in the applicable language.
### Non-code content
All external resources used are listed on the [dev.2ha.me](https://dev.2ha.me) About page. These resources are sourced from the internet. If used for commercial purposes, permission from the original author is required.
Any content not defined in the above code, including but not limited to MDX blog posts, SVG graphics, personal materials, other images, any written content in any format, and any accompanying documents, are licensed under the Creative Commons Attribution-NonCommercial-NoDerivatives 4.0 International (CC BY-NC-ND 4.0). This means you can use and share this content, but you must provide appropriate attribution, may not use it for commercial purposes, and may not distribute modified versions. For more details, please refer to CC BY-NC-ND 4.0.
Extracted from the [Website Code License](LICENSE#website-code-license) section.
[Code License]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge&logo=github&logoColor=fff

70
README_zh-Hans.md Normal file
View File

@@ -0,0 +1,70 @@
![Showcase Card](/public/static/showcase-card.png)
<div align="center">
## dev.2ha.me
### frok by [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
[![Code License]](LICENSE)
[English](README.md) | 简体中文 | [繁體中文](README_zh-Hant.md)
[dev.2ha.me](https://dev.2ha.me) 是我在学习[Next.js](https://nextjs.org/)时练手的项目,我从[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)项目的Examples发现了[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)[dev.2ha.me](https://dev.2ha.me)和[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用了相同的架构,如[Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), 和 [shadcn/ui](https://ui.shadcn.com/)。在此基础修改了一些API展示为中国国内的平台数据, 主体框架与[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的3.0版本一致。
</div>
---
## 技术栈
这些是原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用到的技术栈
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| Framework | [Astro](https://astro.build/) |
| Styling | [Tailwind](https://tailwindcss.com) |
| Components | [shadcn/ui](https://ui.shadcn.com/) |
| Content | [MDX](https://mdxjs.com/) |
| Syntax Highlighting | [Shiki](https://github.com/shikijs/shiki) + [rehype-pretty-code](https://rehype-pretty.pages.dev/) |
| Graphics | [Figma](https://www.figma.com/) |
| Deployment | [Vercel](https://vercel.com) |
> [!NOTE]
> [dev.2ha.me](https://dev.2ha.me) 是基于 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 这个项目的。如果您想参考其他样例的代码库或者使用其中的组件,可以查看[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)或者[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)项目的Examples;
> 我在[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)基础上添加了网易云音乐GiteaCalendar的支持替换了原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)背景图片,修改为我自己喜欢的风格。
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| Components | [MagicUI](magicui.design), [reactbits](https://www.reactbits.dev/), [startlight](https://starlight.astro.build/), [CultUi](https://www.cult-ui.com/) |
| API | [githubCalendar](https://github.com/luckykeeper/giteaCalendar) |
---
## 许可
### 原始模板
摘自许可证中的 “原始模板许可证 ”部分:
> 本网站基于 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git),而 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 是基于 [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)的, [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)则衍生自松散的 MIT 许可项目 [trevortylerlee/astro-micro](https://github.com/trevortylerlee/astro-micro)
[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的许可可以查看enscribe.dev的[Licence](https://github.com/jktrn/enscribe.dev/blob/main/LICENSE)文件
### 站点代码
[![Code License]](LICENSE)
> 本许可证特别适用于对 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)(基于[jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)) 模板所做的自定义修改。它并不延伸至原始模板代码,原始模板代码仍使用其原始 MIT 许可。
就本许可而言,“代码”是指网站的软件组件、配置、布局、样式、功能、脚本和其他功能元素,但不包括此存储库中包含的内容呈现脚本(例如 MDX、Markdown、SVG、等媒体资源文件
此存储库中的所有此类代码均根据 Apache License 2.0 获得许可:
[dev.2ha.me](https://dev.2ha.me) © 2024 iluobei
本文件遵循 Apache 许可证 2.0 版(简称“许可证”);您不得在未遵守该许可证的情况下使用本文件。您可以访问以下网址获取许可证副本:
http://www.apache.org/licenses/LICENSE-2.0
除非适用法律另有规定或双方以书面形式达成一致,否则根据本许可证分发的软件均按“原样”分发,不附带任何明示或暗示的保证或条件。请参阅许可证,了解本许可证下特定语言的权限和限制规定。
### 非代码内容
使用的外部资源已在[dev.2ha.me](https://dev.2ha.me) 关于页面列出,资源均来自网络,如果是商业使用需得到原作者的授权。
任何未在上述代码中定义的内容,包括但不限于 MDX 博客文章、SVG 图形、个人素材、其他图片、任何格式的书面内容以及任何随附文档,均受知识共享署名-非商业性使用-禁止演绎 4.0 国际 (CC BY-NC-ND 4.0) 许可。这意味着您可以使用和分享这些内容,但必须提供适当的署名,不得将其用于商业用途,也不得分发其修改版本。更多详情,请参阅 CC BY-NC-ND 4.0 。
摘自 [Website Code License](LICENSE#website-code-license) 部分
[Code License]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge&logo=github&logoColor=fff

119
README_zh-Hant.md Normal file
View File

@@ -0,0 +1,119 @@
![Showcase Card](/public/static/showcase-card.png)
<div align="center">
## dev.2ha.me
### frok by [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
[![Code License]](LICENSE)
[English](README.md) | [簡體中文](README_zh-Hans.md) | 繁體中文
[dev.2ha.me](https://dev.2ha.me) 是我在學習[Next.js](https://nextjs.org/)時練手的項目,我從[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)項目的Examples發現了[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)[dev.2ha.me](https://dev.2ha.me)和[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用了相同的架構,如[Astro](https://astro.build/), [Tailwind](https://tailwindcss.com/), 和 [shadcn/ui](https://ui.shadcn.com/)。在此基礎修改了一些API展示為中國國內的平臺數據, 主體框架與[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的3.0版本一致。
</div>
---
## 技術棧
這些是原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用到的技術棧
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| Framework | [Astro](https://astro.build/) |
| Styling | [Tailwind](https://tailwindcss.com) |
| Components | [shadcn/ui](https://ui.shadcn.com/) |
| Content | [MDX](https://mdxjs.com/) |
| Syntax Highlighting | [Shiki](https://github.com/shikijs/shiki) + [rehype-pretty-code](https://rehype-pretty.pages.dev/) |
| Graphics | [Figma](https://www.figma.com/) |
| Deployment | [Vercel](https://vercel.com) |
> [!NOTE]
> [dev.2ha.me](https://dev.2ha.me) 是基於 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 這個項目的。如果您想參考其他樣例的代碼庫或者使用其中的組件,可以查看[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)或者[tailwind-nextjs-starter-blog](https://github.com/timlrx/tailwind-nextjs-starter-blog)項目的Examples;
> 我在[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)基礎上添加了網易雲音樂GiteaCalendar的支持替換了原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)背景圖片,修改為我自己喜歡的風格。
| Category | Technology Name |
| ------------------- | -------------------------------------------------------------------------------------------------- |
| Components | [MagicUI](magicui.design), [reactbits](https://www.reactbits.dev/), [startlight](https://starlight.astro.build/), [CultUi](https://www.cult-ui.com/) |
| API | [githubCalendar](https://github.com/luckykeeper/giteaCalendar) |
---
## 許可
### 原始模板
摘自許可證中的 「原始模板許可證 」部分:
> 本網站基於 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git),而 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git) 是基於 [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)的, [jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)則衍生自松散的 MIT 許可項目 [trevortylerlee/astro-micro](https://github.com/trevortylerlee/astro-micro)
[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的許可可以查看enscribe.dev的[Licence](https://github.com/jktrn/enscribe.dev/blob/main/LICENSE)文件
### 站點代碼
[![Code License]](LICENSE)
> 本許可證特別適用於對 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)(基於[jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)) 模板所做的自定義修改。它並不延伸至原始模板代碼,原始模板代碼仍使用其原始 MIT 許可。
就本許可而言,「代碼」是指網站的軟件組件、配置、布局、樣式、功能、腳本和其他功能元素,但不包括此存儲庫中包含的內容呈現腳本(例如 MDX、Markdown、SVG、等媒體資源文件
此存儲庫中的所有此類代碼均根據 Apache License 2.0 獲得許可:
[dev.2ha.me](https://dev.2ha.me) © 2024 iluobei
本文件遵循 Apache 許可證 2.0 版(簡稱「許可證」);您不得在未遵守該許可證的情況下使用本文件。您可以訪問以下網址獲取許可證副本:
http://www.apache.org/licenses/LICENSE-2.0
除非適用法律另有規定或雙方以書面形式達成一致,否則根據本許可證分發的軟件均按「原樣」分發,不附帶任何明示或暗示的保證或條件。請參閱許可證,了解本許可證下特定語言的權限和限製規定。
### 非代碼內容
使用的外部資源已在[dev.2ha.me](https://dev.2ha.me) 關於頁面列出,資源均來自網絡,如果是商業使用需得到原作者的授權。
任何未在上述代碼中定義的內容,包括但不限於 MDX 博客文章、SVG 圖形、個人素材、其他圖片、任何格式的書面內容以及任何隨附文檔,均受知識共享署名-非商業性使用-禁止演繹 4.0 國際 (CC BY-NC-ND 4.0) 許可。這意味著您可以使用和分享這些內容,但必須提供適當的署名,不得將其用於商業用途,也不得分發其修改版本。更多詳情,請參閱 CC BY-NC-ND 4.0 。
摘自 [Website Code License](LICENSE#website-code-license) 部分
[Code License]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge&logo=github&logoColor=fff

BIN
dist.zip

Binary file not shown.

1008
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -9,7 +9,8 @@
"build": "astro check && astro build",
"preview": "astro preview",
"astro": "astro",
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}"
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}",
"postinstall": "patch-package"
},
"dependencies": {
"@astrojs/check": "^0.9.4",
@@ -60,6 +61,7 @@
},
"devDependencies": {
"@tailwindcss/typography": "^0.5.15",
"patch-package": "^8.0.1",
"prettier": "^3.4.2",
"prettier-plugin-astro": "^0.13.0",
"prettier-plugin-astro-organize-imports": "^0.4.11",

View File

@@ -0,0 +1,14 @@
diff --git a/node_modules/@shikijs/transformers/dist/index.mjs b/node_modules/@shikijs/transformers/dist/index.mjs
index db4db63..c7f3ea2 100644
--- a/node_modules/@shikijs/transformers/dist/index.mjs
+++ b/node_modules/@shikijs/transformers/dist/index.mjs
@@ -410,6 +410,9 @@ function transformerRenderWhitespace(options = {}) {
return token;
if (position === "trailing" && index !== last)
return token;
+ if (token.children.length === 0) {
+ return token;
+ }
const node = token.children[0];
if (node.type !== "text" || !node.value)
return token;

BIN
public/static/avatar.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 87 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 9.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 8.1 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 10 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.4 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 7.8 KiB

BIN
public/static/nyan-cat.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

BIN
public/static/nyancat.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 65 KiB

View File

@@ -69,7 +69,7 @@ const socialLinks: SocialLink[] = [
<p class="text-sm text-muted-foreground">{bio}</p>
<p class="text-sm text-muted-foreground flex items-center">
这是一个开源的站点,你可以点击
<Link href="https://code.2ha.me/dev.2ha.me/dev.2ha.me" target="_blank">
<Link href="https://github.com/iluobei/dev.2ha.me" target="_blank">
<svg
class="size-4 ml-1 mr-1"
style="filter: invert(100%);"

View File

@@ -67,9 +67,9 @@ const SHORTS_CUTS_CLASS_NAMES: string[] = [
'z-10 max-w max-h ml-[-0.2em] mt-[3.0em] inline-block sm:ml-[0em] sm:mt-[2.5em]',
'z-10 max-w max-h mt-[-5.2em] inline-block sm:mt-[-3em]',
'z-10 max-w max-h mt-[0.1em] inline-block sm:mt-[0.6em]',
'z-10 max-w max-h ml-[0.15em] mt-[-7.5em] inline-block sm:ml-[0em] sm:mt-[-5.3em]',
'z-10 max-w max-h ml-[-0.15em] mt-[0.2em] inline-block sm:mt-[0.6em]',
'z-10 max-w max-h mt-[-7.5em] inline-block sm:mt-[-5.3em]',
'z-10 max-w max-h ml-[0.15em] mt-[-7.5em] inline-block sm:ml-[0em] sm:mt-[-4.8em]',
'z-10 max-w max-h ml-[-0.15em] mt-[0.2em] inline-block sm:mt-[0.9em] sm:ml-[0em]',
'z-10 max-w max-h mt-[-7.5em] inline-block sm:mt-[-4.8em]',
'z-10 max-w max-h mt-[1.88em] inline-block sm:mt-[1em]',
'z-10 max-w max-h ml-[0.15em] mt-[-5.8em] inline-block sm:ml-[0em] sm:mt-[-4em]'
]

View File

@@ -0,0 +1,406 @@
---
import { DEV_LINKS } from '@/consts'
import { Icon } from 'astro-icon/components'
// ------------- Hex math helpers (ported from react-hexgrid) -------------
type Point = { x: number; y: number }
type HexCoord = { q: number; r: number; s: number }
type Orientation = {
f0: number
f1: number
f2: number
f3: number
b0: number
b1: number
b2: number
b3: number
startAngle: number
}
type LayoutDimension = {
size: Point
spacing: number
origin: Point
orientation: Orientation
}
const SQRT3 = Math.sqrt(3)
const ORIENTATION_FLAT: Orientation = {
f0: 3 / 2,
f1: 0,
f2: SQRT3 / 2,
f3: SQRT3,
b0: 2 / 3,
b1: 0,
b2: -1 / 3,
b3: SQRT3 / 3,
startAngle: 0,
}
const BASE_HEX_SIZE = 104
const HEX_SIZE = 168
const BORDER_WIDTH = 24
const layout: LayoutDimension = {
size: { x: HEX_SIZE, y: HEX_SIZE },
spacing: 1,
origin: { x: 0, y: 0 },
orientation: ORIENTATION_FLAT,
}
const GRID_WIDTH = 5
const GRID_HEIGHT = 5
function calculatePolygonPoints(size: number, flat = false) {
const angleOffset = flat ? 0 : Math.PI / 6
const corners: Point[] = []
for (let i = 0; i < 6; i++) {
const angle = (2 * Math.PI * i) / 6 + angleOffset
corners.push({
x: size * Math.cos(angle),
y: size * Math.sin(angle),
})
}
return corners.map((corner) => `${corner.x.toFixed(2)},${corner.y.toFixed(2)}`).join(' ')
}
function coordKey(hex: HexCoord) {
return `${hex.q},${hex.r},${hex.s}`
}
function hexToPixel(hex: HexCoord, layout: LayoutDimension): Point {
const { orientation: M, size, spacing, origin } = layout
let x = (M.f0 * hex.q + M.f1 * hex.r) * size.x
let y = (M.f2 * hex.q + M.f3 * hex.r) * size.y
x *= spacing
y *= spacing
return { x: x + origin.x, y: y + origin.y }
}
function rectangle(mapWidth: number, mapHeight: number): HexCoord[] {
const cells: HexCoord[] = []
for (let r = 0; r < mapHeight; r++) {
const offset = Math.floor(r / 2)
for (let q = -offset; q < mapWidth - offset; q++) {
cells.push({ q, r, s: -q - r })
}
}
return cells
}
function hexagon(mapRadius: number): HexCoord[] {
const cells: HexCoord[] = []
for (let q = -mapRadius; q <= mapRadius; q++) {
const r1 = Math.max(-mapRadius, -q - mapRadius)
const r2 = Math.min(mapRadius, -q + mapRadius)
for (let r = r1; r <= r2; r++) {
cells.push({ q, r, s: -q - r })
}
}
return cells
}
// 根据cells的下标与对应值控制显示在哪些格子里
const ACTIVE_COORDS: HexCoord[] = [
// { q: -1, r: 0, s: 1 },
// { q: 0, r: 0, s: 0 },
// { q: 1, r: 0, s: -1 },
{ q: 2, r: 0, s: -2 },
{ q: 3, r: 0, s: -3 },
{ q: 0, r: 1, s: -1 },
{ q: 1, r: 1, s: -2 },
{ q: 2, r: 1, s: -3 },
{ q: 0, r: 2, s: -2 },
{ q: 1, r: 2, s: -3 },
{ q: 2, r: 2, s: -4 },
{ q: 0, r: 3, s: -3 },
{ q: 1, r: 3, s: -4 },
{ q: 2, r: 3, s: -5 },
]
const gridCoords = rectangle(GRID_WIDTH, GRID_HEIGHT)
const gridKeyMap = new Map(gridCoords.map((hex) => [coordKey(hex), hex]))
const preferredCoords = ACTIVE_COORDS.map((hex) => gridKeyMap.get(coordKey(hex))).filter(
Boolean,
) as HexCoord[]
const preferredKeySet = new Set(preferredCoords.map((hex) => coordKey(hex)))
const remainingGridCoords = gridCoords.filter((hex) => !preferredKeySet.has(coordKey(hex)))
let orderedActiveSlots = [...preferredCoords, ...remainingGridCoords]
if (DEV_LINKS.length > orderedActiveSlots.length) {
const usedKeys = new Set(orderedActiveSlots.map((hex) => coordKey(hex)))
let radius = Math.max(GRID_WIDTH, GRID_HEIGHT)
while (orderedActiveSlots.length < DEV_LINKS.length) {
radius += 1
for (const hex of hexagon(radius)) {
const key = coordKey(hex)
if (usedKeys.has(key)) continue
orderedActiveSlots.push(hex)
usedKeys.add(key)
if (orderedActiveSlots.length === DEV_LINKS.length) break
}
}
}
const assignedCoords = orderedActiveSlots.slice(0, DEV_LINKS.length)
const coordToLink = new Map<string, (typeof DEV_LINKS)[number]>()
assignedCoords.forEach((hex, index) => {
const link = DEV_LINKS[index]
if (!hex || !link) return
coordToLink.set(coordKey(hex), link)
})
const renderCoordMap = new Map<string, HexCoord>()
gridCoords.forEach((hex) => renderCoordMap.set(coordKey(hex), hex))
assignedCoords.forEach((hex) => {
if (!hex) return
renderCoordMap.set(coordKey(hex), hex)
})
const renderCoords = Array.from(renderCoordMap.values())
const hexagonPoints = calculatePolygonPoints(layout.size.x, true)
// const circleRadius = layout.size.x * 0.84
const circleRadius = 80
const baseCells = renderCoords.map((hex) => ({
hex,
link: coordToLink.get(coordKey(hex)) ?? null,
center: hexToPixel(hex, layout),
}))
const xs = baseCells.map((cell) => cell.center.x)
const ys = baseCells.map((cell) => cell.center.y)
const padding = layout.size.x * 0.45
const minX = Math.min(...xs) - padding
const maxX = Math.max(...xs) + padding
const minY = Math.min(...ys) - padding
const maxY = Math.max(...ys) + padding
const sizeScale = HEX_SIZE / BASE_HEX_SIZE
const rawWidth = maxX - minX
const rawHeight = maxY - minY
const centerX = (minX + maxX) / 2
const centerY = (minY + maxY) / 2
const viewWidth = rawWidth / sizeScale
const viewHeight = rawHeight / sizeScale
const viewMinX = centerX - viewWidth / 2
const viewMinY = centerY - viewHeight / 2
const viewBox = `${viewMinX} ${viewMinY} ${viewWidth} ${viewHeight}`
const iconClipId = 'hexIconClip'
const cells = baseCells.map((cell) => ({
...cell,
showContent: Boolean(cell.link),
}))
---
<svg
class="hex-grid hex-grid--polygons"
width="100%"
height="100%"
viewBox={viewBox}
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid slice"
>
<g class="hex-grid__group">
{cells.map(({ link, center }, index) => {
const isGhost = !link
return (
<polygon
class={`hexagon-bg ${isGhost ? 'hexagon-bg--ghost' : ''}`}
points={hexagonPoints}
fill="transparent"
stroke="#252525"
stroke-width={BORDER_WIDTH}
transform={`translate(${center.x}, ${center.y})`}
data-index={index}
/>
)
})}
</g>
</svg>
<svg
class="hex-grid hex-grid--icons"
width="100%"
height="100%"
viewBox={viewBox}
xmlns="http://www.w3.org/2000/svg"
preserveAspectRatio="xMidYMid slice"
>
<defs>
<clipPath id={iconClipId} clipPathUnits="userSpaceOnUse">
<circle cx="0" cy="0" r={circleRadius} />
</clipPath>
</defs>
<g class="hex-grid__group">
{cells.map(({ link, center, showContent }, index) => {
if (!link || !showContent) return null
return (
<g
class="hexagon-wrapper"
transform={`translate(${center.x}, ${center.y})`}
data-index={index}
>
<a
href={link.href}
target="_blank"
rel="noopener noreferrer"
aria-label={link.label}
>
<title>{link.title}</title>
<g class="icon-group">
<circle
r={circleRadius}
fill="hsl(var(--secondary))"
fill-opacity="0.45"
stroke="hsl(var(--border))"
stroke-width={Math.max(4, BORDER_WIDTH * 0.32)}
class="icon-circle z-10"
/>
{link.icon.startsWith('mdi:') ? (
<foreignObject
x={-(layout.size.x * 0.7)}
y={-(layout.size.y * 0.7)}
width={layout.size.x * 1.4}
height={layout.size.y * 1.4}
clip-path={`url(#${iconClipId})`}
class="icon-foreign"
>
<div class="icon-container">
<Icon
name={link.icon}
class="hexagon-icon"
style="color: rgb(233, 211, 182);"
aria-hidden="true"
/>
</div>
</foreignObject>
) : (
<image
href={link.icon}
x={-layout.size.x * 0.3}
y={-layout.size.y * 0.3}
width={layout.size.x * 0.6}
height={layout.size.y * 0.6}
clip-path={`url(#${iconClipId})`}
class="hexagon-image"
/>
)}
</g>
</a>
</g>
)
})}
</g>
</svg>
<style>
.hex-grid {
position: absolute;
inset: 0;
width: 100%;
height: 100%;
pointer-events: none;
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
}
.hex-grid--polygons {
z-index: 1;
}
.hex-grid--icons {
z-index: 20;
}
.hex-grid__group {
transform-origin: center;
transform: translateX(-114px) translateY(60px) scale(1.08);
}
.hexagon-wrapper {
cursor: pointer;
pointer-events: auto;
user-select: none;
-webkit-user-select: none;
-webkit-tap-highlight-color: transparent;
}
.hexagon-wrapper a {
outline: none;
-webkit-tap-highlight-color: transparent;
pointer-events: auto;
cursor: pointer;
}
.hexagon-wrapper a title {
pointer-events: none;
}
.hexagon-bg,
.icon-circle,
.icon-group {
transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);
}
.icon-circle {
stroke-width: 4px;
transform-origin: center;
transform-box: fill-box;
}
.icon-group {
transform-origin: center;
transform-box: fill-box;
filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.45));
}
.hexagon-wrapper:hover .icon-circle {
fill-opacity: 0.85;
stroke: hsl(var(--primary));
stroke-width: 4px;
transform: scale(1.08);
}
.hexagon-wrapper:hover .icon-group {
transform: rotate(12deg);
transform-origin: center;
}
.hexagon-bg--ghost {
stroke-dasharray: none;
}
.icon-container {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100%;
border-radius: 9999px;
}
.hexagon-icon {
width: 100% !important;
height: 100% !important;
max-width: 88px;
max-height: 88px;
}
.hexagon-image {
object-fit: contain;
}
.icon-foreign {
overflow: visible;
}
:global(.hexagon-icon) {
color: rgb(233, 211, 182) !important;
}
</style>

View File

@@ -10,13 +10,13 @@ import logo from '../../public/static/logo.webp'
---
<header
class="sticky top-0 z-20 bg-background/50 backdrop-blur-md"
class="sticky top-0 z-[99] bg-background/50 backdrop-blur-md"
transition:persist
>
<Container>
<div class="flex flex-wrap items-center justify-between gap-4 py-4 ">
<Link href="/">
<Image src={logo} alt="Logo" class="size-8 -scale-x-100 border-2 border-[color:rgba(241,140,110,0.4)] shadow-[4px_4px_0_rgba(0,0,0,0.2)]" />
<Link href="/" class="logo-link">
<Image src={logo} alt="Logo" class="size-8 -scale-x-100 border-2 border-[color:rgba(241,140,110,0.4)] shadow-[4px_4px_0_rgba(0,0,0,0.2)] transition-all duration-300" />
</Link>
<div class="flex items-center gap-2 sm:gap-4">
<nav class="hidden items-center gap-2 text-base sm:flex sm:gap-3">
@@ -38,3 +38,31 @@ import logo from '../../public/static/logo.webp'
</div>
</Container>
</header>
<style>
.logo-link:hover img {
border-color: hsl(var(--primary));
box-shadow:
0 0 12px rgba(241, 140, 110, 0.6),
0 0 24px rgba(241, 140, 110, 0.4),
0 0 36px rgba(241, 140, 110, 0.2),
4px 4px 0 rgba(0, 0, 0, 0.2);
transform: scale(1.05);
}
nav :global(.pixel-button:hover) {
box-shadow:
0 0 12px rgba(241, 140, 110, 0.5),
0 0 24px rgba(241, 140, 110, 0.3),
0 0 36px rgba(241, 140, 110, 0.15),
4px 4px 0 rgba(0, 0, 0, 0.2) !important;
}
.dark nav :global(.pixel-button:hover) {
box-shadow:
0 0 12px rgba(241, 140, 110, 0.5),
0 0 24px rgba(241, 140, 110, 0.3),
0 0 36px rgba(241, 140, 110, 0.15),
4px 4px 0 rgba(0, 0, 0, 0.4) !important;
}
</style>

View File

@@ -1,47 +1,46 @@
import AvatarComponent from '@/components/ui/avatar'
const AuthorPresence = () => {
return (
<div className="relative overflow-hidden sm:aspect-square">
<div className="relative overflow-hidden sm:aspect-square select-none" style={{ cursor: 'default' }}>
<div className="grid size-full grid-rows-4">
<div className="bg-secondary/50"></div>
<div className="row-span-3 flex flex-col gap-3 p-3">
<div className="flex justify-between gap-x-1">
<div className="flex justify-between">
<div className="relative">
<AvatarComponent
src="/static/avatar.webp"
src="/static/avatar.png"
alt="Avatar"
fallback="e"
className="-mt-[4.5rem] aspect-square size-24 rounded-full"
/>
</div>
<div className="flex items-center rounded-xl bg-secondary/50 px-2">
{/* <div className="flex items-center rounded-xl bg-secondary/50 px-2">
<img
src="/static/images/badges.png"
src="https://contrib.rocks/image?repo=iluobei/dev.2ha.me"
alt="Discord Badges"
width={104}
height={24}
className="grayscale"
/>
</div>
</div> */}
</div>
<div className="flex flex-col gap-y-1 rounded-xl bg-secondary/50 p-3">
<span className="text-base leading-none">jimlee</span>
<span className="text-xs leading-none text-muted-foreground">
<div className="flex flex-col items-start gap-y-1 rounded-xl bg-secondary/50 p-3 select-none">
<span className="text-base leading-none select-none cursor-default">(🥕)</span>
<span className="text-xs leading-none text-muted-foreground select-none cursor-default">
li@2ha.me
</span>
</div>
<div className="flex grow rounded-xl bg-secondary/50 px-3 py-2">
<div className="flex size-full flex-col items-center justify-center gap-1">
<div className="flex grow rounded-xl bg-secondary/50 px-3 py-2 select-none">
<div className="flex size-full flex-col items-center justify-center gap-1 select-none">
<img
src="/static/images/lieflat.svg"
alt="No Status Image"
width={64}
height={64}
className="h-full rounded-lg"
className="h-full rounded-lg select-none"
/>
<div className="text-[10px] text-muted-foreground">
<div className="text-[10px] text-muted-foreground select-none cursor-default">
</div>
</div>

View File

@@ -55,12 +55,12 @@ const WakatimeGraph = ({ }: Props) => {
useEffect(() => {
setLanguages([
{ name: 'java', hours: 1009, fill: 'hsl(var(--chart-1))' },
{ name: 'javascript', hours: 476, fill: 'hsl(var(--chart-2))' },
{ name: 'javascript', hours: 586, fill: 'hsl(var(--chart-2))' },
{ name: 'typescript', hours: 519, fill: 'hsl(var(--chart-4))' },
{ name: 'react', hours: 442, fill: 'hsl(var(--chart-5))' },
{ name: 'kotlin', hours: 405, fill: 'hsl(var(--chart-3))' },
{ name: 'typescript', hours: 401, fill: 'hsl(var(--chart-4))' },
{ name: 'react', hours: 257, fill: 'hsl(var(--chart-5))' },
{ name: 'go', hours: 125, fill: 'hsl(var(--chart-6))' },
{ name: 'python', hours: 120, fill: 'hsl(var(--chart-7))' },
{ name: 'go', hours: 301, fill: 'hsl(var(--chart-6))' },
{ name: 'python', hours: 177, fill: 'hsl(var(--chart-7))' },
])
setIsLoading(false)
}, [])

View File

@@ -16,7 +16,7 @@ async function fetchCalendarData(): Promise<ApiResponse> {
const data: ApiResponse | ApiErrorResponse = await response.json()
if (!response.ok) {
throw Error(
`Fetching Gitea(code.2ha.me) contribution data for jimlee failed, see https://github.com/luckykeeper/giteaCalendar.`,
`Fetching Gitea(code.2ha.me) contribution data for luobei failed, see https://github.com/luckykeeper/giteaCalendar.`,
)
}

View File

@@ -40,7 +40,7 @@ export const NAV_LINKS: Link[] = [
]
export const SOCIAL_LINKS: Link[] = [
{ href: 'https://github.com/jimleerx', label: 'GitHub' },
{ href: 'https://github.com/iluobei', label: 'GitHub' },
{ href: 'https://1ms.cc', label: 'HubProxy' },
{ href: 'https://code.2ha.me', label: 'Gitea' },
{ href: 'li@2ha.me', label: 'Email' },
@@ -54,10 +54,10 @@ export const DEV_LINKS: DevLink[] = [
icon: 'mdi:git',
},
{
href: 'https://maven.2ha.me',
href: 'https://img.2ha.me',
label: 'Nexus',
title: 'Maven仓库',
icon: 'mdi:chart-doughnut-variant',
title: '图床',
icon: 'mdi:image-multiple',
},
{
href: 'https://dms.2ha.me',
@@ -69,13 +69,13 @@ export const DEV_LINKS: DevLink[] = [
href: 'https://p.2ha.me',
label: 'Zfile',
title: '网盘',
icon: 'mdi:cloud-arrow-up',
icon: 'mdi:harddisk',
},
{
href: 'https://photo.2ha.me',
label: 'immich',
title: '相册',
icon: 'mdi:camera',
href: 'https://tz.2ha.me',
label: 'VPS Monitor',
title: '探针',
icon: 'mdi:chart-areaspline',
},
{
href: 'https://f.2ha.me',
@@ -83,7 +83,12 @@ export const DEV_LINKS: DevLink[] = [
title: '文件服务器',
icon: 'mdi:file-arrow-up-down-outline',
},
{ href: 'https://v.2ha.me', label: 'Emby', title: 'Emby', icon: 'mdi:emby' },
{
href: 'https://status.2ha.me',
label: 'Domain Status',
title: '站点检测',
icon: 'mdi:cloud-check'
},
{
href: 'https://in.2ha.me',
label: '2ha.me statistics',
@@ -91,15 +96,16 @@ export const DEV_LINKS: DevLink[] = [
icon: 'mdi:sun-azimuth',
},
{
href: 'https://mmwdemo.2ha.me/docs',
href: 'https://miaomiaowu.net',
label: '妙妙屋',
title: '个人Clash订阅管理工具',
icon: '/static/mmw.svg',
// icon: 'mdi:cat',
},
{
href: 'https://1ms.cc',
label: 'hubproxy',
title: 'GitHub&DockerHub代理',
title: 'GitHub&DockerHub加速',
icon: 'mdi:rocket-launch-outline',
},
]

View File

@@ -1,12 +1,12 @@
---
name: 'jimlee'
name: '胡萝北(🥕)'
pronouns: 'he/him'
avatar: 'https://dev.2ha.me/static/avatar_256.webp'
bio: 'd(-_-)b'
website: 'https://dev.2ha.me'
devintro: 'https://code.2ha.me/jimlee'
devintro: 'https://code.2ha.me/luobei'
gitea: 'https://code.2ha.me/dev.2ha.me'
# twitter: 'https://twitter.com/enscry'
github: 'https://github.com/jimleerx'
github: 'https://github.com/iluobei'
mail: 'li@2ha.me'
---

View File

@@ -4,7 +4,7 @@ description: '在debian上根据需要添加nginx模块编译自定义的ngin
date: 2025-04-11
tags: ['nginx', 'debian', 'build']
image: 'assets/nginx.svg'
authors: ['jimlee']
authors: ['胡萝北(🥕)']
---
## 在debian上安装nginx

View File

@@ -4,7 +4,7 @@ description: '常用的kotlin, python, shell, regex等代码块合集'
date: 2025-04-10
tags: ['kotlin', 'python', 'regex', 'linux', 'shell', 'javascript', 'nginx']
image: 'assets/commoncodebanner.webp'
authors: ['jimlee']
authors: ['胡萝北(🥕)']
---
## kotlin

View File

@@ -4,7 +4,7 @@ description: '在dsm7.2上破解emby套件, 输入任意激活码即可获取小
date: 2024-12-31
tags: ['emby', 'synology', 'crack']
image: 'assets/emby_banner.png'
authors: ['jimlee']
authors: ['胡萝北(🥕)']
---
import { Icon } from 'astro-icon/components'
import Link from '@/components/Link.astro'

View File

@@ -4,7 +4,7 @@ description: 'debian安装fail2ban保护nginx服务器阻止暴力破解和
date: 2025-10-06
tags: ['debian', 'nginx', 'fail2ban', 'regex', 'shell']
image: 'assets/nginxfail2ban.png'
authors: ['jimlee']
authors: ['胡萝北(🥕)']
---

View File

@@ -4,7 +4,7 @@ description: '1. shikijs重复依赖打包失败; 2. 使用Rehype Pretty Code/Co
date: 2025-05-12
tags: ['typescript', 'astro', 'shiki']
image: 'assets/shikicodecopybutton.png'
authors: ['jimlee']
authors: ['胡萝北(🥕)']
---
import FileTree from '@/components/starlight/FileTree.astro'

View File

@@ -3,6 +3,6 @@ name: '妙妙屋(个人clash订阅管理工具)'
description: '妙妙屋是一个功能强大的Clash订阅管理平台帮助您轻松管理订阅、节点和用户。'
tags: ['open-source', 'personal', 'clash', 'substore']
image: 'assets/mmw.png'
link: 'https://mmwdemo.2ha.me'
link: 'https://miaomiaowu.net'
startDate: '2025-10-10'
---

View File

@@ -21,7 +21,7 @@ const { title, description, image } = Astro.props
description={description}
image={image}
/>
<script is:inline defer src="https://in.2ha.me/script.js" data-website-id="34634aec-34a9-4ef4-9a8f-08ee96699a84"></script>
<script is:inline async defer src="https://in.2ha.me/script.js" data-website-id="34634aec-34a9-4ef4-9a8f-08ee96699a84"></script>
</head>
<body>
<div

View File

@@ -30,6 +30,6 @@ export function transformerNotationSkip(
return false
},
undefined, // remove empty lines
)
) as ShikiTransformer
}

View File

@@ -59,7 +59,7 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
<Layout title="Blog" description="Blog">
<!-- Fixed Sidebar -->
<aside
class="hidden xl:block fixed left-4 top-32 w-[240px] px-4 py-3 rounded-sm bg-background/80 backdrop-blur-sm border-2 border-[color:rgba(241,140,110,0.22)] shadow-[4px_4px_0_rgba(0,0,0,0.22)] dark:shadow-[4px_4px_0_rgba(0,0,0,0.65)] z-10"
class="hidden xl:block fixed top-1/2 -translate-y-1/2 w-[240px] px-4 py-3 rounded-sm bg-background/80 backdrop-blur-sm border-2 border-[color:rgba(241,140,110,0.22)] shadow-[4px_4px_0_rgba(0,0,0,0.22)] dark:shadow-[4px_4px_0_rgba(0,0,0,0.65)] z-10"
>
<Link
href={`/blog`}
@@ -127,3 +127,21 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
/>
</Container>
</Layout>
<style>
ul li > :global(div:hover) {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
}
.dark ul li > :global(div:hover) {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
}
</style>

View File

@@ -12,7 +12,8 @@ import { getCollection } from 'astro:content'
import GiteaCalendar from '@/components/custom/GiteaCalendar'
import Music163Player from '@/components/custom/Music163Player'
import RandomAnimeBackground from '@/components/custom/RandomAnimeBackgrounds'
import DevShortCuts from '@/components/DevShortCuts.astro'
// import DevShortCuts from '@/components/DevShortCuts.astro'
import DevShortcutsHexagon from '@/components/DevShortcutsHexagon.astro'
const latestPost = await getCollection('blog').then((posts: any[]) =>
posts
@@ -28,7 +29,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
<Layout title="主页" description={SITE.DESCRIPTION}>
<section
class="mx-auto grid w-full grid-cols-1 gap-4 px-4 [grid-template-areas:'a'_'d'_'b'_'i'_'e'_'g'_'f'_'j'_'k'] [grid-template-rows:repeat(9,auto)] *:border-2 *:border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat *:overflow-hidden *:w-full *:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] *:transition-all *:duration-200 sm:max-w-screen-sm sm:grid-cols-2 sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'f_f'_'i_k'] sm:[grid-template-rows:repeat(6,300px)] sm:*:max-w-[615px] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_g'_'d_e_e_i'_'k_j_f_f'] xl:[grid-template-rows:repeat(3,300px)] xl:*:max-w-none dark:*:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&>*:not(.first):hover_.overlay]:opacity-100"
class="mx-auto grid w-full grid-cols-1 gap-4 px-4 [grid-template-areas:'a'_'d'_'b'_'e'_'g'_'f'_'j'_'i'_'k'] [grid-template-rows:repeat(9,auto)] *:border-2 *:border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat *:overflow-hidden *:w-full *:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] *:transition-all *:duration-200 sm:max-w-screen-sm sm:grid-cols-2 sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'f_f'_'i_k'] sm:[grid-template-rows:repeat(6,300px)] sm:*:max-w-[615px] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_g'_'d_e_e_i'_'k_j_f_f'] xl:[grid-template-rows:repeat(3,300px)] xl:*:max-w-none dark:*:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&>*:not(.first):hover_.overlay]:opacity-100"
aria-label="Personal information and activity grid"
>
<div
@@ -42,16 +43,17 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
<div
class="has-overlay relative grid aspect-square grid-cols-4 grid-rows-3 items-center
justify-center bg-[url('/static/honeycomb.webp')] [grid-area:b] short-cuts-template hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
justify-center [grid-area:b] short-cuts-template hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
style="grid-template-columns: 2.85fr 2.95fr 2.9fr 1.3fr;grid-template-rows: 4.7fr 3.4fr 2.8fr;"
aria-label="Developer Stack Shortcuts"
>
<img
class="overlay absolute no-repeat w-full max-w-fit justify-center object-cover z-9"
src="/static/images/shortcuts-bg.png"
class="overlay absolute bottom-0 right-0 no-repeat max-w-[28%] object-contain z-[88]"
src="/static/images/shortcuts-bg-mini.png"
/>
<!-- <DevStackIconsCloud client:load/> -->
<DevShortCuts />
<!-- <DevShortCuts /> -->
<DevShortcutsHexagon />
</div>
<div class="relative overflow-hidden [grid-area:d] aspect-square hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.26)] hover:-translate-y-1 hover:border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]">
@@ -63,7 +65,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
style="grid-template-rows: 9fr 1fr; "
>
<div
class="overlay absolute inset-0 size-full rounded-3xl bg-[url('/static/images/lastblogbg-sm.webp')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/lastblogbg.webp')] xl:opacity-100"
class="overlay absolute inset-0 size-full rounded-3xl bg-[url('/static/images/lastblogbg-sm.webp')] bg-cover bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/lastblogbg.webp')] xl:opacity-100"
>
</div>
{
@@ -169,3 +171,21 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
</div>
</section>
</Layout>
<style>
section > div:hover {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
}
.dark section > div:hover {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
}
</style>

View File

@@ -37,3 +37,21 @@ const years = Object.keys(projectsByYear).sort(
</div>
</Container>
</Layout>
<style>
ul li > :global(div:hover) {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.26) !important;
}
.dark ul li > :global(div:hover) {
box-shadow:
0 0 16px rgba(241, 140, 110, 0.5),
0 0 32px rgba(241, 140, 110, 0.3),
0 0 48px rgba(241, 140, 110, 0.15),
6px 6px 0 rgba(0, 0, 0, 0.75) !important;
}
</style>

View File

@@ -4,14 +4,6 @@
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'haipaiqiangdiaosenxiyuan';
src: url('/fonts/haipaiqiangdiaosenxiyuan.woff');
font-weight: 100 800;
font-style: normal;
/* unicode-range: U+2E80-2EFF,U+3400-4DBF,U+4E00-9FFF; */
}
@font-face {
font-family: 'OPlusSans3-Medium';
src: url('/fonts/OPlusSans3-Medium.woff2') format('woff2'),
@@ -334,161 +326,46 @@
0px 4px 4px hsla(0, 0%, 0%, 0.16), 0px 4px 2px hsla(0, 0%, 0%, 0.04);
}
@layer components {
article {
@apply prose-headings:scroll-mt-20 prose-headings:break-words;
@apply prose-p:break-words;
@apply prose-a:!text-primary prose-a:!decoration-primary/50 prose-a:!underline-offset-[3px] prose-a:transition-colors hover:prose-a:!decoration-inherit;
@apply prose-blockquote:!not-italic prose-blockquote:!text-muted-foreground;
@apply prose-pre:!px-0;
@apply prose-img:mx-auto prose-img:rounded-xl prose-img:border;
@apply prose-table:mx-auto prose-table:block prose-table:max-w-fit prose-table:overflow-x-auto prose-td:break-words;
@apply sm:prose-table:mx-0 sm:prose-table:table sm:prose-table:max-w-none;
/* Fixing Katex display */
.katex-display {
@apply overflow-x-auto overflow-y-hidden py-4;
}
/* Fixing Katex fractions */
.frac-line {
@apply border-foreground;
}
/* Removes background from <mark> elements */
mark {
@apply bg-transparent;
}
/* Blanket syntax highlighting */
code[data-theme*=' '] {
span {
color: var(--shiki-dark);
}
.dark & span {
color: var(--shiki-dark);
}
}
/* Inline code */
:not(pre) > code {
@apply relative rounded bg-muted/50 px-[0.3rem] py-[0.2rem] font-mono text-sm font-medium before:!content-none after:!content-none;
}
/* Code blocks */
figure[data-rehype-pretty-code-figure] {
@apply relative;
/* Code block titles */
[data-rehype-pretty-code-title] {
@apply break-words rounded-t-xl border-x border-t px-4 py-2 text-sm font-medium text-foreground;
/* Remove top margin from code block if a title is present */
& + pre {
@apply mt-0 rounded-t-none;
}
}
/* Shadcn-like scrollbar */
pre::-webkit-scrollbar {
@apply h-2.5 w-2.5;
}
pre::-webkit-scrollbar-track {
@apply bg-transparent;
}
pre::-webkit-scrollbar-thumb {
@apply rounded-full bg-border bg-clip-padding p-px;
}
/* Code block styles */
pre {
@apply static max-h-[600px] overflow-auto rounded-xl border bg-secondary/20 py-4 text-sm leading-loose;
/* Code block content */
> code {
@apply whitespace-pre-wrap;
counter-reset: line;
/* For code blocks with line numbers */
&[data-line-numbers] {
> [data-line]::before {
counter-increment: line;
content: counter(line);
@apply mr-4 inline-block w-4 text-right text-muted-foreground;
}
}
/* For each line in the code block */
> [data-line] {
@apply px-4;
}
/* Highlighted lines */
[data-highlighted-line] {
@apply bg-foreground/10;
}
/* Highlighted characters */
[data-highlighted-chars] > span {
@apply bg-muted-foreground/40 py-[7px];
}
.tab {
@apply relative;
}
.tab::before {
@apply absolute opacity-30;
content: '⇥';
}
/* Skip lines */
.skip {
@apply my-2 bg-foreground/5 text-center text-foreground;
&::before {
content: '' !important;
}
}
/* Diff lines */
.diff {
&.add {
@apply bg-additive/15;
}
&.remove {
@apply bg-destructive/15;
&::before {
content: '-';
counter-increment: none;
}
}
&.highlight {
@apply bg-foreground/10;
}
}
/* Copy button */
> button:has(> span) {
@apply right-1 top-1 m-0 size-8 rounded-lg bg-secondary p-1 backdrop-blur-none transition-all duration-200;
}
}
}
}
}
}
@layer base {
* {
@apply border-border;
}
/* Prevent text selection cursor on interactive elements */
/* Prevent text selection cursor on all elements by default */
* {
user-select: none;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-webkit-tap-highlight-color: transparent;
cursor: default;
}
/* Allow text selection in content areas */
article,
article *,
.prose,
.prose *,
input,
textarea,
[contenteditable="true"] {
user-select: text;
-webkit-user-select: text;
}
/* Only inputs and textareas should have text cursor */
input,
textarea,
[contenteditable="true"] {
cursor: text;
}
/* Ensure proper cursor on interactive elements */
button,
a,
div,
img,
svg,
.pixel-button,
[role="button"],
[role="link"] {
@@ -496,6 +373,42 @@
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
cursor: pointer;
}
/* Children of interactive elements should not have text cursor */
button *,
a *,
.pixel-button *,
[role="button"] *,
[role="link"] * {
cursor: pointer;
user-select: none;
}
/* Images should never show text cursor */
img,
svg,
video,
canvas {
user-select: none;
-webkit-user-select: none;
cursor: default;
}
/* Allow text selection for content elements but keep default cursor */
p,
span,
h1,
h2,
h3,
h4,
h5,
h6,
li {
user-select: text;
-webkit-user-select: text;
cursor: default;
}
body {

View File

@@ -2,14 +2,6 @@
@tailwind components;
@tailwind utilities;
@font-face {
font-family: 'haipaiqiangdiaosenxiyuan';
src: url('/fonts/haipaiqiangdiaosenxiyuan.woff');
font-weight: 100 800;
font-style: normal;
/* unicode-range: U+2E80-2EFF,U+3400-4DBF,U+4E00-9FFF; */
}
@font-face {
font-family: 'OPlusSans3-Medium';
src: url('/fonts/OPlusSans3-Medium.woff');

View File

@@ -9,7 +9,6 @@ const config: Config = {
fontFamily: {
sans: [
'OPlusSans3-Medium',
'haipaiqiangdiaosenxiyuan',
...defaultTheme.fontFamily.sans
],
mono: [