Compare commits

...
This repository has been archived on 2025-05-12. You can view files and clone it, but you cannot make any changes to it's state, such as pushing and creating new issues, pull requests or comments.

96 commits
dev ... master

Author SHA1 Message Date
Timothy Rogers
81c60d8931 adding new metrics service 2025-01-15 20:39:09 -05:00
Timothy Rogers
054d8e3bb8 Adding a new post plus some upgrades from upstream 2025-01-15 18:56:31 -05:00
hack13
73d324bec1 Merge remote-tracking branch 'github.com/master' 2024-08-23 09:35:57 -04:00
hack13
32467fdbc9 Adding new post + upgrade astro 2024-08-23 09:35:15 -04:00
Timothy Rogers
54cdc43981
Adding site metrics 2024-08-08 08:01:57 -04:00
Timothy Rogers
e4a725b781 New post 2024-08-07 21:51:27 -04:00
Timothy Rogers
a74a0539f2 Update footer to include astro 2024-08-07 21:51:16 -04:00
Timothy Rogers
aa8e43fb48 fixing some posts 2024-07-08 21:22:18 -04:00
Timothy Rogers
306cd23ed2 new post 2024-07-07 13:52:15 -04:00
Timothy Rogers
1fb6b46b17 fixing date on post 2024-07-07 12:56:33 -04:00
Timothy Rogers
8263030f87 new post for moving to astro 2024-07-07 12:54:17 -04:00
Timothy Rogers
5995155073 adding support for old links 2024-07-07 12:54:06 -04:00
Timothy Rogers
a06878c84c add redirect for feed from wordpress 2024-07-07 09:24:36 -04:00
Timothy Rogers
eb845d0f1b Fixing description 2024-07-07 00:13:28 -04:00
Timothy Rogers
6b38e6edc9 adding correct domain 2024-07-06 23:58:36 -04:00
Timothy Rogers
4e1e514545 maybe this time 2024-07-06 23:51:37 -04:00
Timothy Rogers
e387e942f7 hopefully fixing pnpm 2024-07-06 23:43:05 -04:00
Timothy Rogers
7b04ed640c fix build... 2024-07-06 23:31:10 -04:00
Timothy Rogers
46daf98523 switched to astro 2024-07-06 23:03:52 -04:00
hack13
9c8264f186
add new post for MFF 2023-12-06 21:17:16 -05:00
hack13
2d4958223e
updated to point at personal git 2023-09-16 11:38:32 -04:00
hack13
235ee86a2f
updating baseURL 2023-09-16 11:26:42 -04:00
hack13
fc5c5bab76
would help if I put used correct dir 2023-09-16 11:13:48 -04:00
hack13
c2b5feae5c
oops missed one 2023-09-16 11:12:24 -04:00
hack13
6b04f8ff09
fixing to point at github for action step 2023-09-16 11:11:11 -04:00
hack13
527d554bf0
new blog post 2023-09-16 11:05:58 -04:00
hack13
01a832e34e
removing github runner 2023-09-16 11:05:46 -04:00
hack13
ad20d08cb2
migrate to forgejo runners 2023-09-16 11:05:20 -04:00
Timothy Rogers
e5b4b8f929 new blog post
Signed-off-by: Timothy Rogers <github@hack13.me>
2023-05-26 21:23:35 -04:00
Timothy Rogers
0eea14836d
update title 2023-05-03 10:06:34 -04:00
Timothy Rogers
9eb456d04b
added new post 2023-05-03 10:04:04 -04:00
Timothy Rogers
be7f38bf8a
Create README.md 2023-03-23 10:37:16 -04:00
Timothy Rogers
01ffc0df29 more oops fixes 2023-03-12 16:34:32 -04:00
Timothy Rogers
a761cc2736 fixing variables, oops 2023-03-12 16:33:24 -04:00
Timothy Rogers
396c93adcf trying different tool 2023-03-12 16:31:49 -04:00
Timothy Rogers
a5ef469a24 updated github action 2023-03-12 16:18:55 -04:00
Timothy Rogers
dd5f6926dd fixing mistake 2023-01-24 09:15:14 -05:00
Timothy Rogers
a9a779655e updating rss stuff 2023-01-24 09:11:43 -05:00
Timothy Rogers
c9a258d345 switching back, but adding perm fixer 2023-01-22 14:28:37 -05:00
Timothy Rogers
6dd0da0e88 changing out hug builder 2023-01-22 14:21:51 -05:00
Timothy Rogers
0286443b89 Updating blog to auto-deploy and new post 2023-01-22 14:14:31 -05:00
hack13
faadb821d3 updating to be blog focused version of site 2023-01-22 08:14:31 -05:00
Timothy Rogers
4f61252525 attempt to fix UTF-8 chars 2022-09-22 08:13:37 -04:00
Timothy Rogers
52dc97118d Removing link to hosted version of plausible 2022-06-20 15:42:28 -04:00
Timothy Rogers
627caa1fd5 Added personal plausible tracker 2022-06-20 15:05:22 -04:00
Timothy Rogers
084d57b763 ripping out the google analytics... don't like them 2022-06-20 14:21:21 -04:00
Timothy Rogers
09135e3987 Added support for google analytics 2022-06-20 12:31:26 -04:00
Timothy Rogers
d2c102fcfb Adding Favicon 2022-06-01 12:32:20 -04:00
Timothy Rogers
a4fa616a1d Changing to plausible 2022-06-01 09:13:05 -04:00
Timothy Rogers
522cbd4880 Ackee Selfhosted Analytics 2022-06-01 08:52:41 -04:00
Timothy Rogers
ee41b50dd3 Adding Cloudflare Analytics snippit 2022-06-01 08:27:01 -04:00
Timothy Rogers
0e282bf6c6 Needs slash for hosted version 2022-06-01 08:12:38 -04:00
Timothy Rogers
2763fd4326 Adding noopener (security fix) 2022-06-01 08:10:38 -04:00
Timothy Rogers
1a3bfa10db FontAwesome 5 update & RSS Support 2022-06-01 08:06:33 -04:00
Timothy Rogers
6634b6b6dd New post: continuing to find myself 2022-05-08 22:34:26 -04:00
Timothy Rogers
54e58c3fe2 Adding support to verify my site on Mastodon 2022-04-26 19:58:14 -04:00
Timothy Rogers
0ab5fafec9 oops... 2022-04-17 13:44:52 -04:00
Timothy Rogers
a5cd35ceb2 more image optimization
Updated all pictures since hugo migration to webp from png
2022-04-17 11:40:21 -04:00
Timothy Rogers
dfc923e4b1 switch to webp
improvement of image size for optimization
2022-04-17 11:35:12 -04:00
Timothy Rogers
32611a9fd1 Nardoragon update 2022-04-17 09:20:55 -04:00
Timothy Rogers
3bf485e50c neosvr event post
adding new post to blog for the neosvr event I assisted with
2022-04-17 09:11:46 -04:00
Timothy Rogers
a7faa5321e Adding spacing to the posts list 2022-03-26 01:49:51 +00:00
Timothy Rogers
0244f0a13b Update gitignore to support nova IDE 2022-03-26 01:49:30 +00:00
Timothy Rogers
2f3052540d Updating some spelling mistakes and old issues 2022-03-26 01:40:17 +00:00
Timothy Rogers
9dddd7bc65 Updating picture on main page to webp for optimization 2022-03-11 21:17:59 -05:00
Timothy Rogers
4783306432 Fixed title and tag in article itself 2022-03-05 07:43:01 -05:00
Timothy Rogers
f0bc3c7ed3 Create .gitignore 2022-02-17 19:04:00 -05:00
Timothy Rogers
8bcbec92ca New avatar, home, and about page 2022-02-17 18:42:12 -05:00
Timothy Rogers
5c8a0bb9c5 Fixed up some more things... 2022-02-17 18:41:49 -05:00
Timothy Rogers
104a0d1dde Spelling mistake 2022-02-17 18:40:31 -05:00
Timothy Rogers
c1a780394a New post 2022-02-17 18:09:59 -05:00
Timothy Rogers
f05e1a010f Adding back button to blog posts, to ease navigation. 2022-02-03 11:00:08 -05:00
Timothy Rogers
9f3d6df16d Added posts to main page, updated blog navigation with pagination 2022-02-02 18:32:26 -05:00
Timothy Rogers
f4cfdd00f4 Fixed color of statuses on Projects page… oops… 2022-02-02 05:16:44 -05:00
Timothy Rogers
be4d0e8cd4 Updated projects and fixed publishing error on latest blog 2022-02-02 05:11:19 -05:00
Timothy Rogers
ae61c50a55 Adding new post… 2022-02-02 04:32:48 -05:00
hack13
f828a55cdf Revert "syncing..."
This reverts commit 1485cae929.
2021-12-01 21:13:16 -05:00
hack13
1485cae929 syncing... 2021-12-01 21:10:28 -05:00
Timothy Rogers
636e401f82 Centered post images 2021-10-29 10:30:19 -04:00
Timothy Rogers
9897104230 Fixing link to pictures in CF Workers and Pages post... 2021-10-29 09:35:28 -04:00
Timothy Rogers
d6e854b01c Adding CF Workers & Pages Post 2021-10-29 09:30:42 -04:00
Timothy Rogers
858f751c2f Updated the API docs to reflect changes 2021-09-14 18:18:15 -04:00
Timothy Rogers
e740e031cc Regression... rate limits prevent art link 2021-09-02 06:46:36 -04:00
Timothy Rogers
7c46ff506c Added Album Art to API 2021-09-01 19:53:55 -04:00
Timothy Rogers
0b10e16d09 Added link to source code 2021-09-01 10:05:07 -04:00
Timothy Rogers
d9b2ba32de Corrected issue with API and Projects conflicts 2021-08-31 17:51:47 -04:00
Timothy Rogers
2c314f44ba Added API Documentation 2021-08-31 17:46:50 -04:00
Timothy Rogers
040acb45ac added other pages 2021-08-29 18:36:43 -04:00
Timothy Rogers
dbf609e054 removing test page, fixing pathing 2021-08-29 18:03:55 -04:00
Timothy Rogers
8d9671e2f0 Updating to make blog work 2021-08-29 17:55:47 -04:00
Timothy Rogers
c5892a5972 imported theme and posts from WP 2021-06-27 03:03:18 -04:00
Timothy Rogers
2266e3ddc5 completely re-working everything! 2021-06-25 19:11:18 -04:00
hack13
2a6fdc1a9c starting work on theme 2021-06-24 09:19:15 -04:00
hack13
9fcb3940ec Starting the work to switch to Hugo 2021-06-24 07:28:11 -04:00
Timothy Rogers
1bf16622c2 updated to add privacy info 2021-02-21 15:40:04 -05:00
Timothy Rogers
18daff87ee Added privacy policy 2021-02-21 15:38:40 -05:00
188 changed files with 17739 additions and 437 deletions

24
.gitignore vendored
View file

@ -1 +1,23 @@
.vscode # build output
dist/
# generated types
.astro/
# dependencies
node_modules/
# logs
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
# environment variables
.env
.env.production
# macOS-specific files
.DS_Store
.vercel

View file

@ -1,6 +0,0 @@
RewriteEngine On
RewriteRule ^home/?$ index.php
RewriteRule ^about/?$ about.php
RewriteRule ^projects/?$ projects.php
RewriteRule ^guides/?$ guides.php
RewriteRule ^projects/opennic?$ projects/opennic.php

3
.vscode/extensions.json vendored Normal file
View file

@ -0,0 +1,3 @@
{
"recommendations": ["biomejs.biome", "astro-build.astro-vscode"]
}

22
.vscode/settings.json vendored Normal file
View file

@ -0,0 +1,22 @@
{
"editor.formatOnSave": true,
"editor.defaultFormatter": "biomejs.biome",
"[javascript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[javascriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescript]": {
"editor.defaultFormatter": "biomejs.biome"
},
"[typescriptreact]": {
"editor.defaultFormatter": "biomejs.biome"
},
"editor.codeActionsOnSave": {
"source.fixAll": "explicit",
"quickfix.biome": "always",
"source.organizeImports.biome": "always"
},
"frontMatter.dashboard.openOnStart": false
}

21
LICENSE Normal file
View file

@ -0,0 +1,21 @@
MIT License
Copyright (c) 2024 saicaca
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

58
README.es.md Normal file
View file

