Compare commits
1 Commits
beian
...
build_1.1.
| Author | SHA1 | Date | |
|---|---|---|---|
| 351c398553 |
5
.gitignore
vendored
@@ -1,6 +1,5 @@
|
||||
# build output
|
||||
dist/
|
||||
dist.zip
|
||||
# generated types
|
||||
.astro/
|
||||
|
||||
@@ -25,6 +24,4 @@ pnpm-debug.log*
|
||||
.idea/
|
||||
|
||||
teaser.pptx
|
||||
~$teaser.pptx
|
||||
|
||||
.claude
|
||||
~$teaser.pptx
|
||||
58
README.md
@@ -5,22 +5,16 @@
|
||||
## dev.2ha.me
|
||||
### frok by [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
|
||||
|
||||
[![Code License]](LICENSE.md)
|
||||
|
||||
[![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).
|
||||
[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>
|
||||
|
||||
---
|
||||
|
||||
> [!WARNING]
|
||||
This project does not use i18n.
|
||||
## 技术栈
|
||||
|
||||
## Technology Stack
|
||||
|
||||
These are the technology stacks originally used in [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)
|
||||
这些是原[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)使用到的技术栈
|
||||
|
||||
| Category | Technology Name |
|
||||
| ------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
@@ -33,10 +27,9 @@ These are the technology stacks originally used in [enscribe.dev](https://github
|
||||
| Deployment | [Vercel](https://vercel.com) |
|
||||
|
||||
|
||||
|
||||
|
||||
> [!NOTE]
|
||||
[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.
|
||||
> [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 |
|
||||
| ------------------- | -------------------------------------------------------------------------------------------------- |
|
||||
@@ -44,34 +37,31 @@ These are the technology stacks originally used in [enscribe.dev](https://github
|
||||
| API | [githubCalendar](https://github.com/luckykeeper/giteaCalendar) |
|
||||
---
|
||||
|
||||
## 许可
|
||||
|
||||
## License
|
||||
### 原始模板
|
||||
|
||||
Original Template
|
||||
摘自许可证中的 “原始模板许可证 ”部分:
|
||||
|
||||
Extract from the "Original Template License" section of the license:
|
||||
> 本网站基于 [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)
|
||||
|
||||
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).
|
||||
[enscribe.dev](https://github.com/jktrn/enscribe.dev.git)的许可可以查看enscribe.dev的[Licence](https://github.com/jktrn/enscribe.dev/blob/main/LICENSE.md)文件
|
||||
### 站点代码
|
||||
|
||||
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.
|
||||
[![Code License]](LICENSE.md)
|
||||
> 本许可证特别适用于对 [enscribe.dev](https://github.com/jktrn/enscribe.dev.git)(基于[jktrn/astro-erudite](https://github.com/jktrn/astro-erudite)) 模板所做的自定义修改。它并不延伸至原始模板代码,原始模板代码仍使用其原始 MIT 许可。
|
||||
|
||||
### Site Code
|
||||
就本许可而言,“代码”是指网站的软件组件、配置、布局、样式、功能、脚本和其他功能元素,但不包括此存储库中包含的内容呈现脚本(例如 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
|
||||
除非适用法律另有规定或双方以书面形式达成一致,否则根据本许可证分发的软件均按“原样”分发,不附带任何明示或暗示的保证或条件。请参阅许可证,了解本许可证下特定语言的权限和限制规定。
|
||||
|
||||
[![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.
|
||||
### 非代码内容
|
||||
使用的外部资源已在[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]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?style=for-the-badge&logo=github&logoColor=fff
|
||||
@@ -1,70 +0,0 @@
|
||||

|
||||
|
||||
<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
|
||||
@@ -1,119 +0,0 @@
|
||||

|
||||
|
||||
|
||||
|
||||
<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
|
||||
1008
package-lock.json
generated
@@ -9,8 +9,7 @@
|
||||
"build": "astro check && astro build",
|
||||
"preview": "astro preview",
|
||||
"astro": "astro",
|
||||
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}",
|
||||
"postinstall": "patch-package"
|
||||
"prettier": "prettier --write ./src/**/*.{astro,ts,tsx,css}"
|
||||
},
|
||||
"dependencies": {
|
||||
"@astrojs/check": "^0.9.4",
|
||||
@@ -61,7 +60,6 @@
|
||||
},
|
||||
"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",
|
||||
|
||||
@@ -1,14 +0,0 @@
|
||||
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;
|
||||
|
Before Width: | Height: | Size: 87 KiB |
BIN
public/static/images/contributions-silhouette.png
Normal file
|
After Width: | Height: | Size: 8.3 KiB |
BIN
public/static/images/github-silhouette.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
public/static/images/github-silhouette_old.png
Normal file
|
After Width: | Height: | Size: 9.3 KiB |
BIN
public/static/images/lastblogbg-silhouette.png
Normal file
|
After Width: | Height: | Size: 8.1 KiB |
BIN
public/static/images/lastblogbg-silhouette.webp
Normal file
|
After Width: | Height: | Size: 46 KiB |
BIN
public/static/images/music163-silhouette.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
|
Before Width: | Height: | Size: 41 KiB |
BIN
public/static/images/shortcuts-bg-silhouette.png
Normal file
|
After Width: | Height: | Size: 6.4 KiB |
BIN
public/static/images/waketime-silhouette.png
Normal file
|
After Width: | Height: | Size: 7.8 KiB |
|
Before Width: | Height: | Size: 81 KiB |
|
Before Width: | Height: | Size: 70 KiB |
|
Before Width: | Height: | Size: 65 KiB |
|
Before Width: | Height: | Size: 2.8 MiB After Width: | Height: | Size: 959 KiB |
|
Before Width: | Height: | Size: 959 KiB |
@@ -37,7 +37,7 @@ const socialLinks: SocialLink[] = [
|
||||
---
|
||||
|
||||
<div
|
||||
class="border-2 border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] bg-secondary/25 p-4 [box-shadow:4px_4px_0_rgba(0,0,0,0.22)] transition-all duration-200 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:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
|
||||
class="overflow-hidden rounded-xl border p-4 transition-colors duration-300 ease-in-out has-[a:hover]:bg-secondary/50"
|
||||
>
|
||||
<div class="flex flex-wrap gap-4">
|
||||
<Link
|
||||
@@ -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://github.com/iluobei/dev.2ha.me" target="_blank">
|
||||
<Link href="https://code.2ha.me/dev.2ha.me/dev.2ha.me" target="_blank">
|
||||
<svg
|
||||
class="size-4 ml-1 mr-1"
|
||||
style="filter: invert(100%);"
|
||||
|
||||
@@ -1,192 +0,0 @@
|
||||
---
|
||||
import { Badge as BadgeComponent } from '@/components/ui/badge'
|
||||
import { cn } from '@/lib/utils'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
|
||||
interface Props {
|
||||
variant?: 'default' | 'secondary' | 'destructive' | 'outline'
|
||||
className?: string
|
||||
children?: any
|
||||
text?: string
|
||||
showIcon?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
variant = 'secondary',
|
||||
className = '',
|
||||
children,
|
||||
text,
|
||||
showIcon = true,
|
||||
} = Astro.props
|
||||
|
||||
const categoryMappings = [
|
||||
{
|
||||
keywords: ['crypto'],
|
||||
style: {
|
||||
color:
|
||||
'bg-yellow-50 text-yellow-700 dark:bg-yellow-950/30 dark:text-yellow-200',
|
||||
icon: 'lucide:key',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['web'],
|
||||
style: {
|
||||
color: 'bg-blue-50 text-blue-700 dark:bg-blue-950/30 dark:text-blue-200',
|
||||
icon: 'lucide:globe',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['reverse', 'rev'],
|
||||
style: {
|
||||
color:
|
||||
'bg-orange-50 text-orange-700 dark:bg-orange-950/30 dark:text-orange-200',
|
||||
icon: 'lucide:rotate-ccw',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['pwn', 'binary exploitation'],
|
||||
style: {
|
||||
color: 'bg-red-50 text-red-700 dark:bg-red-950/30 dark:text-red-200',
|
||||
icon: 'lucide:zap',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['misc'],
|
||||
style: {
|
||||
color:
|
||||
'bg-stone-50 text-stone-700 dark:bg-stone-950/30 dark:text-stone-200',
|
||||
icon: 'lucide:puzzle',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['forensic'],
|
||||
style: {
|
||||
color:
|
||||
'bg-green-50 text-green-700 dark:bg-green-950/30 dark:text-green-200',
|
||||
icon: 'lucide:search',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['osint'],
|
||||
style: {
|
||||
color:
|
||||
'bg-purple-50 text-purple-700 dark:bg-purple-950/30 dark:text-purple-200',
|
||||
icon: 'lucide:eye',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['blockchain'],
|
||||
style: {
|
||||
color: 'bg-teal-50 text-teal-700 dark:bg-teal-950/30 dark:text-teal-200',
|
||||
icon: 'lucide:link',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['ppc', 'programming'],
|
||||
style: {
|
||||
color:
|
||||
'bg-indigo-50 text-indigo-700 dark:bg-indigo-950/30 dark:text-indigo-200',
|
||||
icon: 'lucide:code',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['commercial'],
|
||||
style: {
|
||||
color: 'text-foreground bg-foreground/10',
|
||||
icon: 'lucide:building-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['personal'],
|
||||
style: {
|
||||
// color: 'bg-sky-50 text-sky-700 dark:bg-sky-950/30 dark:text-sky-200',
|
||||
icon: 'lucide:user',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['open-source'],
|
||||
style: {
|
||||
// color: 'bg-emerald-50 text-emerald-700 dark:bg-emerald-950/30 dark:text-emerald-200',
|
||||
icon: 'lucide:git-branch',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['freelance'],
|
||||
style: {
|
||||
// color: 'bg-teal-50 text-teal-700 dark:bg-teal-950/30 dark:text-teal-200',
|
||||
icon: 'lucide:briefcase',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['team'],
|
||||
style: {
|
||||
// color: 'bg-violet-50 text-violet-700 dark:bg-violet-950/30 dark:text-violet-200',
|
||||
icon: 'lucide:users',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['contract'],
|
||||
style: {
|
||||
// color: 'bg-rose-50 text-rose-700 dark:bg-rose-950/30 dark:text-rose-200',
|
||||
icon: 'lucide:file-text',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['astro'],
|
||||
style: {
|
||||
// color: 'bg-orange-50 text-orange-700 dark:bg-orange-950/30 dark:text-orange-200',
|
||||
icon: 'lucide:rocket',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['shopify'],
|
||||
style: {
|
||||
// color: 'bg-green-50 text-green-700 dark:bg-green-950/30 dark:text-green-200',
|
||||
icon: 'lucide:shopping-bag',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['html'],
|
||||
style: {
|
||||
// color: 'bg-red-50 text-red-700 dark:bg-red-950/30 dark:text-red-200',
|
||||
icon: 'lucide:code-2',
|
||||
},
|
||||
},
|
||||
{
|
||||
keywords: ['figma'],
|
||||
style: {
|
||||
// color: 'bg-purple-50 text-purple-700 dark:bg-purple-950/30 dark:text-purple-200',
|
||||
icon: 'lucide:palette',
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
const getCategoryStyle = (content: string) => {
|
||||
const lowerContent = content.toLowerCase()
|
||||
|
||||
const match = categoryMappings.find((category) =>
|
||||
category.keywords.some((keyword) => lowerContent.includes(keyword)),
|
||||
)
|
||||
|
||||
return match?.style || null
|
||||
}
|
||||
|
||||
const content = text || (typeof children === 'string' ? children : '')
|
||||
const categoryStyle = getCategoryStyle(content)
|
||||
---
|
||||
|
||||
<BadgeComponent
|
||||
variant={categoryStyle ? 'secondary' : variant}
|
||||
className={cn(categoryStyle?.color, className)}
|
||||
client:load
|
||||
>
|
||||
{
|
||||
showIcon && (
|
||||
<Icon
|
||||
name={categoryStyle ? categoryStyle.icon : 'lucide:tag'}
|
||||
class="size-3"
|
||||
/>
|
||||
)
|
||||
}
|
||||
<slot>{text}</slot>
|
||||
</BadgeComponent>
|
||||
@@ -24,14 +24,13 @@ const subposts = allPosts.filter((p) => p.data.parentTitle === entry.data.title)
|
||||
const totalBody = [entry.body!, ...subposts.map((p) => p.body!)]
|
||||
.map(stripCodeBlocks)
|
||||
.join('')
|
||||
const wordCount = totalBody.split(/\s+/).filter(Boolean).length
|
||||
const readTime = readingTime(wordCount)
|
||||
const readTime = readingTime(totalBody)
|
||||
|
||||
const authors = await parseAuthors(entry.data.authors ?? [])
|
||||
---
|
||||
|
||||
<div
|
||||
class="not-prose border-2 border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] bg-secondary/25 p-4 [box-shadow:4px_4px_0_rgba(0,0,0,0.22)] transition-all duration-200 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:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
|
||||
class="not-prose rounded-xl border p-4 transition-colors duration-300 ease-in-out hover:bg-secondary/50"
|
||||
>
|
||||
<Link
|
||||
href={`/${entry.collection}/${entry.id}`}
|
||||
|
||||
@@ -63,15 +63,15 @@ import Link from './Link.astro'
|
||||
// ]
|
||||
const SHORTS_CUTS_CLASS_NAMES: string[] = [
|
||||
'z-10 max-w max-h mt-[2.6em] inline-block sm:mt-[2.35em]',
|
||||
'z-10 max-w max-h ml-[0.1em] mt-[-4.7em] inline-block sm:ml-[0em] sm:mt-[-3.4em]',
|
||||
'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 ml-[0.1em] mt-[-3.7em] inline-block sm:ml-[0em] sm:mt-[-3.4em]',
|
||||
'z-10 max-w max-h ml-[-0.1em] mt-[2.5em] inline-block sm:ml-[0em] sm:mt-[2.5em]',
|
||||
'z-10 max-w max-h mt-[-4em] 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-[-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]'
|
||||
'z-10 max-w max-h ml-[0.1em] mt-[-6.5em] inline-block sm:ml-[0em] sm:mt-[-5.3em]',
|
||||
'z-10 max-w max-h mt-[0.2em] inline-block sm:mt-[0.6em]',
|
||||
'z-10 max-w max-h mt-[-6.5em] inline-block sm:mt-[-5.3em]',
|
||||
'z-10 max-w max-h mt-[1.49em] inline-block sm:mt-[1em]',
|
||||
'z-10 max-w max-h mt-[-5.2em] inline-block sm:mt-[-4.3em]'
|
||||
]
|
||||
|
||||
---
|
||||
@@ -87,23 +87,14 @@ const SHORTS_CUTS_CLASS_NAMES: string[] = [
|
||||
target="_blank"
|
||||
>
|
||||
<div
|
||||
class="bottom-0 right-0 w-fit items-end rounded-full border-2 border-[color-mix(in_srgb,hsl(var(--primary))_30%,hsl(var(--border)))] bg-secondary/50 p-3 text-primary transition-all duration-300 hover:rotate-12 hover:scale-110 hover:border-[color-mix(in_srgb,hsl(var(--primary))_50%,hsl(var(--border)))]"
|
||||
class="bottom-0 right-0 w-fit items-end rounded-full border bg-secondary/50 p-3 text-primary transition-all duration-300 hover:rotate-12 hover:ring-1 hover:ring-primary"
|
||||
>
|
||||
{item.icon.startsWith('mdi:') ? (
|
||||
<Icon
|
||||
style="color: rgb(233, 211, 182);"
|
||||
name={item.icon}
|
||||
class="z-[1] size-1/2 size-8 text-primary sm:size-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
) : (
|
||||
<img
|
||||
src={item.icon}
|
||||
alt={item.title}
|
||||
class="z-[1] size-1/2 size-8 sm:size-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
)}
|
||||
<Icon
|
||||
style="color: rgb(233, 211, 182);"
|
||||
name={item.icon}
|
||||
class="z-[1] size-1/2 size-8 text-primary sm:size-8"
|
||||
aria-hidden="true"
|
||||
/>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -1,406 +0,0 @@
|
||||
---
|
||||
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>
|
||||
@@ -4,30 +4,27 @@ import Link from '@/components/Link.astro'
|
||||
import MobileMenu from '@/components/ui/mobile-menu'
|
||||
import { NAV_LINKS } from '@/consts'
|
||||
import { Image } from 'astro:assets'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import logo from '../../public/static/logo.webp'
|
||||
|
||||
---
|
||||
|
||||
<header
|
||||
class="sticky top-0 z-[99] bg-background/50 backdrop-blur-md"
|
||||
class="sticky top-0 z-20 bg-background/50 backdrop-blur-md"
|
||||
transition:persist
|
||||
>
|
||||
<Container>
|
||||
<div class="flex flex-wrap items-center justify-between gap-4 py-4 ">
|
||||
<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 href="/">
|
||||
<Image src={logo} alt="Logo" class="size-8 -scale-x-100" />
|
||||
</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">
|
||||
<nav class="hidden items-center gap-4 text-base sm:flex sm:gap-6">
|
||||
{
|
||||
NAV_LINKS.map((item) => (
|
||||
<Link
|
||||
href={item.href}
|
||||
class="pixel-button inline-flex items-center justify-center gap-2 px-4 py-2 h-9 text-sm font-semibold uppercase tracking-wider bg-background/75 text-foreground border-[color:rgba(137,110,96,0.45)] hover:bg-primary/20 hover:text-primary hover:border-[color:rgba(217,119,87,0.65)] dark:bg-input/30 dark:border-[color:rgba(255,255,255,0.18)] dark:hover:bg-primary/25 dark:hover:text-primary dark:hover:border-[color:rgba(241,140,110,0.75)] transition-all"
|
||||
class="capitalize text-foreground/60 transition-colors hover:text-foreground/80"
|
||||
>
|
||||
<Icon name={item.icon} class="size-[18px] shrink-0" />
|
||||
<span>{item.label}</span>
|
||||
{item.label}
|
||||
</Link>
|
||||
))
|
||||
}
|
||||
@@ -38,31 +35,3 @@ 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>
|
||||
|
||||
@@ -1,36 +0,0 @@
|
||||
---
|
||||
import { SITE } from '@/consts'
|
||||
|
||||
interface Props {
|
||||
title?: string
|
||||
description?: string
|
||||
noindex?: boolean
|
||||
}
|
||||
|
||||
const {
|
||||
title = SITE.TITLE,
|
||||
description = SITE.DESCRIPTION,
|
||||
noindex = false,
|
||||
} = Astro.props
|
||||
const image = new URL('/static/twitter-card.png', Astro.site)
|
||||
---
|
||||
|
||||
<title>{`${title} | ${SITE.TITLE}`}</title>
|
||||
<meta name="description" content={description} />
|
||||
<link rel="canonical" href={SITE.SITEURL} />
|
||||
{noindex && <meta name="robots" content="noindex, nofollow" />}
|
||||
|
||||
<meta property="og:title" content={`${title} | ${SITE.TITLE}`} />
|
||||
<meta property="og:description" content={description} />
|
||||
<meta property="og:image" content={image} />
|
||||
<meta property="og:image:alt" content={title} />
|
||||
<meta property="og:type" content="website" />
|
||||
<meta property="og:locale" content={SITE.locale} />
|
||||
<meta property="og:site_name" content={SITE.TITLE} />
|
||||
<meta property="og:url" content={Astro.url} />
|
||||
|
||||
<meta name="twitter:title" content={title} />
|
||||
<meta name="twitter:description" content={description} />
|
||||
<meta name="twitter:image" content={image} />
|
||||
<meta name="twitter:image:alt" content={title} />
|
||||
<meta name="twitter:card" content="summary_large_image" />
|
||||
@@ -1,13 +1,10 @@
|
||||
---
|
||||
import Badge from '@/components/Badge.astro'
|
||||
import Link from '@/components/Link.astro'
|
||||
import { Separator } from '@/components/ui/separator'
|
||||
import { extractDomain, formatMonthYear } from '@/lib/utils'
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import { Badge } from '@/components/ui/badge'
|
||||
import { Image } from 'astro:assets'
|
||||
import type { CollectionEntry } from 'astro:content'
|
||||
|
||||
interface Props {
|
||||
type Props = {
|
||||
project: CollectionEntry<'projects'>
|
||||
}
|
||||
|
||||
@@ -15,63 +12,30 @@ const { project } = Astro.props
|
||||
---
|
||||
|
||||
<div
|
||||
class="border-2 border-[color-mix(in_srgb,hsl(var(--primary))_22%,hsl(var(--border)))] bg-secondary/25 p-4 [box-shadow:4px_4px_0_rgba(0,0,0,0.22)] transition-all duration-200 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:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] dark:hover:[box-shadow:6px_6px_0_rgba(0,0,0,0.75)]"
|
||||
class="overflow-hidden rounded-xl border transition-colors duration-300 ease-in-out hover:bg-secondary/50"
|
||||
>
|
||||
<Link
|
||||
href={project.data.link}
|
||||
class="flex flex-col gap-4 sm:flex-row"
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
{
|
||||
project.data.image && (
|
||||
<div class="max-w-[225px] sm:flex-shrink-0">
|
||||
<Image
|
||||
src={project.data.image}
|
||||
alt={project.data.name}
|
||||
width={1200}
|
||||
height={630}
|
||||
class="object-cover"
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="grow">
|
||||
<h3 class="mb-1 text-lg font-semibold">
|
||||
{project.data.name}
|
||||
</h3>
|
||||
<p class="text-muted-foreground mb-2 text-sm">
|
||||
<Link href={project.data.link} class="block">
|
||||
<Image
|
||||
src={project.data.image}
|
||||
alt={project.data.name}
|
||||
width={400}
|
||||
height={200}
|
||||
class="w-full object-cover"
|
||||
/>
|
||||
<div class="p-4">
|
||||
<h3 class="mb-2 text-lg font-semibold">{project.data.name}</h3>
|
||||
<p class="mb-4 text-sm text-muted-foreground">
|
||||
{project.data.description}
|
||||
</p>
|
||||
{
|
||||
project.data.startDate && (
|
||||
<div class="text-muted-foreground/70 mb-2 flex flex-wrap items-center gap-x-2 text-xs">
|
||||
<span class="flex items-center gap-x-1.5">
|
||||
<Icon name="lucide:calendar" class="size-3" />
|
||||
<span>
|
||||
{formatMonthYear(project.data.startDate)}
|
||||
{project.data.endDate
|
||||
? ` → ${formatMonthYear(project.data.endDate)}`
|
||||
: ' → Present'}
|
||||
</span>
|
||||
</span>
|
||||
<Separator orientation="vertical" className="h-4!" />
|
||||
<span class="flex items-center gap-x-1">
|
||||
<Icon name="lucide:external-link" class="size-3" />
|
||||
<span>{extractDomain(project.data.link)}</span>
|
||||
</span>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
{
|
||||
project.data.tags && (
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{project.data.tags.map((tag: string) => (
|
||||
<Badge text={tag} />
|
||||
))}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
<div class="flex flex-wrap gap-2">
|
||||
{
|
||||
project.data.tags.map((tag) => (
|
||||
<Badge variant="secondary" showHash={false}>
|
||||
{tag}
|
||||
</Badge>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
@@ -22,7 +22,6 @@ const iconMap = {
|
||||
Gitea: 'mdi:git',
|
||||
Maven: 'mavenrepo',
|
||||
DevIntro: 'lucide:info',
|
||||
HubProxy: 'lucide:rocket',
|
||||
}
|
||||
|
||||
const getSocialLink = ({ href, label }: SocialLink) => ({
|
||||
|
||||
@@ -1,46 +1,47 @@
|
||||
import AvatarComponent from '@/components/ui/avatar'
|
||||
|
||||
const AuthorPresence = () => {
|
||||
|
||||
|
||||
return (
|
||||
<div className="relative overflow-hidden sm:aspect-square select-none" style={{ cursor: 'default' }}>
|
||||
<div className="relative overflow-hidden sm:aspect-square">
|
||||
<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">
|
||||
<div className="flex justify-between gap-x-1">
|
||||
<div className="relative">
|
||||
<AvatarComponent
|
||||
src="/static/avatar.png"
|
||||
src="/static/avatar.webp"
|
||||
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="https://contrib.rocks/image?repo=iluobei/dev.2ha.me"
|
||||
src="/static/images/badges.png"
|
||||
alt="Discord Badges"
|
||||
width={104}
|
||||
height={24}
|
||||
className="grayscale"
|
||||
/>
|
||||
</div> */}
|
||||
</div>
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
li@2ha.me
|
||||
</span>
|
||||
</div>
|
||||
<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">
|
||||
<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">
|
||||
<img
|
||||
src="/static/images/lieflat.svg"
|
||||
alt="No Status Image"
|
||||
width={64}
|
||||
height={64}
|
||||
className="h-full rounded-lg select-none"
|
||||
className="h-full rounded-lg"
|
||||
/>
|
||||
<div className="text-[10px] text-muted-foreground select-none cursor-default">
|
||||
<div className="text-[10px] text-muted-foreground">
|
||||
但行好事,莫问前程。
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -55,12 +55,12 @@ const WakatimeGraph = ({ }: Props) => {
|
||||
useEffect(() => {
|
||||
setLanguages([
|
||||
{ name: 'java', hours: 1009, fill: 'hsl(var(--chart-1))' },
|
||||
{ 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: 'go', hours: 301, fill: 'hsl(var(--chart-6))' },
|
||||
{ name: 'python', hours: 177, fill: 'hsl(var(--chart-7))' },
|
||||
{ name: 'kotlin', hours: 346, fill: 'hsl(var(--chart-2))' },
|
||||
{ name: 'javascript', hours: 311, fill: 'hsl(var(--chart-3))' },
|
||||
{ name: 'typescript', hours: 287, fill: 'hsl(var(--chart-4))' },
|
||||
{ name: 'python', hours: 120, fill: 'hsl(var(--chart-5))' },
|
||||
{ name: 'react', hours: 85, fill: 'hsl(var(--chart-6))' },
|
||||
{ name: 'go', hours: 9, fill: 'hsl(var(--chart-7))' },
|
||||
])
|
||||
setIsLoading(false)
|
||||
}, [])
|
||||
|
||||
@@ -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 luobei failed, see https://github.com/luckykeeper/giteaCalendar.`,
|
||||
`Fetching Gitea(code.2ha.me) contribution data for jimlee failed, see https://github.com/luckykeeper/giteaCalendar.`,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -62,13 +62,6 @@ const Music163Player = () => {
|
||||
})
|
||||
}, [])
|
||||
|
||||
// Set audio volume to 50%
|
||||
useEffect(() => {
|
||||
if (audioRef.current) {
|
||||
audioRef.current.volume = 0.5
|
||||
}
|
||||
}, [displayData])
|
||||
|
||||
if (isLoading) {
|
||||
return (
|
||||
<div className="relative flex h-full w-full flex-col justify-between rounded-3xl p-6">
|
||||
@@ -109,7 +102,7 @@ const Music163Player = () => {
|
||||
alt="Album art"
|
||||
width={128}
|
||||
height={128}
|
||||
className="mb-2 w-[55%] border-2 border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))]"
|
||||
className="mb-2 w-[55%] rounded-xl border border-border"
|
||||
/>
|
||||
</a>
|
||||
<div className="flex min-w-0 flex-1 flex-col justify-end overflow-hidden">
|
||||
@@ -148,7 +141,7 @@ const Music163Player = () => {
|
||||
<span className="w-[85%] truncate text-xs text-muted-foreground">
|
||||
<span className="font-semibold text-secondary-foreground">
|
||||
<div>
|
||||
<audio ref={audioRef}>
|
||||
<audio ref={audioRef} >
|
||||
<source src={outerurl} type="audio/mp3" />
|
||||
<source src={backupurl} type="audio/mp3" />
|
||||
</audio>
|
||||
|
||||
@@ -58,7 +58,7 @@ const RandomAnimeBackground = () => {
|
||||
// }
|
||||
|
||||
return (
|
||||
<video ref={videoRef} width="100" height="100" className="no-repeat relative w-full justify-center object-cover"
|
||||
<video ref={videoRef} width="100" height="100" className="no-repeat relative w-full justify-center rounded-[1.4em] object-cover"
|
||||
src={'/static/anime-bg/' + videoBackgrounds[index]}
|
||||
style={{ display: isLoading ? 'none' : 'block' }}
|
||||
onEnded={handleVideoEnded}
|
||||
|
||||
@@ -24,7 +24,7 @@ export default function GradientText({
|
||||
// console.log(children)
|
||||
return (
|
||||
<div
|
||||
className={`relative mx-auto ml-2 flex max-w-fit cursor-pointer flex-row items-center justify-center rounded-[1.25rem] font-medium backdrop-blur transition-shadow duration-500 ${className}`}
|
||||
className={`relative mx-auto ml-2 md:max-h-[1em] flex max-w-fit cursor-pointer flex-row items-center justify-center overflow-hidden rounded-[1.25rem] font-medium backdrop-blur transition-shadow duration-500 ${className}`}
|
||||
>
|
||||
{showBorder && (
|
||||
<div
|
||||
|
||||
@@ -280,7 +280,7 @@ const LetterGlitch = ({
|
||||
}, [glitchSpeed, smooth])
|
||||
|
||||
return (
|
||||
<div className="relative h-full w-full overflow-hidden bg-black">
|
||||
<div className="relative h-full w-full overflow-hidden rounded-3xl bg-black">
|
||||
<canvas ref={canvasRef} className="block h-full w-full" />
|
||||
{outerVignette && (
|
||||
<div className="pointer-events-none absolute left-0 top-0 h-full w-full bg-[radial-gradient(circle,_rgba(0,0,0,0)_60%,_rgba(0,0,0,1)_100%)]"></div>
|
||||
|
||||
@@ -5,25 +5,25 @@ import { cva, type VariantProps } from 'class-variance-authority'
|
||||
import { cn } from '@/lib/utils'
|
||||
|
||||
const buttonVariants = cva(
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap text-sm font-medium transition-all focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0 border-2',
|
||||
'inline-flex items-center justify-center gap-2 whitespace-nowrap rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:pointer-events-none disabled:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0',
|
||||
{
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
'bg-primary text-primary-foreground [box-shadow:3px_3px_0_rgba(0,0,0,0.18)] hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] hover:-translate-y-[1px] active:[box-shadow:1px_1px_0_rgba(0,0,0,0.16)] active:translate-y-0 dark:[box-shadow:3px_3px_0_rgba(0,0,0,0.45)] dark:hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.55)] dark:active:[box-shadow:1px_1px_0_rgba(0,0,0,0.35)]',
|
||||
'bg-primary text-primary-foreground shadow hover:bg-primary/90',
|
||||
destructive:
|
||||
'bg-destructive text-destructive-foreground [box-shadow:3px_3px_0_rgba(0,0,0,0.18)] hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] hover:-translate-y-[1px] dark:[box-shadow:3px_3px_0_rgba(0,0,0,0.45)]',
|
||||
'bg-destructive text-destructive-foreground shadow-sm hover:bg-destructive/90',
|
||||
outline:
|
||||
'border-input bg-background [box-shadow:3px_3px_0_rgba(0,0,0,0.18)] hover:bg-accent hover:text-accent-foreground hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] hover:-translate-y-[1px] dark:[box-shadow:3px_3px_0_rgba(0,0,0,0.45)] dark:hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.55)]',
|
||||
'border border-input bg-background shadow-sm hover:bg-accent hover:text-accent-foreground',
|
||||
secondary:
|
||||
'bg-secondary text-secondary-foreground [box-shadow:3px_3px_0_rgba(0,0,0,0.18)] hover:bg-secondary/80 hover:[box-shadow:4px_4px_0_rgba(0,0,0,0.22)] hover:-translate-y-[1px] dark:[box-shadow:3px_3px_0_rgba(0,0,0,0.45)]',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground border-transparent',
|
||||
link: 'text-primary underline-offset-4 hover:underline border-transparent',
|
||||
'bg-secondary text-secondary-foreground shadow-sm hover:bg-secondary/80',
|
||||
ghost: 'hover:bg-accent hover:text-accent-foreground',
|
||||
link: 'text-primary underline-offset-4 hover:underline',
|
||||
},
|
||||
size: {
|
||||
default: 'h-9 px-4 py-2',
|
||||
sm: 'h-8 px-3 text-xs',
|
||||
lg: 'h-10 px-8',
|
||||
sm: 'h-8 rounded-md px-3 text-xs',
|
||||
lg: 'h-10 rounded-md px-8',
|
||||
icon: 'h-9 w-9',
|
||||
},
|
||||
},
|
||||
|
||||
@@ -28,7 +28,7 @@ const DropdownMenuSubTrigger = React.forwardRef<
|
||||
<DropdownMenuPrimitive.SubTrigger
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'flex cursor-default select-none items-center px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
'flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none focus:bg-accent data-[state=open]:bg-accent',
|
||||
inset && 'pl-8',
|
||||
className,
|
||||
)}
|
||||
@@ -48,7 +48,7 @@ const DropdownMenuSubContent = React.forwardRef<
|
||||
<DropdownMenuPrimitive.SubContent
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden border-2 border-[color:rgba(241,140,110,0.22)] bg-popover p-1 text-popover-foreground [box-shadow:4px_4px_0_rgba(0,0,0,0.22)] dark:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)] data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-lg data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
@@ -66,7 +66,7 @@ const DropdownMenuContent = React.forwardRef<
|
||||
ref={ref}
|
||||
sideOffset={sideOffset}
|
||||
className={cn(
|
||||
'z-50 min-w-[8rem] overflow-hidden border-2 border-[color:rgba(241,140,110,0.22)] bg-popover p-1 text-popover-foreground [box-shadow:4px_4px_0_rgba(0,0,0,0.22)] dark:[box-shadow:4px_4px_0_rgba(0,0,0,0.65)]',
|
||||
'z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover p-1 text-popover-foreground shadow-md',
|
||||
'data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2',
|
||||
className,
|
||||
)}
|
||||
@@ -85,7 +85,7 @@ const DropdownMenuItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.Item
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex cursor-default select-none items-center px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex cursor-default select-none items-center rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
inset && 'pl-8',
|
||||
className,
|
||||
)}
|
||||
@@ -101,7 +101,7 @@ const DropdownMenuCheckboxItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.CheckboxItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
checked={checked}
|
||||
@@ -125,7 +125,7 @@ const DropdownMenuRadioItem = React.forwardRef<
|
||||
<DropdownMenuPrimitive.RadioItem
|
||||
ref={ref}
|
||||
className={cn(
|
||||
'relative flex cursor-default select-none items-center py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
'relative flex cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50',
|
||||
className,
|
||||
)}
|
||||
{...props}
|
||||
|
||||
@@ -7,20 +7,7 @@ import {
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu'
|
||||
import { NAV_LINKS } from '@/consts'
|
||||
import { Menu, Home, FileText, FolderGit2, BadgeInfo } from 'lucide-react'
|
||||
|
||||
const getIconComponent = (iconName?: string) => {
|
||||
if (!iconName) return Home
|
||||
|
||||
const iconMap: Record<string, any> = {
|
||||
'lucide:home': Home,
|
||||
'lucide:file-text': FileText,
|
||||
'lucide:folder-git-2': FolderGit2,
|
||||
'lucide:badge-info': BadgeInfo,
|
||||
}
|
||||
|
||||
return iconMap[iconName] || Home
|
||||
}
|
||||
import { Menu } from 'lucide-react'
|
||||
|
||||
const MobileMenu = () => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
@@ -46,29 +33,25 @@ const MobileMenu = () => {
|
||||
<Button
|
||||
variant="outline"
|
||||
size="icon"
|
||||
className="sm:hidden pixel-button h-9 w-9 bg-background/75 border-[color:rgba(137,110,96,0.45)] hover:bg-primary/20 hover:text-primary hover:border-[color:rgba(217,119,87,0.65)] dark:bg-input/30 dark:border-[color:rgba(255,255,255,0.18)] dark:hover:bg-primary/25 dark:hover:text-primary dark:hover:border-[color:rgba(241,140,110,0.75)]"
|
||||
className="sm:hidden"
|
||||
title="Menu"
|
||||
>
|
||||
<Menu className="h-5 w-5" />
|
||||
<span className="sr-only">Toggle menu</span>
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end" className="bg-background min-w-[140px] w-auto p-0">
|
||||
{NAV_LINKS.map((item) => {
|
||||
const Icon = getIconComponent(item.icon)
|
||||
return (
|
||||
<DropdownMenuItem key={item.href} asChild className="p-0">
|
||||
<a
|
||||
href={item.href}
|
||||
className="flex items-center justify-center gap-2 w-full px-4 py-2.5 text-base font-semibold uppercase tracking-wider cursor-pointer hover:bg-primary/20 hover:text-primary focus:bg-primary/20 focus:text-primary dark:hover:bg-primary/25 dark:hover:text-primary border-b border-[color:rgba(241,140,110,0.15)] last:border-b-0 transition-colors"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
<Icon className="size-[18px] shrink-0" />
|
||||
<span>{item.label}</span>
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
)
|
||||
})}
|
||||
<DropdownMenuContent align="end" className="bg-background">
|
||||
{NAV_LINKS.map((item) => (
|
||||
<DropdownMenuItem key={item.href} asChild>
|
||||
<a
|
||||
href={item.href}
|
||||
className="w-full text-lg font-medium capitalize"
|
||||
onClick={() => setIsOpen(false)}
|
||||
>
|
||||
{item.label}
|
||||
</a>
|
||||
</DropdownMenuItem>
|
||||
))}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
)
|
||||
|
||||
@@ -5,13 +5,11 @@ export type Site = {
|
||||
NUM_POSTS_ON_HOMEPAGE: number
|
||||
POSTS_PER_PAGE: number
|
||||
SITEURL: string
|
||||
locale: string
|
||||
}
|
||||
|
||||
export type Link = {
|
||||
href: string
|
||||
label: string
|
||||
icon?: string
|
||||
}
|
||||
|
||||
export type DevLink = {
|
||||
@@ -28,20 +26,19 @@ export const SITE: Site = {
|
||||
NUM_POSTS_ON_HOMEPAGE: 2,
|
||||
POSTS_PER_PAGE: 4,
|
||||
SITEURL: 'https://dev.2ha.me',
|
||||
locale: 'zh-CN',
|
||||
}
|
||||
|
||||
export const NAV_LINKS: Link[] = [
|
||||
{ href: '/', label: '主页', icon: 'lucide:home' },
|
||||
{ href: '/blog', label: '博客', icon: 'lucide:file-text' },
|
||||
// { href: '/tags', label: '标签' },
|
||||
{ href: '/projects', label: '项目', icon: 'lucide:folder-git-2'},
|
||||
{ href: '/authors', label: '关于', icon: 'lucide:badge-info' },
|
||||
{ href: '/', label: '主页' },
|
||||
{ href: '/blog', label: '博客' },
|
||||
{ href: '/tags', label: '标签' },
|
||||
// { href: '/authors', label: '作者' },
|
||||
{ href: '/authors', label: '关于' },
|
||||
]
|
||||
|
||||
export const SOCIAL_LINKS: Link[] = [
|
||||
{ href: 'https://github.com/iluobei', label: 'GitHub' },
|
||||
{ href: 'https://1ms.cc', label: 'HubProxy' },
|
||||
{ href: 'https://github.com/jimleerx', label: 'GitHub' },
|
||||
{ href: 'https://maven.2ha.me', label: 'Maven' },
|
||||
{ href: 'https://code.2ha.me', label: 'Gitea' },
|
||||
{ href: 'li@2ha.me', label: 'Email' },
|
||||
]
|
||||
@@ -54,10 +51,10 @@ export const DEV_LINKS: DevLink[] = [
|
||||
icon: 'mdi:git',
|
||||
},
|
||||
{
|
||||
href: 'https://img.2ha.me',
|
||||
href: 'https://maven.2ha.me',
|
||||
label: 'Nexus',
|
||||
title: '图床',
|
||||
icon: 'mdi:image-multiple',
|
||||
title: 'Maven仓库',
|
||||
icon: 'mdi:chart-doughnut-variant',
|
||||
},
|
||||
{
|
||||
href: 'https://dms.2ha.me',
|
||||
@@ -69,13 +66,13 @@ export const DEV_LINKS: DevLink[] = [
|
||||
href: 'https://p.2ha.me',
|
||||
label: 'Zfile',
|
||||
title: '网盘',
|
||||
icon: 'mdi:harddisk',
|
||||
icon: 'mdi:cloud-arrow-up',
|
||||
},
|
||||
{
|
||||
href: 'https://tz.2ha.me',
|
||||
label: 'VPS Monitor',
|
||||
title: '探针',
|
||||
icon: 'mdi:chart-areaspline',
|
||||
href: 'https://photo.2ha.me',
|
||||
label: 'immich',
|
||||
title: '相册',
|
||||
icon: 'mdi:camera',
|
||||
},
|
||||
{
|
||||
href: 'https://f.2ha.me',
|
||||
@@ -83,12 +80,7 @@ export const DEV_LINKS: DevLink[] = [
|
||||
title: '文件服务器',
|
||||
icon: 'mdi:file-arrow-up-down-outline',
|
||||
},
|
||||
{
|
||||
href: 'https://status.2ha.me',
|
||||
label: 'Domain Status',
|
||||
title: '站点检测',
|
||||
icon: 'mdi:cloud-check'
|
||||
},
|
||||
{ href: 'https://v.2ha.me', label: 'Emby', title: 'Emby', icon: 'mdi:emby' },
|
||||
{
|
||||
href: 'https://in.2ha.me',
|
||||
label: '2ha.me statistics',
|
||||
@@ -96,16 +88,15 @@ export const DEV_LINKS: DevLink[] = [
|
||||
icon: 'mdi:sun-azimuth',
|
||||
},
|
||||
{
|
||||
href: 'https://miaomiaowu.net',
|
||||
label: '妙妙屋',
|
||||
title: '个人Clash订阅管理工具',
|
||||
icon: '/static/mmw.svg',
|
||||
// icon: 'mdi:cat',
|
||||
href: 'https://mp.2ha.me',
|
||||
label: 'MoviePilot',
|
||||
title: '媒体订阅工具',
|
||||
icon: 'mdi:youtube-creator-studio',
|
||||
},
|
||||
{
|
||||
href: 'https://1ms.cc',
|
||||
label: 'hubproxy',
|
||||
title: 'GitHub&DockerHub加速',
|
||||
href: 'https://g.2ha.me',
|
||||
label: 'GHProxy',
|
||||
title: 'GitHub代理',
|
||||
icon: 'mdi:rocket-launch-outline',
|
||||
},
|
||||
]
|
||||
|
||||
@@ -11,7 +11,6 @@ const blog = defineCollection({
|
||||
image: image().optional(),
|
||||
tags: z.array(z.string()).optional(),
|
||||
authors: z.array(z.string()).optional(),
|
||||
order: z.number().optional(),
|
||||
draft: z.boolean().optional(),
|
||||
hidden: z.boolean().optional(),
|
||||
parentTitle: z.string().optional(),
|
||||
@@ -67,9 +66,6 @@ const projects = defineCollection({
|
||||
tags: z.array(z.string()),
|
||||
image: image(),
|
||||
link: z.string().url(),
|
||||
startDate: z.coerce.date().optional(),
|
||||
endDate: z.coerce.date().optional(),
|
||||
order: z.number().optional(),
|
||||
}),
|
||||
})
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
---
|
||||
name: '胡萝北(🥕)'
|
||||
name: 'jimlee'
|
||||
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/luobei'
|
||||
devintro: 'https://code.2ha.me/jimlee'
|
||||
gitea: 'https://code.2ha.me/dev.2ha.me'
|
||||
# twitter: 'https://twitter.com/enscry'
|
||||
github: 'https://github.com/iluobei'
|
||||
github: 'https://github.com/jimleerx'
|
||||
mail: 'li@2ha.me'
|
||||
---
|
||||
|
||||
18
src/content/blog/buildnginx-2025/buildnginx.mdx
vendored
@@ -4,7 +4,7 @@ description: '在debian上根据需要添加nginx模块,编译自定义的ngin
|
||||
date: 2025-04-11
|
||||
tags: ['nginx', 'debian', 'build']
|
||||
image: 'assets/nginx.svg'
|
||||
authors: ['胡萝北(🥕)']
|
||||
authors: ['jimlee']
|
||||
---
|
||||
|
||||
## 在debian上安装nginx
|
||||
@@ -15,15 +15,15 @@ debian默认软件库的nginx没有fancy-index模块, fancy-index是一个html
|
||||
sudo apt install -y build-essential libpcre3 libpcre3-dev zlib1g-dev openssl libssl-dev
|
||||
```
|
||||
## 2. 下载并解压nginx源码
|
||||
官网查看最新版本(当前20251111为1.29.3)
|
||||
官网查看最新版本(当前20250111为1.26.3)
|
||||
https://nginx.org/en/download.html
|
||||
```shellscript title="shell"
|
||||
wget https://nginx.org/download/nginx-1.29.3.tar.gz
|
||||
tar -xf nginx-1.29.3.tar.gz
|
||||
wget https://nginx.org/download/nginx-1.26.3.tar.gz
|
||||
tar -xf nginx-1.26.3.tar.gz
|
||||
```
|
||||
## 3. 下载并解压nginx-fancyindex模块
|
||||
```shellscript title="shell"
|
||||
cd nginx-1.29.3
|
||||
cd nginx-1.26.3
|
||||
wget https://github.com/aperezdc/ngx-fancyindex/releases/download/v0.5.2/ngx-fancyindex-0.5.2.tar.xz
|
||||
tar -xf ngx-fancyindex-0.5.2.tar.xz
|
||||
```
|
||||
@@ -34,15 +34,11 @@ mkdir -p /var/cache/nginx
|
||||
mkdir -p /var/log/nginx
|
||||
```
|
||||
NGINX_ROOT_PATH = nginx的安装目录, 需要替换成你想要安装的目录
|
||||
```
|
||||
export NGINX_ROOT_PATH=/usr/local/nginx
|
||||
```
|
||||
|
||||
```shellscript
|
||||
./configure --add-module=./ngx-fancyindex-0.5.2 \
|
||||
--prefix=${NGINX_ROOT_PATH} \
|
||||
--user=$(whoami) \
|
||||
--group=$(id -Gn) \
|
||||
--user=jimlee \
|
||||
--group=jimlee \
|
||||
--sbin-path=${NGINX_ROOT_PATH}/sbin/nginx \
|
||||
--conf-path=${NGINX_ROOT_PATH}/nginx.conf \
|
||||
--error-log-path=/var/log/nginx/error.log \
|
||||
|
||||
@@ -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: ['胡萝北(🥕)']
|
||||
authors: ['jimlee']
|
||||
---
|
||||
|
||||
## kotlin
|
||||
|
||||
@@ -4,7 +4,7 @@ description: '在dsm7.2上破解emby套件, 输入任意激活码即可获取小
|
||||
date: 2024-12-31
|
||||
tags: ['emby', 'synology', 'crack']
|
||||
image: 'assets/emby_banner.png'
|
||||
authors: ['胡萝北(🥕)']
|
||||
authors: ['jimlee']
|
||||
---
|
||||
import { Icon } from 'astro-icon/components'
|
||||
import Link from '@/components/Link.astro'
|
||||
|
||||
@@ -4,7 +4,7 @@ description: 'debian安装fail2ban保护nginx服务器,阻止暴力破解和
|
||||
date: 2025-10-06
|
||||
tags: ['debian', 'nginx', 'fail2ban', 'regex', 'shell']
|
||||
image: 'assets/nginxfail2ban.png'
|
||||
authors: ['胡萝北(🥕)']
|
||||
authors: ['jimlee']
|
||||
---
|
||||
|
||||
|
||||
|
||||
@@ -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: ['胡萝北(🥕)']
|
||||
authors: ['jimlee']
|
||||
---
|
||||
import FileTree from '@/components/starlight/FileTree.astro'
|
||||
|
||||
|
||||
9
src/content/project.mdx
vendored
@@ -1,9 +0,0 @@
|
||||
---
|
||||
---
|
||||
|
||||
<h3 class="not-prose text-lg font-medium mb-1">
|
||||
Some work I’ve done <span class="text-muted-foreground">ヽ(o^ ^o)ノ</span>
|
||||
</h3>
|
||||
<span class="not-prose text-muted-foreground text-xs">
|
||||
Last updated: 2025-08-11
|
||||
</span>
|
||||
|
Before Width: | Height: | Size: 1013 KiB |
@@ -1,8 +0,0 @@
|
||||
---
|
||||
name: '妙妙屋(个人clash订阅管理工具)'
|
||||
description: '妙妙屋是一个功能强大的Clash订阅管理平台,帮助您轻松管理订阅、节点和用户。'
|
||||
tags: ['open-source', 'personal', 'clash', 'substore']
|
||||
image: 'assets/mmw.png'
|
||||
link: 'https://miaomiaowu.net'
|
||||
startDate: '2025-10-10'
|
||||
---
|
||||
@@ -14,14 +14,14 @@ const { title, description, image } = Astro.props
|
||||
---
|
||||
|
||||
<!doctype html>
|
||||
<html lang="zh" class="dark">
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<Head
|
||||
title={`${title} | ${SITE.TITLE}`}
|
||||
description={description}
|
||||
image={image}
|
||||
/>
|
||||
<script is:inline async defer src="https://in.2ha.me/script.js" data-website-id="34634aec-34a9-4ef4-9a8f-08ee96699a84"></script>
|
||||
<script defer src="https://in.2ha.me/script.js" data-website-id="34634aec-34a9-4ef4-9a8f-08ee96699a84"></script>
|
||||
</head>
|
||||
<body>
|
||||
<div
|
||||
|
||||
@@ -1,325 +0,0 @@
|
||||
import { getCollection, render, type CollectionEntry } from 'astro:content'
|
||||
import { readingTime, calculateWordCountFromHtml } from '@/lib/utils'
|
||||
|
||||
export async function getAllAuthors(): Promise<CollectionEntry<'authors'>[]> {
|
||||
return await getCollection('authors')
|
||||
}
|
||||
|
||||
export async function getAllPosts(): Promise<CollectionEntry<'blog'>[]> {
|
||||
const posts = await getCollection('blog')
|
||||
return posts
|
||||
.filter((post) => !post.data.draft && !isSubpost(post.id))
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
}
|
||||
|
||||
export async function getAllPostsAndSubposts(): Promise<
|
||||
CollectionEntry<'blog'>[]
|
||||
> {
|
||||
const posts = await getCollection('blog')
|
||||
return posts
|
||||
.filter((post) => !post.data.draft)
|
||||
.sort((a, b) => b.data.date.valueOf() - a.data.date.valueOf())
|
||||
}
|
||||
|
||||
export async function getAllTags(): Promise<Map<string, number>> {
|
||||
const posts = await getAllPosts()
|
||||
return posts.reduce((acc, post) => {
|
||||
post.data.tags?.forEach((tag) => {
|
||||
acc.set(tag, (acc.get(tag) || 0) + 1)
|
||||
})
|
||||
return acc
|
||||
}, new Map<string, number>())
|
||||
}
|
||||
|
||||
export async function getAllProjects(): Promise<CollectionEntry<'projects'>[]> {
|
||||
const projects = await getCollection('projects')
|
||||
return projects.sort((a, b) => {
|
||||
const orderA = a.data.order ?? 0
|
||||
const orderB = b.data.order ?? 0
|
||||
if (orderA !== orderB) {
|
||||
return orderA - orderB
|
||||
}
|
||||
const dateA = a.data.startDate?.getTime() || 0
|
||||
const dateB = b.data.startDate?.getTime() || 0
|
||||
return dateB - dateA
|
||||
})
|
||||
}
|
||||
|
||||
export async function getAdjacentPosts(currentId: string): Promise<{
|
||||
newer: CollectionEntry<'blog'> | null
|
||||
older: CollectionEntry<'blog'> | null
|
||||
parent: CollectionEntry<'blog'> | null
|
||||
}> {
|
||||
const allPosts = await getAllPosts()
|
||||
|
||||
if (isSubpost(currentId)) {
|
||||
const parentId = getParentId(currentId)
|
||||
const allPosts = await getAllPosts()
|
||||
const parent = allPosts.find((post) => post.id === parentId) || null
|
||||
|
||||
const posts = await getCollection('blog')
|
||||
const subposts = posts
|
||||
.filter(
|
||||
(post) =>
|
||||
isSubpost(post.id) &&
|
||||
getParentId(post.id) === parentId &&
|
||||
!post.data.draft,
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const dateDiff = a.data.date.valueOf() - b.data.date.valueOf()
|
||||
if (dateDiff !== 0) return dateDiff
|
||||
|
||||
const orderA = a.data.order ?? 0
|
||||
const orderB = b.data.order ?? 0
|
||||
return orderA - orderB
|
||||
})
|
||||
|
||||
const currentIndex = subposts.findIndex((post) => post.id === currentId)
|
||||
if (currentIndex === -1) {
|
||||
return { newer: null, older: null, parent }
|
||||
}
|
||||
|
||||
return {
|
||||
newer:
|
||||
currentIndex < subposts.length - 1 ? subposts[currentIndex + 1] : null,
|
||||
older: currentIndex > 0 ? subposts[currentIndex - 1] : null,
|
||||
parent,
|
||||
}
|
||||
}
|
||||
|
||||
const parentPosts = allPosts.filter((post) => !isSubpost(post.id))
|
||||
const currentIndex = parentPosts.findIndex((post) => post.id === currentId)
|
||||
|
||||
if (currentIndex === -1) {
|
||||
return { newer: null, older: null, parent: null }
|
||||
}
|
||||
|
||||
return {
|
||||
newer: currentIndex > 0 ? parentPosts[currentIndex - 1] : null,
|
||||
older:
|
||||
currentIndex < parentPosts.length - 1
|
||||
? parentPosts[currentIndex + 1]
|
||||
: null,
|
||||
parent: null,
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPostsByAuthor(
|
||||
authorId: string,
|
||||
): Promise<CollectionEntry<'blog'>[]> {
|
||||
const posts = await getAllPosts()
|
||||
return posts.filter((post) => post.data.authors?.includes(authorId))
|
||||
}
|
||||
|
||||
export async function getPostsByTag(
|
||||
tag: string,
|
||||
): Promise<CollectionEntry<'blog'>[]> {
|
||||
const posts = await getAllPosts()
|
||||
return posts.filter((post) => post.data.tags?.includes(tag))
|
||||
}
|
||||
|
||||
export async function getRecentPosts(
|
||||
count: number,
|
||||
): Promise<CollectionEntry<'blog'>[]> {
|
||||
const posts = await getAllPosts()
|
||||
return posts.slice(0, count)
|
||||
}
|
||||
|
||||
export async function getSortedTags(): Promise<
|
||||
{ tag: string; count: number }[]
|
||||
> {
|
||||
const tagCounts = await getAllTags()
|
||||
return [...tagCounts.entries()]
|
||||
.map(([tag, count]) => ({ tag, count }))
|
||||
.sort((a, b) => {
|
||||
const countDiff = b.count - a.count
|
||||
return countDiff !== 0 ? countDiff : a.tag.localeCompare(b.tag)
|
||||
})
|
||||
}
|
||||
|
||||
export function getParentId(subpostId: string): string {
|
||||
return subpostId.split('/')[0]
|
||||
}
|
||||
|
||||
export async function getSubpostsForParent(
|
||||
parentId: string,
|
||||
): Promise<CollectionEntry<'blog'>[]> {
|
||||
const posts = await getCollection('blog')
|
||||
return posts
|
||||
.filter(
|
||||
(post) =>
|
||||
!post.data.draft &&
|
||||
isSubpost(post.id) &&
|
||||
getParentId(post.id) === parentId,
|
||||
)
|
||||
.sort((a, b) => {
|
||||
const dateDiff = a.data.date.valueOf() - b.data.date.valueOf()
|
||||
if (dateDiff !== 0) return dateDiff
|
||||
|
||||
const orderA = a.data.order ?? 0
|
||||
const orderB = b.data.order ?? 0
|
||||
return orderA - orderB
|
||||
})
|
||||
}
|
||||
|
||||
export function groupPostsByYear(
|
||||
posts: CollectionEntry<'blog'>[],
|
||||
): Record<string, CollectionEntry<'blog'>[]> {
|
||||
return posts.reduce(
|
||||
(acc: Record<string, CollectionEntry<'blog'>[]>, post) => {
|
||||
const year = post.data.date.getFullYear().toString()
|
||||
;(acc[year] ??= []).push(post)
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export function groupProjectsByYear(
|
||||
projects: CollectionEntry<'projects'>[],
|
||||
): Record<string, CollectionEntry<'projects'>[]> {
|
||||
return projects.reduce(
|
||||
(acc: Record<string, CollectionEntry<'projects'>[]>, project) => {
|
||||
// Use startDate for grouping, fallback to current year if no date
|
||||
const year = project.data.startDate
|
||||
? project.data.startDate.getFullYear().toString()
|
||||
: new Date().getFullYear().toString()
|
||||
;(acc[year] ??= []).push(project)
|
||||
return acc
|
||||
},
|
||||
{},
|
||||
)
|
||||
}
|
||||
|
||||
export async function hasSubposts(postId: string): Promise<boolean> {
|
||||
const subposts = await getSubpostsForParent(postId)
|
||||
return subposts.length > 0
|
||||
}
|
||||
|
||||
export function isSubpost(postId: string): boolean {
|
||||
return postId.includes('/')
|
||||
}
|
||||
|
||||
export async function getParentPost(
|
||||
subpostId: string,
|
||||
): Promise<CollectionEntry<'blog'> | null> {
|
||||
if (!isSubpost(subpostId)) {
|
||||
return null
|
||||
}
|
||||
|
||||
const parentId = getParentId(subpostId)
|
||||
const allPosts = await getAllPosts()
|
||||
return allPosts.find((post) => post.id === parentId) || null
|
||||
}
|
||||
|
||||
export async function parseAuthors(authorIds: string[] = []) {
|
||||
if (!authorIds.length) return []
|
||||
|
||||
const allAuthors = await getAllAuthors()
|
||||
const authorMap = new Map(allAuthors.map((author) => [author.id, author]))
|
||||
|
||||
return authorIds.map((id) => {
|
||||
const author = authorMap.get(id)
|
||||
return {
|
||||
id,
|
||||
name: author?.data?.name || id,
|
||||
avatar: author?.data?.avatar || '/static/logo.png',
|
||||
isRegistered: !!author,
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
export async function getPostById(
|
||||
postId: string,
|
||||
): Promise<CollectionEntry<'blog'> | null> {
|
||||
const allPosts = await getAllPostsAndSubposts()
|
||||
return allPosts.find((post) => post.id === postId) || null
|
||||
}
|
||||
|
||||
export async function getSubpostCount(parentId: string): Promise<number> {
|
||||
const subposts = await getSubpostsForParent(parentId)
|
||||
return subposts.length
|
||||
}
|
||||
|
||||
export async function getCombinedReadingTime(postId: string): Promise<string> {
|
||||
const post = await getPostById(postId)
|
||||
if (!post) return readingTime(0)
|
||||
|
||||
let totalWords = calculateWordCountFromHtml(post.body)
|
||||
|
||||
if (!isSubpost(postId)) {
|
||||
const subposts = await getSubpostsForParent(postId)
|
||||
for (const subpost of subposts) {
|
||||
totalWords += calculateWordCountFromHtml(subpost.body)
|
||||
}
|
||||
}
|
||||
|
||||
return readingTime(totalWords)
|
||||
}
|
||||
|
||||
export async function getPostReadingTime(postId: string): Promise<string> {
|
||||
const post = await getPostById(postId)
|
||||
if (!post) return readingTime(0)
|
||||
|
||||
const wordCount = calculateWordCountFromHtml(post.body)
|
||||
return readingTime(wordCount)
|
||||
}
|
||||
|
||||
export type TOCHeading = {
|
||||
slug: string
|
||||
text: string
|
||||
depth: number
|
||||
isSubpostTitle?: boolean
|
||||
}
|
||||
|
||||
export type TOCSection = {
|
||||
type: 'parent' | 'subpost'
|
||||
title: string
|
||||
headings: TOCHeading[]
|
||||
subpostId?: string
|
||||
}
|
||||
|
||||
export async function getTOCSections(postId: string): Promise<TOCSection[]> {
|
||||
const post = await getPostById(postId)
|
||||
if (!post) return []
|
||||
|
||||
const parentId = isSubpost(postId) ? getParentId(postId) : postId
|
||||
const parentPost = isSubpost(postId) ? await getPostById(parentId) : post
|
||||
|
||||
if (!parentPost) return []
|
||||
|
||||
const sections: TOCSection[] = []
|
||||
|
||||
const { headings: parentHeadings } = await render(parentPost)
|
||||
if (parentHeadings.length > 0) {
|
||||
sections.push({
|
||||
type: 'parent',
|
||||
title: 'Overview',
|
||||
headings: parentHeadings.map((heading) => ({
|
||||
slug: heading.slug,
|
||||
text: heading.text,
|
||||
depth: heading.depth,
|
||||
})),
|
||||
})
|
||||
}
|
||||
|
||||
const subposts = await getSubpostsForParent(parentId)
|
||||
for (const subpost of subposts) {
|
||||
const { headings: subpostHeadings } = await render(subpost)
|
||||
if (subpostHeadings.length > 0) {
|
||||
sections.push({
|
||||
type: 'subpost',
|
||||
title: subpost.data.title,
|
||||
headings: subpostHeadings.map((heading, index) => ({
|
||||
slug: heading.slug,
|
||||
text: heading.text,
|
||||
depth: heading.depth,
|
||||
isSubpostTitle: index === 0,
|
||||
})),
|
||||
subpostId: subpost.id,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return sections
|
||||
}
|
||||
@@ -30,6 +30,6 @@ export function transformerNotationSkip(
|
||||
return false
|
||||
},
|
||||
undefined, // remove empty lines
|
||||
) as ShikiTransformer
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
@@ -13,36 +13,13 @@ export function formatDate(date: Date) {
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
export function formatMonthYear(date: Date) {
|
||||
return Intl.DateTimeFormat('en-US', {
|
||||
year: 'numeric',
|
||||
month: 'short',
|
||||
}).format(date)
|
||||
}
|
||||
|
||||
export function calculateWordCountFromHtml(
|
||||
html: string | null | undefined,
|
||||
): number {
|
||||
if (!html) return 0
|
||||
export function readingTime(html: string) {
|
||||
const textOnly = html.replace(/<[^>]+>/g, '')
|
||||
return textOnly.split(/\s+/).filter(Boolean).length
|
||||
}
|
||||
|
||||
export function readingTime(wordCount: number): string {
|
||||
const readingTimeMinutes = Math.max(1, Math.round(wordCount / 200))
|
||||
const wordCount = textOnly.split(/\s+/).length
|
||||
const readingTimeMinutes = (wordCount / 200 + 1).toFixed()
|
||||
return `${readingTimeMinutes} min read`
|
||||
}
|
||||
|
||||
export function getHeadingMargin(depth: number): string {
|
||||
const margins: Record<number, string> = {
|
||||
3: 'ml-4',
|
||||
4: 'ml-8',
|
||||
5: 'ml-12',
|
||||
6: 'ml-16',
|
||||
}
|
||||
return margins[depth] || ''
|
||||
}
|
||||
|
||||
export function getElapsedTime(unixTimestamp: number): string {
|
||||
const createdAt = new Date(unixTimestamp)
|
||||
const now = new Date()
|
||||
@@ -56,12 +33,3 @@ export function getElapsedTime(unixTimestamp: number): string {
|
||||
.toString()
|
||||
.padStart(2, '0')}:${seconds.toString().padStart(2, '0')} elapsed`
|
||||
}
|
||||
|
||||
export function extractDomain(url: string): string {
|
||||
try {
|
||||
const domain = new URL(url).hostname
|
||||
return domain.startsWith('www.') ? domain.slice(4) : domain
|
||||
} catch {
|
||||
return url
|
||||
}
|
||||
}
|
||||
|
||||
@@ -61,8 +61,7 @@ const subposts = allPosts.filter((p) => p.data.parentTitle === post.data.title)
|
||||
const totalBody = [post.body!, ...subposts.map((p) => p.body!)]
|
||||
.map(stripCodeBlocks)
|
||||
.join('')
|
||||
const wordCount = totalBody.split(/\s+/).filter(Boolean).length
|
||||
const readTime = readingTime(wordCount)
|
||||
const readTime = readingTime(totalBody)
|
||||
---
|
||||
|
||||
<Layout
|
||||
|
||||
@@ -57,24 +57,35 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
|
||||
---
|
||||
|
||||
<Layout title="Blog" description="Blog">
|
||||
<!-- Fixed Sidebar -->
|
||||
<aside
|
||||
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"
|
||||
>
|
||||
<Container class="flex grow flex-col gap-y-6">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: 'Blog', href: '/blog', icon: 'lucide:archive' },
|
||||
{ label: `Page ${page.currentPage}`, icon: 'lucide:folder-open' },
|
||||
]}
|
||||
/>
|
||||
|
||||
<div
|
||||
class="sm:none hidden h-full max-h-screen min-w-[280px] max-w-[280px] flex-wrap overflow-auto rounded-sm bg-gray-50 pt-5 shadow-md lg:contents dark:bg-gray-900/70 dark:shadow-gray-800/40"
|
||||
>
|
||||
<div
|
||||
class="absolute left-4 top-[50%] px-4 py-2"
|
||||
style="transform: translateY(-50%);"
|
||||
>
|
||||
<Link
|
||||
href={`/blog`}
|
||||
class="block mb-2 font-bold uppercase text-primary hover:text-primary/80 transition-colors"
|
||||
class="hover:text-primary-500 dark:hover:text-primary-500 font-bold uppercase"
|
||||
>
|
||||
All Posts
|
||||
</Link>
|
||||
<ul class="space-y-0.5">
|
||||
<ul>
|
||||
{
|
||||
sortedTags.map((t) => {
|
||||
return (
|
||||
<li value={t}>
|
||||
<li value={t} class="my-0">
|
||||
<Link
|
||||
href={`/tags/${slug(t)}`}
|
||||
class="block px-2 py-1.5 text-sm font-medium uppercase text-foreground/60 hover:text-primary hover:bg-primary/10 rounded transition-colors"
|
||||
class="hover:text-primary-500 dark:hover:text-primary-500 px-3 py-2 text-sm font-medium uppercase text-gray-400 dark:text-gray-300"
|
||||
aria-label={`View posts tagged ${t}`}
|
||||
>
|
||||
{`${t} (${tagCounts[t]})`}
|
||||
@@ -84,25 +95,22 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
|
||||
})
|
||||
}
|
||||
</ul>
|
||||
<div class="mt-2 pt-2 border-t border-[color:rgba(241,140,110,0.15)]">
|
||||
<a
|
||||
aria-label="View all blog categories"
|
||||
class="text-sm font-medium text-primary hover:text-primary/80 transition-colors"
|
||||
href="/tags/"
|
||||
<span
|
||||
class="ms-auto inline-flex h-6 items-center text-base sm:text-end"
|
||||
>
|
||||
View all →
|
||||
</a>
|
||||
<a
|
||||
aria-label="View all blog categories"
|
||||
class="sm:hover:text-accent-two font-medium text-accent"
|
||||
style="color: #e9d3b6; font-size: 12px;"
|
||||
href="/tags/"
|
||||
>
|
||||
View all →
|
||||
</a>
|
||||
</span>
|
||||
</div>
|
||||
</aside>
|
||||
</div>
|
||||
|
||||
<Container class="flex grow flex-col gap-y-6">
|
||||
<Breadcrumbs
|
||||
items={[
|
||||
{ label: 'Blog', href: '/blog', icon: 'lucide:archive' },
|
||||
{ label: `Page ${page.currentPage}`, icon: 'lucide:folder-open' },
|
||||
]}
|
||||
/>
|
||||
<div class="flex min-h-[calc(100vh-18rem)] flex-col gap-y-8">
|
||||
<div class="flex min-h-[calc(100vh-18rem)] flex-col gap-y-8">
|
||||
{
|
||||
years.map((year) => (
|
||||
<section class="flex flex-col gap-y-4">
|
||||
@@ -117,31 +125,13 @@ const sortedTags = tagKeys.sort((a, b) => tagCounts[b] - tagCounts[a])
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PaginationComponent
|
||||
<PaginationComponent
|
||||
currentPage={page.currentPage}
|
||||
totalPages={page.lastPage}
|
||||
baseUrl="/blog/"
|
||||
client:load
|
||||
/>
|
||||
/>
|
||||
</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>
|
||||
|
||||
@@ -12,8 +12,7 @@ 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 DevShortcutsHexagon from '@/components/DevShortcutsHexagon.astro'
|
||||
import DevShortCuts from '@/components/DevShortCuts.astro'
|
||||
|
||||
const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
posts
|
||||
@@ -29,11 +28,11 @@ 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'_'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"
|
||||
class="mx-auto grid max-w-[375px] grid-cols-2 gap-4 px-4 [grid-template-areas:'a_a'_'a_a'_'b_b'_'b_b'_'e_e'_'h_i'_'h_c'_'k_c'_'d_d'_'d_d'_'g_g'_'g_g'_'f_f'_'j_j'_'j_j'] *:rounded-3xl *:border *:bg-secondary/25 *:bg-cover *:bg-center *:bg-no-repeat sm:max-w-screen-sm sm:[grid-template-areas:'a_a'_'b_d'_'e_e'_'j_g'_'h_i'_'h_c'_'k_c'_'f_f'] xl:max-w-screen-xl xl:grid-cols-4 xl:[grid-template-areas:'a_a_b_c'_'d_e_e_c'_'h_f_f_g'_'h_i_j_k'] xl:[&:hover:has(>.has-overlay:hover)>.first>.overlay]:opacity-100 xl:[&:hover>*:not(.first):hover_.overlay]:opacity-100"
|
||||
aria-label="Personal information and activity grid"
|
||||
>
|
||||
<div
|
||||
class="first flex flex-row xl:max-h-[298px] xl:min-w-[615px] grow-[0] justify-center aspect-square bg-[url('/static/loading.gif')] border bg-cover bg-center bg-position-inherit bg-no-repeat [grid-area:a] sm:aspect-[2.1/1] xl:aspect-auto 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)]"
|
||||
class="first flex flex-row xl:max-h-[298px] grow-[0] justify-center aspect-square rounded-3xl bg-[url('/static/loading.gif')] border bg-cover bg-center bg-position-inherit bg-no-repeat [grid-area:a] sm:aspect-[2.1/1] xl:aspect-auto"
|
||||
role="img"
|
||||
|
||||
aria-label="Introduction"
|
||||
@@ -43,29 +42,41 @@ 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 [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 bg-[url('/static/honeycomb.webp')] [grid-area:b] short-cuts-template"
|
||||
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 bottom-0 right-0 no-repeat max-w-[28%] object-contain z-[88]"
|
||||
src="/static/images/shortcuts-bg-mini.png"
|
||||
class="overlay absolute no-repeat w-full max-w-fit justify-center rounded-3xl object-cover z-9"
|
||||
src="/static/images/shortcuts-bg.png"
|
||||
/>
|
||||
<!-- <DevStackIconsCloud client:load/> -->
|
||||
<!-- <DevShortCuts /> -->
|
||||
<DevShortcutsHexagon />
|
||||
<DevShortCuts />
|
||||
</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)]">
|
||||
<div class="aspect-[1/2.1] [grid-area:c] bg-[url('/static/images/ump45.png')] xl:aspect-auto bg-color" aria-hidden="true">
|
||||
<!-- <MagnetLines
|
||||
client:load
|
||||
rows={9}
|
||||
columns={4}
|
||||
containerSize="100%"
|
||||
lineColor="#e9d3b6"
|
||||
lineWidth="0.8vmin"
|
||||
lineHeight="4vmin"
|
||||
baseAngle={0}
|
||||
/> -->
|
||||
</div>
|
||||
|
||||
<div class="relative overflow-hidden [grid-area:d] sm:aspect-square min-h-[300px]">
|
||||
<AuthorPresence client:load />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="has-overlay min-h-[300px] relative flex grid aspect-[6/5] grid-rows-2 items-start overflow-hidden p-1 [grid-area:e] sm:aspect-[2.1/1] sm:items-center xl:aspect-auto 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)]"
|
||||
class="has-overlay min-h-[300px] relative flex grid aspect-[6/5] grid-rows-2 items-start overflow-hidden p-1 [grid-area:e] sm:aspect-[2.1/1] sm:items-center xl:aspect-auto"
|
||||
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-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-center bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/lastblogbg.webp')] xl:opacity-100"
|
||||
>
|
||||
</div>
|
||||
{
|
||||
@@ -78,7 +89,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
alt={`Featured image for the latest post: ${latestPost.data.title}`}
|
||||
width={477}
|
||||
height={251}
|
||||
class="w-full border-2 border-[color-mix(in_srgb,hsl(var(--primary))_40%,hsl(var(--border)))] sm:w-[82%]"
|
||||
class="w-full rounded-2xl border border-border sm:w-[82%] "
|
||||
/>
|
||||
</Link>
|
||||
|
||||
@@ -98,7 +109,7 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
aria-label={`Read latest blog post: ${latestPost.data.title}`}
|
||||
title={`Read latest blog post: ${latestPost.data.title}`}
|
||||
>
|
||||
<div class="absolute top-0 right-0 m-3 flex w-fit items-end rounded-full border bg-secondary/50 p-3 text-primary transition-all duration-300 hover:rotate-12 hover:ring-1 hover:ring-primary z-10">
|
||||
<div class="absolute top-0 right-0 m-3 flex w-fit items-end rounded-full border bg-secondary/50 p-3 text-primary transition-all duration-300 hover:rotate-12 hover:ring-1 hover:ring-primary">
|
||||
<Icon name="lucide:move-up-right" size={16} />
|
||||
</div>
|
||||
</Link>
|
||||
@@ -108,30 +119,36 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="has-overlay xl:min-w-[615px] relative flex aspect-square items-center justify-center overflow-hidden [grid-area:f] sm:aspect-[2.1/1] xl:aspect-auto 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)]"
|
||||
class="has-overlay relative flex aspect-square items-center justify-center overflow-hidden [grid-area:f] sm:aspect-[2.1/1] xl:aspect-auto"
|
||||
>
|
||||
<div
|
||||
class="overlay absolute inset-0 z-[1] size-full bg-[url('/static/images/contributions-square.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/contributions.png')] xl:opacity-100"
|
||||
class="overlay absolute inset-0 z-[1] size-full rounded-3xl bg-[url('/static/images/contributions-square.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 sm:bg-[url('/static/images/contributions.png')] xl:opacity-100"
|
||||
>
|
||||
</div>
|
||||
<GiteaCalendar client:load />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="has-overlay relative aspect-square [grid-area:g] hover:bg-none 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)]"
|
||||
class="has-overlay relative aspect-square [grid-area:g] hover:bg-none"
|
||||
>
|
||||
<div
|
||||
class="overlay absolute inset-0 z-0 size-full bg-[url('/static/images/music163.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 xl:opacity-100"
|
||||
class="overlay absolute inset-0 z-0 size-full rounded-3xl bg-[url('/static/images/music163.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 xl:opacity-100"
|
||||
>
|
||||
</div>
|
||||
<Music163Player client:load />
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="has-overlay relative flex aspect-square items-center justify-center [grid-area:i] 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)]"
|
||||
class="aspect-[1/2.1] bg-[url('/static/images/ump9.png')] [grid-area:h] xl:aspect-auto bg-color"
|
||||
aria-hidden="true"
|
||||
>
|
||||
</div>
|
||||
|
||||
<div
|
||||
class="has-overlay relative flex aspect-square items-center justify-center [grid-area:i] "
|
||||
>
|
||||
<div
|
||||
class="overlay absolute inset-0 size-full bg-[url('/static/images/github.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 xl:opacity-100"
|
||||
class="overlay absolute inset-0 size-full rounded-3xl bg-[url('/static/images/github.png')] bg-cover bg-center bg-no-repeat transition-opacity duration-200 xl:opacity-100"
|
||||
>
|
||||
</div>
|
||||
<Icon
|
||||
@@ -153,12 +170,12 @@ const latestPost = await getCollection('blog').then((posts: any[]) =>
|
||||
</Link>
|
||||
</div>
|
||||
|
||||
<div class="has-overlay aspect-square [grid-area:j] bg-[url('/static/images/waketime.png')] bg-cover bg-center bg-no-repeat 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)]">
|
||||
<div class="has-overlay aspect-square [grid-area:j] bg-[url('/static/images/waketime.png')] bg-cover bg-center bg-no-repeat ">
|
||||
<WakatimeGraph omitLanguages={['Markdown', 'JSON']} client:load />
|
||||
</div>
|
||||
|
||||
<!-- 字符滚动 -->
|
||||
<div class="aspect-square [grid-area:k] 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)]">
|
||||
<div class="aspect-square [grid-area:k]">
|
||||
<LetterGlitch
|
||||
client:load
|
||||
glitchColors={['#de17a5', '#5617de', '#e9d3b6']}
|
||||
@@ -171,21 +188,3 @@ 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>
|
||||
|
||||
@@ -1,57 +0,0 @@
|
||||
---
|
||||
import Breadcrumbs from '@/components/Breadcrumbs.astro'
|
||||
import Container from '@/components/Container.astro'
|
||||
import PageHead from '@/components/PageHead.astro'
|
||||
import ProjectCard from '@/components/ProjectCard.astro'
|
||||
import Layout from '@/layouts/Layout.astro'
|
||||
import { getAllProjects, groupProjectsByYear } from '@/lib/data-utils'
|
||||
|
||||
const projects = await getAllProjects()
|
||||
const projectsByYear = groupProjectsByYear(projects)
|
||||
const years = Object.keys(projectsByYear).sort(
|
||||
(a, b) => parseInt(b) - parseInt(a),
|
||||
)
|
||||
---
|
||||
|
||||
<Layout title="项目" description="dev.2ha.me的项目">
|
||||
<PageHead slot="head" title="Project" />
|
||||
|
||||
<Container class="flex grow flex-col gap-y-6">
|
||||
<Breadcrumbs items={[{ label: 'Project', icon: 'lucide:briefcase' }]} />
|
||||
|
||||
<div class="flex min-h-[calc(100vh-18rem)] flex-col gap-y-8">
|
||||
{
|
||||
years.map((year) => (
|
||||
<section class="flex flex-col gap-y-4">
|
||||
<div class="font-semibold">{year}</div>
|
||||
<ul class="not-prose flex flex-col gap-4">
|
||||
{projectsByYear[year].map((project) => (
|
||||
<li>
|
||||
<ProjectCard project={project} />
|
||||
</li>
|
||||
))}
|
||||
</ul>
|
||||
</section>
|
||||
))
|
||||
}
|
||||
</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>
|
||||
@@ -1,16 +1,12 @@
|
||||
@import './pixel-components.css';
|
||||
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'OPlusSans3-Medium';
|
||||
src: url('/fonts/OPlusSans3-Medium.woff2') format('woff2'),
|
||||
url('/fonts/OPlusSans3-Medium.woff') format('woff');
|
||||
font-weight: 500;
|
||||
font-family: 'haipaiqiangdiaosenxiyuan';
|
||||
src: url('/fonts/haipaiqiangdiaosenxiyuan.woff');
|
||||
font-weight: 100 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
/* unicode-range: U+2E80-2EFF,U+3400-4DBF,U+4E00-9FFF; */
|
||||
}
|
||||
|
||||
@@ -36,117 +32,100 @@
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
/* MiaoMiaoWu Brand Colors */
|
||||
--brand-50: #fef5f2;
|
||||
--brand-100: #fde8e2;
|
||||
--brand-200: #fbd4c9;
|
||||
--brand-300: #f7b5a3;
|
||||
--brand-400: #f18c6e;
|
||||
--brand-500: #d97757;
|
||||
--brand-600: #c55438;
|
||||
--brand-700: #a4432d;
|
||||
--brand-800: #873829;
|
||||
--brand-900: #713128;
|
||||
|
||||
/* Semantic Colors - Light Mode Default */
|
||||
--background: 60 20% 99%;
|
||||
--foreground: 16 62% 15%;
|
||||
--primary: 15 66% 59%; /* #d97757 橙色 */
|
||||
--primary-foreground: 33 100% 99%;
|
||||
--secondary: 22 64% 93%;
|
||||
--secondary-foreground: 15 60% 27%;
|
||||
--muted: 30 43% 94%;
|
||||
--muted-foreground: 15 25% 51%;
|
||||
--accent: 15 62% 78%; /* #f7b5a3 */
|
||||
--accent-foreground: 16 73% 11%;
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 34.12deg 53.68% 81.37%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 15 30% 55% / 0.38;
|
||||
--ring: 15 66% 59% / 0.6;
|
||||
--input: 15 30% 55% / 0.45;
|
||||
--radius: 0; /* 无圆角! */
|
||||
--border: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
|
||||
--chart-1: 15 66% 59%;
|
||||
--chart-2: 15 71% 63%;
|
||||
--chart-3: 45 97% 63%;
|
||||
--chart-4: 203 92% 70%;
|
||||
--chart-5: 186 78% 54%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 60 20% 99%;
|
||||
--card-foreground: 16 68% 13%;
|
||||
--popover: 60 20% 99%;
|
||||
--popover-foreground: 16 62% 14%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
}
|
||||
|
||||
.light {
|
||||
/* Same as :root for light mode */
|
||||
--background: 60 20% 99%;
|
||||
--foreground: 16 62% 15%;
|
||||
--primary: 15 66% 59%;
|
||||
--primary-foreground: 33 100% 99%;
|
||||
--secondary: 22 64% 93%;
|
||||
--secondary-foreground: 15 60% 27%;
|
||||
--muted: 30 43% 94%;
|
||||
--muted-foreground: 15 25% 51%;
|
||||
--accent: 15 62% 78%;
|
||||
--accent-foreground: 16 73% 11%;
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 72% 51%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 15 30% 55% / 0.38;
|
||||
--ring: 15 66% 59% / 0.6;
|
||||
--input: 15 30% 55% / 0.45;
|
||||
--radius: 0;
|
||||
--border: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--chart-1: 15 66% 59%;
|
||||
--chart-2: 15 71% 63%;
|
||||
--chart-3: 45 97% 63%;
|
||||
--chart-4: 203 92% 70%;
|
||||
--chart-5: 186 78% 54%;
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 60 20% 99%;
|
||||
--card-foreground: 16 68% 13%;
|
||||
--popover: 60 20% 99%;
|
||||
--popover-foreground: 16 62% 14%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--input: 240 5.9% 90%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
/* Dark Mode - MiaoMiaoWu Style */
|
||||
--background: 222 29% 8%; /* #10131c 深蓝灰 */
|
||||
--foreground: 33 83% 97%; /* #f9f4f1 暖白 */
|
||||
--primary: 15 71% 69%; /* #f18c6e 亮橙色 */
|
||||
--primary-foreground: 16 65% 13%; /* #30160f 深色 */
|
||||
--secondary: 225 23% 13%; /* #1d2232 深蓝灰 */
|
||||
--secondary-foreground: 22 64% 93%;
|
||||
--muted: 224 24% 12%; /* #1c2131 深蓝 */
|
||||
--muted-foreground: 15 25% 75%; /* #cfb8af 浅灰棕 */
|
||||
--accent: 15 71% 69% / 0.22; /* 半透明橙 */
|
||||
--accent-foreground: 22 100% 93%; /* #ffe5da */
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 70% 68%; /* #f87171 亮红 */
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 0 0% 100% / 0.08; /* 半透明白 */
|
||||
--ring: 15 71% 69% / 0.45;
|
||||
--input: 0 0% 100% / 0.12;
|
||||
--radius: 0;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
|
||||
--chart-1: 15 71% 69%;
|
||||
--chart-2: 199 89% 61%;
|
||||
--chart-3: 271 70% 67%;
|
||||
--chart-4: 45 98% 54%;
|
||||
--chart-5: 186 78% 54%;
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 222 29% 8%;
|
||||
--card-foreground: 22 75% 94%;
|
||||
--popover: 222 29% 8%;
|
||||
--popover-foreground: 33 83% 97%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
}
|
||||
|
||||
*,
|
||||
@@ -157,7 +136,6 @@
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
overflow-y: scroll;
|
||||
@apply bg-background text-foreground forced-color-adjust-none;
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@@ -326,109 +304,159 @@
|
||||
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 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"] {
|
||||
user-select: none;
|
||||
-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 {
|
||||
@apply text-foreground;
|
||||
/* MiaoMiaoWu Background Gradients */
|
||||
background-image:
|
||||
radial-gradient(1100px circle at 5% -10%, rgba(217, 119, 87, 0.2), transparent 60%),
|
||||
radial-gradient(900px circle at 90% 0%, rgba(96, 165, 250, 0.12), transparent 65%),
|
||||
linear-gradient(180deg, rgba(255, 247, 242, 0.98), rgba(255, 247, 242, 1));
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
background-color: hsl(var(--background));
|
||||
}
|
||||
.dark body {
|
||||
background-image:
|
||||
radial-gradient(900px circle at 15% -5%, rgba(241, 140, 110, 0.32), transparent 65%),
|
||||
radial-gradient(800px circle at 82% 0%, rgba(56, 189, 248, 0.18), transparent 70%),
|
||||
linear-gradient(180deg, rgba(16, 19, 28, 1), rgba(10, 12, 20, 1));
|
||||
background-attachment: fixed;
|
||||
background-size: cover;
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,525 +0,0 @@
|
||||
@tailwind base;
|
||||
@tailwind components;
|
||||
@tailwind utilities;
|
||||
|
||||
@font-face {
|
||||
font-family: 'OPlusSans3-Medium';
|
||||
src: url('/fonts/OPlusSans3-Medium.woff');
|
||||
font-weight: 100 800;
|
||||
font-style: normal;
|
||||
/* unicode-range: U+2E80-2EFF,U+3400-4DBF,U+4E00-9FFF; */
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('/fonts/JetBrainsMono[wght].woff2') format('woff2-variations');
|
||||
font-weight: 100 800;
|
||||
font-style: normal;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
@font-face {
|
||||
font-family: 'JetBrains Mono';
|
||||
src: url('/fonts/JetBrainsMono-Italic[wght].woff2') format('woff2-variations');
|
||||
font-weight: 100 800;
|
||||
font-style: italic;
|
||||
font-display: swap;
|
||||
}
|
||||
|
||||
.styles-module_calendar__sT1ND text {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
@layer base {
|
||||
:root {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 34.12deg 53.68% 81.37%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
}
|
||||
|
||||
.light {
|
||||
--background: 0 0% 100%;
|
||||
--foreground: 240 10% 3.9%;
|
||||
--primary: 240 5.9% 10%;
|
||||
--primary-foreground: 0 0% 98%;
|
||||
--secondary: 240 4.8% 95.9%;
|
||||
--secondary-foreground: 240 5.9% 10%;
|
||||
--muted: 240 4.8% 95.9%;
|
||||
--muted-foreground: 240 3.8% 46.1%;
|
||||
--accent: 240 4.8% 95.9%;
|
||||
--accent-foreground: 240 5.9% 10%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 84.2% 60.2%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 5.9% 90%;
|
||||
--ring: 240 10% 3.9%;
|
||||
|
||||
--chart-1: 12 76% 61%;
|
||||
--chart-2: 173 58% 39%;
|
||||
--chart-3: 197 37% 24%;
|
||||
--chart-4: 43 74% 66%;
|
||||
--chart-5: 27 87% 67%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 0 0% 100%;
|
||||
--card-foreground: 240 10% 3.9%;
|
||||
--popover: 0 0% 100%;
|
||||
--popover-foreground: 240 10% 3.9%;
|
||||
--input: 240 5.9% 90%;
|
||||
--radius: 0.5rem;
|
||||
}
|
||||
|
||||
.dark {
|
||||
--background: 240 10% 3.9%;
|
||||
--foreground: 0 0% 98%;
|
||||
--primary: 0 0% 98%;
|
||||
--primary-foreground: 240 5.9% 10%;
|
||||
--secondary: 240 3.7% 15.9%;
|
||||
--secondary-foreground: 0 0% 98%;
|
||||
--muted: 240 3.7% 15.9%;
|
||||
--muted-foreground: 240 5% 64.9%;
|
||||
--accent: 240 3.7% 15.9%;
|
||||
--accent-foreground: 0 0% 98%;
|
||||
--additive: 112 50% 36%;
|
||||
--additive-foreground: 0 0% 9%;
|
||||
--destructive: 0 62.8% 30.6%;
|
||||
--destructive-foreground: 0 0% 98%;
|
||||
--border: 240 3.7% 15.9%;
|
||||
--ring: 240 4.9% 83.9%;
|
||||
|
||||
--chart-1: 220 70% 50%;
|
||||
--chart-2: 160 60% 45%;
|
||||
--chart-3: 30 80% 55%;
|
||||
--chart-4: 280 65% 60%;
|
||||
--chart-5: 340 75% 55%;
|
||||
--chart-6: 33 12% 33%;
|
||||
--chart-7: 32 12% 25%;
|
||||
--card: 240 10% 3.9%;
|
||||
--card-foreground: 0 0% 98%;
|
||||
--popover: 240 10% 3.9%;
|
||||
--popover-foreground: 0 0% 98%;
|
||||
--input: 240 3.7% 15.9%;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
@apply border-border;
|
||||
}
|
||||
|
||||
html {
|
||||
color-scheme: dark;
|
||||
@apply bg-background text-foreground forced-color-adjust-none;
|
||||
|
||||
::-webkit-scrollbar-corner {
|
||||
@apply bg-transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.disable-transitions,
|
||||
.disable-transitions * {
|
||||
@apply !transition-none;
|
||||
}
|
||||
.theme {
|
||||
--animate-gradient: gradient 8s linear infinite;
|
||||
}
|
||||
}
|
||||
|
||||
.svg-bg-color {
|
||||
background-color: hsl(var(--primary));
|
||||
}
|
||||
|
||||
:root,
|
||||
::backdrop {
|
||||
--sl-color-white: hsl(0, 0%, 100%);
|
||||
--sl-color-gray-1: hsl(224, 20%, 94%);
|
||||
--sl-color-gray-2: hsl(224, 6%, 77%);
|
||||
--sl-color-gray-3: hsl(224, 6%, 56%);
|
||||
--sl-color-gray-4: hsl(224, 7%, 36%);
|
||||
--sl-color-gray-5: hsl(224, 10%, 23%);
|
||||
--sl-color-gray-6: hsl(224, 14%, 16%);
|
||||
--sl-color-black: hsl(224, 10%, 10%);
|
||||
--sl-hue-orange: 41;
|
||||
--sl-color-orange-low: hsl(var(--sl-hue-orange), 39%, 22%);
|
||||
--sl-color-orange: hsl(var(--sl-hue-orange), 82%, 63%);
|
||||
--sl-color-orange-high: hsl(var(--sl-hue-orange), 82%, 87%);
|
||||
--sl-hue-green: 101;
|
||||
--sl-color-green-low: hsl(var(--sl-hue-green), 39%, 22%);
|
||||
--sl-color-green: hsl(var(--sl-hue-green), 82%, 63%);
|
||||
--sl-color-green-high: hsl(var(--sl-hue-green), 82%, 80%);
|
||||
--sl-hue-blue: 234;
|
||||
--sl-color-blue-low: hsl(var(--sl-hue-blue), 54%, 20%);
|
||||
--sl-color-blue: hsl(var(--sl-hue-blue), 100%, 60%);
|
||||
--sl-color-blue-high: hsl(var(--sl-hue-blue), 100%, 87%);
|
||||
--sl-hue-purple: 281;
|
||||
--sl-color-purple-low: hsl(var(--sl-hue-purple), 39%, 22%);
|
||||
--sl-color-purple: hsl(var(--sl-hue-purple), 82%, 63%);
|
||||
--sl-color-purple-high: hsl(var(--sl-hue-purple), 82%, 89%);
|
||||
--sl-hue-red: 339;
|
||||
--sl-color-red-low: hsl(var(--sl-hue-red), 39%, 22%);
|
||||
--sl-color-red: hsl(var(--sl-hue-red), 82%, 63%);
|
||||
--sl-color-red-high: hsl(var(--sl-hue-red), 82%, 87%);
|
||||
--sl-color-accent-low: hsl(224, 54%, 20%);
|
||||
--sl-color-accent: hsl(224, 100%, 60%);
|
||||
--sl-color-accent-high: hsl(224, 100%, 85%);
|
||||
--sl-color-text: var(--sl-color-gray-2);
|
||||
--sl-color-text-accent: var(--sl-color-accent-high);
|
||||
--sl-color-text-invert: var(--sl-color-accent-low);
|
||||
--sl-color-bg: var(--sl-color-black);
|
||||
--sl-color-bg-nav: var(--sl-color-gray-6);
|
||||
--sl-color-bg-sidebar: var(--sl-color-gray-6);
|
||||
--sl-color-bg-inline-code: var(--sl-color-gray-5);
|
||||
--sl-color-bg-accent: var(--sl-color-accent-high);
|
||||
--sl-color-hairline-light: var(--sl-color-gray-5);
|
||||
--sl-color-hairline: var(--sl-color-gray-6);
|
||||
--sl-color-hairline-shade: var(--sl-color-black);
|
||||
--sl-color-backdrop-overlay: hsla(223, 13%, 10%, 0.66);
|
||||
--sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.12),
|
||||
0px 2px 1px hsla(0, 0%, 0%, 0.24);
|
||||
--sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.08),
|
||||
0px 5px 2px hsla(0, 0%, 0%, 0.08), 0px 3px 2px hsla(0, 0%, 0%, 0.12),
|
||||
0px 1px 1px hsla(0, 0%, 0%, 0.15);
|
||||
--sl-shadow-lg: 0px 25px 7px hsla(0, 0%, 0%, 0.03),
|
||||
0px 16px 6px hsla(0, 0%, 0%, 0.1), 0px 9px 5px hsla(223, 13%, 10%, 0.33),
|
||||
0px 4px 4px hsla(0, 0%, 0%, 0.75), 0px 4px 2px hsla(0, 0%, 0%, 0.25);
|
||||
--sl-text-2xs: 0.75rem;
|
||||
--sl-text-xs: 0.8125rem;
|
||||
--sl-text-sm: 0.875rem;
|
||||
--sl-text-base: 1rem;
|
||||
--sl-text-lg: 1.125rem;
|
||||
--sl-text-xl: 1.25rem;
|
||||
--sl-text-2xl: 1.5rem;
|
||||
--sl-text-3xl: 1.8125rem;
|
||||
--sl-text-4xl: 2.1875rem;
|
||||
--sl-text-5xl: 2.625rem;
|
||||
--sl-text-6xl: 4rem;
|
||||
--sl-text-body: var(--sl-text-base);
|
||||
--sl-text-body-sm: var(--sl-text-xs);
|
||||
--sl-text-code: var(--sl-text-sm);
|
||||
--sl-text-code-sm: var(--sl-text-xs);
|
||||
--sl-text-h1: var(--sl-text-4xl);
|
||||
--sl-text-h2: var(--sl-text-3xl);
|
||||
--sl-text-h3: var(--sl-text-2xl);
|
||||
--sl-text-h4: var(--sl-text-xl);
|
||||
--sl-text-h5: var(--sl-text-lg);
|
||||
--sl-line-height: 1.75;
|
||||
--sl-line-height-headings: 1.2;
|
||||
--sl-font-system: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont,
|
||||
'Segoe UI', Roboto, 'Helvetica Neue', Arial, 'Noto Sans', sans-serif,
|
||||
'Apple Color Emoji', 'Segoe UI Emoji', 'Segoe UI Symbol', 'Noto Color Emoji';
|
||||
--sl-font-system-mono: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas,
|
||||
'Liberation Mono', 'Courier New', monospace;
|
||||
--__sl-font: var(--sl-font, var(--sl-font-system)), var(--sl-font-system);
|
||||
--__sl-font-mono: var(--sl-font-mono, var(--sl-font-system-mono)),
|
||||
var(--sl-font-system-mono);
|
||||
--sl-nav-height: 3.5rem;
|
||||
--sl-nav-pad-x: 1rem;
|
||||
--sl-nav-pad-y: 0.75rem;
|
||||
--sl-mobile-toc-height: 3rem;
|
||||
--sl-sidebar-width: 18.75rem;
|
||||
--sl-sidebar-pad-x: 1rem;
|
||||
--sl-content-width: 45rem;
|
||||
--sl-content-pad-x: 1rem;
|
||||
--sl-menu-button-size: 2rem;
|
||||
--sl-nav-gap: var(--sl-content-pad-x);
|
||||
--sl-outline-offset-inside: -0.1875rem;
|
||||
--sl-z-index-toc: 4;
|
||||
--sl-z-index-menu: 5;
|
||||
--sl-z-index-navbar: 10;
|
||||
--sl-z-index-skiplink: 20;
|
||||
}
|
||||
|
||||
:root[data-theme='light'],
|
||||
[data-theme='light'] ::backdrop {
|
||||
--sl-color-white: hsl(224, 10%, 10%);
|
||||
--sl-color-gray-1: hsl(224, 14%, 16%);
|
||||
--sl-color-gray-2: hsl(224, 10%, 23%);
|
||||
--sl-color-gray-3: hsl(224, 7%, 36%);
|
||||
--sl-color-gray-4: hsl(224, 6%, 56%);
|
||||
--sl-color-gray-5: hsl(224, 6%, 77%);
|
||||
--sl-color-gray-6: hsl(224, 20%, 94%);
|
||||
--sl-color-gray-7: hsl(224, 19%, 97%);
|
||||
--sl-color-black: hsl(0, 0%, 100%);
|
||||
--sl-color-orange-high: hsl(var(--sl-hue-orange), 80%, 25%);
|
||||
--sl-color-orange: hsl(var(--sl-hue-orange), 90%, 60%);
|
||||
--sl-color-orange-low: hsl(var(--sl-hue-orange), 90%, 88%);
|
||||
--sl-color-green-high: hsl(var(--sl-hue-green), 80%, 22%);
|
||||
--sl-color-green: hsl(var(--sl-hue-green), 90%, 46%);
|
||||
--sl-color-green-low: hsl(var(--sl-hue-green), 85%, 90%);
|
||||
--sl-color-blue-high: hsl(var(--sl-hue-blue), 80%, 30%);
|
||||
--sl-color-blue: hsl(var(--sl-hue-blue), 90%, 60%);
|
||||
--sl-color-blue-low: hsl(var(--sl-hue-blue), 88%, 90%);
|
||||
--sl-color-purple-high: hsl(var(--sl-hue-purple), 90%, 30%);
|
||||
--sl-color-purple: hsl(var(--sl-hue-purple), 90%, 60%);
|
||||
--sl-color-purple-low: hsl(var(--sl-hue-purple), 80%, 90%);
|
||||
--sl-color-red-high: hsl(var(--sl-hue-red), 80%, 30%);
|
||||
--sl-color-red: hsl(var(--sl-hue-red), 90%, 60%);
|
||||
--sl-color-red-low: hsl(var(--sl-hue-red), 80%, 90%);
|
||||
--sl-color-accent-high: hsl(234, 80%, 30%);
|
||||
--sl-color-accent: hsl(234, 90%, 60%);
|
||||
--sl-color-accent-low: hsl(234, 88%, 90%);
|
||||
--sl-color-text-accent: var(--sl-color-accent);
|
||||
--sl-color-text-invert: var(--sl-color-black);
|
||||
--sl-color-bg-nav: var(--sl-color-gray-7);
|
||||
--sl-color-bg-sidebar: var(--sl-color-bg);
|
||||
--sl-color-bg-inline-code: var(--sl-color-gray-6);
|
||||
--sl-color-bg-accent: var(--sl-color-accent);
|
||||
--sl-color-hairline-light: var(--sl-color-gray-6);
|
||||
--sl-color-hairline-shade: var(--sl-color-gray-6);
|
||||
--sl-color-backdrop-overlay: hsla(225, 9%, 36%, 0.66);
|
||||
--sl-shadow-sm: 0px 1px 1px hsla(0, 0%, 0%, 0.06),
|
||||
0px 2px 1px hsla(0, 0%, 0%, 0.06);
|
||||
--sl-shadow-md: 0px 8px 4px hsla(0, 0%, 0%, 0.03),
|
||||
0px 5px 2px hsla(0, 0%, 0%, 0.03), 0px 3px 2px hsla(0, 0%, 0%, 0.06),
|
||||
0px 1px 1px hsla(0, 0%, 0%, 0.06);
|
||||
--sl-shadow-lg: 0px 25px 7px rgba(0, 0, 0, 0.01),
|
||||
0px 16px 6px hsla(0, 0%, 0%, 0.03), 0px 9px 5px hsla(223, 13%, 10%, 0.08),
|
||||
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;
|
||||
}
|
||||
body {
|
||||
@apply bg-background text-foreground;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes shake {
|
||||
0% {
|
||||
transform: translate(1px, 1px) rotate(0deg);
|
||||
}
|
||||
10% {
|
||||
transform: translate(-1px, -2px) rotate(-1deg);
|
||||
}
|
||||
20% {
|
||||
transform: translate(-3px, 0px) rotate(1deg);
|
||||
}
|
||||
30% {
|
||||
transform: translate(3px, 2px) rotate(0deg);
|
||||
}
|
||||
40% {
|
||||
transform: translate(1px, -1px) rotate(1deg);
|
||||
}
|
||||
50% {
|
||||
transform: translate(-1px, 2px) rotate(-1deg);
|
||||
}
|
||||
60% {
|
||||
transform: translate(-3px, 1px) rotate(0deg);
|
||||
}
|
||||
70% {
|
||||
transform: translate(3px, 1px) rotate(-1deg);
|
||||
}
|
||||
80% {
|
||||
transform: translate(-1px, -1px) rotate(1deg);
|
||||
}
|
||||
90% {
|
||||
transform: translate(1px, 2px) rotate(0deg);
|
||||
}
|
||||
100% {
|
||||
transform: translate(1px, -2px) rotate(-1deg);
|
||||
}
|
||||
}
|
||||
|
||||
@theme inline {
|
||||
@keyframes gradient {
|
||||
to {
|
||||
backgroundposition: var(--bg-size, 300%) 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* patch */
|
||||
.recharts-cartesian-grid-vertical {
|
||||
display: none;
|
||||
}
|
||||
/*
|
||||
@media screen and (min-width: 1280px) {
|
||||
html {
|
||||
zoom: 0.95;
|
||||
}
|
||||
} */
|
||||
|
||||
.bg-color {
|
||||
background-color: #e9d3b6 !important;
|
||||
}
|
||||
|
||||
.shiki-transformer-button-copy {
|
||||
border: none !important;
|
||||
background-color: hsl(0deg 0% 0% / 0%) !important;
|
||||
}
|
||||
@@ -1,252 +0,0 @@
|
||||
/* MiaoMiaoWu Pixel Style Components */
|
||||
|
||||
/* Pixel Border - 像素边框 */
|
||||
.pixel-border {
|
||||
position: relative;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: color-mix(in srgb, hsl(var(--primary)) 60%, transparent);
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.18),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
transition: border-color 0.2s ease, box-shadow 0.2s ease, transform 0.2s ease;
|
||||
}
|
||||
|
||||
.pixel-border:hover {
|
||||
border-color: color-mix(in srgb, hsl(var(--primary)) 75%, transparent);
|
||||
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.24),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.dark .pixel-border {
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.45),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.08);
|
||||
}
|
||||
|
||||
.dark .pixel-border:hover {
|
||||
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.55),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
/* Pixel Button - 像素按钮 */
|
||||
.pixel-button {
|
||||
position: relative;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: transparent;
|
||||
border-radius: 0;
|
||||
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.18);
|
||||
transition:
|
||||
transform 0.18s ease,
|
||||
box-shadow 0.18s ease,
|
||||
background-color 0.18s ease,
|
||||
border-color 0.18s ease;
|
||||
}
|
||||
|
||||
.pixel-button:hover {
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.22);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.pixel-button:active {
|
||||
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.16);
|
||||
transform: translate(0);
|
||||
}
|
||||
|
||||
.dark .pixel-button {
|
||||
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.45);
|
||||
}
|
||||
|
||||
.dark .pixel-button:hover {
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.55);
|
||||
}
|
||||
|
||||
.dark .pixel-button:active {
|
||||
box-shadow: 1px 1px 0 rgba(0, 0, 0, 0.35);
|
||||
}
|
||||
|
||||
/* Pixel Card - 像素卡片 */
|
||||
.pixel-card {
|
||||
position: relative;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: color-mix(in srgb, hsl(var(--primary)) 22%, hsl(var(--border)));
|
||||
background: hsl(var(--card));
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.22),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||
overflow: hidden;
|
||||
transition: transform 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
|
||||
}
|
||||
|
||||
.pixel-card:hover {
|
||||
transform: translateY(-2px);
|
||||
border-color: color-mix(in srgb, hsl(var(--primary)) 40%, hsl(var(--border)));
|
||||
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.26),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
/* 悬停时显示斜纹背景 */
|
||||
.pixel-card::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image: repeating-linear-gradient(
|
||||
45deg,
|
||||
transparent 0,
|
||||
transparent 6px,
|
||||
rgba(255, 255, 255, 0.04) 6px,
|
||||
rgba(255, 255, 255, 0.04) 12px
|
||||
);
|
||||
opacity: 0;
|
||||
transition: opacity 0.2s ease;
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.pixel-card:hover::before {
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.pixel-card > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.dark .pixel-card {
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.65),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.04);
|
||||
}
|
||||
|
||||
.dark .pixel-card:hover {
|
||||
box-shadow: 6px 6px 0 rgba(0, 0, 0, 0.75),
|
||||
0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
/* Pixel Badge - 像素徽章 */
|
||||
.pixel-badge {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.3rem;
|
||||
padding: 0.25rem 0.65rem;
|
||||
border-radius: 0;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: rgba(241, 140, 110, 0.3);
|
||||
background: rgba(241, 140, 110, 0.12);
|
||||
text-transform: uppercase;
|
||||
font-size: 0.75rem;
|
||||
letter-spacing: 0.08em;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
/* Pixel Pill - 药丸按钮 */
|
||||
.pixel-pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
border-radius: 0;
|
||||
border-width: 2px;
|
||||
border-style: solid;
|
||||
border-color: rgba(217, 119, 87, 0.45);
|
||||
padding: 0.35rem 0.85rem;
|
||||
background: color-mix(in srgb, hsl(var(--secondary)) 40%, transparent);
|
||||
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.15),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
transition: all 0.2s ease;
|
||||
}
|
||||
|
||||
.pixel-pill:hover {
|
||||
background: rgba(217, 119, 87, 0.16);
|
||||
border-color: rgba(217, 119, 87, 0.65);
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.2),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
|
||||
.dark .pixel-pill {
|
||||
border-color: rgba(241, 140, 110, 0.55);
|
||||
box-shadow: 3px 3px 0 rgba(0, 0, 0, 0.45),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.06);
|
||||
}
|
||||
|
||||
.dark .pixel-pill:hover {
|
||||
background: rgba(241, 140, 110, 0.22);
|
||||
border-color: rgba(241, 140, 110, 0.75);
|
||||
box-shadow: 4px 4px 0 rgba(0, 0, 0, 0.55),
|
||||
inset 0 0 0 1px rgba(255, 255, 255, 0.12);
|
||||
}
|
||||
|
||||
/* Pixel Text - 像素文字效果 */
|
||||
.pixel-text {
|
||||
letter-spacing: 0.08em;
|
||||
text-transform: uppercase;
|
||||
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.25),
|
||||
-1px -1px 0 rgba(255, 241, 232, 0.18);
|
||||
}
|
||||
|
||||
.dark .pixel-text {
|
||||
text-shadow: 2px 2px 0 rgba(0, 0, 0, 0.55),
|
||||
-1px -1px 0 rgba(255, 241, 232, 0.08);
|
||||
}
|
||||
|
||||
/* Grid Pattern - 网格图案 */
|
||||
.grid-pattern {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.grid-pattern::after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
inset: 0;
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(241, 140, 110, 0.05) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(241, 140, 110, 0.05) 1px, transparent 1px);
|
||||
background-size: 32px 32px;
|
||||
opacity: 0.6;
|
||||
pointer-events: none;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.dark .grid-pattern::after {
|
||||
background-image:
|
||||
linear-gradient(to right, rgba(241, 140, 110, 0.08) 1px, transparent 1px),
|
||||
linear-gradient(to bottom, rgba(241, 140, 110, 0.08) 1px, transparent 1px);
|
||||
opacity: 0.4;
|
||||
}
|
||||
|
||||
/* Scanlines - 扫描线效果 */
|
||||
.scanlines {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
rgba(255, 255, 255, 0.05) 0,
|
||||
rgba(255, 255, 255, 0.05) 1px,
|
||||
transparent 1px,
|
||||
transparent 6px
|
||||
);
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
.dark .scanlines {
|
||||
background-image: repeating-linear-gradient(
|
||||
-45deg,
|
||||
rgba(255, 255, 255, 0.03) 0,
|
||||
rgba(255, 255, 255, 0.03) 1px,
|
||||
transparent 1px,
|
||||
transparent 6px
|
||||
);
|
||||
}
|
||||
|
||||
/* Remove rounded corners from common elements */
|
||||
button:not(.short-cuts-template *, [class*="rounded-full"]),
|
||||
input,
|
||||
textarea,
|
||||
select,
|
||||
.card,
|
||||
.badge {
|
||||
border-radius: 0 !important;
|
||||
}
|
||||
|
||||
/* Keep rounded corners for shortcut buttons and other rounded-full elements */
|
||||
.short-cuts-template div[class*="rounded-full"],
|
||||
div[class*="rounded-full"] {
|
||||
border-radius: 9999px !important;
|
||||
}
|
||||
@@ -8,7 +8,7 @@ const config: Config = {
|
||||
extend: {
|
||||
fontFamily: {
|
||||
sans: [
|
||||
'OPlusSans3-Medium',
|
||||
'haipaiqiangdiaosenxiyuan',
|
||||
...defaultTheme.fontFamily.sans
|
||||
],
|
||||
mono: [
|
||||
@@ -19,18 +19,6 @@ const config: Config = {
|
||||
colors: {
|
||||
background: 'hsl(var(--background))',
|
||||
foreground: 'hsl(var(--foreground))',
|
||||
brand: {
|
||||
50: 'var(--brand-50)',
|
||||
100: 'var(--brand-100)',
|
||||
200: 'var(--brand-200)',
|
||||
300: 'var(--brand-300)',
|
||||
400: 'var(--brand-400)',
|
||||
500: 'var(--brand-500)',
|
||||
600: 'var(--brand-600)',
|
||||
700: 'var(--brand-700)',
|
||||
800: 'var(--brand-800)',
|
||||
900: 'var(--brand-900)'
|
||||
},
|
||||
primary: {
|
||||
DEFAULT: 'hsl(var(--primary))',
|
||||
foreground: 'hsl(var(--primary-foreground))'
|
||||
@@ -75,10 +63,9 @@ const config: Config = {
|
||||
}
|
||||
},
|
||||
borderRadius: {
|
||||
lg: '0',
|
||||
md: '0',
|
||||
sm: '0',
|
||||
DEFAULT: '0'
|
||||
lg: 'var(--radius)',
|
||||
md: 'calc(var(--radius) - 2px)',
|
||||
sm: 'calc(var(--radius) - 4px)'
|
||||
},
|
||||
animation: {
|
||||
orbit: 'orbit calc(var(--duration)*1s) linear infinite',
|
||||
|
||||