@ -0,0 +1,58 @@
# 🍥Fuwari
Un tema estático para blogs construido con [Astro](https://astro.build).
[**🖥️ Demostración en Vivo (Vercel)**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;
[**📦 Versión Antigua de Hexo**](https://github.com/saicaca/hexo-theme-vivia)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;
> Versión del README: `2024-04-07`
![Imagen de Vista Previa](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ Características
- [x] Construido con [Astro](https://astro.build) y [Tailwind CSS](https://tailwindcss.com)
- [x] Animaciones suaves y transiciones de página
- [x] Modo claro / oscuro
- [x] Colores del tema y banner personalizables
- [x] Diseño responsivo
- [ ] Comentarios
- [x] Buscador
- [ ] TOC (Tabla de Contenidos)
## 🚀 Cómo Usar
1. [Genera un nuevo repositorio](https://github.com/saicaca/fuwari/generate) desde esta plantilla o haz un fork de este repositorio.
2. Para editar tu blog localmente, clona tu repositorio, ejecuta `pnpm install` y `pnpm add sharp` para instalar las dependencias.
- Instala [pnpm](https://pnpm.io) `npm install -g pnpm` si aún no lo tienes.
3. Edita el archivo de configuración `src/config.ts` para personalizar tu blog.
4. Ejecuta `pnpm new-post <nombre-de-archivo>` para crear una nueva entrada y edítala en `src/content/posts/`.
5. Despliega tu blog en Vercel, Netlify, GitHub Pages, etc., siguiendo [las guías](https://docs.astro.build/en/guides/deploy/). Necesitas editar la configuración del sitio en `astro.config.mjs` antes del despliegue.
## ⚙️ Cabecera de las Entradas
```yaml
---
title: Mi Primer Post en el Blog
published: 2023-09-09
description: Esta es la primera entrada de mi nuevo blog con Astro.
image: /images/cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
```
## 🧞 Comandos
Todos los comandos se ejecutan desde la raíz del proyecto, desde una terminal:
| Comando | Acción |
|:------------------------------------|:--------------------------------------------------|
| `pnpm install` y `pnpm add sharp` | Instala las dependencias |
| `pnpm dev` | Inicia el servidor de desarrollo local en `localhost:4321` |
| `pnpm build` | Compila tu web para producción en `./dist/` |
| `pnpm preview` | Previsualiza la web localmente, antes del despliegue |
| `pnpm new-post <nombre-de-archivo>` | Crea una nueva entrada |
| `pnpm astro ...` | Ejecuta comandos CLI como `astro add`, `astro check` |
| `pnpm astro --help` | Obtén ayuda para usar el CLI de Astro |

55
README.ja-JP.md Normal file
View file

@ -0,0 +1,55 @@
# 🍥Fuwari
[Astro](https://astro.build)で構築された静的ブログテンプレート
[**🖥️ライブデモ (Vercel)**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏中文**](https://github.com/saicaca/fuwari/blob/main/README.zh-CN.md)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏日本語**](https://github.com/saicaca/fuwari/blob/main/README.ja-JP.md)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**📦旧Hexoバージョン**](https://github.com/saicaca/hexo-theme-vivia)
![Preview Image](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ 特徴
- [x] [Astro](https://astro.build)及び [Tailwind CSS](https://tailwindcss.com)で構築
- [x] スムーズなアニメーションとページ遷移
- [x] ライト/ダークテーマ対応
- [x] カスタマイズ可能なテーマカラーとバナー
- [x] レスポンシブデザイン
- [ ] コメント機能
- [x] 検索機能
- [ ] 目次
## 🚀 使用方法
1. [テンプレート](https://github.com/saicaca/fuwari/generate)から新しいリポジトリを作成するかCloneをします。
2. ブログをローカルで編集するには、リポジトリをクローンした後、`pnpm install``pnpm add sharp` を実行して依存関係をインストールします。
- [pnpm](https://pnpm.io)がインストールされていない場合は `npm install -g pnpm` で導入可能です。
3. `src/config.ts`ファイルを編集する事でブログを自分好みにカスタマイズ出来ます。
4. `pnpm new-post <filename>`で新しい記事を作成し、`src/content/posts/`.フォルダ内で編集します。
5. 作成したブログをVercel、Netlify、GitHub Pagesなどにデプロイするには[ガイド](https://docs.astro.build/ja/guides/deploy/)に従って下さい。加えて、別途デプロイを行う前に`astro.config.mjs`を編集してサイト構成を変更する必要があります。
## ⚙️ 記事のフロントマター
```yaml
---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: /images/cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
```
## 🧞 コマンド
すべてのコマンドは、ターミナルでプロジェクトのルートから実行する必要があります:
| Command | Action |
|:------------------------------------|:-------------------------------------------------|
| `pnpm install` AND `pnpm add sharp` | 依存関係のインストール |
| `pnpm dev` | `localhost:4321`で開発用ローカルサーバーを起動 |
| `pnpm build` | `./dist/`にビルド内容を出力 |
| `pnpm preview` | デプロイ前の内容をローカルでプレビュー |
| `pnpm new-post <filename>` | 新しい投稿を作成 |
| `pnpm astro ...` | `astro add`, `astro check`の様なコマンドを実行する際に使用 |
| `pnpm astro --help` | Astro CLIのヘルプを表示 |

57
README.ko.md Normal file
View file

@ -0,0 +1,57 @@
# 🍥Fuwari
[Astro](https://astro.build)로 구축된 정적 블로그 템플릿입니다.
[**🖥️미리보기 (Vercel)**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;
[**📦Old Hexo Version**](https://github.com/saicaca/hexo-theme-vivia)
> README 버전: `2024-04-07`
![Preview Image](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ 특징
- [x] [Astro](https://astro.build) 및 [Tailwind CSS](https://tailwindcss.com)로 구축됨
- [x] 부드러운 애니메이션 및 페이지 전환
- [x] 라이트 모드 / 다크 모드
- [x] 사용자 정의 가능한 테마 색상 및 배너
- [x] 반응형 디자인
- [ ] 댓글
- [x] 검색
- [ ] 목차
## 🚀 사용하는 방법
1. 이 템플릿에서 [새 저장소를 생성](https://github.com/saicaca/fuwari/generate)하거나 이 저장소를 포크하세요.
2. 블로그를 로컬에서 편집하려면 저장소를 복제하고 `pnpm install``pnpm add sharp`를 실행하여 종속성을 설치하세요.
- 아직 [pnpm](https://pnpm.io)을 설치하지 않았다면 `npm install -g pnpm`을 실행하여 [pnpm](https://pnpm.io)을 설치하세요.
3. 블로그를 사용자 정의하려면 `src/config.ts` 구성 파일을 편집하세요.
4. `pnpm new-post <filename>`을 실행하여 새 게시물을 만들고 `src/content/posts/`에서 편집하세요.
5. [가이드](https://docs.astro.build/en/guides/deploy/)에 따라 블로그를 Vercel, Netlify, GitHub 페이지 등에 배포하세요. 배포하기 전에 `astro.config.mjs`에서 사이트 구성을 편집해야 합니다.
## ⚙️ 게시물의 머리말 설정
```yaml
---
title: 내 첫 블로그 게시물
published: 2023-09-09
description: 내 새로운 Astro 블로그의 첫 번째 게시물입니다!
image: /images/cover.jpg
tags: [푸, 바, 오]
category: 앞-끝
draft: false
---
```
## 🧞 명령어
모든 명령어는 프로젝트 최상단, 터미널에서 실행됩니다:
| Command | Action |
|:------------------------------------|:-------------------------------------------------|
| `pnpm install` AND `pnpm add sharp` | 종속성을 설치합니다. |
| `pnpm dev` | `localhost:4321`에서 로컬 개발 서버를 시작합니다. |
| `pnpm build` | `./dist/`에 프로덕션 사이트를 구축합니다. |
| `pnpm preview` | 배포하기 전에 로컬에서 빌드 미리보기 |
| `pnpm new-post <filename>` | 새 게시물 작성 |
| `pnpm astro ...` | `astro add`, `astro check`와 같은 CLI 명령어 실행 |
| `pnpm astro --help` | Astro CLI를 사용하여 도움 받기 |

55
README.md Normal file
View file

@ -0,0 +1,55 @@
# 🍥Fuwari
A static blog template built with [Astro](https://astro.build).
[**🖥Live Demo (Vercel)**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏中文 README**](https://github.com/saicaca/fuwari/blob/main/README.zh-CN.md)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏日本語 README**](https://github.com/saicaca/fuwari/blob/main/README.ja-JP.md)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**📦Old Hexo Version**](https://github.com/saicaca/hexo-theme-vivia)
![Preview Image](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ Features
- [x] Built with [Astro](https://astro.build) and [Tailwind CSS](https://tailwindcss.com)
- [x] Smooth animations and page transitions
- [x] Light / dark mode
- [x] Customizable theme colors & banner
- [x] Responsive design
- [ ] Comments
- [x] Search
- [ ] TOC
## 🚀 How to Use
1. [Generate a new repository](https://github.com/saicaca/fuwari/generate) from this template or fork this repository.
2. To edit your blog locally, clone your repository, run `pnpm install` AND `pnpm add sharp` to install dependencies.
- Install [pnpm](https://pnpm.io) `npm install -g pnpm` if you haven't.
3. Edit the config file `src/config.ts` to customize your blog.
4. Run `pnpm new-post <filename>` to create a new post and edit it in `src/content/posts/`.
5. Deploy your blog to Vercel, Netlify, GitHub Pages, etc. following [the guides](https://docs.astro.build/en/guides/deploy/). You need to edit the site configuration in `astro.config.mjs` before deployment.
## ⚙️ Frontmatter of Posts
```yaml
---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: /images/cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
```
## 🧞 Commands
All commands are run from the root of the project, from a terminal:
| Command | Action |
|:------------------------------------|:-------------------------------------------------|
| `pnpm install` AND `pnpm add sharp` | Installs dependencies |
| `pnpm dev` | Starts local dev server at `localhost:4321` |
| `pnpm build` | Build your production site to `./dist/` |
| `pnpm preview` | Preview your build locally, before deploying |
| `pnpm new-post <filename>` | Create a new post |
| `pnpm astro ...` | Run CLI commands like `astro add`, `astro check` |
| `pnpm astro --help` | Get help using the Astro CLI |

59
README.th.md Normal file
View file

@ -0,0 +1,59 @@
# 🍥Fuwari
แม่แบบสำหรับเว็บบล็อกแบบ static สร้างด้วย [Astro](https://astro.build)
[**🖥️ ตัวอย่างการใช้งานจริง (Vercel)**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;
[**📦 เวอร์ชั่นเก่าสำหรับ Hexo**](https://github.com/saicaca/hexo-theme-vivia)
> เวอร์ชั่นของ README: `2024-09-10`
![ภาพตัวอย่าง](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ คุณสมบัติ
- [x] สร้างด้วย [Astro](https://astro.build) และ [Tailwind CSS](https://tailwindcss.com)
- [x] มีอนิเมชั่นและการเปลี่ยนหน้าอย่างลื่นไหล
- [x] รองรับโหมดสว่าง / โหมดมืด
- [x] ปรับแต่งสีธีมและแบนเนอร์ได้
- [x] Responsive design (หน้าตาเว็บปรับเปลี่ยนตามขนาดจอ)
- [ ] การแสดงความคิดเห็น
- [x] การค้นหา
- [ ] TOC (สารบัญ)
## 🚀 วิธีใช้งาน
1. [Generate repository ใหม่](https://github.com/saicaca/fuwari/generate)ขึ้นมาจากแม่แบบนี้ หรือจะ fork repository นี้ก็ได้
2. เริ่มแก้ไขบล็อกของคุณแบบ local โดยการ clone repository ของคุณ (จากข้อ 1) ไว้ในเครื่องของคุณ แล้วรันคำสั่ง `pnpm install` และ `pnpm add sharp` เพื่อติดตั้ง dependencies ที่จำเป็น
- ติดตั้ง [pnpm](https://pnpm.io) ด้วยคำสั่ง `npm install -g pnpm` ถ้ายังไม่เคยติดตั้ง
3. แก้ไขไฟล์การตั้งค่า `src/config.ts` เพื่อปรับแต่งบล็อกของคุณ
4. รันคำสั่ง `pnpm new-post <filename>` เพื่อสร้างโพสต์ใหม่ใน `src/content/posts/` และแก้ไขไฟล์โพสต์นั้นๆ ให้สมบูรณ์
5. Deploy เว็บบล็อกของคุณไปยัง Vercel, Netlify, GitHub Pages หรือบริการอื่นๆ โดยอ้างอิงวิธีการจาก[คู่มือนี้](https://docs.astro.build/en/guides/deploy/) อย่าลืมแก้ไขการตั้งค่าเว็บไซต์ในไฟล์ `astro.config.mjs` ก่อนที่คุณจะ deploy เว็บ
## ⚙️ Frontmatter ของโพสต์
```yaml
---
title: โพสต์แรกของฉัน
published: 2023-09-09
description: นี่คือโพสต์แรกของเว็บบล็อก Astro อันใหม่ของฉัน
image: ./cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
lang: jp # เขียนค่านี้เมื่อภาษาของโพสต์นั้นๆ แตกต่างจากภาษาของเว็บไซต์ที่ตั้งค่าไว้ใน `config.ts` เท่านั้น
---
```
## 🧞 คำสั่ง
คำสั่งที่รันได้ใน terminal จาก root ของโปรเจค:
| คำสั่ง | ผลที่เกิด |
|:------------------------------------|:--------------------------------------------------|
| `pnpm install` และ `pnpm add sharp` | ติดตั้ง dependencies |
| `pnpm dev` | เปิดเซิร์ฟเวอร์เพื่อพัฒนาเว็บแบบ local ที่ `localhost:4321` |
| `pnpm build` | Build เว็บไซต์แบบพร้อมใช้งานจริงไปยังโฟลเดอร์ `./dist/` |
| `pnpm preview` | ดูตัวอย่าง build ของคุณแบบ local ก่อนที่จะ deploy จริง |
| `pnpm new-post <filename>` | สร้างโพสต์ใหม่ |
| `pnpm astro ...` | รันคำสั่ง CLI เช่น `astro add`, `astro check` |
| `pnpm astro --help` | ดูข้อมูลเพิ่มเติมเกี่ยวกับ Astro CLI |

55
README.zh-CN.md Normal file
View file

@ -0,0 +1,55 @@
# 🍥Fuwari
基于 [Astro](https://astro.build) 开发的静态博客模板。
[**🖥在线预览Vercel**](https://fuwari.vercel.app)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏English README**](https://github.com/saicaca/fuwari)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**🌏日本語 README**](https://github.com/saicaca/fuwari/blob/main/README.ja-JP.md)&nbsp;&nbsp;&nbsp;/&nbsp;&nbsp;&nbsp;[**📦旧 Hexo 版本**](https://github.com/saicaca/hexo-theme-vivia)
![Preview Image](https://raw.githubusercontent.com/saicaca/resource/main/fuwari/home.png)
## ✨ 功能特性
- [x] 基于 Astro 和 Tailwind CSS 开发
- [x] 流畅的动画和页面过渡
- [x] 亮色 / 暗色模式
- [x] 自定义主题色和横幅图片
- [x] 响应式设计
- [ ] 评论
- [x] 搜索
- [ ] 文内目录
## 🚀 使用方法
1. 使用此模板[生成新仓库](https://github.com/saicaca/fuwari/generate)或 Fork 此仓库
2. 进行本地开发Clone 新的仓库,执行 `pnpm install``pnpm add sharp` 以安装依赖
- 若未安装 [pnpm](https://pnpm.io),执行 `npm install -g pnpm`
3. 通过配置文件 `src/config.ts` 自定义博客
4. 执行 `pnpm new-post <filename>` 创建新文章,并在 `src/content/posts/` 目录中编辑
5. 参考[官方指南](https://docs.astro.build/zh-cn/guides/deploy/)将博客部署至 Vercel, Netlify, GitHub Pages 等;部署前需编辑 `astro.config.mjs` 中的站点设置。
## ⚙️ 文章 Frontmatter
```yaml
---
title: My First Blog Post
published: 2023-09-09
description: This is the first post of my new Astro blog.
image: /images/cover.jpg
tags: [Foo, Bar]
category: Front-end
draft: false
---
```
## 🧞 指令
下列指令均需要在项目根目录执行:
| Command | Action |
|:----------------------------------|:----------------------------------|
| `pnpm install``pnpm add sharp` | 安装依赖 |
| `pnpm dev` | 在 `localhost:4321` 启动本地开发服务器 |
| `pnpm build` | 构建网站至 `./dist/` |
| `pnpm preview` | 本地预览已构建的网站 |
| `pnpm new-post <filename>` | 创建新文章 |
| `pnpm astro ...` | 执行 `astro add`, `astro check` 等指令 |
| `pnpm astro --help` | 显示 Astro CLI 帮助 |

View file

@ -1,62 +0,0 @@
<?php include('views/https.php'); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>HACK13 Site</title>
<link rel="stylesheet" type="text/css" href="/assets/bootstrap.min.css"/>
</head>
<body>
<?php include('views/navbar.php'); ?>
<main role="main">
<div class="container">
<h1 class="mt-5">About Me</h1>
<h3>Short Bio:</h3>
<p>Hi there, I am by day a UNIX\Linux System Administrator and the rest of the time I am just a big old Linux nerd. I have been running Linux as my daily driver pretty much since I was nine years old. I found it after getting my first computer, and it was so slow and wanted to find something that would run faster. I found something called ZenWalk Linux back then (sadly not around anymore), which was based on Slackware Linux which is still around. Shortly after that, I started to dive more into Linux over the years. I have used many different distributions over the years as a daily driver. Ubuntu, Arch, Debian, Mandriva, OpenSUSE, and Solus just to name a few.</p>
<p>These days I tend to use Fedora as my daily driver, I like being able to run more up to date software that isnt really modified for the distribution. Fedora feels very stock and since I manage mostly RHEL(Red Hat Enterprise Linux) for my day job, it is nice to use the upstream version of the system to just stay in the same ecosystem. That said, I have nothing against any other distribution.</p>
<h3>Contact:</h3>
<div class="container">
<center>
<div class="row">
<div class="col">
<i class="fab fa-telegram-plane fa-5x"></i><br><a href="https://t.me/kite5521" target="_blank">@kite5521</a>
</div>
<div class="col">
<i class="fab fa-discord fa-5x"></i><br>@hack13#4761
</div>
<div class="col">
<i class="fab fa-mastodon fa-5x"></i><br><a href="https://meow.social/@hack13" target="_blank">@hack13</a>
</div>
<div class="col">
<i class="fab fa-twitter fa-5x"></i><br><a href="https://twitter.com/kite552" target="_blank">@kite552</a>
</div>
</div>
<br>
<div class="row">
<div class="col">
<i class="fab fa-github fa-5x"></i><br><a href="https://github.com/hack13" target="_blank">hack13</a>
</div>
<div class="col">
<i class="fab fa-gitlab fa-5x"></i><br><a href="https://gitlab.com/hack13" target="_blank">hack13</a>
</div>
<div class="col">
<i class="fab fa-lastfm fa-5x"></i><br><a href="https://www.last.fm/user/dothacker552" target="_blank">dothacker552</a>
</div>
<div class="col">
<i class="far fa-envelope-open fa-5x"></i><br><a href="mailto:me@hack13.me">me<i class="fas fa-at"></i>hack13.me</a>
</div>
</div>
</center>
</div>
</div>
</main>
</body>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted"><i class="far fa-copyright"></i>HACK13<i>(Timothy Rogers)</i></span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
</footer>
</html>

Binary file not shown.

Before

Width:  |  Height:  |  Size: 23 KiB

File diff suppressed because one or more lines are too long

Binary file not shown.

Before

Width:  |  Height:  |  Size: 26 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 24 KiB

View file

@ -1,3 +0,0 @@
h1 {
color: deepskyblue;
}

192
astro.config.mjs Normal file
View file

@ -0,0 +1,192 @@
import sitemap from '@astrojs/sitemap'
import svelte from '@astrojs/svelte'
import tailwind from '@astrojs/tailwind'
import swup from '@swup/astro'
import Compress from 'astro-compress'
import icon from 'astro-icon'
import { defineConfig } from 'astro/config'
import Color from 'colorjs.io'
import rehypeAutolinkHeadings from 'rehype-autolink-headings'
import rehypeComponents from 'rehype-components' /* Render the custom directive content */
import rehypeKatex from 'rehype-katex'
import rehypeSlug from 'rehype-slug'
import remarkDirective from 'remark-directive' /* Handle directives */
import remarkGithubAdmonitionsToDirectives from 'remark-github-admonitions-to-directives'
import remarkMath from 'remark-math'
import { AdmonitionComponent } from './src/plugins/rehype-component-admonition.mjs'
import { GithubCardComponent } from './src/plugins/rehype-component-github-card.mjs'
import { parseDirectiveNode } from './src/plugins/remark-directive-rehype.js'
import { remarkReadingTime } from './src/plugins/remark-reading-time.mjs'
const oklchToHex = str => {
const DEFAULT_HUE = 250
const regex = /-?\d+(\.\d+)?/g
const matches = str.string.match(regex)
const lch = [matches[0], matches[1], DEFAULT_HUE]
return new Color('oklch', lch).to('srgb').toString({
format: 'hex',
})
}
// https://astro.build/config
export default defineConfig({
site: 'https://hack13.blog/',
base: '/',
trailingSlash: 'always',
redirects: {
'/feed': '/rss.xml',
'/2010/06/new-blog': '/posts/new-blog',
'/2012/04/been-a-while': '/posts/been-a-while',
'/2012/04/my-take-on-opensim-grids': '/posts/my-take-on-opensim-grids',
'/2012/04/my-vision-of-the-hypergrid': '/posts/my-vision-of-the-hypergrid',
'/2012/04/we-have-hypergrid-use-it': '/posts/we-have-hypergrid-use-it',
'/2012/05/coming-changes': '/posts/coming-changes',
'/2012/06/timothys-thoughts': '/posts/timothys-thoughts',
'/2012/07/finding-my-place': '/posts/finding-my-place',
'/2012/07/lil-about-me-some-rage': '/posts/lil-about-me-some-rage',
'/2012/08/battle-of-business-vs-opensource':
'/posts/2012-08-09-battle-of-business-vs-opensource',
'/2012/12/new-years-resolutions': '/posts/2012-12-31-new-years-resolutions',
'/2013/02/explaining-opensim-memory-usage':
'/posts/2013-02-11-explaining-opensim-memory-usage',
'/2013/02/thank-you': '/posts/2013-02-13-thank-you',
'/2013/02/needing-rest': '/posts/2013-02-20-needing-rest',
'/2013/02/top-10-reasons-not-to-give-up-on-aurora-sim':
'/posts/2013-02-21-top-10-reasons-not-to-give-up-on-aurora-sim',
'/2013/02/who-is-timothy-vyperhoxleyrogers':
'/posts/2013-02-23-who-is-timothy-vyperhoxleyrogers',
'/2013/03/why-i-closed-aurorascape':
'/posts/2013-03-05-why-i-closed-aurorascape',
'/2013/03/closed-vs-open-really': '/posts/2013-03-11-closed-vs-open-really',
'/2013/04/moving-up-around': '/posts/2013-04-08-moving-up-around',
'/2013/04/my-clocks-are-ticking': '/posts/2013-04-26-my-clocks-are-ticking',
'/2013/06/upset-with-stiffled-innovation':
'/posts/2013-06-19-upset-with-stiffled-innovation',
'/2013/08/depression': '/posts/2013-08-12-depression',
'/2013/09/my-response-to-kitely-marketplace':
'/posts/2013-09-02-my-response-to-kitely-marketplace',
'/2013/11/why-i-dont-use-the-cloud':
'/posts/2013-11-03-why-i-dont-use-the-cloud',
'/2016/01/moved-to-wordpress': '/posts/2016-01-04-moved-to-wordpress',
'/2018/01/where-has-tim-been': '/posts/2018-01-06-where-has-tim-been',
'/2018/01/taking-control-of-my-cloud-storage':
'/posts/2018-01-14-taking-control-of-my-cloud-storage',
'/2018/04/finding-my-linux-distro':
'/posts/2018-04-26-finding-my-linux-distro',
'/2019/01/2019-goals': '/posts/2019-01-26-2019-goals',
'/2019/01/speeding-up-zadaroo-files':
'/posts/2019-01-28-speeding-up-zadaroo-files',
'/2021/10/cloudflare-workers-and-pages':
'/posts/2021-10-29-cloudflare-workers-and-pages',
'/2022/02/struggling-with-anxiety':
'/posts/2022-02-02-struggling-with-anxiety',
'/2022/02/nft-polarization': '/posts/2022-02-17-nft-polarization',
'/2022/04/neosvr-event-hosting': '/posts/2022-04-17-neosvr-event-hosting',
'/2022/05/continuing-to-find-myself':
'/posts/2022-05-08-continuing-to-find-myself',
'/2023/05/decentralized-protocols':
'/posts/2023-05-03-decentralized-protocols',
'/2023/09/perspectives': '/posts/2023-09-16-upset-over-crypto',
'/2023/12/mff-2023-fedi-panel': '/posts/2023-12-06-mff-2023-panel',
},
integrations: [
tailwind(),
swup({
theme: false,
animationClass: 'transition-',
containers: ['main'],
smoothScrolling: true,
cache: true,
preload: true,
accessibility: true,
globalInstance: true,
}),
icon({
include: {
'material-symbols': ['*'],
'fa6-brands': ['*'],
'fa6-regular': ['*'],
'fa6-solid': ['*'],
},
}),
Compress({
Image: false,
}),
svelte(),
sitemap(),
],
markdown: {
remarkPlugins: [
remarkMath,
remarkReadingTime,
remarkGithubAdmonitionsToDirectives,
remarkDirective,
parseDirectiveNode,
],
rehypePlugins: [
rehypeKatex,
rehypeSlug,
[
rehypeComponents,
{
components: {
github: GithubCardComponent,
note: (x, y) => AdmonitionComponent(x, y, 'note'),
tip: (x, y) => AdmonitionComponent(x, y, 'tip'),
important: (x, y) => AdmonitionComponent(x, y, 'important'),
caution: (x, y) => AdmonitionComponent(x, y, 'caution'),
warning: (x, y) => AdmonitionComponent(x, y, 'warning'),
},
},
],
[
rehypeAutolinkHeadings,
{
behavior: 'append',
properties: {
className: ['anchor'],
},
content: {
type: 'element',
tagName: 'span',
properties: {
className: ['anchor-icon'],
'data-pagefind-ignore': true,
},
children: [
{
type: 'text',
value: '#',
},
],
},
},
],
],
},
vite: {
build: {
rollupOptions: {
onwarn(warning, warn) {
// temporarily suppress this warning
if (
warning.message.includes('is dynamically imported by') &&
warning.message.includes('but also statically imported by')
) {
return
}
warn(warning)
},
},
},
css: {
preprocessorOptions: {
stylus: {
define: {
oklchToHex: oklchToHex,
},
},
},
},
},
})

66
biome.json Normal file
View file

@ -0,0 +1,66 @@
{
"$schema": "https://biomejs.dev/schemas/1.4.1/schema.json",
"extends": [],
"files": { "ignoreUnknown": true },
"organizeImports": {
"enabled": true
},
"formatter": {
"enabled": true,
"formatWithErrors": false,
"ignore": [],
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
},
"javascript": {
"parser": {
"unsafeParameterDecoratorsEnabled": true
},
"formatter": {
"quoteStyle": "single",
"jsxQuoteStyle": "single",
"trailingComma": "all",
"semicolons": "asNeeded",
"arrowParentheses": "asNeeded"
}
},
"json": {
"parser": { "allowComments": true },
"formatter": {
"enabled": true,
"indentStyle": "space",
"indentWidth": 2,
"lineWidth": 80
}
},
"linter": {
"ignore": [],
"rules": {
"a11y": {
"recommended": true
},
"complexity": {
"recommended": true
},
"correctness": {
"recommended": true
},
"performance": {
"recommended": true
},
"security": {
"recommended": true
},
"style": {
"recommended": true
},
"suspicious": {
"recommended": true
},
"nursery": {
"recommended": true
}
}
}
}

62
frontmatter.json Normal file
View file

@ -0,0 +1,62 @@
{
"$schema": "https://frontmatter.codes/frontmatter.schema.json",
"frontMatter.framework.id": "astro",
"frontMatter.preview.host": "http://localhost:4321",
"frontMatter.content.publicFolder": "public",
"frontMatter.content.pageFolders": [
{
"title": "posts",
"path": "[[workspace]]/src/content/posts"
}
],
"frontMatter.taxonomy.contentTypes": [
{
"name": "default",
"pageBundle": true,
"previewPath": "'blog'",
"filePrefix": null,
"clearEmpty": true,
"fields": [
{
"title": "title",
"name": "title",
"type": "string",
"single": true
},
{
"title": "description",
"name": "description",
"type": "string"
},
{
"title": "published",
"name": "published",
"type": "datetime",
"default": "{{now}}",
"isPublishDate": true
},
{
"title": "preview",
"name": "image",
"type": "image",
"isPreviewImage": true
},
{
"title": "tags",
"name": "tags",
"type": "list"
},
{
"title": "category",
"name": "category",
"type": "string"
},
{
"title": "draft",
"name": "draft",
"type": "boolean"
}
]
}
]
}

View file

@ -1,54 +0,0 @@
<?php include('views/https.php'); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>HACK13 Site</title>
<link rel="stylesheet" type="text/css" href="/assets/bootstrap.min.css"/>
</head>
<body>
<?php include('views/navbar.php'); ?>
<main role="main">
<div class="container">
<h1 class="mt-5">Guides</h1>
<div class="container">
<div class="row">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">About</th>
<th scope="col">Download</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Load Balancing OpenSimulator</th>
<td>This is a simple guide to breaking apart your OpenSimulator Robust and load balancing between the multiple instances.</td>
<td>
<a href="#" target="_blank" class="btn btn-secondary"><i class="fab fa-markdown"></i></a>
</td>
</tr>
<tr>
<th scope="row">Automated Borg Backups</th>
<td>Simple guide to setting up backups with borg backup software on your server.</td>
<td>
<a href="#" target="_blank" class="btn btn-secondary"><i class="fab fa-markdown"></i></a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
</body>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted"><i class="far fa-copyright"></i>HACK13<i>(Timothy Rogers)</i></span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
</footer>
</html>

View file

@ -1,58 +0,0 @@
<?php include('views/https.php'); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>HACK13 Site</title>
<link rel="stylesheet" type="text/css" href="/assets/bootstrap.min.css"/>
</head>
<body>
<?php include('views/navbar.php'); ?>
<main role="main">
<div class="jumbotron">
<div class="container">
<div class="row">
<div class="col-md-auto">
<img src="assets/avatar_1.png" class="rounded float-left" alt="avatar"/>
</div>
<div class="col-6">
<h1 class="display-3">HACK13's Site</h1>
<p>Just another fox on the internet.<br>
<strong>Other Aliases:</strong> Timothy Vyper, kite552, dothacker552</p>
</div>
</div>
</div>
</div>
<div class="container">
<div class="row">
<div class="col-md-4">
<h2>Blog</h2>
<p>Read my ramblings from my history as well as few things I post from time to time. I don't write on it often, but I do plan to try and contribute more when I have time. </p>
<p><a class="btn btn-secondary" href="https://blog.hack13.me/" role="button">Read Blog &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Guides</h2>
<p>From time to time I will write up a guide on how to accomplish something that I did so that if someone else comes upon it they can read what I did to accomplish the same. </p>
<p><a class="btn btn-secondary" href="/guides" role="button">View Guides &raquo;</a></p>
</div>
<div class="col-md-4">
<h2>Projects</h2>
<p>While I am primarily a System Administrator, I really enjoy open source and I like to code here and there. So if you are interested in seeing some of my work and projects I list them here.</p>
<p><a class="btn btn-secondary" href="/projects" role="button">Projects &raquo;</a></p>
</div>
</div>
<hr>
</div>
</main>
</body>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted"><i class="far fa-copyright"></i>HACK13<i>(Timothy Rogers)</i></span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
</footer>
</html>

72
package.json Normal file
View file

@ -0,0 +1,72 @@
{
"name": "fuwari",
"type": "module",
"version": "0.0.1",
"scripts": {
"dev": "astro dev",
"start": "astro dev",
"build": "astro build && pagefind --site dist",
"preview": "astro preview",
"astro": "astro",
"new-post": "node scripts/new-post.js",
"format": "biome format --write ./src",
"lint": "biome check --apply ./src"
},
"dependencies": {
"@astrojs/check": "^0.9.3",
"@astrojs/rss": "^4.0.7",
"@astrojs/sitemap": "^3.1.6",
"@astrojs/svelte": "^5.7.0",
"@astrojs/tailwind": "^5.1.0",
"@fontsource-variable/jetbrains-mono": "^5.0.21",
"@fontsource/roboto": "^5.0.13",
"@swup/astro": "^1.4.1",
"astro": "^4.14.5",
"astro-compress": "^2.2.28",
"astro-icon": "1.1.0",
"colorjs.io": "^0.5.0",
"hastscript": "^9.0.0",
"markdown-it": "^14.1.0",
"mdast-util-to-string": "^4.0.0",
"overlayscrollbars": "^2.8.3",
"pagefind": "^1.1.0",
"reading-time": "^1.5.0",
"rehype-autolink-headings": "^7.1.0",
"rehype-components": "^0.3.0",
"rehype-katex": "^7.0.0",
"rehype-slug": "^6.0.0",
"remark-directive": "^3.0.0",
"remark-directive-rehype": "^0.4.2",
"remark-math": "^6.0.0",
"sanitize-html": "^2.13.0",
"sharp": "^0.33.4",
"svelte": "^4.2.18",
"tailwindcss": "^3.4.4",
"typescript": "^5.5.2",
"unist-util-visit": "^5.0.0"
},
"devDependencies": {
"@astrojs/ts-plugin": "^1.10.1",
"@biomejs/biome": "1.8.2",
"@iconify-json/fa6-brands": "^1.1.19",
"@iconify-json/fa6-regular": "^1.1.19",
"@iconify-json/fa6-solid": "^1.1.21",
"@iconify-json/material-symbols": "^1.1.82",
"@iconify/svelte": "^4.0.2",
"@rollup/plugin-yaml": "^4.1.2",
"@tailwindcss/typography": "^0.5.13",
"@types/markdown-it": "^14.1.1",
"@types/mdast": "^4.0.4",
"@types/sanitize-html": "^2.11.0",
"remark-github-admonitions-to-directives": "^1.0.5",
"sass": "^1.77.6",
"stylus": "^0.63.0"
},
"pnpm": {
"overrides": {
"vite-imagetools": "^6.2.7",
"sharp": "^0.33.0"
}
},
"packageManager": "pnpm@9.5.0-beta.3+sha512.8b0c4d7fd9937d73f06ad284e681ddfd0f08b8f792a4f854a3ea24d1c7ade57dfe952ce88ed174d57da694e8323f0bb5a25011f780ed2787759c647b35263fa1"
}

10581
pnpm-lock.yaml generated Normal file

File diff suppressed because it is too large Load diff

11
postcss.config.mjs Normal file
View file

@ -0,0 +1,11 @@
import postcssImport from 'postcss-import';
import postcssNesting from 'tailwindcss/nesting/index.js';
import tailwindcss from 'tailwindcss';
export default {
plugins: {
'postcss-import': postcssImport, // to combine multiple css files
'tailwindcss/nesting': postcssNesting,
tailwindcss: tailwindcss,
}
};

View file

@ -1,120 +0,0 @@
<?php include('views/https.php'); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>HACK13 Site</title>
<link rel="stylesheet" type="text/css" href="/assets/bootstrap.min.css"/>
</head>
<body>
<?php include('views/navbar.php'); ?>
<main role="main">
<div class="container">
<h1 class="mt-5">Projects</h1>
<h3>Services:</h3>
<div class="container">
<div class="row">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">About</th>
<th scope="col">Tags</th>
<th scope="col">Link(s)</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Tier 2 Virginia OpenNIC Resolvers</th>
<td>Two OpenNIC Public Tier 2 resolvers with DoH(DNS over HTTPS) support for anyone to use.</td>
<td><span class="badge badge-info">DNS</span> <span class="badge badge-info">Service</span></td>
<td>
<a href="/projects/opennic" class="btn btn-secondary">Learn More</a>
</td>
</tr>
<tr>
<th scope="row">CyberWrld Grid</th>
<td>Free public grid used for testing and development of software and tools.</td>
<td><span class="badge badge-info">OpenSimulator</span> <span class="badge badge-info">Service</span></td>
<td>
<a href="/projects/cyberwrld" class="btn btn-secondary">Learn More</a>
</td>
</tr>
<tr>
<th scope="row">Zadaroo</th>
<td>Free public OpenSimulator resources licensed under CC0.</td>
<td><span class="badge badge-info">OpenSimulator</span> <span class="badge badge-info">Service</span></td>
<td>
<a href="https://zadaroo.com" target="_blank" class="btn btn-secondary">Visit</a>
</td>
</tr>
</tbody>
</table>
</div>
</div>
<h3>Code:</h3>
<div class="container">
<div class="row">
<table class="table table-hover">
<thead>
<tr>
<th scope="col">Name</th>
<th scope="col">About</th>
<th scope="col">Tags</th>
<th scope="col">Link(s)</th>
<th scope="col">Status</th>
</tr>
</thead>
<tbody>
<tr>
<th scope="row">Ansible OpenNIC Setup</th>
<td>Simple Ansible Playbook to install and configure an OpenNIC Tier 2 node on multiple distributions of Linux.</td>
<td><span class="badge badge-info">Ansible Playbook</span> <span class="badge badge-info">Automation</span></td>
<td>
<a href="https://github.com/hack13/ansible-opennic-setup" target="_blank" class="btn btn-secondary"><i class="fab fa-github"></i></a>
</td>
<td><h4><span class="badge badge-primary">Maintained</span></h4></td>
</tr>
<tr>
<th scope="row">Ansible OpenSim Grid</th>
<td>Grid Setup for OpenSim using Ansible on CentOS 7</td>
<td><span class="badge badge-info">Ansible Playbook</span> <span class="badge badge-info">Automation</span></td>
<td>
<a href="https://github.com/hack13/ansible-opensim-grid" target="_blank" class="btn btn-secondary"><i class="fab fa-github"></i></a>
</td>
<td><h4><span class="badge badge-primary">Maintained</span></h4></td>
</tr>
<tr>
<th scope="row">OpenSim Libre Panel</th>
<td>OpenSimulator Standalone/Grid Management System</td>
<td><span class="badge badge-info">PHP</span> <span class="badge badge-info">SQL</span></td>
<td>
<a href="https://github.com/hack13/opensim-libre-panel" target="_blank" class="btn btn-secondary"><i class="fab fa-github"></i></a>
</td>
<td><h4><span class="badge badge-info">Work In Progress</span></h4></td>
</tr>
<tr>
<th scope="row">Zadaroo</th>
<td>A static redesign of the Zadaroo site, to provide a more streamlined site.</td>
<td><span class="badge badge-info">HTML</span> <span class="badge badge-info">JavaScript</span></td>
<td>
<a href="https://github.com/hack13/Zadaroo" target="_blank" class="btn btn-secondary"><i class="fab fa-github"></i></a>
</td>
<td><h4><span class="badge badge-info">Work In Progress</span></h4></td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
</main>
</body>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted"><i class="far fa-copyright"></i>HACK13<i>(Timothy Rogers)</i></span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
</footer>
</html>

View file

@ -1,75 +0,0 @@
<?php include('../views/https.php'); ?>
<!DOCTYPE html>
<html lang="en">
<head>
<title>HACK13 Site</title>
<link rel="stylesheet" type="text/css" href="/assets/bootstrap.min.css"/>
</head>
<body>
<?php include('../views/navbar.php'); ?>
<main role="main">
<div class="container">
<h1 class="mt-5">Tier 2 Virginia OpenNIC Resolvers</h1>
<hr>
<div class="container">
<div class="row">
<h3>What is OpenNIC?</h3>
<p>OpenNIC is an open and democratic alternative to DNS root. The project is ran by hobbyists who work together to operate a decentralized DNS resolver to ensure DNS Neutrality. OpenNIC also provides its own set to TLDs that operators are all free registration. OpenNIC is also peered with other networks such as Emercoin, New Nations, and FurNIC. More information about the project as a whole can be found out from the official site.</p>
<p><a href="https://opennic.org/" class="btn btn-primary" target="_blank">Learn More</a></p>
</div>
<div class="row">
<h3>DNS over HTTPS Service</h3>
<p>I provide a free DoH service that load balances between my two OpenNIC Recursive Resolvers. You can add it to your Firefox browser by going into your preferences, or to any other DoH capable system/browser.</p>
<p><strong>DoH Address:</strong> https://doh.hack13.me/dns-query </p>
<h4>Instructions to Add to DoH to Firefox</h4>
<ol>
<li>Open your preferences by click on the hamburger menu on the top right, and select Preferences. <br/> <img src="/assets/firefox-1.jpg" /></li>
<li>Scroll down to the bottom second labled "Network Settings" and click "Settings". <br/> <img src="/assets/firefox-2.jpg" /></li>
<li>Check the box at the bottom labeled "Enable DNS over HTTPS" and select "Custom" and enter "https://doh.hack13.me/dns-query" and click OK. <br/> <img src="/assets/firefox-3.jpg" /></li>
</ol>
</div>
<div class="row">
<h3>DNS Servers</h3>
</div>
<div class="row">
<div class="col-6">
<div class="card">
<h5 class="card-header">OpenNIC Virginia USA NS1</h5>
<div class="card-body">
<strong>Hostname:</strong> ns1.va.us.dns.opennic.glue <br/>
<strong>A Record:</strong> 147.135.113.37 <br/>
<strong>AAAA Record:</strong> 2604:2dc0:101:200::3de <br/>
<strong>Location:</strong> Vint Hill, Virginia <br/>
<strong>Logging Policy:</strong> No logs kept, routed to /dev/null <br/>
<a href="https://servers.opennicproject.org/edit.php?srv=ns1.va.us.dns.opennic.glue" class="btn btn-primary" target="_blank">OpenNIC Servers Report</a>
</div>
</div>
</div>
<div class="col-6">
<div class="card">
<h5 class="card-header">OpenNIC Virginia USA NS2</h5>
<div class="card-body">
<strong>Hostname:</strong> ns2.va.us.dns.opennic.glue <br/>
<strong>A Record:</strong> 147.135.115.88 <br/>
<strong>AAAA Record:</strong> 2604:2dc0:101:200::465 <br/>
<strong>Location:</strong> Vint Hill, Virginia <br/>
<strong>Logging Policy:</strong> No logs kept, routed to /dev/null <br/>
<a href="https://servers.opennicproject.org/edit.php?srv=ns2.va.us.dns.opennic.glue" class="btn btn-primary" target="_blank">OpenNIC Servers Report</a>
</div>
</div>
</div>
</div>
</div>
</div>
</main>
</body>
<footer class="footer mt-auto py-3">
<div class="container">
<span class="text-muted"><i class="far fa-copyright"></i>HACK13<i>(Timothy Rogers)</i></span>
</div>
<script src="https://code.jquery.com/jquery-3.3.1.slim.min.js" integrity="sha384-q8i/X+965DzO0rT7abK41JStQIAqVgRVzpbzo5smXKp4YfRvH+8abtTE1Pi6jizo" crossorigin="anonymous"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/popper.js/1.14.7/umd/popper.min.js" integrity="sha384-UO2eT0CpHqdSJQ6hJty5KVphtPhzWj9WO1clHTMGa3JDZwrnQq4sF86dIHNDz0W1" crossorigin="anonymous"></script>
<script src="https://stackpath.bootstrapcdn.com/bootstrap/4.3.1/js/bootstrap.min.js" integrity="sha384-JjSmVgyd0p3pXB1rRibZUAYoIIy6OrQ6VrjIEaFf/nJGzIxFDsf4x0xIM+B07jRM" crossorigin="anonymous"></script>
<script defer src="https://use.fontawesome.com/releases/v5.8.1/js/all.js" integrity="sha384-g5uSoOSBd7KkhAMlnQILrecXvzst9TdC09/VM+pjDTCM+1il8RHz5fKANTFFb+gQ" crossorigin="anonymous"></script>
</footer>
</html>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 426 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 554 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 53 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 21 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 270 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 565 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 192 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 588 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 525 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 686 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 71 KiB

52
scripts/new-post.js Normal file
View file

@ -0,0 +1,52 @@
/* This is a script to create a new post markdown file with front-matter */
import fs from "fs"
import path from "path"
function getDate() {
const today = new Date()
const year = today.getFullYear()
const month = String(today.getMonth() + 1).padStart(2, "0")
const day = String(today.getDate()).padStart(2, "0")
return `${year}-${month}-${day}`
}
const args = process.argv.slice(2)
if (args.length === 0) {
console.error(`Error: No filename argument provided
Usage: npm run new-post -- <filename>`)
process.exit(1) // Terminate the script and return error code 1
}
let fileName = args[0]
// Add .md extension if not present
const fileExtensionRegex = /\.(md|mdx)$/i
if (!fileExtensionRegex.test(fileName)) {
fileName += ".md"
}
const targetDir = "./src/content/posts/"
const fullPath = path.join(targetDir, fileName)
if (fs.existsSync(fullPath)) {
console.error(`ErrorFile ${fullPath} already exists `)
process.exit(1)
}
const content = `---
title: ${args[0]}
published: ${getDate()}
description: ''
image: ''
tags: []
category: ''
draft: false
---
`
fs.writeFileSync(path.join(targetDir, fileName), content)
console.log(`Post ${fullPath} created`)

Binary file not shown.

After

Width:  |  Height:  |  Size: 406 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 877 KiB

BIN
src/assets/images/hack-avatar.png Executable file

Binary file not shown.

After

Width:  |  Height:  |  Size: 558 KiB

View file

@ -0,0 +1,128 @@
---
import {getSortedPosts} from "../utils/content-utils";
import {getPostUrlBySlug} from "../utils/url-utils";
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {UNCATEGORIZED} from "@constants/constants";
interface Props {
keyword: string;
tags: string[];
categories: string[];
}
const { keyword, tags, categories} = Astro.props;
let posts = await getSortedPosts()
if (Array.isArray(tags) && tags.length > 0) {
posts = posts.filter(post =>
Array.isArray(post.data.tags) && post.data.tags.some(tag => tags.includes(tag))
);
}
if (Array.isArray(categories) && categories.length > 0) {
posts = posts.filter(post =>
(post.data.category && categories.includes(post.data.category)) ||
(!post.data.category && categories.includes(UNCATEGORIZED))
);
}
const groups = function () {
const groupedPosts = posts.reduce((grouped, post) => {
const year = post.data.published.getFullYear()
if (!grouped[year]) {
grouped[year] = []
}
grouped[year].push(post)
return grouped
}, {})
// convert the object to an array
const groupedPostsArray = Object.keys(groupedPosts).map(key => ({
year: key,
posts: groupedPosts[key]
}))
// sort years by latest first
groupedPostsArray.sort((a, b) => b.year - a.year)
return groupedPostsArray;
}();
function formatDate(date: Date) {
const month = (date.getMonth() + 1).toString().padStart(2, '0');
const day = date.getDate().toString().padStart(2, '0');
return `${month}-${day}`;
}
function formatTag(tag: string[]) {
return tag.map(t => `#${t}`).join(' ');
}
---
<div class="card-base px-8 py-6">
{
groups.map(group => (
<div>
<div class="flex flex-row w-full items-center h-[3.75rem]">
<div class="w-[15%] md:w-[10%] transition text-2xl font-bold text-right text-75">{group.year}</div>
<div class="w-[15%] md:w-[10%]">
<div class="h-3 w-3 bg-none rounded-full outline outline-[var(--primary)] mx-auto -outline-offset-[2px] z-50 outline-3"></div>
</div>
<div class="w-[70%] md:w-[80%] transition text-left text-50">{group.posts.length} {i18n(I18nKey.postsCount)}</div>
</div>
{group.posts.map(post => (
<a href={getPostUrlBySlug(post.slug)}
aria-label={post.data.title}
class="group btn-plain block h-10 w-full rounded-lg hover:text-[initial]"
>
<div class="flex flex-row justify-start items-center h-full">
<!-- date -->
<div class="w-[15%] md:w-[10%] transition text-sm text-right text-50">
{formatDate(post.data.published)}
</div>
<!-- dot and line -->
<div class="w-[15%] md:w-[10%] relative dash-line h-full flex items-center">
<div class="transition-all mx-auto w-1 h-1 rounded group-hover:h-5
bg-[oklch(0.5_0.05_var(--hue))] group-hover:bg-[var(--primary)]
outline outline-4 z-50
outline-[var(--card-bg)]
group-hover:outline-[var(--btn-plain-bg-hover)]
group-active:outline-[var(--btn-plain-bg-active)]
"
></div>
</div>
<!-- post title -->
<div class="w-[70%] md:max-w-[65%] md:w-[65%] text-left font-bold
group-hover:translate-x-1 transition-all group-hover:text-[var(--primary)]
text-75 pr-8 whitespace-nowrap overflow-ellipsis overflow-hidden"
>
{post.data.title}
</div>
<!-- tag list -->
<div class="hidden md:block md:w-[15%] text-left text-sm transition
whitespace-nowrap overflow-ellipsis overflow-hidden
text-30"
>{formatTag(post.data.tags)}</div>
</div>
</a>
))}
</div>
))
}
</div>
<style>
@tailwind components;
@tailwind utilities;
@layer components {
.dash-line {
}
.dash-line::before {
content: "";
@apply w-[10%] h-full absolute -top-1/2 left-[calc(50%_-_1px)] -top-[50%] border-l-[2px]
border-dashed pointer-events-none border-[var(--line-color)] transition
}
}
</style>

View file

@ -0,0 +1,8 @@
---
import {siteConfig} from "../config";
---
<div id="config-carrier" data-hue={siteConfig.themeColor.hue}>
</div>

13
src/components/Footer.astro Executable file
View file

@ -0,0 +1,13 @@
---
import { profileConfig } from '../config'
---
<div class="card-base max-w-[var(--page-width)] min-h-[4.5rem] rounded-b-none mx-auto flex items-center px-6">
<div class="transition text-50 text-sm">
© 2024 {profileConfig.name}. All Rights Reserved.
<br>
Powered by <a class="link text-[var(--primary)] font-medium" target="_blank" href="https://github.com/saicaca/fuwari">Fuwari</a> & <a class="link text-[var(--primary)] font-medium" target="_blank" href="https://astro.build">Astro</a>
</div>
</div>
<script defer src="https://stats.hack13.cloud/script.js" data-website-id="d1001cd6-aa86-4ae0-82ca-8562331a4c96"></script>

View file

@ -0,0 +1,298 @@
---
---
<div>
<slot/>
</div>
<style is:global lang="stylus">
/* utils */
white(a)
rgba(255, 255, 255, a)
black(a)
rgba(0, 0, 0, a)
isOklch(c)
return substr(c, 0, 5) == 'oklch'
oklch_fallback(c)
str = '' + c // convert color value to string
if isOklch(str)
return convert(oklchToHex(str))
return c
color_set(colors)
@supports (color: oklch(0 0 0))
:root
for key, value in colors
{key}: value[0]
:root.dark
for key, value in colors
if length(value) > 1
{key}: value[1]
/* provide fallback color for oklch */
@supports not (color: oklch(0 0 0))
:root
for key, value in colors
{key}: oklch_fallback(value[0])
:root.dark
for key, value in colors
if length(value) > 1
{key}: oklch_fallback(value[1])
rainbow-light = linear-gradient(to right, oklch(0.80 0.10 0), oklch(0.80 0.10 30), oklch(0.80 0.10 60), oklch(0.80 0.10 90), oklch(0.80 0.10 120), oklch(0.80 0.10 150), oklch(0.80 0.10 180), oklch(0.80 0.10 210), oklch(0.80 0.10 240), oklch(0.80 0.10 270), oklch(0.80 0.10 300), oklch(0.80 0.10 330), oklch(0.80 0.10 360))
rainbow-dark = linear-gradient(to right, oklch(0.70 0.10 0), oklch(0.70 0.10 30), oklch(0.70 0.10 60), oklch(0.70 0.10 90), oklch(0.70 0.10 120), oklch(0.70 0.10 150), oklch(0.70 0.10 180), oklch(0.70 0.10 210), oklch(0.70 0.10 240), oklch(0.70 0.10 270), oklch(0.70 0.10 300), oklch(0.70 0.10 330), oklch(0.70 0.10 360))
:root
--radius-large 1rem
--banner-height-home 60vh
--banner-height 40vh
--content-delay 150ms
color_set({
--primary: oklch(0.70 0.14 var(--hue)) oklch(0.75 0.14 var(--hue))
--page-bg: oklch(0.95 0.01 var(--hue)) oklch(0.16 0.014 var(--hue))
--card-bg: white oklch(0.23 0.015 var(--hue))
--btn-content: oklch(0.55 0.12 var(--hue)) oklch(0.75 0.1 var(--hue))
--btn-regular-bg: oklch(0.95 0.025 var(--hue)) oklch(0.33 0.035 var(--hue))
--btn-regular-bg-hover: oklch(0.9 0.05 var(--hue)) oklch(0.38 0.04 var(--hue))
--btn-regular-bg-active: oklch(0.85 0.08 var(--hue)) oklch(0.43 0.045 var(--hue))
--btn-plain-bg-hover: oklch(0.95 0.025 var(--hue)) oklch(0.30 0.035 var(--hue))
--btn-plain-bg-active: oklch(0.98 0.01 var(--hue)) oklch(0.27 0.025 var(--hue))
--btn-card-bg-hover: oklch(0.98 0.005 var(--hue)) oklch(0.3 0.03 var(--hue))
--btn-card-bg-active: oklch(0.9 0.03 var(--hue)) oklch(0.35 0.035 var(--hue))
--enter-btn-bg: var(--btn-regular-bg)
--enter-btn-bg-hover: var(--btn-regular-bg-hover)
--enter-btn-bg-active: var(--btn-regular-bg-active)
--deep-text: oklch(0.25 0.02 var(--hue))
--title-active: oklch(0.6 0.1 var(--hue))
--line-divider: black(0.08) white(0.08)
--line-color: black(0.1) white(0.1)
--meta-divider: black(0.2) white(0.2)
--inline-code-bg: var(--btn-regular-bg)
--inline-code-color: var(--btn-content)
--selection-bg: oklch(0.90 0.05 var(--hue)) oklch(0.40 0.08 var(--hue))
--codeblock-selection: oklch(0.40 0.08 var(--hue))
--codeblock-bg: oklch(0.2 0.015 var(--hue)) oklch(0.17 0.015 var(--hue))
--license-block-bg: black(0.03) var(--codeblock-bg)
--link-underline: oklch(0.93 0.04 var(--hue)) oklch(0.40 0.08 var(--hue))
--link-hover: oklch(0.95 0.025 var(--hue)) oklch(0.40 0.08 var(--hue))
--link-active: oklch(0.90 0.05 var(--hue)) oklch(0.35 0.07 var(--hue))
--float-panel-bg: white oklch(0.19 0.015 var(--hue))
--scrollbar-bg-light: black(0.4)
--scrollbar-bg-hover-light: black(0.5)
--scrollbar-bg-active-light: black(0.6)
--scrollbar-bg-dark: white(0.4)
--scrollbar-bg-hover-dark: white(0.5)
--scrollbar-bg-active-dark: white(0.6)
--scrollbar-bg: var(--scrollbar-bg-light) var(--scrollbar-bg-dark)
--scrollbar-bg-hover: var(--scrollbar-bg-hover-light) var(--scrollbar-bg-hover-dark)
--scrollbar-bg-active: var(--scrollbar-bg-active-light) var(--scrollbar-bg-active-dark)
--color-selection-bar: rainbow-light rainbow-dark
--display-light-icon: 1 0
--display-dark-icon: 0 1
--admonitions-color-tip: oklch(0.7 0.14 180) oklch(0.75 0.14 180)
--admonitions-color-note: oklch(0.7 0.14 250) oklch(0.75 0.14 250)
--admonitions-color-important: oklch(0.7 0.14 310) oklch(0.75 0.14 310)
--admonitions-color-warning: oklch(0.7 0.14 60) oklch(0.75 0.14 60)
--admonitions-color-caution: oklch(0.6 0.2 25) oklch(0.65 0.2 25)
})
/* some global styles */
::selection
background-color: var(--selection-bg)
.scrollbar-base.os-scrollbar
transition: width 0.15s ease-in-out, height 0.15s ease-in-out, opacity 0.15s, visibility 0.15s, top 0.15s, right 0.15s, bottom 0.15s, left 0.15s;
pointer-events: unset;
&.os-scrollbar-horizontal
padding-top: 4px;
padding-bottom: 4px;
height: 16px;
.os-scrollbar-track .os-scrollbar-handle
height: 4px;
border-radius: 4px;
&:hover
.os-scrollbar-track .os-scrollbar-handle
height: 8px;
&.px-2
padding-left: 8px;
padding-right: 8px;
&.os-scrollbar-vertical
padding-left: 4px;
padding-right: 4px;
width: 16px;
.os-scrollbar-track .os-scrollbar-handle
width: 4px;
border-radius: 4px;
&:hover
.os-scrollbar-track .os-scrollbar-handle
width: 8px;
&.py-1
padding-top: 4px;
padding-bottom: 4px;
.scrollbar-auto
&.os-scrollbar
--os-handle-bg: var(--scrollbar-bg);
--os-handle-bg-hover: var(--scrollbar-bg-hover);
--os-handle-bg-active: var(--scrollbar-bg-active);
.scrollbar-dark
&.os-scrollbar
--os-handle-bg: var(--scrollbar-bg-dark);
--os-handle-bg-hover: var(--scrollbar-bg-hover-dark);
--os-handle-bg-active: var(--scrollbar-bg-active-dark);
.scrollbar-light
&.os-scrollbar
--os-handle-bg: var(--scrollbar-bg-light);
--os-handle-bg-hover: var(--scrollbar-bg-hover-light);
--os-handle-bg-active: var(--scrollbar-bg-active-light);
</style>
<style is:global lang="scss">
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.card-base {
@apply rounded-[var(--radius-large)] overflow-hidden bg-[var(--card-bg)] transition;
}
h1, h2, h3, h4, h5, h6, p, a, span, li, ul, ol, blockquote, code, pre, table, th, td, strong {
@apply transition;
}
.card-shadow {
@apply drop-shadow-[0_2px_4px_rgba(0,0,0,0.005)]
}
.expand-animation {
@apply relative before:ease-out before:transition active:bg-none hover:before:bg-[var(--btn-plain-bg-hover)] active:before:bg-[var(--btn-plain-bg-active)] z-0
before:absolute before:rounded-[inherit] before:inset-0 before:scale-[0.85] hover:before:scale-100 before:-z-10
}
.link {
@apply transition rounded-md p-1 -m-1 expand-animation;
}
.link-lg {
@apply transition rounded-md p-1.5 -m-1.5 expand-animation;
}
.float-panel {
@apply top-[5.25rem] rounded-[var(--radius-large)] overflow-hidden bg-[var(--float-panel-bg)] transition shadow-xl dark:shadow-none
}
.float-panel-closed {
@apply -translate-y-1 opacity-0 pointer-events-none
}
.search-panel mark {
@apply bg-transparent text-[var(--primary)]
}
.btn-card {
@apply transition flex items-center justify-center bg-[var(--card-bg)] hover:bg-[var(--btn-card-bg-hover)]
active:bg-[var(--btn-card-bg-active)]
}
.btn-card.disabled {
@apply pointer-events-none text-black/10 dark:text-white/10
}
.btn-plain {
@apply transition relative flex items-center justify-center bg-none
text-black/75 hover:text-[var(--primary)] dark:text-white/75 dark:hover:text-[var(--primary)];
&:not(.scale-animation) {
@apply hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)]
}
&.scale-animation {
@apply expand-animation;
&.current-theme-btn {
@apply before:scale-100 before:opacity-100 before:bg-[var(--btn-plain-bg-hover)] text-[var(--primary)]
}
}
}
.btn-regular {
@apply transition flex items-center justify-center bg-[var(--btn-regular-bg)] hover:bg-[var(--btn-regular-bg-hover)] active:bg-[var(--btn-regular-bg-active)]
text-[var(--btn-content)] dark:text-white/75
}
.link-underline {
@apply transition underline decoration-2 decoration-dashed decoration-[var(--link-underline)]
hover:decoration-[var(--link-hover)] active:decoration-[var(--link-active)] underline-offset-[0.25rem]
}
.text-90 {
@apply text-black/90 dark:text-white/90
}
.text-75 {
@apply text-black/75 dark:text-white/75
}
.text-50 {
@apply text-black/50 dark:text-white/50
}
.text-30 {
@apply text-black/30 dark:text-white/30
}
.text-25 {
@apply text-black/25 dark:text-white/25
}
html.is-changing .transition-fade {
@apply transition-all duration-200
}
html.is-animating .transition-fade {
@apply opacity-0 translate-y-4
}
}
@keyframes fade-in-up {
0% {
transform: translateY(2rem);
opacity: 0;
}
100% {
transform: translateY(0);
opacity: 1;
}
}
.onload-animation {
opacity: 0;
animation: 300ms fade-in-up;
animation-fill-mode: forwards;
}
#top-row {
animation-delay: 0ms
}
#sidebar {
animation-delay: 100ms
}
#content-wrapper {
animation-delay: var(--content-delay);
}
#footer {
animation-delay: 400ms;
}
</style>

View file

@ -0,0 +1,120 @@
<script lang="ts">
import type { LIGHT_DARK_MODE } from '@/types/config.ts'
import {
AUTO_MODE,
DARK_MODE,
LIGHT_MODE,
} from '@constants/constants.ts'
import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
import Icon from '@iconify/svelte'
import {
applyThemeToDocument,
getStoredTheme,
setTheme,
} from '@utils/setting-utils.ts'
import { onMount } from 'svelte'
const seq: LIGHT_DARK_MODE[] = [
LIGHT_MODE,
DARK_MODE,
AUTO_MODE,
]
let mode: LIGHT_DARK_MODE = AUTO_MODE
onMount(() => {
mode = getStoredTheme()
const darkModePreference = window.matchMedia(
'(prefers-color-scheme: dark)',
)
const changeThemeWhenSchemeChanged: Parameters<
typeof darkModePreference.addEventListener<'change'>
>[1] = e => {
applyThemeToDocument(mode)
}
darkModePreference.addEventListener(
'change',
changeThemeWhenSchemeChanged,
)
return () => {
darkModePreference.removeEventListener(
'change',
changeThemeWhenSchemeChanged,
)
}
})
function switchScheme(newMode: LIGHT_DARK_MODE) {
mode = newMode
setTheme(newMode)
}
function toggleScheme() {
let i = 0
for (; i < seq.length; i++) {
if (seq[i] === mode) {
break
}
}
switchScheme(seq[(i + 1) % seq.length])
}
function showPanel() {
const panel = document.querySelector('#light-dark-panel')
panel.classList.remove('float-panel-closed')
}
function hidePanel() {
const panel = document.querySelector('#light-dark-panel')
panel.classList.add('float-panel-closed')
}
</script>
<!-- z-50 make the panel higher than other float panels -->
<div class="relative z-50" role="menu" tabindex="-1" on:mouseleave={hidePanel}>
<button aria-label="Light/Dark Mode" role="menuitem" class="relative btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="scheme-switch" on:click={toggleScheme} on:mouseenter={showPanel}>
<div class="absolute" class:opacity-0={mode !== LIGHT_MODE}>
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem]"></Icon>
</div>
<div class="absolute" class:opacity-0={mode !== DARK_MODE}>
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem]"></Icon>
</div>
<div class="absolute" class:opacity-0={mode !== AUTO_MODE}>
<Icon icon="material-symbols:radio-button-partial-outline" class="text-[1.25rem]"></Icon>
</div>
</button>
<div id="light-dark-panel" class="hidden lg:block absolute transition float-panel-closed top-11 -right-2 pt-5" >
<div class="card-base float-panel p-2">
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95 mb-0.5"
class:current-theme-btn={mode === LIGHT_MODE}
on:click={() => switchScheme(LIGHT_MODE)}
>
<Icon icon="material-symbols:wb-sunny-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
{i18n(I18nKey.lightMode)}
</button>
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95 mb-0.5"
class:current-theme-btn={mode === DARK_MODE}
on:click={() => switchScheme(DARK_MODE)}
>
<Icon icon="material-symbols:dark-mode-outline-rounded" class="text-[1.25rem] mr-3"></Icon>
{i18n(I18nKey.darkMode)}
</button>
<button class="flex transition whitespace-nowrap items-center justify-start w-full btn-plain scale-animation rounded-lg h-9 px-3 font-medium active:scale-95"
class:current-theme-btn={mode === AUTO_MODE}
on:click={() => switchScheme(AUTO_MODE)}
>
<Icon icon="material-symbols:radio-button-partial-outline" class="text-[1.25rem] mr-3"></Icon>
{i18n(I18nKey.systemMode)}
</button>
</div>
</div>
</div>
<style lang="css">
.current-setting {
background: var(--btn-plain-bg-hover);
color: var(--primary);
}
</style>

120
src/components/Navbar.astro Normal file
View file

@ -0,0 +1,120 @@
---
import { Icon } from 'astro-icon/components';
import DisplaySettings from "./widget/DisplaySettings.svelte";
import {LinkPreset, NavBarLink} from "../types/config";
import {navBarConfig, siteConfig} from "../config";
import NavMenuPanel from "./widget/NavMenuPanel.astro";
import Search from "./Search.svelte";
import {LinkPresets} from "../constants/link-presets";
import LightDarkSwitch from "./LightDarkSwitch.svelte";
import {url} from "../utils/url-utils";
const className = Astro.props.class;
let links: NavBarLink[] = navBarConfig.links.map((item: NavBarLink | LinkPreset): NavBarLink => {
if (typeof item === "number") {
return LinkPresets[item]
}
return item;
});
---
<div class:list={[
className,
"card-base sticky top-0 overflow-visible max-w-[var(--page-width)] h-[4.5rem] rounded-t-none mx-auto flex items-center justify-between px-4"]}>
<a href={url('/')} class="btn-plain scale-animation rounded-lg h-[3.25rem] px-5 font-bold active:scale-95">
<div class="flex flex-row text-[var(--primary)] items-center text-md">
<Icon name="material-symbols:home-outline-rounded" size={"1.75rem"} class="mb-1 mr-2" />
{siteConfig.title}
</div>
</a>
<div class="hidden md:flex">
{links.map((l) => {
return <a aria-label={l.name} href={l.external ? l.url : url(l.url)} target={l.external ? "_blank" : null}
class="btn-plain scale-animation rounded-lg h-11 font-bold px-5 active:scale-95"
>
<div class="flex items-center">
{l.name}
{l.external && <Icon size="14" name="fa6-solid:arrow-up-right-from-square" class="transition -translate-y-[1px] ml-1 text-black/[0.2] dark:text-white/[0.2]"></Icon>}
</div>
</a>;
})}
</div>
<div class="flex">
<!--<SearchPanel client:load>-->
<Search client:load>
<Icon slot="search-icon" name="material-symbols:search" size={"1.25rem"} class="absolute pointer-events-none ml-3 transition my-auto text-black/30 dark:text-white/30"></Icon>
<!--<Icon slot="arrow-icon" name="material-symbols:chevron-right-rounded" size={"1.25rem"} class="transition my-auto text-[var(&#45;&#45;primary)]"></Icon>-->
<Icon slot="arrow-icon" name="fa6-solid:chevron-right" size={"0.75rem"} class="transition translate-x-0.5 my-auto text-[var(--primary)]"></Icon>
<Icon slot="search-switch" name="material-symbols:search" size={"1.25rem"}></Icon>
</Search>
{!siteConfig.themeColor.fixed && (
<button aria-label="Display Settings" class="btn-plain scale-animation rounded-lg h-11 w-11 active:scale-90" id="display-settings-switch">
<Icon name="material-symbols:palette-outline" size={"1.25rem"}></Icon>
</button>
)}
<LightDarkSwitch client:load></LightDarkSwitch>
<button aria-label="Menu" name="Nav Menu" class="btn-plain scale-animation rounded-lg w-11 h-11 active:scale-90 md:hidden" id="nav-menu-switch">
<Icon name="material-symbols:menu-rounded" size={"1.25rem"}></Icon>
</button>
</div>
<NavMenuPanel links={links}></NavMenuPanel>
<DisplaySettings client:only="svelte">
<Icon slot="restore-icon" name="fa6-solid:arrow-rotate-left" size={"0.875rem"} class=""></Icon>
</DisplaySettings>
</div>
<style lang="stylus">
</style>
<script>
function switchTheme() {
if (localStorage.theme === 'dark') {
document.documentElement.classList.remove('dark');
localStorage.theme = 'light';
} else {
document.documentElement.classList.add('dark');
localStorage.theme = 'dark';
}
}
function loadButtonScript() {
let switchBtn = document.getElementById("scheme-switch");
switchBtn.addEventListener("click", function () {
switchTheme()
});
let settingBtn = document.getElementById("display-settings-switch");
if (settingBtn) {
settingBtn.addEventListener("click", function () {
let settingPanel = document.getElementById("display-setting");
settingPanel.classList.toggle("float-panel-closed");
});
}
let menuBtn = document.getElementById("nav-menu-switch");
menuBtn.addEventListener("click", function () {
let menuPanel = document.getElementById("nav-menu-panel");
menuPanel.classList.toggle("float-panel-closed");
});
}
loadButtonScript();
document.addEventListener('astro:after-swap', () => {
loadButtonScript();
}, { once: false });
</script>
{import.meta.env.PROD && <script is:inline define:vars={{scriptUrl: url('/pagefind/pagefind.js')}}>
async function loadPagefind() {
const pagefind = await import(scriptUrl)
await pagefind.options({
'excerptLength': 20
})
pagefind.init()
window.pagefind = pagefind
pagefind.search('') // speed up the first search
}
loadPagefind()
</script>}

View file

@ -0,0 +1,95 @@
---
import path from "path";
import PostMetadata from "./PostMeta.astro";
import ImageWrapper from "./misc/ImageWrapper.astro";
import { Icon } from 'astro-icon/components';
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {getDir} from "../utils/url-utils";
interface Props {
class: string;
entry: any;
title: string;
url: string;
published: Date;
tags: string[];
category: string;
image: string;
description: string;
words: number;
draft: boolean;
style: string;
}
const { entry, title, url, published, tags, category, image, description, words, style } = Astro.props;
const className = Astro.props.class;
const hasCover = image !== undefined && image !== null && image !== '';
const coverWidth = "28%";
const { remarkPluginFrontmatter } = await entry.render();
---
<div class:list={["card-base flex flex-col-reverse md:flex-col w-full rounded-[var(--radius-large)] overflow-hidden relative", className]} style={style}>
<div class:list={["pl-6 md:pl-9 pr-6 md:pr-2 pt-6 md:pt-7 pb-6 relative", {"w-full md:w-[calc(100%_-_52px_-_12px)]": !hasCover, "w-full md:w-[calc(100%_-_var(--coverWidth)_-_12px)]": hasCover}]}>
<a href={url}
class="transition group w-full block font-bold mb-3 text-3xl text-90
hover:text-[var(--primary)] dark:hover:text-[var(--primary)]
active:text-[var(--title-active)] dark:active:text-[var(--title-active)]
before:w-1 before:h-5 before:rounded-md before:bg-[var(--primary)]
before:absolute before:top-[35px] before:left-[18px] before:hidden md:before:block
">
{title}
<Icon class="inline text-[var(--primary)] md:hidden translate-y-0.5 absolute" name="material-symbols:chevron-right-rounded" size="2rem" ></Icon>
<Icon class="text-[var(--primary)] transition hidden md:inline absolute translate-y-0.5 opacity-0 group-hover:opacity-100 -translate-x-1 group-hover:translate-x-0" name="material-symbols:chevron-right-rounded" size="2rem" ></Icon>
</a>
<!-- metadata -->
<PostMetadata published={published} tags={tags} category={category} hideTagsForMobile={true} class="mb-4"></PostMetadata>
<!-- description -->
<div class="transition text-75 mb-3.5 pr-4">
{ description }
</div>
<!-- word count and read time -->
<div class="text-sm text-black/30 dark:text-white/30 flex gap-4 transition">
<div>{remarkPluginFrontmatter.words} {" " + i18n(I18nKey.wordsCount)}</div>
<div>|</div>
<div>{remarkPluginFrontmatter.minutes} {" " + i18n(I18nKey.minutesCount)}</div>
</div>
</div>
{hasCover && <a href={url} aria-label={title}
class:list={["group",
"max-h-[20vh] md:max-h-none mx-4 mt-4 -mb-2 md:mb-0 md:mx-0 md:mt-0",
"md:w-[var(--coverWidth)] relative md:absolute md:top-3 md:bottom-3 md:right-3 rounded-xl overflow-hidden active:scale-95"
]} >
<div class="absolute pointer-events-none z-10 w-full h-full group-hover:bg-black/30 group-active:bg-black/50 transition"></div>
<div class="absolute pointer-events-none z-20 w-full h-full flex items-center justify-center ">
<Icon name="material-symbols:chevron-right-rounded"
class="transition opacity-0 group-hover:opacity-100 scale-50 group-hover:scale-100 text-white text-5xl">
</Icon>
</div>
<ImageWrapper src={image} basePath={path.join("content/posts/", getDir(entry.id))} alt="Cover Image of the Post"
class="w-full h-full">
</ImageWrapper>
</a>}
{!hasCover &&
<a href={url} aria-label={title} class="hidden md:flex btn-regular w-[3.25rem]
absolute right-3 top-3 bottom-3 rounded-xl bg-[var(--enter-btn-bg)]
hover:bg-[var(--enter-btn-bg-hover)] active:bg-[var(--enter-btn-bg-active)] active:scale-95
">
<Icon name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)] text-4xl mx-auto">
</Icon>
</a>
}
</div>
<div class="transition border-t-[1px] border-dashed mx-6 border-black/10 dark:border-white/[0.15] last:border-t-0 md:hidden"></div>
<style lang="stylus" define:vars={{coverWidth}}>
</style>

View file

@ -0,0 +1,77 @@
---
import {formatDateToYYYYMMDD} from "../utils/date-utils";
import { Icon } from 'astro-icon/components';
import {i18n} from "../i18n/translation";
import I18nKey from "../i18n/i18nKey";
import {url} from "../utils/url-utils";
interface Props {
class: string;
published: Date;
tags: string[];
category: string;
hideTagsForMobile: boolean;
}
const {published, tags, category, hideTagsForMobile} = Astro.props;
const className = Astro.props.class;
---
<div class:list={["flex flex-wrap text-neutral-500 dark:text-neutral-400 items-center gap-4 gap-x-4 gap-y-2", className]}>
<!-- publish date -->
<div class="flex items-center">
<div class="meta-icon"
>
<Icon name="material-symbols:calendar-today-outline-rounded" class="text-xl"></Icon>
</div>
<span class="text-50 text-sm font-medium">{formatDateToYYYYMMDD(published)}</span>
</div>
<!-- categories -->
<div class="flex items-center">
<div class="meta-icon"
>
<Icon name="material-symbols:menu-rounded" class="text-xl"></Icon>
</div>
<div class="flex flex-row flex-nowrap items-center">
<a href={url(`/archive/category/${category || 'uncategorized'}/`)} aria-label=`View all posts in the ${category} category`
class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
{category || i18n(I18nKey.uncategorized)}
</a>
</div>
</div>
<!-- tags -->
<div class:list={["items-center", {"flex": !hideTagsForMobile, "hidden md:flex": hideTagsForMobile}]}>
<div class="meta-icon"
>
<Icon name="material-symbols:tag-rounded" class="text-xl"></Icon>
</div>
<div class="flex flex-row flex-nowrap items-center">
{(tags && tags.length > 0) && tags.map((tag, i) => (
<div class:list={[{"hidden": i == 0}, "mx-1.5 text-[var(--meta-divider)] text-sm"]}>/</div>
<a href={url(`/archive/tag/${tag}/`)} aria-label=`View all posts with the ${tag} tag`
class="link-lg transition text-50 text-sm font-medium
hover:text-[var(--primary)] dark:hover:text-[var(--primary)] whitespace-nowrap">
{tag}
</a>
))}
{!(tags && tags.length > 0) && <div class="transition text-50 text-sm font-medium">{i18n(I18nKey.noTags)}</div>}
</div>
</div>
</div>
<style>
@tailwind components;
@layer components {
.meta-icon {
@apply w-8 h-8 transition rounded-md flex items-center justify-center bg-[var(--btn-regular-bg)]
text-[var(--btn-content)] mr-2
}
.with-divider {
@apply before:content-['/'] before:ml-1.5 before:mr-1.5 before:text-[var(--meta-divider)] before:text-sm
before:font-medium before:first-of-type:hidden before:transition
}
}
</style>

View file

@ -0,0 +1,28 @@
---
import {getPostUrlBySlug} from "@utils/url-utils";
import PostCard from "./PostCard.astro";
const {page} = Astro.props;
let delay = 0
const interval = 50
---
<div class="transition flex flex-col rounded-[var(--radius-large)] bg-[var(--card-bg)] py-1 md:py-0 md:bg-transparent md:gap-4 mb-4">
{page.data.map((entry: { data: { draft: boolean; title: string; tags: string[]; category: string; published: Date; image: string; description: string; }; slug: string; }) => {
return (
<PostCard
entry={entry}
title={entry.data.title}
tags={entry.data.tags}
category={entry.data.category}
published={entry.data.published}
url={getPostUrlBySlug(entry.slug)}
image={entry.data.image}
description={entry.data.description}
draft={entry.data.draft}
class:list="onload-animation"
style={`animation-delay: calc(var(--content-delay) + ${delay++ * interval}ms);`}
></PostCard>
);
})}
</div>

View file

@ -0,0 +1,116 @@
<script lang="ts">
import { onMount } from 'svelte'
import {url} from "@utils/url-utils.ts"
import { i18n } from '@i18n/translation';
import I18nKey from '@i18n/i18nKey';
let keywordDesktop = ''
let keywordMobile = ''
let result = []
const fakeResult = [{
url: url('/'),
meta: {
title: 'This Is a Fake Search Result'
},
excerpt: 'Because the search cannot work in the <mark>dev</mark> environment.'
}, {
url: url('/'),
meta: {
title: 'If You Want to Test the Search'
},
excerpt: 'Try running <mark>npm build && npm preview</mark> instead.'
}]
let search = (keyword: string, isDesktop: boolean) => {}
onMount(() => {
search = async (keyword: string, isDesktop: boolean) => {
let panel = document.getElementById('search-panel')
if (!panel)
return
if (!keyword && isDesktop) {
panel.classList.add("float-panel-closed")
return
}
let arr = [];
if (import.meta.env.PROD) {
const ret = await pagefind.search(keyword)
for (const item of ret.results) {
arr.push(await item.data())
}
} else {
// Mock data for non-production environment
// arr = JSON.parse('[{"url":"/","content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","word_count":187,"filters":{},"meta":{"title":"This Is a Fake Search Result"},"anchors":[{"element":"h2","id":"front-matter-of-posts","text":"Front-matter of Posts","location":34},{"element":"h2","id":"where-to-place-the-post-files","text":"Where to Place the Post Files","location":151}],"weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"raw_content":"Simple Guides for Fuwari. Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers in the Astro Docs. Front-matter of Posts. --- title: My First Blog Post published: 2023-09-09 description: This is the first post of my new Astro blog. image: ./cover.jpg tags: [Foo, Bar] category: Front-end draft: false ---AttributeDescription title. The title of the post. published. The date the post was published. description. A short description of the post. Displayed on index page. image. The cover image path of the post. 1. Start with http:// or https://: Use web image 2. Start with /: For image in public dir 3. With none of the prefixes: Relative to the markdown file. tags. The tags of the post. category. The category of the post. draft. If this post is still a draft, which wont be displayed. Where to Place the Post Files. Your post files should be placed in src/content/posts/ directory. You can also create sub-directories to better organize your posts and assets. src/content/posts/ ├── post-1.md └── post-2/ ├── cover.png └── index.md.","raw_url":"/posts/guide/","excerpt":"Because the search cannot work in the <mark>dev</mark> environment.","sub_results":[{"title":"Simple Guides for Fuwari - Fuwari","url":"/posts/guide/","weighted_locations":[{"weight":10,"balanced_score":57600,"location":3}],"locations":[3],"excerpt":"Simple Guides for <mark>Fuwari.</mark> Cover image source: Source. This blog template is built with Astro. For the things that are not mentioned in this guide, you may find the answers"}]},{"url":"/","content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","word_count":25,"filters":{},"meta":{"title":"If You Want to Test the Search"},"anchors":[{"element":"h1","id":"about","text":"About","location":0},{"element":"h3","id":"sources-of-images-used-in-this-site","text":"Sources of images used in this site","location":8}],"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"raw_content":"About. This is the demo site for Fuwari. Sources of images used in this site. Unsplash. 星と少女 by Stella. Rabbit - v1.4 Showcase by Rabbit_YourMajesty.","raw_url":"/about/","excerpt":"Try running <mark>npm build && npm preview</mark> instead.","sub_results":[{"title":"About","url":"/about/#about","anchor":{"element":"h1","id":"about","text":"About","location":0},"weighted_locations":[{"weight":1,"balanced_score":576,"location":7}],"locations":[7],"excerpt":"About. This is the demo site for <mark>Fuwari.</mark>"}]}]')
arr = fakeResult
}
if (!arr.length && isDesktop) {
panel.classList.add("float-panel-closed")
return
}
if (isDesktop) {
panel.classList.remove("float-panel-closed")
}
result = arr
}
})
const togglePanel = () => {
let panel = document.getElementById('search-panel')
panel?.classList.toggle("float-panel-closed")
}
$: search(keywordDesktop, true)
$: search(keywordMobile, false)
</script>
<!-- search bar for desktop view -->
<div id="search-bar" class="hidden lg:flex transition-all items-center h-11 mr-2 rounded-lg
bg-black/[0.04] hover:bg-black/[0.06] focus-within:bg-black/[0.06]
dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10
">
<slot name="search-icon"></slot>
<input placeholder="{i18n(I18nKey.search)}" bind:value={keywordDesktop} on:focus={() => search(keywordDesktop, true)}
class="transition-all pl-10 text-sm bg-transparent outline-0
h-full w-40 active:w-60 focus:w-60 text-black/50 dark:text-white/50"
>
</div>
<!-- toggle btn for phone/tablet view -->
<button on:click={togglePanel} aria-label="Search Panel" id="search-switch"
class="btn-plain scale-animation lg:hidden rounded-lg w-11 h-11 active:scale-90">
<slot name="search-switch"></slot>
</button>
<!-- search panel -->
<div id="search-panel" class="float-panel float-panel-closed search-panel absolute md:w-[30rem]
top-20 left-4 md:left-[unset] right-4 shadow-2xl rounded-2xl p-2">
<!-- search bar inside panel for phone/tablet -->
<div id="search-bar-inside" class="flex relative lg:hidden transition-all items-center h-11 rounded-xl
bg-black/[0.04] hover:bg-black/[0.06] focus-within:bg-black/[0.06]
dark:bg-white/5 dark:hover:bg-white/10 dark:focus-within:bg-white/10
">
<slot name="search-icon"></slot>
<input placeholder="Search" bind:value={keywordMobile}
class="pl-10 absolute inset-0 text-sm bg-transparent outline-0
focus:w-60 text-black/50 dark:text-white/50"
>
</div>
<!-- search results -->
{#each result as item}
<a href={item.url}
class="transition first-of-type:mt-2 lg:first-of-type:mt-0 group block
rounded-xl text-lg px-3 py-2 hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)]">
<div class="transition text-90 inline-flex font-bold group-hover:text-[var(--primary)]">
{item.meta.title}<slot name="arrow-icon"></slot>
</div>
<div class="transition text-sm text-50">
{@html item.excerpt}
</div>
</a>
{/each}
</div>

View file

@ -0,0 +1,57 @@
---
import { Icon } from 'astro-icon/components';
---
<!-- There can't be a filter on parent element, or it will break `fixed` -->
<div class="back-to-top-wrapper hidden lg:block">
<div id="back-to-top-btn" class="back-to-top-btn hide flex items-center rounded-2xl overflow-hidden transition" onclick="backToTop()">
<button aria-label="Back to Top" class="btn-card h-[3.75rem] w-[3.75rem]">
<Icon name="material-symbols:keyboard-arrow-up-rounded" class="mx-auto"></Icon>
</button>
</div>
</div>
<style lang="stylus">
.back-to-top-wrapper
width: 3.75rem
height: 3.75rem
position: absolute
right: 0
top: 0
.back-to-top-btn
color: var(--primary)
font-size: 2.25rem
font-weight: bold
border: none
position: fixed
bottom: 15rem
opacity: 1
cursor: pointer
transform: translateX(5rem)
i
font-size: 1.75rem
&.hide
transform: translateX(5rem) scale(0.9)
opacity: 0
pointer-events: none
&:active
transform: translateX(5rem) scale(0.9)
</style>
<script is:raw>
function backToTop() {
window.scroll({ top: 0, behavior: 'smooth' });
}
function scrollFunction() {
let btn = document.getElementById('back-to-top-btn');
if (document.body.scrollTop > 600 || document.documentElement.scrollTop > 600) {
btn.classList.remove('hide')
} else {
btn.classList.add('hide')
}
}
window.onscroll = scrollFunction
</script>

View file

@ -0,0 +1,43 @@
---
interface Props {
badge?: string
url?: string
label?: string
}
const { badge, url, name } = Astro.props
---
<a href={url} aria-label={name}>
<button
class:list={`
w-full
h-10
rounded-lg
bg-none
hover:bg-[var(--btn-plain-bg-hover)]
active:bg-[var(--btn-plain-bg-active)]
transition-all
pl-2
hover:pl-3
text-neutral-700
hover:text-[var(--primary)]
dark:text-neutral-300
dark:hover:text-[var(--primary)]
`
}
>
<div class="flex items-center justify-between relative mr-2">
<div class="overflow-hidden text-left whitespace-nowrap overflow-ellipsis ">
<slot></slot>
</div>
{ badge !== undefined && badge !== null && badge !== '' &&
<div class="transition h-7 ml-4 min-w-[2rem] rounded-lg text-sm font-bold
text-[var(--btn-content)] dark:text-[var(--deep-text)]
bg-[oklch(0.95_0.025_var(--hue))] dark:bg-[var(--primary)]
flex items-center justify-center">
{ badge }
</div>
}
</div>
</button>
</a>

View file

@ -0,0 +1,13 @@
---
interface Props {
size?: string;
dot?: boolean;
href?: string;
label?: string;
}
const { size, dot, href, label }: Props = Astro.props;
---
<a href={href} aria-label={label} class="btn-regular h-8 text-sm px-3 rounded-lg">
{dot && <div class="h-1 w-1 bg-[var(--btn-content)] dark:bg-[var(--card-bg)] transition rounded-md mr-2"></div>}
<slot></slot>
</a>

View file

@ -0,0 +1,91 @@
---
import type { Page } from "astro";
import { Icon } from 'astro-icon/components';
import {url} from "../../utils/url-utils";
interface Props {
page: Page;
class?: string;
style?: string;
}
const {page, style} = Astro.props;
const HIDDEN = -1;
const className = Astro.props.class;
const ADJ_DIST = 2;
const VISIBLE = ADJ_DIST * 2 + 1;
// for test
let count = 1;
let l = page.currentPage, r = page.currentPage;
while (0 < l - 1 && r + 1 <= page.lastPage && count + 2 <= VISIBLE) {
count += 2;
l--;
r++;
}
while (0 < l - 1 && count < VISIBLE) {
count++;
l--;
}
while (r + 1 <= page.lastPage && count < VISIBLE) {
count++;
r++;
}
let pages: number[] = [];
if (l > 1)
pages.push(1);
if (l == 3)
pages.push(2);
if (l > 3)
pages.push(HIDDEN);
for (let i = l; i <= r; i++)
pages.push(i);
if (r < page.lastPage - 2)
pages.push(HIDDEN);
if (r == page.lastPage - 2)
pages.push(page.lastPage - 1);
if (r < page.lastPage)
pages.push(page.lastPage);
const getPageUrl = (p: number) => {
if (p == 1)
return '/';
return `/${p}/`;
}
---
<div class:list={[className, "flex flex-row gap-3 justify-center"]} style={style}>
<a href={url(page.url.prev)} aria-label={page.url.prev ? "Previous Page" : null}
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.prev == undefined}
]}
>
<Icon name="material-symbols:chevron-left-rounded" size="1.75rem"></Icon>
</a>
<div class="bg-[var(--card-bg)] flex flex-row rounded-lg items-center text-neutral-700 dark:text-neutral-300 font-bold">
{pages.map((p) => {
if (p == HIDDEN)
return <Icon name="material-symbols:more-horiz" class="mx-1"/>;
if (p == page.currentPage)
return <div class="h-11 w-11 rounded-lg bg-[var(--primary)] flex items-center justify-center
font-bold text-white dark:text-black/70"
>
{p}
</div>
return <a href={url(getPageUrl(p))} aria-label=`Page ${p}`
class="btn-card w-11 h-11 rounded-lg overflow-hidden active:scale-[0.85]"
>{p}</a>
})}
</div>
<a href={url(page.url.next)} aria-label={page.url.next ? "Next Page" : null}
class:list={["btn-card overflow-hidden rounded-lg text-[var(--primary)] w-11 h-11",
{"disabled": page.url.next == undefined}
]}
>
<Icon name="material-symbols:chevron-right-rounded" size="1.75rem"></Icon>
</a>
</div>

View file

@ -0,0 +1,37 @@
---
import path from "path";
interface Props {
id?: string
src: string;
class?: string;
alt?: string
position?: string;
basePath?: string
}
import { Image } from 'astro:assets';
import { url } from "../../utils/url-utils";
const {id, src, alt, position = 'center', basePath = '/'} = Astro.props;
const className = Astro.props.class;
const isLocal = !(src.startsWith('/') || src.startsWith('http') || src.startsWith('https') || src.startsWith('data:'));
const isPublic = src.startsWith('/');
// TODO temporary workaround for images dynamic import
// https://github.com/withastro/astro/issues/3373
let img;
if (isLocal) {
const files = import.meta.glob<ImageMetadata>("../../**", { import: 'default' });
let normalizedPath = path.normalize(path.join("../../", basePath, src)).replace(/\\/g, "/");
img = await (files[normalizedPath])();
}
const imageClass = 'w-full h-full object-cover';
const imageStyle = `object-position: ${position}`
---
<div class:list={[className, 'overflow-hidden relative']}>
<div class="transition absolute inset-0 dark:bg-black/10 bg-opacity-50 pointer-events-none"></div>
{isLocal && <Image src={img} alt={alt || ""} class={imageClass} style={imageStyle}/>}
{!isLocal && <img src={isPublic ? url(src) : src} alt={alt || ""} class={imageClass} style={imageStyle}/>}
</div>

View file

@ -0,0 +1,44 @@
---
import {formatDateToYYYYMMDD} from "../../utils/date-utils";
import { Icon } from 'astro-icon/components';
import {licenseConfig, profileConfig} from "../../config";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
interface Props {
title: string;
slug: string;
pubDate: Date;
class: string;
}
const { title, slug, pubDate } = Astro.props;
const className = Astro.props.class;
const profileConf = profileConfig;
const licenseConf = licenseConfig;
const postUrl = decodeURIComponent(Astro.url.toString());
---
<div class=`relative transition overflow-hidden bg-[var(--license-block-bg)] py-5 px-6 ${className}`>
<div class="transition font-bold text-black/75 dark:text-white/75">
{title}
</div>
<a href={postUrl} class="link text-[var(--primary)]">
{postUrl}
</a>
<div class="flex gap-6 mt-2">
<div>
<div class="transition text-black/30 dark:text-white/30 text-sm">{i18n(I18nKey.author)}</div>
<div class="transition text-black/75 dark:text-white/75 whitespace-nowrap">{profileConf.name}</div>
</div>
<div>
<div class="transition text-black/30 dark:text-white/30 text-sm">{i18n(I18nKey.publishedAt)}</div>
<div class="transition text-black/75 dark:text-white/75 whitespace-nowrap">{formatDateToYYYYMMDD(pubDate)}</div>
</div>
<div>
<div class="transition text-black/30 dark:text-white/30 text-sm">{i18n(I18nKey.license)}</div>
<a href={licenseConf.url} target="_blank" class="link text-[var(--primary)] whitespace-nowrap">{licenseConf.name}</a>
</div>
</div>
<Icon name="fa6-brands:creative-commons" class="transition absolute pointer-events-none right-6 top-1/2 -translate-y-1/2 text-black/5 dark:text-white/5" size="240"></Icon>
</div>

View file

@ -0,0 +1,491 @@
---
import '@fontsource-variable/jetbrains-mono';
import '@fontsource-variable/jetbrains-mono/wght-italic.css';
interface Props {
class: string;
}
const className = Astro.props.class;
---
<div data-pagefind-body class=`prose dark:prose-invert prose-base max-w-none custom-md ${className}`>
<!--<div class="prose dark:prose-invert max-w-none custom-md">-->
<!--<div class="max-w-none custom-md">-->
<slot/>
</div>
<script>
const observer = new MutationObserver(addPreCopyButton);
observer.observe(document.body, { childList: true, subtree: true });
document.addEventListener("DOMContentLoaded", addPreCopyButton);
function addPreCopyButton() {
observer.disconnect();
let codeBlocks = Array.from(document.querySelectorAll("pre"));
for (let codeBlock of codeBlocks) {
if (codeBlock.parentElement?.nodeName === "DIV" && codeBlock.parentElement?.classList.contains("code-block")) continue
let wrapper = document.createElement("div");
wrapper.className = "relative code-block";
let copyButton = document.createElement("button");
copyButton.className = "copy-btn btn-regular-dark absolute active:scale-90 h-8 w-8 top-2 right-2 opacity-75 text-sm p-1.5 rounded-lg transition-all ease-in-out";
codeBlock.setAttribute("tabindex", "0");
if (codeBlock.parentNode) {
codeBlock.parentNode.insertBefore(wrapper, codeBlock);
}
let copyIcon = `<svg class="copy-btn-icon copy-icon" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="M368.37-237.37q-34.48 0-58.74-24.26-24.26-24.26-24.26-58.74v-474.26q0-34.48 24.26-58.74 24.26-24.26 58.74-24.26h378.26q34.48 0 58.74 24.26 24.26 24.26 24.26 58.74v474.26q0 34.48-24.26 58.74-24.26 24.26-58.74 24.26H368.37Zm0-83h378.26v-474.26H368.37v474.26Zm-155 238q-34.48 0-58.74-24.26-24.26-24.26-24.26-58.74v-515.76q0-17.45 11.96-29.48 11.97-12.02 29.33-12.02t29.54 12.02q12.17 12.03 12.17 29.48v515.76h419.76q17.45 0 29.48 11.96 12.02 11.97 12.02 29.33t-12.02 29.54q-12.03 12.17-29.48 12.17H213.37Zm155-238v-474.26 474.26Z"/></svg>`
let successIcon = `<svg class="copy-btn-icon success-icon" xmlns="http://www.w3.org/2000/svg" height="20px" viewBox="0 -960 960 960" width="20px"><path d="m389-377.13 294.7-294.7q12.58-12.67 29.52-12.67 16.93 0 29.61 12.67 12.67 12.68 12.67 29.53 0 16.86-12.28 29.14L419.07-288.41q-12.59 12.67-29.52 12.67-16.94 0-29.62-12.67L217.41-430.93q-12.67-12.68-12.79-29.45-.12-16.77 12.55-29.45 12.68-12.67 29.62-12.67 16.93 0 29.28 12.67L389-377.13Z"/></svg>`
copyButton.innerHTML = `<div>${copyIcon} ${successIcon}</div>
`
wrapper.appendChild(codeBlock);
wrapper.appendChild(copyButton);
let timeout;
copyButton.addEventListener("click", async () => {
if (timeout) {
clearTimeout(timeout);
}
let text = codeBlock?.querySelector("code")?.innerText;
await navigator.clipboard.writeText(text);
copyButton.classList.add("success");
timeout = setTimeout(() => {
copyButton.classList.remove("success");
}, 1000);
});
}
observer.observe(document.body, { childList: true, subtree: true });
}
</script>
<!-- Styles for copy-code-button -->
<style lang="css" is:global>
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.btn-regular-dark {
@apply flex items-center justify-center
bg-[oklch(0.45_0.01_var(--hue))] hover:bg-[oklch(0.50_0.01_var(--hue))] active:bg-[oklch(0.55_0.01_var(--hue))]
dark:bg-[oklch(0.30_0.02_var(--hue))] dark:hover:bg-[oklch(0.35_0.03_var(--hue))] dark:active:bg-[oklch(0.40_0.03_var(--hue))]
}
.btn-regular-dark.success {
@apply bg-[oklch(0.75_0.14_var(--hue))] dark:bg-[oklch(0.75_0.14_var(--hue))]
}
.copy-btn-icon {
@apply absolute top-1/2 left-1/2 transition -translate-x-1/2 -translate-y-1/2
}
.copy-btn .copy-icon {
@apply opacity-100 fill-white dark:fill-white/75
}
.copy-btn.success .copy-icon {
@apply opacity-0 fill-[var(--deep-text)]
}
.copy-btn .success-icon {
@apply opacity-0
}
.copy-btn.success .success-icon {
@apply opacity-100
}
}
</style>
<style lang="stylus" is:global>
.custom-md
h1, h2, h3, h4, h5, h6
.anchor
margin: -0.125rem !important
margin-left: 0.2ch !important
padding: 0.125rem !important
user-select: none !important
opacity: 0 !important
text-decoration: none !important
transition: opacity 0.15s ease-in-out, background 0.15s ease-in-out !important
.anchor-icon
margin-left: 0.45ch !important
margin-right: 0.45ch !important
&:hover
.anchor
opacity: 1 !important
a:not(.no-styling)
position: relative
background: none
margin: -0.25rem
padding: 0.25rem
border-radius: 0.375rem
font-weight: 500
color: var(--primary)
text-decoration-line: underline
text-decoration-color: var(--link-underline)
text-decoration-thickness: 0.125rem
text-decoration-style: dashed
text-underline-offset: 0.25rem
/*&:after*/
/* content: ''*/
/* position: absolute*/
/* left: 2px*/
/* right: 2px*/
/* bottom: 4px*/
/* height: 6px*/
/* border-radius: 3px*/
/* background: var(--link-hover)*/
/* transition: background 0.15s ease-in-out;*/
/* z-index: -1;*/
&:hover
background: var(--link-hover)
text-decoration-color: var(--link-hover)
&:active
background: var(--link-active)
text-decoration-color: var(--link-active)
code
font-family: 'JetBrains Mono Variable', ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, Liberation Mono, Courier New, monospace
background: var(--inline-code-bg)
color: var(--inline-code-color)
padding: 0.125rem 0.25rem
border-radius: 0.25rem
overflow: hidden
counter-reset: line
&:before
content: none
&:after
content: none
span.line
&:before
content: counter(line)
counter-increment: line
direction: rtl
display: inline-block
margin-right: 1rem
width: 1rem
color: rgba(255, 255, 255, 0.25)
&:last-child:empty, &:last-child:has(> span:empty:only-child)
display: none
pre
background: var(--codeblock-bg) !important
border-radius: 0.75rem
padding-left: 1.25rem
padding-right: 1.25rem
code
color: unset
font-size: 0.875rem
padding: 0
background: none
::selection
background: var(--codeblock-selection)
span.br::selection
background: var(--codeblock-selection)
ul
li
&::marker
color: var(--primary)
ol
li
&::marker
color: var(--primary)
blockquote
font-style: normal
font-weight: inherit
border-left-color: rgba(0, 0, 0, 0)
position: relative;
&:before
content: ''
position: absolute
left: -0.25rem
display: block
transition: background 0.15s ease-in-out;
background: var(--btn-regular-bg)
height: 100%
width: 0.25rem
border-radius: 1rem
p
&:before
content: none
&:after
content: none
blockquote.admonition
.bdm-title
display: flex
align-items: center
margin-bottom: -.9rem
font-weight: bold
&:before
content: ' '
display: inline-block
font-size: inherit
overflow: visible
margin-right: .6rem
height: 1em
width: 1em
vertical-align: -.126em
mask-size: contain
mask-position: center
mask-repeat: no-repeat
transform: translateY(-0.0625rem)
&.bdm-tip
.bdm-title
color: var(--admonitions-color-tip)
&:before
background: var(--admonitions-color-tip)
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath d='M8 1.5c-2.363 0-4 1.69-4 3.75 0 .984.424 1.625.984 2.304l.214.253c.223.264.47.556.673.848.284.411.537.896.621 1.49a.75.75 0 0 1-1.484.211c-.04-.282-.163-.547-.37-.847a8.456 8.456 0 0 0-.542-.68c-.084-.1-.173-.205-.268-.32C3.201 7.75 2.5 6.766 2.5 5.25 2.5 2.31 4.863 0 8 0s5.5 2.31 5.5 5.25c0 1.516-.701 2.5-1.328 3.259-.095.115-.184.22-.268.319-.207.245-.383.453-.541.681-.208.3-.33.565-.37.847a.751.751 0 0 1-1.485-.212c.084-.593.337-1.078.621-1.489.203-.292.45-.584.673-.848.075-.088.147-.173.213-.253.561-.679.985-1.32.985-2.304 0-2.06-1.637-3.75-4-3.75ZM5.75 12h4.5a.75.75 0 0 1 0 1.5h-4.5a.75.75 0 0 1 0-1.5ZM6 15.25a.75.75 0 0 1 .75-.75h2.5a.75.75 0 0 1 0 1.5h-2.5a.75.75 0 0 1-.75-.75Z'%3E%3C/path%3E%3C/svg%3E")
&:before
background: var(--admonitions-color-tip)
&.bdm-note
.bdm-title
color: var(--admonitions-color-note)
&:before
background: var(--admonitions-color-note)
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath fill='var(--admonitions-color-tip)' d='M0 8a8 8 0 1 1 16 0A8 8 0 0 1 0 8Zm8-6.5a6.5 6.5 0 1 0 0 13 6.5 6.5 0 0 0 0-13ZM6.5 7.75A.75.75 0 0 1 7.25 7h1a.75.75 0 0 1 .75.75v2.75h.25a.75.75 0 0 1 0 1.5h-2a.75.75 0 0 1 0-1.5h.25v-2h-.25a.75.75 0 0 1-.75-.75ZM8 6a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'%3E%3C/path%3E%3C/svg%3E")
&:before
background: var(--admonitions-color-note)
&.bdm-important
.bdm-title
color: var(--admonitions-color-important)
&:before
background: var(--admonitions-color-important)
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath d='M0 1.75C0 .784.784 0 1.75 0h12.5C15.216 0 16 .784 16 1.75v9.5A1.75 1.75 0 0 1 14.25 13H8.06l-2.573 2.573A1.458 1.458 0 0 1 3 14.543V13H1.75A1.75 1.75 0 0 1 0 11.25Zm1.75-.25a.25.25 0 0 0-.25.25v9.5c0 .138.112.25.25.25h2a.75.75 0 0 1 .75.75v2.19l2.72-2.72a.749.749 0 0 1 .53-.22h6.5a.25.25 0 0 0 .25-.25v-9.5a.25.25 0 0 0-.25-.25Zm7 2.25v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 9a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z'%3E%3C/path%3E%3C/svg%3E")
&:before
background: var(--admonitions-color-important)
&.bdm-warning
.bdm-title
color: var(--admonitions-color-warning)
&:before
background: var(--admonitions-color-warning)
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath d='M6.457 1.047c.659-1.234 2.427-1.234 3.086 0l6.082 11.378A1.75 1.75 0 0 1 14.082 15H1.918a1.75 1.75 0 0 1-1.543-2.575Zm1.763.707a.25.25 0 0 0-.44 0L1.698 13.132a.25.25 0 0 0 .22.368h12.164a.25.25 0 0 0 .22-.368Zm.53 3.996v2.5a.75.75 0 0 1-1.5 0v-2.5a.75.75 0 0 1 1.5 0ZM9 11a1 1 0 1 1-2 0 1 1 0 0 1 2 0Z'%3E%3C/path%3E%3C/svg%3E")
&:before
background: var(--admonitions-color-warning)
&.bdm-caution
.bdm-title
color: var(--admonitions-color-caution)
&:before
background: var(--admonitions-color-caution)
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' version='1.1' width='16' height='16' aria-hidden='true'%3E%3Cpath d='M4.47.22A.749.749 0 0 1 5 0h6c.199 0 .389.079.53.22l4.25 4.25c.141.14.22.331.22.53v6a.749.749 0 0 1-.22.53l-4.25 4.25A.749.749 0 0 1 11 16H5a.749.749 0 0 1-.53-.22L.22 11.53A.749.749 0 0 1 0 11V5c0-.199.079-.389.22-.53Zm.84 1.28L1.5 5.31v5.38l3.81 3.81h5.38l3.81-3.81V5.31L10.69 1.5ZM8 4a.75.75 0 0 1 .75.75v3.5a.75.75 0 0 1-1.5 0v-3.5A.75.75 0 0 1 8 4Zm0 8a1 1 0 1 1 0-2 1 1 0 0 1 0 2Z'%3E%3C/path%3E%3C/svg%3E")
&:before
background: var(--admonitions-color-caution)
img
border-radius: 0.75rem
hr
border-color: var(--line-divider)
border-style: dashed
iframe
border-radius: 0.75rem
margin-left: auto
margin-right: auto
max-width: 100%
a.card-github
display: block
background: var(--license-block-bg)
position: relative
margin: 0.5rem 0
padding: 1.1rem 1.5rem 1.1rem 1.5rem
color: var(--tw-prose-body)
border-radius: var(--radius-large)
text-decoration-thickness: 0px
text-decoration-line: none
&:hover
background-color: var(--btn-regular-bg-hover)
.gc-titlebar
color: var(--btn-content)
.gc-stars, .gc-forks, .gc-license, .gc-description
color: var(--tw-prose-headings)
&:before
background-color: var(--tw-prose-headings)
&:active
scale: .98
background-color: var(--btn-regular-bg-active);
.gc-titlebar
display: flex
align-items: center
justify-content: space-between
margin-bottom: 0.5rem
color: var(--tw-prose-headings)
font-size: 1.25rem
font-weight: 500
.gc-titlebar-left
display: flex
flex-flow: row nowrap
gap: 0.5rem
.gc-repo
font-weight: bold
.gc-owner
font-weight: 300
position: relative
display: flex
flex-flow: row nowrap
gap: 0.5rem
align-items: center
.gc-avatar
display: block
overflow: hidden
width: 1.5rem
height: 1.5rem
margin-top: -0.1rem
background-color: var(--primary)
background-size: cover
border-radius: 50%
.gc-description
margin-bottom: 0.7rem
font-size: 1rem
font-weight: 300
line-height: 1.5rem
color: var(--tw-prose-body)
.gc-infobar
display: flex
flex-flow: row nowrap
gap: 1.5rem
color: var(--tw-prose-body)
width: fit-content
.gc-language
display: none
.gc-stars, .gc-forks, .gc-license, .github-logo
font-weight: 500
font-size: 0.875rem
opacity: 0.9;
&:before
content: ' '
display: inline-block
height: 1.3em
width: 1.3em
margin-right: .4rem
vertical-align: -.24em
font-size: inherit
background-color: var(--tw-prose-body)
overflow: visible
mask-size: contain
mask-position: center
mask-repeat: no-repeat
transition-property: background-color, background;
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)
transition-duration: 0.15s
.gc-stars
&:before
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16'%3E%3Cpath d='M8 .25a.75.75 0 0 1 .673.418l1.882 3.815 4.21.612a.75.75 0 0 1 .416 1.279l-3.046 2.97.719 4.192a.751.751 0 0 1-1.088.791L8 12.347l-3.766 1.98a.75.75 0 0 1-1.088-.79l.72-4.194L.818 6.374a.75.75 0 0 1 .416-1.28l4.21-.611L7.327.668A.75.75 0 0 1 8 .25Zm0 2.445L6.615 5.5a.75.75 0 0 1-.564.41l-3.097.45 2.24 2.184a.75.75 0 0 1 .216.664l-.528 3.084 2.769-1.456a.75.75 0 0 1 .698 0l2.77 1.456-.53-3.084a.75.75 0 0 1 .216-.664l2.24-2.183-3.096-.45a.75.75 0 0 1-.564-.41L8 2.694Z'%3E%3C/path%3E%3C/svg%3E")
.gc-license
&:before
margin-right: .5rem
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16'%3E%3Cpath d='M8.75.75V2h.985c.304 0 .603.08.867.231l1.29.736c.038.022.08.033.124.033h2.234a.75.75 0 0 1 0 1.5h-.427l2.111 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.006.005-.01.01-.045.04c-.21.176-.441.327-.686.45C14.556 10.78 13.88 11 13 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L12.178 4.5h-.162c-.305 0-.604-.079-.868-.231l-1.29-.736a.245.245 0 0 0-.124-.033H8.75V13h2.5a.75.75 0 0 1 0 1.5h-6.5a.75.75 0 0 1 0-1.5h2.5V3.5h-.984a.245.245 0 0 0-.124.033l-1.289.737c-.265.15-.564.23-.869.23h-.162l2.112 4.692a.75.75 0 0 1-.154.838l-.53-.53.529.531-.001.002-.002.002-.006.006-.016.015-.045.04c-.21.176-.441.327-.686.45C4.556 10.78 3.88 11 3 11a4.498 4.498 0 0 1-2.023-.454 3.544 3.544 0 0 1-.686-.45l-.045-.04-.016-.015-.006-.006-.004-.004v-.001a.75.75 0 0 1-.154-.838L2.178 4.5H1.75a.75.75 0 0 1 0-1.5h2.234a.249.249 0 0 0 .125-.033l1.288-.737c.265-.15.564-.23.869-.23h.984V.75a.75.75 0 0 1 1.5 0Zm2.945 8.477c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L13 6.327Zm-10 0c.285.135.718.273 1.305.273s1.02-.138 1.305-.273L3 6.327Z'%3E%3C/path%3E%3C/svg%3E")
.gc-forks
&:before
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' aria-hidden='true' height='16' viewBox='0 0 16 16' version='1.1' width='16'%3E%3Cpath d='M5 5.372v.878c0 .414.336.75.75.75h4.5a.75.75 0 0 0 .75-.75v-.878a2.25 2.25 0 1 1 1.5 0v.878a2.25 2.25 0 0 1-2.25 2.25h-1.5v2.128a2.251 2.251 0 1 1-1.5 0V8.5h-1.5A2.25 2.25 0 0 1 3.5 6.25v-.878a2.25 2.25 0 1 1 1.5 0ZM5 3.25a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Zm6.75.75a.75.75 0 1 0 0-1.5.75.75 0 0 0 0 1.5Zm-3 8.75a.75.75 0 1 0-1.5 0 .75.75 0 0 0 1.5 0Z'%3E%3C/path%3E%3C/svg%3E")
.github-logo
font-size: 1.25rem
&:before
background-color: var(--tw-prose-headings)
margin-right: 0
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='31' height='32' viewBox='0 0 496 512'%3E%3Cpath fill='%23a1f7cb' d='M165.9 397.4c0 2-2.3 3.6-5.2 3.6c-3.3.3-5.6-1.3-5.6-3.6c0-2 2.3-3.6 5.2-3.6c3-.3 5.6 1.3 5.6 3.6m-31.1-4.5c-.7 2 1.3 4.3 4.3 4.9c2.6 1 5.6 0 6.2-2s-1.3-4.3-4.3-5.2c-2.6-.7-5.5.3-6.2 2.3m44.2-1.7c-2.9.7-4.9 2.6-4.6 4.9c.3 2 2.9 3.3 5.9 2.6c2.9-.7 4.9-2.6 4.6-4.6c-.3-1.9-3-3.2-5.9-2.9M244.8 8C106.1 8 0 113.3 0 252c0 110.9 69.8 205.8 169.5 239.2c12.8 2.3 17.3-5.6 17.3-12.1c0-6.2-.3-40.4-.3-61.4c0 0-70 15-84.7-29.8c0 0-11.4-29.1-27.8-36.6c0 0-22.9-15.7 1.6-15.4c0 0 24.9 2 38.6 25.8c21.9 38.6 58.6 27.5 72.9 20.9c2.3-16 8.8-27.1 16-33.7c-55.9-6.2-112.3-14.3-112.3-110.5c0-27.5 7.6-41.3 23.6-58.9c-2.6-6.5-11.1-33.3 2.6-67.9c20.9-6.5 69 27 69 27c20-5.6 41.5-8.5 62.8-8.5s42.8 2.9 62.8 8.5c0 0 48.1-33.6 69-27c13.7 34.7 5.2 61.4 2.6 67.9c16 17.7 25.8 31.5 25.8 58.9c0 96.5-58.9 104.2-114.8 110.5c9.2 7.9 17 22.9 17 46.4c0 33.7-.3 75.4-.3 83.6c0 6.5 4.6 14.4 17.3 12.1C428.2 457.8 496 362.9 496 252C496 113.3 383.5 8 244.8 8M97.2 352.9c-1.3 1-1 3.3.7 5.2c1.6 1.6 3.9 2.3 5.2 1c1.3-1 1-3.3-.7-5.2c-1.6-1.6-3.9-2.3-5.2-1m-10.8-8.1c-.7 1.3.3 2.9 2.3 3.9c1.6 1 3.6.7 4.3-.7c.7-1.3-.3-2.9-2.3-3.9c-2-.6-3.6-.3-4.3.7m32.4 35.6c-1.6 1.3-1 4.3 1.3 6.2c2.3 2.3 5.2 2.6 6.5 1c1.3-1.3.7-4.3-1.3-6.2c-2.2-2.3-5.2-2.6-6.5-1m-11.4-14.7c-1.6 1-1.6 3.6 0 5.9c1.6 2.3 4.3 3.3 5.6 2.3c1.6-1.3 1.6-3.9 0-6.2c-1.4-2.3-4-3.3-5.6-2'/%3E%3C/svg%3E")
a.card-github.fetch-waiting
pointer-events: none
opacity: 0.7
transition: opacity 0.15s ease-in-out
.gc-description, .gc-infobar
background-color: var(--tw-prose-body)
color: transparent
opacity: 0.5;
border-radius: 0.5rem
animation: pulsate 2s infinite linear
user-select: none
&:before
background-color: transparent
.gc-avatar
display: none
.gc-repo
margin-left: -0.1rem
a.card-github.fetch-error
pointer-events: all
opacity: 1
@keyframes pulsate
0%
opacity: 0.15
50%
opacity: 0.25
100%
opacity: 0.15
.card-github, .gc-description, .gc-titlebar, .gc-stars, .gc-forks, .gc-license, .gc-avatar, github-logo
transition-property: all
transition-timing-function: cubic-bezier(0.4, 0, 0.2, 1)
transition-duration: 0.15s
</style>
<style lang="css" is:global>
@tailwind base;
@tailwind components;
@tailwind utilities;
@layer components {
.custom-md h1 {
@apply text-3xl
}
}
</style>

View file

@ -0,0 +1,38 @@
---
import WidgetLayout from "./WidgetLayout.astro";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
import {Category, getCategoryList} from "../../utils/content-utils";
import {getCategoryUrl} from "../../utils/url-utils";
import ButtonLink from "../control/ButtonLink.astro";
const categories = await getCategoryList();
const COLLAPSED_HEIGHT = "7.5rem";
const COLLAPSE_THRESHOLD = 5;
const isCollapsed = categories.length >= COLLAPSE_THRESHOLD;
interface Props {
class?: string;
style?: string;
}
const className = Astro.props.class
const style = Astro.props.style
---
<WidgetLayout name={i18n(I18nKey.categories)} id="categories" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT}
class={className} style={style}
>
{categories.map((c) =>
<ButtonLink
url={getCategoryUrl(c.name)}
badge={c.count}
label=`View all posts in the ${c.name} category`
>
{c.name}
</ButtonLink>
)}
</WidgetLayout>

View file

@ -0,0 +1,91 @@
<script lang="ts">
import {i18n} from '@i18n/translation';
import I18nKey from '@i18n/i18nKey';
import {getDefaultHue, getHue, setHue} from '@utils/setting-utils';
let hue = getHue()
const defaultHue = getDefaultHue()
function resetHue() {
hue = getDefaultHue()
}
$: if (hue || hue === 0) {
setHue(hue)
}
</script>
<div id="display-setting" class="float-panel float-panel-closed absolute transition-all w-80 right-4 px-4 py-4">
<div class="flex flex-row gap-2 mb-3 items-center justify-between">
<div class="flex gap-2 font-bold text-lg text-neutral-900 dark:text-neutral-100 transition relative ml-3
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:-left-3 before:top-[0.33rem]"
>
{i18n(I18nKey.themeColor)}
<button aria-label="Reset to Default" class="btn-regular w-7 h-7 rounded-md active:scale-90"
class:opacity-0={hue === defaultHue} class:pointer-events-none={hue === defaultHue} on:click={resetHue}>
<div class="text-[var(--btn-content)]">
<slot name="restore-icon"></slot>
</div>
</button>
</div>
<div class="flex gap-1">
<div id="hueValue" class="transition bg-[var(--btn-regular-bg)] w-10 h-7 rounded-md flex justify-center
font-bold text-sm items-center text-[var(--btn-content)]">
{hue}
</div>
</div>
</div>
<div class="w-full h-6 px-1 bg-[oklch(0.80_0.10_0)] dark:bg-[oklch(0.70_0.10_0)] rounded select-none">
<input aria-label={i18n(I18nKey.themeColor)} type="range" min="0" max="360" bind:value={hue}
class="slider" id="colorSlider" step="5" style="width: 100%;">
</div>
</div>
<style lang="stylus">
#display-setting
input[type="range"]
-webkit-appearance: none;
height: 1.5rem;
background-image: var(--color-selection-bar)
transition: background-image 0.15s ease-in-out
/* Input Thumb */
::-webkit-slider-thumb
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
&:hover
background: rgba(255, 255, 255, 0.8);
&:active
background: rgba(255, 255, 255, 0.6);
::-moz-range-thumb
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
border-width: 0
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
&:hover
background: rgba(255, 255, 255, 0.8);
&:active
background: rgba(255, 255, 255, 0.6);
&::-ms-thumb
-webkit-appearance: none;
height: 1rem;
width: 0.5rem;
border-radius: 0.125rem;
background: rgba(255, 255, 255, 0.7);
box-shadow: none;
&:hover
background: rgba(255, 255, 255, 0.8);
&:active
background: rgba(255, 255, 255, 0.6);
</style>

View file

@ -0,0 +1,32 @@
---
import {NavBarLink} from "../../types/config";
import {Icon} from "astro-icon/components";
import {url} from "../../utils/url-utils";
interface Props {
links: NavBarLink[],
}
const links = Astro.props.links;
---
<div id="nav-menu-panel" class:list={["float-panel float-panel-closed absolute transition-all fixed right-4 px-2 py-2"]}>
{links.map((link) => (
<a href={link.external ? link.url : url(link.url)} class="group flex justify-between items-center py-2 pl-3 pr-1 rounded-lg gap-8
hover:bg-[var(--btn-plain-bg-hover)] active:bg-[var(--btn-plain-bg-active)] transition
"
target={link.external ? "_blank" : null}
>
<div class="transition text-black/75 dark:text-white/75 font-bold group-hover:text-[var(--primary)] group-active:text-[var(--primary)]">
{link.name}
</div>
{!link.external && <Icon name="material-symbols:chevron-right-rounded"
class="transition text-[var(--primary)]" size="20"
>
</Icon>}
{link.external && <Icon name="fa6-solid:arrow-up-right-from-square"
class="transition text-black/25 dark:text-white/25 -translate-x-1" size="12"
>
</Icon>}
</a>
))}
</div>

View file

@ -0,0 +1,39 @@
---
import ImageWrapper from "../misc/ImageWrapper.astro";
import {Icon} from "astro-icon/components";
import {profileConfig} from "../../config";
import {url} from "../../utils/url-utils";
const config = profileConfig;
---
<div class="card-base p-3">
<a aria-label="Go to About Page" href={url('/about/')}
class="group block relative mx-auto mt-1 lg:mx-0 lg:mt-0 mb-3
max-w-[240px] lg:max-w-none overflow-hidden rounded-xl active:scale-95">
<div class="absolute transition pointer-events-none group-hover:bg-black/30 group-active:bg-black/50
w-full h-full z-50 flex items-center justify-center">
<Icon name="fa6-regular:address-card"
class="transition opacity-0 scale-90 group-hover:scale-100 group-hover:opacity-100 text-white text-5xl">
</Icon>
</div>
<ImageWrapper src={config.avatar} alt="Profile Image of the Author" class="mx-auto lg:w-full h-full lg:mt-0 "></ImageWrapper>
</a>
<div class="px-2">
<div class="font-bold text-xl text-center mb-1 dark:text-neutral-50 transition">{config.name}</div>
<div class="h-1 w-5 bg-[var(--primary)] mx-auto rounded-full mb-2 transition"></div>
<div class="text-center text-neutral-400 mb-2.5 transition">{config.bio}</div>
<div class="flex gap-2 justify-center mb-1">
{config.links.length > 1 && config.links.map(item =>
<a rel="me" aria-label={item.name} href={item.url} target="_blank" class="btn-regular rounded-lg h-10 w-10 active:scale-90">
<Icon name={item.icon} size="1.5rem"></Icon>
</a>
)}
{config.links.length == 1 && <a rel="me" aria-label={config.links[0].name} href={config.links[0].url} target="_blank"
class="btn-regular rounded-lg h-10 gap-2 px-3 font-bold active:scale-95">
<Icon name={config.links[0].icon} size="1.5rem"></Icon>
{config.links[0].name}
</a>}
</div>
</div>
</div>

View file

@ -0,0 +1,16 @@
---
import Profile from "./Profile.astro";
import Tag from "./Tags.astro";
import Categories from "./Categories.astro";
const className = Astro.props.class;
---
<div id="sidebar" class:list={[className, "w-full"]}>
<div class="flex flex-col w-full gap-4 mb-4">
<Profile></Profile>
</div>
<div class="flex flex-col w-full gap-4 top-4 sticky top-4">
<Categories class="onload-animation" style="animation-delay: 150ms"></Categories>
<Tag class="onload-animation" style="animation-delay: 200ms"></Tag>
</div>
</div>

View file

@ -0,0 +1,261 @@
---
import type { MarkdownHeading } from 'astro';
import { siteConfig } from "../../config";
interface Props {
class?: string
headings: MarkdownHeading[]
}
let { headings = [] } = Astro.props;
let minDepth = 10;
for (const heading of headings) {
minDepth = Math.min(minDepth, heading.depth);
}
const className = Astro.props.class
const removeTailingHash = (text: string) => {
let lastIndexOfHash = text.lastIndexOf('#');
if (lastIndexOfHash != text.length - 1) {
return text;
}
return text.substring(0, lastIndexOfHash);
}
let heading1Count = 1;
const maxLevel = siteConfig.toc.depth;
---
<table-of-contents class:list={[className, "group"]}>
{headings.filter((heading) => heading.depth < minDepth + maxLevel).map((heading) =>
<a href={`#${heading.slug}`} class="px-2 flex gap-2 relative transition w-full min-h-9 rounded-xl
hover:bg-[var(--toc-btn-hover)] active:bg-[var(--toc-btn-active)] py-2
">
<div class:list={["transition w-5 h-5 shrink-0 rounded-lg text-xs flex items-center justify-center font-bold",
{
"bg-[var(--toc-badge-bg)] text-[var(--btn-content)]": heading.depth == minDepth,
"ml-4": heading.depth == minDepth + 1,
"ml-8": heading.depth == minDepth + 2,
}
]}
>
{heading.depth == minDepth && heading1Count++}
{heading.depth == minDepth + 1 && <div class="transition w-2 h-2 rounded-[0.1875rem] bg-[var(--toc-badge-bg)]"></div>}
{heading.depth == minDepth + 2 && <div class="transition w-1.5 h-1.5 rounded-sm bg-black/5 dark:bg-white/10"></div>}
</div>
<div class:list={["transition text-sm", {
"text-50": heading.depth == minDepth || heading.depth == minDepth + 1,
"text-30": heading.depth == minDepth + 2,
}]}>{removeTailingHash(heading.text)}</div>
</a>
)}
<div id="active-indicator" class:list={[{'hidden': headings.length == 0}, "-z-10 absolute bg-[var(--toc-btn-hover)] left-0 right-0 rounded-xl transition-all " +
"group-hover:bg-transparent border-2 border-[var(--toc-btn-hover)] group-hover:border-[var(--toc-btn-active)] border-dashed"]}></div>
</table-of-contents>
<script>
class TableOfContents extends HTMLElement {
tocEl: HTMLElement | null = null;
visibleClass = "visible";
observer: IntersectionObserver;
anchorNavTarget: HTMLElement | null = null;
headingIdxMap = new Map<string, number>();
headings: HTMLElement[] = [];
sections: HTMLElement[] = [];
tocEntries: HTMLAnchorElement[] = [];
active: boolean[] = [];
activeIndicator: HTMLElement | null = null;
constructor() {
super();
this.observer = new IntersectionObserver(
this.markVisibleSection, { threshold: 0 }
);
};
markVisibleSection = (entries: IntersectionObserverEntry[]) => {
entries.forEach((entry) => {
const id = entry.target.children[0]?.getAttribute("id");
const idx = id ? this.headingIdxMap.get(id) : undefined;
if (idx != undefined)
this.active[idx] = entry.isIntersecting;
if (entry.isIntersecting && this.anchorNavTarget == entry.target.firstChild)
this.anchorNavTarget = null;
});
if (!this.active.includes(true))
this.fallback();
this.update();
};
toggleActiveHeading = () => {
let i = this.active.length - 1;
let min = this.active.length - 1, max = 0;
while (i >= 0 && !this.active[i]) {
this.tocEntries[i].classList.remove(this.visibleClass);
i--;
}
while (i >= 0 && this.active[i]) {
this.tocEntries[i].classList.add(this.visibleClass);
min = Math.min(min, i);
max = Math.max(max, i);
i--;
}
while (i >= 0) {
this.tocEntries[i].classList.remove(this.visibleClass);
i--;
}
let parentOffset = this.tocEl?.getBoundingClientRect().top || 0;
let scrollOffset = this.tocEl?.scrollTop || 0;
let top = this.tocEntries[min].getBoundingClientRect().top - parentOffset + scrollOffset;
let bottom = this.tocEntries[max].getBoundingClientRect().bottom - parentOffset + scrollOffset;
this.activeIndicator?.setAttribute("style", `top: ${top}px; height: ${bottom - top}px`);
};
scrollToActiveHeading = () => {
// If the TOC widget can accommodate both the topmost
// and bottommost items, scroll to the topmost item.
// Otherwise, scroll to the bottommost one.
if (this.anchorNavTarget || !this.tocEl) return;
const activeHeading =
document.querySelectorAll<HTMLDivElement>(`#toc .${this.visibleClass}`);
if (!activeHeading.length) return;
const topmost = activeHeading[0];
const bottommost = activeHeading[activeHeading.length - 1];
const tocHeight = this.tocEl.clientHeight;
let top;
if (bottommost.getBoundingClientRect().bottom -
topmost.getBoundingClientRect().top < 0.9 * tocHeight)
top = topmost.offsetTop - 32;
else
top = bottommost.offsetTop - tocHeight * 0.8;
this.tocEl.scrollTo({
top,
left: 0,
behavior: "smooth",
});
};
update = () => {
requestAnimationFrame(() => {
this.toggleActiveHeading();
// requestAnimationFrame(() => {
this.scrollToActiveHeading();
// });
});
};
fallback = () => {
if (!this.sections.length) return;
for (let i = 0; i < this.sections.length; i++) {
let offsetTop = this.sections[i].getBoundingClientRect().top;
let offsetBottom = this.sections[i].getBoundingClientRect().bottom;
if (this.isInRange(offsetTop, 0, window.innerHeight)
|| this.isInRange(offsetBottom, 0, window.innerHeight)
|| (offsetTop < 0 && offsetBottom > window.innerHeight)) {
this.markActiveHeading(i);
}
else if (offsetTop > window.innerHeight) break;
}
};
markActiveHeading = (idx: number)=> {
this.active[idx] = true;
};
handleAnchorClick = (event: Event) => {
const anchor = event
.composedPath()
.find((element) => element instanceof HTMLAnchorElement);
if (anchor) {
const id = decodeURIComponent(anchor.hash?.substring(1));
const idx = this.headingIdxMap.get(id);
if (idx !== undefined) {
this.anchorNavTarget = this.headings[idx];
} else {
this.anchorNavTarget = null;
}
}
};
isInRange(value: number, min: number, max: number) {
return min < value && value < max;
};
connectedCallback() {
// wait for the onload animation to finish, which makes the `getBoundingClientRect` return correct values
const element = document.querySelector('.prose');
if (element) {
element.addEventListener('animationend', () => {
this.init();
}, { once: true });
} else {
console.warn('Animation element not found');
}
};
init() {
this.tocEl = document.getElementById(
"toc-inner-wrapper"
);
if (!this.tocEl) return;
this.tocEl.addEventListener("click", this.handleAnchorClick, {
capture: true,
});
this.activeIndicator = document.getElementById("active-indicator");
this.tocEntries = Array.from(
document.querySelectorAll<HTMLAnchorElement>("#toc a[href^='#']")
);
if (this.tocEntries.length === 0) return;
this.sections = new Array(this.tocEntries.length);
this.headings = new Array(this.tocEntries.length);
for (let i = 0; i < this.tocEntries.length; i++) {
const id = decodeURIComponent(this.tocEntries[i].hash?.substring(1));
const heading = document.getElementById(id);
const section = heading?.parentElement;
if (heading instanceof HTMLElement && section instanceof HTMLElement) {
this.headings[i] = heading;
this.sections[i] = section;
this.headingIdxMap.set(id, i);
}
}
this.active = new Array(this.tocEntries.length).fill(false);
this.sections.forEach((section) =>
this.observer.observe(section)
);
this.fallback();
this.update();
};
disconnectedCallback() {
this.sections.forEach((section) =>
this.observer.unobserve(section)
);
this.observer.disconnect();
this.tocEl?.removeEventListener("click", this.handleAnchorClick);
};
}
customElements.define("table-of-contents", TableOfContents);
</script>

View file

@ -0,0 +1,32 @@
---
import WidgetLayout from "./WidgetLayout.astro";
import ButtonTag from "../control/ButtonTag.astro";
import {getTagList} from "../../utils/content-utils";
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
import {url} from "../../utils/url-utils";
const tags = await getTagList();
const COLLAPSED_HEIGHT = "7.5rem";
const isCollapsed = tags.length >= 20;
interface Props {
class?: string;
style?: string;
}
const className = Astro.props.class
const style = Astro.props.style
---
<WidgetLayout name={i18n(I18nKey.tags)} id="tags" isCollapsed={isCollapsed} collapsedHeight={COLLAPSED_HEIGHT} class={className} style={style}>
<div class="flex gap-2 flex-wrap">
{tags.map(t => (
<ButtonTag href={url(`/archive/tag/${t.name}/`)} label={`View all posts with the ${t.name} tag`}>
{t.name}
</ButtonTag>
))}
</div>
</WidgetLayout>

View file

@ -0,0 +1,65 @@
---
import { Icon } from 'astro-icon/components';
import {i18n} from "../../i18n/translation";
import I18nKey from "../../i18n/i18nKey";
interface Props {
id: string;
name?: string;
isCollapsed?: boolean;
collapsedHeight?: string;
class?: string;
style?: string;
}
const props = Astro.props;
const {
id,
name,
isCollapsed,
collapsedHeight,
style,
} = Astro.props
const className = Astro.props.class
---
<widget-layout data-id={id} data-is-collapsed={isCollapsed} class={"pb-4 card-base " + className} style={style}>
<div class="font-bold transition text-lg text-neutral-900 dark:text-neutral-100 relative ml-8 mt-4 mb-2
before:w-1 before:h-4 before:rounded-md before:bg-[var(--primary)]
before:absolute before:left-[-16px] before:top-[5.5px]">{name}</div>
<div id={id} class:list={["collapse-wrapper px-4 overflow-hidden", {"collapsed": isCollapsed}]}>
<slot></slot>
</div>
{isCollapsed && <div class="expand-btn px-4 -mb-2">
<button class="btn-plain rounded-lg w-full h-9">
<div class="text-[var(--primary)] flex items-center justify-center gap-2 -translate-x-2">
<Icon name="material-symbols:more-horiz" size={28}></Icon> {i18n(I18nKey.more)}
</div>
</button>
</div>}
</widget-layout>
<style define:vars={{ collapsedHeight }}>
.collapsed {
height: var(--collapsedHeight);
}
</style>
<script>
class WidgetLayout extends HTMLElement {
constructor() {
super();
if (this.dataset.isCollapsed === undefined || this.dataset.isCollapsed === false)
return;
const id = this.dataset.id;
const btn = this.querySelector('.expand-btn');
const wrapper = this.querySelector(`#${id}`)
btn.addEventListener('click', () => {
wrapper.classList.remove('collapsed');
btn.classList.add('hidden');
})
}
}
customElements.define('widget-layout', WidgetLayout);
</script>

74
src/config.ts Normal file
View file

@ -0,0 +1,74 @@
import type {
LicenseConfig,
NavBarConfig,
ProfileConfig,
SiteConfig,
} from './types/config'
import { LinkPreset } from './types/config'
export const siteConfig: SiteConfig = {
title: 'Hack13 Blog',
subtitle: 'The blog of a enby foxo',
lang: 'en', // 'en', 'zh_CN', 'zh_TW', 'ja'
themeColor: {
hue: 340, // Default hue for the theme color, from 0 to 360. e.g. red: 0, teal: 200, cyan: 250, pink: 345
fixed: true, // Hide the theme color picker for visitors
},
banner: {
enable: false,
src: 'assets/images/demo-banner.png', // Relative to the /src directory. Relative to the /public directory if it starts with '/'
position: 'center', // Equivalent to object-position, defaults center
},
favicon: [
// Leave this array empty to use the default favicon
// {
// src: '/favicon/icon.png', // Path of the favicon, relative to the /public directory
// theme: 'light', // (Optional) Either 'light' or 'dark', set only if you have different favicons for light and dark mode
// sizes: '32x32', // (Optional) Size of the favicon, set only if you have favicons of different sizes
// }
],
}
export const navBarConfig: NavBarConfig = {
links: [
LinkPreset.Home,
LinkPreset.Archive,
LinkPreset.About,
{
name: 'GitHub',
url: 'https://github.com/hack13', // Internal links should not include the base path, as it is automatically added
external: true, // Show an external link icon and will open in a new tab
},
],
}
export const profileConfig: ProfileConfig = {
avatar: 'assets/images/hack-avatar.png', // Relative to the /src directory. Relative to the /public directory if it starts with '/'
name: 'Hack13',
bio: 'A nerdy enby foxo on the internet.',
links: [
{
name: 'Twitter',
icon: 'fa6-brands:mastodon', // Visit https://icones.js.org/ for icon codes
// You will need to install the corresponding icon set if it's not already included
// `pnpm add @iconify-json/<icon-set-name>`
url: 'https://cyberfurz.social/@hack13',
},
{
name: 'Telegram',
icon: 'fa6-brands:telegram',
url: 'https://t.me/kite5521',
},
{
name: 'Discord',
icon: 'fa6-brands:git',
url: 'https://git.hack13.dev/hack13',
},
],
}
export const licenseConfig: LicenseConfig = {
enable: true,
name: 'CC BY-NC-SA 4.0',
url: 'https://creativecommons.org/licenses/by-nc-sa/4.0/',
}

View file

@ -0,0 +1,6 @@
export const UNCATEGORIZED = '__uncategorized__'
export const PAGE_SIZE = 8
export const LIGHT_MODE = 'light', DARK_MODE = 'dark', AUTO_MODE = 'auto'
export const DEFAULT_THEME = AUTO_MODE

37
src/constants/icon.ts Normal file
View file

@ -0,0 +1,37 @@
import type {Favicon} from "@/types/config.ts";
export const defaultFavicons: Favicon[] = [
{
src: '/favicon/favicon-light-32.png',
theme: 'light',
sizes: '32x32',
}, {
src: '/favicon/favicon-light-128.png',
theme: 'light',
sizes: '128x128',
}, {
src: '/favicon/favicon-light-180.png',
theme: 'light',
sizes: '180x180',
}, {
src: '/favicon/favicon-light-192.png',
theme: 'light',
sizes: '192x192',
}, {
src: '/favicon/favicon-dark-32.png',
theme: 'dark',
sizes: '32x32',
}, {
src: '/favicon/favicon-dark-128.png',
theme: 'dark',
sizes: '128x128',
}, {
src: '/favicon/favicon-dark-180.png',
theme: 'dark',
sizes: '180x180',
}, {
src: '/favicon/favicon-dark-192.png',
theme: 'dark',
sizes: '192x192',
}
]

View file

@ -0,0 +1,18 @@
import { LinkPreset, type NavBarLink } from '@/types/config'
import I18nKey from '@i18n/i18nKey'
import { i18n } from '@i18n/translation'
export const LinkPresets: { [key in LinkPreset]: NavBarLink } = {
[LinkPreset.Home]: {
name: i18n(I18nKey.home),
url: '/',
},
[LinkPreset.About]: {
name: i18n(I18nKey.about),
url: '/about/',
},
[LinkPreset.Archive]: {
name: i18n(I18nKey.archive),
url: '/archive/',
},
}

16
src/content/config.ts Normal file
View file

@ -0,0 +1,16 @@
import { defineCollection, z } from 'astro:content'
const postsCollection = defineCollection({
schema: z.object({
title: z.string(),
published: z.date(),
draft: z.boolean().optional(),
description: z.string().optional(),
image: z.string().optional(),
tags: z.array(z.string()).optional(),
category: z.string().optional(),
}),
})
export const collections = {
posts: postsCollection,
}

View file

@ -0,0 +1,21 @@
---
title: Battle of Business vs OpenSource
description: Alright lately I have been getting overwhelmed with all the drama of the oldest argument in the world of internet technologies. So who should win? Big business or opensource projects? Well the answer is simple, THE ARE NOT AT WAR!!!!
tags: [OpenSim, Opinion]
published: 2012-08-09
url: /2012/08/battle-of-business-vs-opensource/
category: Archive
draft: false
---
Alright lately I have been getting overwhelmed with all the drama of the oldest argument in the world of internet technologies. So who should win? Big business or opensource projects? Well the answer is simple, THE ARE NOT AT WAR!!!!
So let us start off with the ATTACK against an innocent content contribute to the opensim community. Linda Kellie, is a large fan girl of opensim, and that is all she is. She is just like everyone else who uses opensim, she just so happens to release all her stuff completely free on a website. She does this because she likes to see people use her stuff. She doesn't want any sort of credit, in fact all of her stuff is completely royalty free. The only reason people are upset with her is because she has influence in the opensim community, how? Because she just gives away stuff, that doesn't mean she is a representative of the opensim community.
Linda Kellie is no more of a representative of the opensimulator community than I am. She just wants to login, play, and share stuff she creates. Hey, I do the same thing, but I am not a great builder so I provide opensimulator hosting at affordable costs. Yes she is now on a grid that doesn't have the greatest reputation, but I am going to go into that in the next segment of this blog. She didn't do all this for PR for their grid, she just logged in and fell in love with we what they are doing. I was there in the call when she stated she wanted to go back into making things for money, no one even said anything to her about it. She just decided to do it for SpotOn3D because she really likes it there, so why should we be mad at her for doing what she wants and enjoys. If people fallow her, it is THEIR choice, that is the thing that people need to realize, humans are likes herds of sheep. They go where they see others going, but you know what, they still have free will, if you are nervous they are dragging them away from your product DO SOMETHING BETTER TO ATTRACT ATTENTION DON'T BITCH AND BLAME IT ON SOMEONE ELSE!!!!
Next onto the SpotOn3D, I am sick and tired of people upset with this grid obtaining patents on OpenSimulator. Hell did you know that most of your human body organs is patented by medical companies, I am not lying it is!!! Now, why I am not throwing a fit about all the patents they have. Well  my answer is super simple: I MUCH RATHER HAVE SPOTON3D HAVE THE PATENTS THAN A COMPANY WHO IS SUE HAPPY LIKE WORLDS INC. I know Tessa from a while back, and well I am pretty sure she is not going to use these to stop opensource development? Why because she needs it too, just like any company using opensource. She has her grid upgraded with each opensim release, so honestly, yeah I don't think she is a threat, I think of her as a protector of the development since they are not contributing back code wise, but as long as she doesn't abuse the power of these patents she is protected the sourcecode from companies like Worlds Inc.
Now onto SecondLife with its latest changes to the viewer, now I am upset like the rest of you. But we need to realize that, OPENSIM NEEDED TO BREAK AWAY, this is good in my opinion. This opens so many new doors, let us make our own changes viewer side as well as server side, we will no longer have to rely on mimicking SecondLife's new features to make our own versions of them. Let us start going a bit more original, come up with features that will be kinda like a slap in the face and make SecondLife really upset that they dropped us, by introducing features they haven't even thought of yet. I know we can do it, we are a strong and large community, so why don't we push to make it happen.
Side not on viewers: I read that people are upset with Firestorm Team about the whole thing with them getting Armin and that they attacked the angstrom viewer dev. I was in the Aurora-Dev channel when it all went down, they were just upset and asked why the credits of firestorm were removed? They felt really upset that all their work would go un-credited. I would be sad and upset too, but it wasn't really an attack, but while they were there, I spoke with them and was able to show them how many people use their viewer on OSgrid thanks to key's blog. So yeah, just wanted to get that out there, GIVE CREDIT WHERE CREDIT IS DUE.

View file

@ -0,0 +1,19 @@
---
title: New Years Resolutions
description: It is that time of the year, when people start making New Year Resolutions. Well I have never really made any other than once for a class assignment. But this year I really think I need to make some real resolutions. So I thought I would post them here on my personal blog for everyone to read and be aware.
tags: [Personal]
published: 2012-12-31
url: /2012/12/new-years-resolutions/
category: Archive
draft: false
---
It is that time of the year, when people start making New Year Resolutions. Well I have never really made any other than once for a class assignment. But this year I really think I need to make some real resolutions. So I thought I would post them here on my personal blog for everyone to read and be aware.
Focus on business, and put helping others second. Why because I keep helping others and putting myself in debt. Every time I build myself up, I end up falling back down again. So I need to keep my nose to the grindstone, and quit screwing myself over. But I am still going to help out my friends, just got to make sure I can first.
Quit with procrastination. I get off track so much, mostly due to my ADHD, but I have to tighten down and make sure I stay focused and get my work done. Time is money, and money is not everything, but it pays the bills, and helps secure my future.
Work on my relationship. I live in a very interesting relationship, I have two partners. We all live together, and we are all happy together. I know it sounds different, but we are happy and completely settled together. Working on year 3 of being together, and I just want to work on actually being there, because too often I was all busy, no play. So I am going to work on that a bit more this year.
On the whole thing of focusing more on work, well I have already started some major work on the stuff I do for a living. Get ready for a big surprise, because I can't believe I am going to be doing this myself.

View file

@ -0,0 +1,41 @@
---
title: Explaining OpenSim Memory Usage
description: I have had a couple people ask me to show them some tips and show them how to conserve memory usage in OpenSim. So I am going to be using sim-on-a-stick for today's tips and tricks...
tags: [Resources, OpenSim]
published: 2013-02-11
url: /2013/02/explaining-opensim-memory-usage/
category: Archive
draft: false
---
I have had a couple people ask me to show them some tips and show them how to conserve memory usage in OpenSim. So I am going to be using sim-on-a-stick for today's tips and tricks. Now the reason for this is because now with sim-on-a-stick, we have the database running in MySQL which saves a bit of memory, and the fact it is already setup and ready to go out of the box.</p>
So out of the box starting everything up, I am now at 95mb of ram:
![alt text](/wp-import/2013/2013-02-11-04_48_56-Task-Manager.png "task-manager")
This is an empty region with absolutely nothing on it. So lets go load an oar on it, and see what it levels out to be. I am going to be loading Linda Kellie's Tropical Oar. It has 1,445 prims on it and well, that is about the standard for one person who just plans on having a region to go and relax on. A place to call home, do small things, etc. you normally don't use more than 2-3k amount of prims.
![alt text](/wp-import/2013/2013-02-11-04_56_31-Task-Manager.png "task-manager")
After loading the oar we are now at 166.2mb of ram so that is still pretty good. Now this is with no one on the region, so lets go and login to the region and walk around. See what happens simulator side. This is still the Tropical Oar.
![alt text](/wp-import/2013/2013-02-11-05_12_59-Singularity-Viewer-Timothy-Hoxley.png "timothy vyper")
So I logged in and dressed up in the most prim clothing I have which is some rave gear I made a long time ago. You can see below, and I walked around a bit and open closed a few doors and stuff and we are still only topping out at about 169.0mb. So I figured that means like what 1-3mb per avatar on the region depending on scripts and prims, so not bad. But I am not done, we are going to load Linda Kellie's freebie mall #2 that has 23,620 prims which is about what I see most people top out at building wise. So let's go see what happens.
![alt text](/wp-import/2013/2013-02-11-13_45_23-Task-Manager.png "task manager")
Well after loading the oar and letting probably a thousand scripts load into the region, we are now at 798.0mb of ram usage. Then means that we are using quite a bit of ram, but most of that is scripts, and lets face it not that meany people are going to have THAT many scripts on their region, maybe 25k prims but not that many scripts. But this gives you an idea, if you are building a heavy scripted region and need that much it is best to run 1 region per simulator, and have about a gig per region. But that is just for big regions with tons and tons of scripts like Linda Kellie's freebie mall #2. Now lets see what happens when I turn off all the scripts.
![alt text](/wp-import/2013/2013-02-11-14_01_45-Task-Manager.png "task manager")
Wow same region, just with scripts turned off. Wow major difference huh, well see how much scripts impact sims, however you have to keep in mind that Freebie Mall #2 has about 6 scripts in almost all the furniture where there is tons of that, and several scripts in doors and other things. Way more than even a standard fully built up region, this is a store sim, tons of stuff. But we can see a region after all that 23,620 prims don't use much at all.
Now I am not sure how many people know that you can hook more than one region per simulator instance, to add another region you just type create region Regions.ini and answer the questions like you did for the first region. So let's put up another region on this same instance while scripts are still disabled and load Linda Kellie's Country Cabin OAR.
![alt text](/wp-import/2013/2013-02-11-14_10_18-Task-Manager.png "task manager")
Alright so now we have 2 regions on the same instance, and we are still quite low on memory usage. Now I will say it is normally a good idea if you don't have hugely scripted regions to put more than one region on the same instance but I don't recommend more than 4 per instance, it is a good number to stop at in my opinion, unless you are doing mega regions, but that is another story for another day.
I hope this has given you all some insight to hosting regions on your own servers, or rented servers or hosting from other companies to help you understand what is going on in the back ground.

View file

@ -0,0 +1,34 @@
---
title: Thank You
description: Thank You AuroraScape Community
tags: [AuroraSim]
published: 2013-02-13
url: /2013/02/thank-you/
category: Archive
draft: false
---
Thank You AuroraScape Community,
In the short 13 days since our very rough start, we have already hit a number of 67 registered residents, 51 Regions, and an average concurrency of 5-9 people online at any time. This is something that really impresses me for us only being 13 days old. I don't think I could have been any happier with the results of the opening of AuroraScape.
![alt text](/wp-import/2013/2013-02-13-17_17_31-Astra-Viewer.png)
I am so excited, just logging in. I see this wonderful welcome area that has slowly been being filled with residents content. I would still love to see more resident's stuff here in world filling up these shops. But with working with everyone and seeing everything looking so clean and cut.
![alt text](/wp-import/2013/Snapshot_002.png "shopping center")
All of Linda Kellie's collections of furries, zoomtada's, and human avatars available upon login so you can get rid of that ugly ruth body. I have to say I can't think of a single grid that I have seen so many furries in, I am completely being serious here, I have seen so many furries apart of this community here as well as humans.
![alt text](/wp-import/2013/Snapshot_003.png "welcome fireplace")
All our meetings currently being held around the fire in the Welcome area, it is quite cozy with all those pillows and many different custom sits and lays, put in by our very own resident Mia Destiny. Our first grid meeting we ran out of seating, this time was have added plenty more pillow for everyone!
![alt text](/wp-import/2013/Snapshot_004.png "resident home")
Lastly the part that makes me the happiest person ever, is seeing you guys calling this grid home. Putting up your house and moving in, this is what makes me the happiest person ever. I really hope to see more of you guys move in and call AuroraScape your home too.
Thank you all so much for making AuroraScape a success, this is YOUR GRID, and I mean that. I have a Terms of Service, and require you to be 18+, but I do this because I want us to have a grid that we can make sure is going to be around for a long time to come. Remember that this grid will become what you make it. Remember that, as you all are the key components of this grid's success.
Thank You with Love,
Timothy Francis Rogers

View file

@ -0,0 +1,17 @@
---
title: Needing Rest
description: It sure has been a long first 20 days, I can't believe how far all of us have come. Yes after the upgrade happened, we lost a nice chunk of regions, but I am sure we will be getting them back soon. All I can say is I am taking a few steps back for the next few days, from grid upgrades.
tags: [AuroraSim]
published: 2013-02-20
url: /2013/02/needing-rest/
category: Archive
draft: false
---
It sure has been a long first 20 days, I can't believe how far all of us have come. Yes after the upgrade happened, we lost a nice chunk of regions, but I am sure we will be getting them back soon. All I can say is I am taking a few steps back for the next few days, from grid upgrades. I will still be around, doing money cash outs, trouble shooting, and processing orders. I just need a break.
I have been working so hard on the grid, that I got myself sick. I mean physically sick, but I am doing better now, I am just stepping back for a bit. It is really hard running this big grid's back end with just one other volunteer, I been drinking my weight in soda pop(not a coffee man). I tell you if it wasn't for Walmart brand dr. pepper, AuroraScape would suffer from it.
Anyways, I have been working on my own regions recently on my personal account in world. Feel free to add me _Timothy Vyper_ I like having friends. I am also working on some other features for customers of my Zetamex service. Which helps me fund AuroraScape and my caffeine addiction. I am always working hard to make a better world for you, and from the recent response people be giving me, I think you all are loving it.
I shall now retreat to my residence in AuroraScape, and continue to work on this castle for me and others to enjoy. I will see you all around, and I thank you all for your support. I will be back in full swing next week, so let us all relax and hang out together in world.

View file

@ -0,0 +1,33 @@
---
title: Top 10 Reasons Not To Give Up On Aurora-Sim
description: I am very sad to see so many people tell me that I should give up on Aurora-Sim, and that it is a waste. I think it is mostly been placed there because people remember its biggest tank, and the failure of Nova Grid.
tags: [AuroraSim]
published: 2013-02-21
url: /2013/02/top-10-reasons-not-to-give-up-on-aurora-sim/
category: Archive
draft: false
---
I am very sad to see so many people tell me that I should give up on Aurora-Sim, and that it is a waste. I think it is mostly been placed there because people remember its biggest tank, and the failure of Nova Grid. So I am going to show you the top 10 reasons why I think the metaverse should give it another shot, and get back to helping with its development.
**Ready Out of the Box:** When you download the latest aurora-sim master code, and compile it. First time run, it has a built in fully functional WebUI, Fully Working Groups, Fully Working Profiles, Fully Working Search, and easy to use Region GUI. All of this is built into the code, all running for the core code. This all works even in standalone mode all out of the single Aurora.exe!
**Stable Physics:** Physics in Aurora-Sim is amazing, it is the same engine that OpenSim has been using for a long time. However more hooks, and programming went into it, making it much more stable and react much more realistically.
**No HelperURI Needed:** One of the biggest issues a lot of standalones and grids run into, is just selling land for free. This is because OpenSim requires a helperURI to process transactions. This however is no problem in Grid Mode or Standalone Mode, as the helperURI is built in.
**Variable Regions:** You can have regions of any size, and without the parceling issues that Mega Regions suffer in OpenSim. This is because the regions are 1 solid complete full region, however you need to use a viewer which supports it.
**Completely Free & Open:** The source code is complete free and opensource, meaning anyone can open it up, change it, improve it, and contribute it back to make it better for everyone.
**Automated Backups:** We all know that it sucks how we get attacked, however Aurora-Sim is completely built in region backups. It can be configured to make backups and keep backups for customized to your liking. In fact, your regions are completely saved in your backups.
**Less Resources Needed:** You can put double the prims on an Aurora-Sim region than you can on an OpenSim region using the same amount of ram. In fact the default prim count is 80,000 which is far more than anyone could ever use, or is it?.
**Region WindLight:** With recently implemented region WindLight on SecondLife, Aurora-Sim has processed the code and has made it to where can set region WindLight from your viewer as you would in SecondLife. This avoids needing any special scripts, or modules for LightShare settings.
**Multiple Attachments:** I know this is something that a lot of people crave, and well a lot of people sacrifice by moving to OpenSim. Being able to wear more than one attachment on the same point. You can wear more than one attachment on the same point in Aurora-Sim.
**K.I.S.S.:** Aurora-Sim has been modified to be very simple to use, setup, and use.
Now that I have said all that, you should definitely stop in and visit us at ~~aurorascape.com~~_(defunct)_ a completely free and open to connect grid.

View file

@ -0,0 +1,20 @@
---
title: Who Is Timothy Vyper/Hoxley/Rogers
description: I just keep reading all this stuff about all these other grids, and the people who have influences there. Now that I am a grid owner, been making myself know more and more over the past year with all the things I have started. Grid-Press, SoftPaw Estates, Zetamex, and my latest AuroraScape.
tags: [Personal]
published: 2013-02-23
url: /2013/02/who-is-timothy-vyperhoxleyrogers/
category: Archive
draft: false
---
I just keep reading all this stuff about all these other grids, and the people who have influences there. Now that I am a grid owner, been making myself know more and more over the past year with all the things I have started. Grid-Press, SoftPaw Estates, Zetamex, and my latest AuroraScape. I will admit I am not perfect, hell I never even went to college! Also another zinger for all you people, did you all know that I am only 20 years old? This year I turn 21, on April 28th. Which surprises a lot of people when I tell them all that, but I did take some college classes in High School, because my school was merged with the local college (due to budget cuts) and got 3 certification, that don't mean crap now.
![alt text](/wp-import/2013/454036.jpg "timothy vyper")
That was a lot of information in one paragraph, well I am still wanting to clear up some damage I feel that has been done on the opensim/aurorasim community with BDSM people. Yes there are some people who are in BDSM that take it way to far, and that is just crazy when that happens. I know that it easy for some to be brain washed, that it is physically possible. I am not into the full BDSM but more the S&M part, and still there you have to be careful. I should know, being a sub myself. I just hate saying it, but not long after the osgrid thing started, I saw a lot of kinda like very short, and small responses like they had to post it. I mean if I like something I am going to say more than I like, thank you! at least a sentence or two. But that wasn't the only part it was also all the same with tiny changes to the text, but all the same like robots.
I am on my personal account Timothy Vyper, on AuroraScape trying to start up a furry S&M community on Furry Mansion. I can't go into to much detail here, because well this is a PG blog, and want to play it safe. I believe I am already pushing the envelope by talking about BDSM on my blog. I just don't want people to think it is all bad, it provides an out let for people like me. There are people out there who have instinctive urges to do things, and as long as everyone is responsible and plays safe it is a good outlet. But then again I am one of the most open people I know, :p have two partners in RL that I live with and all.
Anyways, that is all I had to say.

View file

@ -0,0 +1,22 @@
---
title: Why I Closed AuroraScape
description: I first want to thank everyone who was on AuroraScape, and I have to apologize to all of you. I know the life of AuroraScape was short but there are multiple reasons to this, and I mean no ill intent to the developers of Aurora by what I have to say in this blog.
tags: [AuroraSim]
published: 2013-03-05
url: /2013/03/why-i-closed-aurorascape/
category: Archive
draft: false
---
I first want to thank everyone who was on AuroraScape, and I have to apologize to all of you. I know the life of AuroraScape was short but there are multiple reasons to this, and I mean no ill intent to the developers of Aurora by what I have to say in this blog. Firstly, AuroraScape was doing pretty good. But that was how it looked on the outside, there were a lot of problems, things I just cannot work on as I am not a programmer. The software is very stable, very easy to use, and works out of the box. That I am not trying to fight with, but there was just too many people bitching about it.
I know I shouldn't let people bitching about the software help me close the grid down, but it is not so simple. Many people were just not willing to even give AuroraScape a shot, and many people who joined began to drop out right away. The interest in Aurora-Sim was so limited, and one of the biggest reasons is because the lack of HyperGrid. There were people who literately said things like I will never support an open grid without hypergrid and that type of thing travels and makes other say it and more people see it. People were publicly bitching and complaining about the grid without even giving it a shot. We kept loosing people because of it.
I did purchase extra hardware to make the grid successful, and now I am going to be using that extra hardware to run others grids and regions as I do already as Zetamex. I was trying so hard to run this grid, but running a grid that brings in virtually no profit at all is very hard. So I am focusing on grid and simulator hosting, stuff that makes money. I have no other job, this is my job, sitting in front of my computer and help the metaverse grow. AuroraScape was just not attracting any attention, it did for a while, and then people just dropped out.
Working on AuroraScape, took a lot of focus from Zetamex, and Zetamex is filled with paying customers who are looking for top notch service. I have been running Zetamex/SoftPaw Estates for almost a year now, so I need to move my focus back to providing service.
If people have less faith in me now because of this, I am sorry, but when you have to feed a family, pay rent, and etc. You have to make wise choices, and well the focus needed to be shifted back to Zetamex.
Thank You,
Timothy Rogers

View file

@ -0,0 +1,32 @@
---
title: Closed vs Open? Really?
description: I am dealing with a lot of people getting stressed over the debate of if grids should all be open or at least have hypergrid open. I am just going to put this out there, there are tons of people who want everything free in life, and never want to pay for anything.
tags: [OpenSim]
published: 2013-03-11T12:03:00+00:00
url: /2013/03/closed-vs-open-really/
category: Archive
draft: false
---
I am dealing with a lot of people getting stressed over the debate of if grids should all be open or at least have hypergrid open. I am just going to put this out there, there are tons of people who want everything free in life, and never want to pay for anything. I am sorry but the world requires money for us to get things done.
I have been hearing and seeing people damage grids reputations for charging for regions, making you pay for services and what not. Well honestly, you are paying them so they can turn around and use that money to do the following things:
1. Pay the server costs (FYI, servers are not cheap, nor is the bandwidth costs normally used to operate them)
2. Their time, these people are managing your entire back end FOR YOU, they deserve your money if you're using thier services.
What you need to see, the back end of things of how open and closed grids work.
**Open Grids:** These grids run on sponsors, this means, companies, colleges, universities, and people donating. Perfect example, how many people know that when OSgrid was about to take the dive it was because "THEIR MAIN SPONSOR WAS DROPPING THEIR SUPPORT! they didn't have any other sponsors, or enough donations to keep it going. So SimHost came in and decided to become the new sponsor, and well I am not going to get into that drama, I am just saying that somewhere along the line "SERVERS GOTTA GET PAID, THE AIN'T FREE!
**Closed Grids:** Why do these grids charge a bit more? Well it is actually the added security, the hands on support, you most likely have in world money investments, etc. These grids charge more because they have a bigger job to worry about. These guys are being PAID to fix your bugs, give your more direct support, manage RL money investment, people go to closed grids to make money. The backend of a grid like InWorldz, Avinations, etc have to be FAR more secure than your standard opensim grid. They gotta have firewalls, bugs fixed that allow you to steal items, etc. People have real investment in these grids.
Now onto the people getting upset with others for wanting to make money in personal grids, well dammit these people have every right to do this. The reason many people go closed is because these grid owners take care of their content, it is much easier for InWorldz to dispute and catch a content thief than it is for a grid like OSgrid or Metropolis. Because they can look and see the whole backend, and yes I know thieves are in the real world and theft is always going to happen, but does that mean we should let it go.
EXAMPLE: If we should let someone steal a complete line of clothing someone makes in InWorldz and Avination, and start giving it way for free on other grids. What do you think this does to their profits, their ability to make ends meet? then you might say "but that is virtual! well it is no different than going and robbing a Real Life clothing store, now they just went out several thousand dollars, gotta replace the door you broke, windows, clothes, etc. IT IS NOT RIGHT NO MATTER HOW SMALL THE CRIME!!!
So to all the people getting upset with content creators who make things for both closed and open grids, DON'T YOU DARE GET MAD AT THEM. They are honest people trying to make a living, that is what the internet is, a ecosystem like RL. I place for people to have fun, mingle and do things for free, but also a place for people to make money too. I mean if you think someone is overcharging for something, go to the competitor and buy the cheaper one, don't steal it. You do that in RL you end up in Jail, just cause it is easier to get away with it online, do you think it is right? No!
Grow up! This is reality, shut up or go home! Cause you know what? If you live in the USA, ISPs have a new contract to catch online thieves like you, so they just made it harder for you to get away with because no matter how you look at it, it is still THEFT! Let content creators put their content where they feel it is safe, just like in real life, a designer store is not going to setup in the ghetto, because chances are higher they are going to get robbed, some still do it and that is their choice, so let the content creator choose, don't demonize them for making the choose that makes them comfortable and not you. Grow up this isn't high school anymore!
I am not hating open or closed grids, I believe each serves it's own purpose. I live in both open and closed grids, I feel both are comfortable places to live and be in. I do love all of you, and I hope you understand why I am so harsh in this blog, it is because it needs to be said, so I said it.

Some files were not shown because too many files have changed in this diff Show more