From dfcbd5fcd368b3a8d7c2d12884e356ef1cc8433c Mon Sep 17 00:00:00 2001
From: aster <137767097+aster-void@users.noreply.github.com>
Date: Sun, 18 May 2025 22:09:25 +0900
Subject: [PATCH 1/2] wip
---
contents/projects/coursemate/index.md | 19 +++++++++-----
contents/projects/itsuhima/index.md | 22 ++++++++++------
contents/projects/nikochan/index.md | 21 ++++++++++-----
contents/projects/syllabus/index.md | 21 ++++++++++-----
contents/projects/webcface/index.md | 16 ++++++++----
docs/contents/projects.md | 34 +++++++++++++++----------
src/components/common/ProjectList.astro | 10 ++++----
src/pages/projects/[...id].astro | 12 +++++++--
src/schema.ts | 33 ++++++++++++++++--------
9 files changed, 123 insertions(+), 65 deletions(-)
diff --git a/contents/projects/coursemate/index.md b/contents/projects/coursemate/index.md
index 87994443..290c5f9c 100644
--- a/contents/projects/coursemate/index.md
+++ b/contents/projects/coursemate/index.md
@@ -1,14 +1,21 @@
---
-title: CourseMate
+app:
+ name: CourseMate
+ description: 同じ授業の友達をつくろう
+ url: https://coursemate.utcode.net/
+ platform: web
+ domain: app
+
+date: 2025-05-14
kind: long-term
status: released
-date: 2025-05-14
+tags: [TypeScript, React]
+
thumbnail:
src: ./thumbnail.png
-description: 同じ授業の友達をつくろう
-tags: [TypeScript, React]
-github: https://github.com/ut-code/coursemate
-website: https://coursemate.utcode.net/
+
+social:
+ github: https://github.com/ut-code/coursemate
---
CourseMate は、同じ授業の友達をつくることができるアプリです。
diff --git a/contents/projects/itsuhima/index.md b/contents/projects/itsuhima/index.md
index 4f2b1eab..1554d487 100644
--- a/contents/projects/itsuhima/index.md
+++ b/contents/projects/itsuhima/index.md
@@ -1,17 +1,23 @@
---
-title: イツヒマ
+app:
+ name: イツヒマ
+ description: "いつ暇?で日程調整できるアプリ"
+ url: https://itsuhima.utcode.net
+ platform: [web]
+ domain: [app]
+
+date: 2025-05-14
kind: long-term
status: released
-date: 2025-05-14
-thumbnail:
- src: ./thumbnail.png
-
-description: "いつ暇?で日程調整できるアプリ"
tags:
- TypeScript
- React
-github: https://github.com/ut-code/itsuhima
-website: https://itsuhima.utcode.net
+
+thumbnail:
+ src: ./thumbnail.png
+
+social:
+ github: https://github.com/ut-code/itsuhima
---
イツヒマは、「いつ暇?」で日程調整できるアプリです。事前に何時間かかるか決まっていない日程調整を手早く行えます。
diff --git a/contents/projects/nikochan/index.md b/contents/projects/nikochan/index.md
index 3d76ed9e..53ed952f 100644
--- a/contents/projects/nikochan/index.md
+++ b/contents/projects/nikochan/index.md
@@ -1,15 +1,22 @@
---
-title: Falling Nikochan
+app:
+ name: Falling Nikochan
+ description: シンプルでかわいい音ゲーです。誰でも譜面を作ってシェアできます。
+ url: https://nikochan.utcode.net/
+ platform: [web]
+ domain: [game]
+
+date: 2024-09-24
kind: long-term
status: released
+tags: [JavaScript, TypeScript, React, Next.js, Hono, MongoDB]
+
thumbnail:
src: ./thumbnail.jpg
-date: 2024-09-24
-description: シンプルでかわいい音ゲーです。誰でも譜面を作ってシェアできます。
-tags: [JavaScript, TypeScript, React, Next.js, Hono, MongoDB]
-github: https://github.com/na-trium-144/falling-nikochan
-youtube: https://www.youtube.com/@nikochan144
-website: https://nikochan.utcode.net/
+
+social:
+ github: https://github.com/na-trium-144/falling-nikochan
+ youtube: https://www.youtube.com/@nikochan144
---
## 概要
diff --git a/contents/projects/syllabus/index.md b/contents/projects/syllabus/index.md
index f9baee39..7eae89d1 100644
--- a/contents/projects/syllabus/index.md
+++ b/contents/projects/syllabus/index.md
@@ -1,15 +1,22 @@
---
-title: シ楽バス
+app:
+ name: シ楽バス
+ description: 前期課程に特化した時間割サービス
+ url: https://syllabus.utcode.net/
+ platform: [web]
+ domain: [tool] # バックエンドついたら app になる
+
order: 3
+date: 2023-10-02
kind: long-term
status: stable
-thumbnail:
- src: ./thumbnail.jpg
-date: 2023-10-02
-description: 前期課程に特化した時間割サービス
tags: [JavaScript]
-github: https://github.com/ut-code/syllabus-frontend
-website: https://syllabus.utcode.net/
+
+thumbnail:
+ src: ./thumbnailjpg
+
+social:
+ github: https://github.com/ut-code/syllabus-frontend
---
## 概要
diff --git a/contents/projects/webcface/index.md b/contents/projects/webcface/index.md
index e24c8640..c4103f38 100644
--- a/contents/projects/webcface/index.md
+++ b/contents/projects/webcface/index.md
@@ -1,13 +1,19 @@
---
-title: WebCFace
+app:
+ name: WebCFace
+ description: プロセス間通信 & GUIを提供するライブラリ
+ platform: [desktop]
+ domain: [tool, lib] # WebUI の部分が tool, パッケージ部分が lib
+
+date: 2023-09-28
kind: long-term
status: stable
+tags: [C++, Python, JavaScript, TypeScript, WebSocket, MessagePack, React]
+
thumbnail:
src: ./thumbnail.png
-date: 2023-09-28
-description: プロセス間通信 & GUIを提供するライブラリ
-tags: [C++, Python, JavaScript, TypeScript, WebSocket, MessagePack, React]
-github: https://github.com/na-trium-144/webcface
+social:
+ github: https://github.com/na-trium-144/webcface
---
- ROS1 のようなプロセス間通信と、GUI によるデータの可視化や関数呼び出し、また Immediate-Mode でのシンプルな UI の作成ができます。
diff --git a/docs/contents/projects.md b/docs/contents/projects.md
index ff377dd7..4bda7752 100644
--- a/docs/contents/projects.md
+++ b/docs/contents/projects.md
@@ -14,20 +14,26 @@
## frontmatter
-| キー | 必須 | 説明 |
-| --------------- | ---- | ------------------------------------------------------------------------------ |
-| `title` | ✅ | プロジェクト名 |
-| `order` | | 表示順。指定されなかった場合は `date` 降順でソートされます。 |
-| `date` | ✅ | 記事の初回執筆日。ソートのみで利用しています。 |
-| `thumbnail` | ✅ | イメージ画像に関するデータ。 |
-| `thumbnail.src` | ✅ | イメージファイルへの markdown からの相対パス。 |
-| `thumbnail.fit` | | イメージのクロップ方法。 default = "cover"。 |
-| `thumbnail.bg` | | イメージの背景色。ロード中と `crop` = "contain" のときの背景に使われています。 |
-| `description` | ✅ | 短い説明。 |
-| `tags` | | 使用されている技術など。現状タグごとのフィルタリング機能等は提供していません。 |
-| `github` | | プロジェクトの GitHub 上での URL。 |
-| `youtube` | | プロジェクトの YouTube 上での URL。 |
-| `website` | | プロジェクトのウェブサイトの URL。 |
+| キー | 必須 | 型 | 説明 |
+| ----------------- | ---- | ------------ | ------------------------------------------------------------------------------------- |
+| `app.name` | ✅ | string | プロジェクト名 |
+| `app.description` | ✅ | string | プロジェクトの短い説明。 |
+| `app.url` | | string->url | アプリにアクセスできる URL。 |
+| `app.platform` | ✅ | string[] | ソフトウェアの配布プラットフォーム。`web`, `mobile`, `desktop`, `cli`。 |
+| `app.domain` | ✅ | string[] | ソフトウェアの種別 (クソ雑ドキュメント)。 `app`, `game`, `tool`, `site`, `lib` など。 |
+| `order` | | number? | 表示順。指定されなかった場合は `date` 降順でソートされます。 |
+| `date` | ✅ | date | 記事の初回執筆日。ソートのみで利用しています。 |
+| `kind` | ✅ | string | アプリケーションの開発体系。`long-term`, `festival`, `hackathon` の 3 つ。 |
+| `status` | ✅ | string | プロジェクトの現状。詳細は `src/schema.ts` を参照。 |
+| `members` | | string[]? | プロジェクトのメンバー。まだメンバーページを作ってなくても問題ないです。 |
+| `tags` | | string[]? | 使用されている技術。タグごとのフィルタリング機能等は提供していません。 |
+| `thumbnail.src` | ✅ | string->path | イメージファイルへの markdown からの相対パス。 |
+| `thumbnail.fit` | | string? | イメージのクロップ方法。 default = "cover"。 |
+| `thumbnail.bg` | | string? | イメージの背景色。ロード中と `crop` = "contain" のときの背景に使われています。 |
+| `social.github` | | string->url? | プロジェクトの GitHub 上での URL。 |
+| `social.website` | | string->url? | プロジェクトのウェブサイトの URL。(`app.url` と別で広報用などの Website がある場合) |
+| `social.youtube` | | string->url? | プロジェクトの YouTube 上での URL。 |
+| `social.twitter` | | string->url? | プロジェクトのツイッター (現 X) の URL。 |
## body について
diff --git a/src/components/common/ProjectList.astro b/src/components/common/ProjectList.astro
index c85ef599..86d2d4a0 100644
--- a/src/components/common/ProjectList.astro
+++ b/src/components/common/ProjectList.astro
@@ -23,7 +23,7 @@ const { projects, variant = "full" } = props;
>
- {project.data.title}
+ {project.data.app.name}
{/* h-14: 2 行はいる */}
- {project.data.description.length < 120
- ? project.data.description
- : project.data.description.slice(0, 120) + "..."}
+ {project.data.app.description.length < 120
+ ? project.data.app.description
+ : project.data.app.description.slice(0, 120) + "..."}
diff --git a/src/pages/projects/[...id].astro b/src/pages/projects/[...id].astro
index eae5627e..0d440b15 100644
--- a/src/pages/projects/[...id].astro
+++ b/src/pages/projects/[...id].astro
@@ -21,8 +21,16 @@ const { project } = Astro.props;
const { Content } = await render(project);
let iconSrc: string | undefined = undefined;
-if (project.data.website && project.data.status !== "dead") {
- iconSrc = await fetchFavicon(project.data.website);
+if (project.data.status !== "dead") {
+ if (project.data.app.url) {
+ iconSrc = await fetchFavicon(project.data.app.url);
+ }
+ if (!iconSrc && project.data.social?.website) {
+ iconSrc = await fetchFavicon(project.data.social.website);
+ }
+ if (!iconSrc && project.data.social?.twitter) {
+ iconSrc = await fetchFavicon(project.data.social.twitter);
+ }
}
---
diff --git a/src/schema.ts b/src/schema.ts
index c9c36bea..8a3028b5 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -45,24 +45,35 @@ export const CreateArticleSchema = ({ image }: { image: ImageFunction }) =>
export type Kind = (typeof kinds)[number]["frontmatter"];
export const CreateProjectSchema = ({ image }: { image: ImageFunction }) =>
z.object({
- title: z.string(),
+ app: z.object({
+ name: z.string(),
+ description: z.string(),
+ url: z.string().url(),
+ // 各プロパティは必要に応じて追加
+ platform: z.enum(["web", "mobile", "desktop", "cli"]),
+ domain: z.enum(["app", "game", "tool", "site", "lib"]),
+ }),
kind: z.enum(kinds.map((kind) => kind.frontmatter) as [Kind, ...Kind[]]),
status: z.enum([
- "plan",
- "under-development",
- "released",
- "stable",
- "finished",
- "dead",
+ "plan", // プラン段階
+ "under-development", // 開発中 (未リリース)
+ "released", // リリース済み
+ "stable", // アクティブな開発は停止
+ "finished", // 開発停止、動いてはいる
+ "dead", // 開発停止、もう動かない
]),
order: z.number().optional(),
date: z.date(),
thumbnail: Thumbnail({ image }),
- description: z.string(),
tags: z.array(z.string()).optional().default([]),
- github: z.string().url().optional(),
- youtube: z.string().url().optional(),
- website: z.string().url().optional(),
+ social: z
+ .object({
+ github: z.string().url().optional(),
+ youtube: z.string().url().optional(),
+ website: z.string().url().optional(),
+ twitter: z.string().url().optional(),
+ })
+ .optional(),
});
export const CreateMemberSchema = ({ image }: { image: ImageFunction }) =>
From e43ea4a317698422de699b2ede9955cafe806e62 Mon Sep 17 00:00:00 2001
From: aster <137767097+aster-void@users.noreply.github.com>
Date: Mon, 19 May 2025 00:34:50 +0900
Subject: [PATCH 2/2] add project descriptors and implement favicon caching
---
contents/projects/coursemate/index.md | 4 +-
contents/projects/create-cpu/index.md | 17 ++--
contents/projects/dot-tutor-learn/index.md | 20 +++--
.../projects/dot-tutor-translate/index.md | 19 ++--
contents/projects/dull-meshi/index.md | 17 ++--
contents/projects/extralearn/index.md | 21 +++--
.../hackathon/2023-08-17/call-paper.md | 13 ++-
.../projects/hackathon/2023-08-17/denigma.md | 14 ++-
.../hackathon/2023-08-17/music-app.md | 14 ++-
.../projects/hackathon/2023-08-17/todo.md | 13 ++-
.../projects/hackathon/2024-06-08/bowling.md | 15 +++-
.../hackathon/2024-06-08/shift-syncer.md | 15 +++-
.../hackathon/2024-06-08/typing-script.md | 16 ++--
.../2025-02-08/tower-battle/index.md | 21 +++--
.../projects/hackathon/2025-02-22/gemmit.md | 24 +++---
.../projects/hackathon/hackathon-template.md_ | 15 +++-
contents/projects/how-match/index.md | 26 +++---
contents/projects/itsuhima/index.md | 2 +-
contents/projects/nikochan/index.md | 2 +-
contents/projects/syllabus/index.md | 2 +-
contents/projects/touhoubeat/index.md | 18 ++--
contents/projects/ut-bridge/index.md | 24 ++++--
contents/projects/utcode-learn/index.md | 21 +++--
docs/contents/projects.md | 40 ++++-----
src/lib/cache.ts | 79 +++++++++++++++++
src/lib/fetch-favicon.ts | 86 +++++++++++--------
src/pages/projects/[...id].astro | 29 ++++---
src/schema.ts | 15 +++-
28 files changed, 414 insertions(+), 188 deletions(-)
create mode 100644 src/lib/cache.ts
diff --git a/contents/projects/coursemate/index.md b/contents/projects/coursemate/index.md
index 290c5f9c..64b1f39c 100644
--- a/contents/projects/coursemate/index.md
+++ b/contents/projects/coursemate/index.md
@@ -3,8 +3,8 @@ app:
name: CourseMate
description: 同じ授業の友達をつくろう
url: https://coursemate.utcode.net/
- platform: web
- domain: app
+ platform: [web]
+ domain: [app]
date: 2025-05-14
kind: long-term
diff --git a/contents/projects/create-cpu/index.md b/contents/projects/create-cpu/index.md
index 0d71e2c7..73dc7ce8 100644
--- a/contents/projects/create-cpu/index.md
+++ b/contents/projects/create-cpu/index.md
@@ -1,14 +1,21 @@
---
-title: CreateCPU
+app:
+ name: CreateCPU
+ url: https://create-cpu.utcode.net/
+ description: 現代コンピュータの頭脳である CPU は、単純な論理回路を大量に組み合わせた巨大なシステムです。CreateCPU を使うと、最も単純な回路素子をつなぎ合わせて自分だけの CPU を作り上げることができます。
+ platform: [web]
+ domain: [tool]
+
kind: long-term
status: released
date: 2023-04-01
+tags: [TypeScript, React]
+
thumbnail:
src: ./thumbnail.png
-description: 現代コンピュータの頭脳である CPU は、単純な論理回路を大量に組み合わせた巨大なシステムです。CreateCPU を使うと、最も単純な回路素子をつなぎ合わせて自分だけの CPU を作り上げることができます。
-tags: [TypeScript, React]
-github: https://github.com/ut-code/create-cpu
-website: https://create-cpu.utcode.net/
+
+social:
+ github: https://github.com/ut-code/create-cpu
---
CreateCPU は、Web ブラウザ上で論理回路を学ぶことのできるプラットフォームです。最終的に CPU が作れるようになることを目指しています。
diff --git a/contents/projects/dot-tutor-learn/index.md b/contents/projects/dot-tutor-learn/index.md
index b1bb1407..b6057994 100644
--- a/contents/projects/dot-tutor-learn/index.md
+++ b/contents/projects/dot-tutor-learn/index.md
@@ -1,15 +1,23 @@
---
-title: Dot Tutor Learn
+app:
+ name: Dot Tutor Learn
+ url: https://learn.dot-tutor.com/
+ description: 体験型点字学習サイト
+ platform: [web]
+ domain: [learn]
+
+date: 2023-01-28
kind: long-term
status: stable
+tags: [TypeScript, React]
+
thumbnail:
src: ./thumbnail.png
fit: contain
-date: 2023-01-28
-description: 体験型点字学習サイト Dot Tutor Learn です。
-tags: [TypeScript, React]
-github: https://github.com/ut-code/dot-tutor
-website: https://learn.dot-tutor.com/
+
+social:
+ github: https://github.com/ut-code/dot-tutor
+ website: https://learn.dot-tutor.com/
---
ut.code();が作成した体験型点字学習サイトです。
diff --git a/contents/projects/dot-tutor-translate/index.md b/contents/projects/dot-tutor-translate/index.md
index ed0ea645..5db6b8f8 100644
--- a/contents/projects/dot-tutor-translate/index.md
+++ b/contents/projects/dot-tutor-translate/index.md
@@ -1,15 +1,22 @@
---
-title: Dot Tutor Translate
+app:
+ name: Dot Tutor Translate
+ url: https://translate.dot-tutor.com/
+ description: 点字翻訳サイト
+ platform: [web]
+ domain: [tool]
+
+date: 2023-01-28
kind: long-term
status: stable
+tags: [TypeScript, React, Python, FastAPI]
+
thumbnail:
src: ./thumbnail.png
fit: contain
-date: 2023-01-28
-description: 点字翻訳サイト Dot Tutor Translate です。
-tags: [TypeScript, React, Python, FastAPI]
-github: https://github.com/ut-code/dot-tutor
-website: https://translate.dot-tutor.com/
+
+social:
+ github: https://github.com/ut-code/dot-tutor
---
Dot Tutor Translate は、ut.code();が作成した自動点字翻訳サイトです。
diff --git a/contents/projects/dull-meshi/index.md b/contents/projects/dull-meshi/index.md
index 1173298a..9237598f 100644
--- a/contents/projects/dull-meshi/index.md
+++ b/contents/projects/dull-meshi/index.md
@@ -1,15 +1,22 @@
---
-title: だるめし
+app:
+ name: だるめし
+ description: 質問に答えていくだけで献立を提案してくれるアプリ
+ platform: [web]
+ domain: [app]
+
+date: 2023-04-05
kind: long-term
status: stable
+tags: [TypeScript, React]
+
thumbnail:
src: ./thumbnail.jpg
fit: contain
bg: "#E6E0DB"
-date: 2023-04-05
-description: 質問に答えていくだけで献立を提案してくれるアプリ
-tags: [TypeScript, React]
-github: https://github.com/ut-code/menu
+
+social:
+ github: https://github.com/ut-code/menu
---
質問に答えていくだけで献立を提案してくれるアプリです。
diff --git a/contents/projects/extralearn/index.md b/contents/projects/extralearn/index.md
index 86171ca1..01f74ff3 100644
--- a/contents/projects/extralearn/index.md
+++ b/contents/projects/extralearn/index.md
@@ -1,22 +1,29 @@
---
+app:
+ name: Extra Learn
+ url: https://extra.utcode.net
+ description: ut.code(); Learn と実際のプロジェクトで使われている最新技術のギャップを埋める
+ platform: [web]
+ domain: [learn]
+
date: 2025-04-04
-kind: long-term
-title: Extra Learn
-description: ut.code(); Learn と実際のプロジェクトで使われている最新技術のギャップを埋める
members:
- ykobayashi
- rtakanaka
leader: ykobayashi
-thumbnail:
- src: ./thumbnail.png
-github: https://github.com/ut-code/extralearn
-website: https://extra.utcode.net
+kind: long-term
status: under-development
tags:
- TypeScript
- Astro
- Starlight
- Cloudflare
+
+thumbnail:
+ src: ./thumbnail.png
+
+social:
+ github: https://github.com/ut-code/extralearn
---
# 現状と問題
diff --git a/contents/projects/hackathon/2023-08-17/call-paper.md b/contents/projects/hackathon/2023-08-17/call-paper.md
index 4f6e5ad4..442ea536 100644
--- a/contents/projects/hackathon/2023-08-17/call-paper.md
+++ b/contents/projects/hackathon/2023-08-17/call-paper.md
@@ -1,5 +1,11 @@
---
-title: Call Paper
+app:
+ name: Call Paper
+ url: https://call-paper.pages.dev
+ description: 論文の引用関係を可視化できるアプリ
+ platform: [web]
+ domain: [tool]
+
date: 2023-08-17
kind: hackathon
status: finished
@@ -8,7 +14,6 @@ thumbnail:
src: ./call-paper.png
position: left
-description: 論文の引用関係を可視化できるアプリ
-github: https://github.com/ut-code/call-paper
-website: https://call-paper.pages.dev
+social:
+ github: https://github.com/ut-code/call-paper
---
diff --git a/contents/projects/hackathon/2023-08-17/denigma.md b/contents/projects/hackathon/2023-08-17/denigma.md
index e0457d01..c3d0fc30 100644
--- a/contents/projects/hackathon/2023-08-17/denigma.md
+++ b/contents/projects/hackathon/2023-08-17/denigma.md
@@ -1,13 +1,19 @@
---
-title: Denigma
+app:
+ name: Denigma
+ url: https://utcode-denigma.onrender.com/
+ description: 暗号解読ゲーム。シーザー暗号やRSA暗号など、基本的な暗号に触れてみよう!
+ platform: [web]
+ domain: [game]
+
date: 2023-08-17
kind: hackathon
status: dead
+
thumbnail:
src: ./denigma.png
position: left
-description: "暗号解読ゲーム。シーザー暗号やRSA暗号など、基本的な暗号に触れてみよう!"
-github: https://github.com/ut-code/denigma
-website: https://utcode-denigma.onrender.com/
+social:
+ github: https://github.com/ut-code/denigma
---
diff --git a/contents/projects/hackathon/2023-08-17/music-app.md b/contents/projects/hackathon/2023-08-17/music-app.md
index 67c46f3f..e6f41eef 100644
--- a/contents/projects/hackathon/2023-08-17/music-app.md
+++ b/contents/projects/hackathon/2023-08-17/music-app.md
@@ -1,15 +1,21 @@
---
-title: music-app
+app:
+ name: music-app
+ url: https://ut-code.github.io/music-app
+ description: その日の気分にあった作業用BGMを生成してくれるアプリ
+ platform: [web]
+ domain: [app]
+
date: 2023-08-17
kind: hackathon
status: finished
+
thumbnail:
src: ./music-app.png
position: top
fit: contain
bg: white
-description: その日の気分にあった作業用BGMを生成してくれるアプリ
-github: https://github.com/ut-code/music-app
-website: https://ut-code.github.io/music-app
+social:
+ github: https://github.com/ut-code/music-app
---
diff --git a/contents/projects/hackathon/2023-08-17/todo.md b/contents/projects/hackathon/2023-08-17/todo.md
index 5364e904..13e191b5 100644
--- a/contents/projects/hackathon/2023-08-17/todo.md
+++ b/contents/projects/hackathon/2023-08-17/todo.md
@@ -1,10 +1,17 @@
---
-title: todoアプリ
+app:
+ name: todoアプリ
+ description: ToDoを管理できる Web アプリ
+ platform: [web]
+ domain: [app]
+
date: 2023-08-17
kind: hackathon
status: finished
+
thumbnail:
src: ./todo.png
-description: ToDoを管理できる Web アプリ
-github: https://github.com/ut-code/todo-2023-hackathon
+
+social:
+ github: https://github.com/ut-code/todo-2023-hackathon
---
diff --git a/contents/projects/hackathon/2024-06-08/bowling.md b/contents/projects/hackathon/2024-06-08/bowling.md
index 57652e9d..970a3104 100644
--- a/contents/projects/hackathon/2024-06-08/bowling.md
+++ b/contents/projects/hackathon/2024-06-08/bowling.md
@@ -1,11 +1,18 @@
---
-title: 壁よけボウリング
+app:
+ name: 壁よけボウリング
+ url: https://ut-code.github.io/bowling-app/
+ description: 障害物を避けてピンを倒そう! どこか懐かしくてどこか新感覚のボウリングゲームをお楽しみあれ!
+ platform: [web]
+ domain: [game]
+
kind: hackathon
status: finished
date: 2024-06-08
-description: 障害物を避けてピンを倒そう! どこか懐かしくてどこか新感覚のボウリングゲームをお楽しみあれ!
+
thumbnail:
src: ./bowling.png
-website: https://ut-code.github.io/bowling-app/
-github: https://github.com/ut-code/bowling-app
+
+social:
+ github: https://github.com/ut-code/bowling-app
---
diff --git a/contents/projects/hackathon/2024-06-08/shift-syncer.md b/contents/projects/hackathon/2024-06-08/shift-syncer.md
index 56bc0a8a..7de69eb6 100644
--- a/contents/projects/hackathon/2024-06-08/shift-syncer.md
+++ b/contents/projects/hackathon/2024-06-08/shift-syncer.md
@@ -1,12 +1,19 @@
---
-title: ShiftSyncer
+app:
+ name: ShiftSyncer
+ description: 複数人から登録されたシフトの希望を基に、最適なシフトを作成するアプリです。
+ platform: [web]
+ domain: [app]
+
+date: 2024-06-08
kind: hackathon
status: finished
-date: 2024-06-08
-description: 複数人から登録されたシフトの希望を基に、最適なシフトを作成するアプリです。
+
thumbnail:
src: ./shift-syncer.png
-github: https://github.com/ut-code/ShiftSyncer
+
+social:
+ github: https://github.com/ut-code/ShiftSyncer
---
# ShiftSyncer
diff --git a/contents/projects/hackathon/2024-06-08/typing-script.md b/contents/projects/hackathon/2024-06-08/typing-script.md
index f3935686..d8130fc0 100644
--- a/contents/projects/hackathon/2024-06-08/typing-script.md
+++ b/contents/projects/hackathon/2024-06-08/typing-script.md
@@ -1,14 +1,20 @@
---
-title: TypingScript
+app:
+ name: TypingScript
+ url: https://ut-code.github.io/TypingScript
+ description: ソースコードに特化したタイピング練習サイトです。
+ platform: [web]
+ domain: [game]
+
+date: 2024-06-08
kind: hackathon
status: finished
-date: 2024-06-08
-description: ソースコードに特化したタイピング練習サイトです。
+
thumbnail:
src: ./typing-script.png
fit: contain
bg: white
-website: https://ut-code.github.io/TypingScript
-github: https://github.com/ut-code/TypingScript
+social:
+ github: https://github.com/ut-code/TypingScript
---
diff --git a/contents/projects/hackathon/2025-02-08/tower-battle/index.md b/contents/projects/hackathon/2025-02-08/tower-battle/index.md
index f7091077..39313f62 100644
--- a/contents/projects/hackathon/2025-02-08/tower-battle/index.md
+++ b/contents/projects/hackathon/2025-02-08/tower-battle/index.md
@@ -1,18 +1,23 @@
---
-title: Tower Battle
+app:
+ name: Tower Battle
+ url: https://tower-d5g.pages.dev
+ description: 自機も移動して攻撃するタワーディフェンスゲーム
+ platform: [web]
+ domain: [game]
+
date: 2025-02-08
kind: hackathon
status: stable
-thumbnail:
- src: ./thumbnail.png
-description: "簡単なタワーディフェンスゲームです。自機も移動して攻撃すると言う特徴があります。"
-members:
- - ykobayashi
tags:
- TypeScript
- Svelte
- DaisyUI
- Cloudflare
-github: https://github.com/aster-void/tower-battle
-website: https://tower-d5g.pages.dev
+
+thumbnail:
+ src: ./thumbnail.png
+
+social:
+ github: https://github.com/aster-void/tower-battle
---
diff --git a/contents/projects/hackathon/2025-02-22/gemmit.md b/contents/projects/hackathon/2025-02-22/gemmit.md
index ce89d3d8..035648f8 100644
--- a/contents/projects/hackathon/2025-02-22/gemmit.md
+++ b/contents/projects/hackathon/2025-02-22/gemmit.md
@@ -1,20 +1,24 @@
---
-title: Gemmit
+app:
+ name: Gemmit
+ url: https://www.npmjs.com/package/@tknkaa/gemmit
+ description: Git の差分から Gemini がコミットメッセージを考えてくれるツール
+ platform: [cli]
+ domain: [tool]
+
kind: hackathon
date: 2025-02-22
status: released
-description: "Git の差分から Gemini がコミットメッセージを考えてくれるツール"
+tags:
+ - Rust
+ - AI
+
thumbnail:
src: ./gemmit.png
position: top
bg: white
-members:
- - rtakanaka
-tags:
- - Rust
- - AI
-
-github: https://github.com/tknkaa/gemmit
-website: https://www.npmjs.com/package/@tknkaa/gemmit
+social:
+ github: https://github.com/tknkaa/gemmit
+ website: https://www.npmjs.com/package/@tknkaa/gemmit
---
diff --git a/contents/projects/hackathon/hackathon-template.md_ b/contents/projects/hackathon/hackathon-template.md_
index 409dedef..a84c0968 100644
--- a/contents/projects/hackathon/hackathon-template.md_
+++ b/contents/projects/hackathon/hackathon-template.md_
@@ -1,11 +1,18 @@
---
-title: Title
+app:
+ name: Title
+ url: https://your-project.pages.dev
+ description: あなたが作った世界で一つだけのアプリ
+ platform: [web] # web / desktop / cli / ...
+ domain: [app] # app / game / tool / ...
+
date: hackathon-start-date
kind: hackathon
status: finished
+
thumbnail:
src: ./thumbnail.png
-description: "簡単なタワーディフェンスゲームです。自機も移動して攻撃すると言う特徴があります。"
-github: https://github.com/ut-code/your-project
-website: https://your-project.pages.dev
+
+social:
+ github: https://github.com/ut-code/your-project
---
diff --git a/contents/projects/how-match/index.md b/contents/projects/how-match/index.md
index 46391a58..b1f5de21 100644
--- a/contents/projects/how-match/index.md
+++ b/contents/projects/how-match/index.md
@@ -1,18 +1,16 @@
---
-title: How match
-kind: long-term
-description: マッチング理論を用いた、担当振り分けアプリです。
+app:
+ name: How match
+ url: https://howmatch.pages.dev
+ description: マッチング理論を用いた、担当振り分けアプリ
+ platform: [web]
+ domain: [tool]
+
date: 2025-01-01
+kind: long-term
members: [ykobayashi, snakamura, kshibayama, tyasumura]
leader: ykobayashi
-thumbnail:
- src: ./thumbnail.png
- fit: cover
- bg: white
-
status: released
-github: https://github.com/ut-code/how-match
-website: https://howmatch.pages.dev
tags:
- TypeScript
- Svelte
@@ -22,5 +20,13 @@ tags:
- Hono
- Drizzle
- Cloudflare
+
+thumbnail:
+ src: ./thumbnail.png
+ fit: cover
+ bg: white
+
+social:
+ github: https://github.com/ut-code/how-match
---
diff --git a/contents/projects/itsuhima/index.md b/contents/projects/itsuhima/index.md
index 1554d487..2edee00d 100644
--- a/contents/projects/itsuhima/index.md
+++ b/contents/projects/itsuhima/index.md
@@ -1,7 +1,7 @@
---
app:
name: イツヒマ
- description: "いつ暇?で日程調整できるアプリ"
+ description: いつ暇?で日程調整できるアプリ
url: https://itsuhima.utcode.net
platform: [web]
domain: [app]
diff --git a/contents/projects/nikochan/index.md b/contents/projects/nikochan/index.md
index 53ed952f..7416fbc2 100644
--- a/contents/projects/nikochan/index.md
+++ b/contents/projects/nikochan/index.md
@@ -1,7 +1,7 @@
---
app:
name: Falling Nikochan
- description: シンプルでかわいい音ゲーです。誰でも譜面を作ってシェアできます。
+ description: シンプルでかわいい音ゲー
url: https://nikochan.utcode.net/
platform: [web]
domain: [game]
diff --git a/contents/projects/syllabus/index.md b/contents/projects/syllabus/index.md
index 7eae89d1..0bfa9753 100644
--- a/contents/projects/syllabus/index.md
+++ b/contents/projects/syllabus/index.md
@@ -13,7 +13,7 @@ status: stable
tags: [JavaScript]
thumbnail:
- src: ./thumbnailjpg
+ src: ./thumbnail.jpg
social:
github: https://github.com/ut-code/syllabus-frontend
diff --git a/contents/projects/touhoubeat/index.md b/contents/projects/touhoubeat/index.md
index e0460d61..f166e5e6 100644
--- a/contents/projects/touhoubeat/index.md
+++ b/contents/projects/touhoubeat/index.md
@@ -1,13 +1,21 @@
---
-title: 東方競争曲
+app:
+ name: 東方競争曲
+ url: https://th-beat.net/
+ description: 東方Projectのアレンジ曲で遊べるリズムゲーム
+ platform: [web]
+ domain: [site]
+
+date: 2023-12-19
kind: long-term
+tags: [C#, Unity, AWS, Go, TypeScript, React]
status: stable
+
thumbnail:
src: ./thumbnail.png
-date: 2023-12-19
-description: 東方Projectのアレンジ曲で遊べるリズムゲーム
-tags: [C#, Unity, AWS, Go, TypeScript, React]
-website: https://th-beat.net/
+
+social:
+ website: https://th-beat.net/
---
## 概要
diff --git a/contents/projects/ut-bridge/index.md b/contents/projects/ut-bridge/index.md
index 5fad09ce..dfd0296e 100644
--- a/contents/projects/ut-bridge/index.md
+++ b/contents/projects/ut-bridge/index.md
@@ -1,13 +1,15 @@
---
-title: UT-Bridge
-kind: long-term
-status: released
+app:
+ name: UT-Bridge
+ url: https://ut-bridge-web.pages.dev
+ description: 同じキャンパスにいる留学生と手軽に交流できるアプリ
+ platform: [web]
+ domain: [app]
+
order: 2
date: 2025-04-01
-thumbnail:
- src: ./thumbnail.png
-
-description: "同じキャンパスにいる留学生と手軽に交流できるアプリです。従来の言語交換プログラムとは異なり、面倒な手続きなしで、気軽に国際交流を楽しめます。"
+kind: long-term
+status: released
tags:
- TypeScript
- React
@@ -19,8 +21,12 @@ tags:
- Cloudflare
- Firebase
- Fly.io
-github: https://github.com/ut-code/ut-bridge
-website: https://ut-bridge-web.pages.dev
+
+thumbnail:
+ src: ./thumbnail.png
+
+social:
+ github: https://github.com/ut-code/ut-bridge
---
## UT-Bridgeとは?
diff --git a/contents/projects/utcode-learn/index.md b/contents/projects/utcode-learn/index.md
index 4e886d17..48936eb8 100644
--- a/contents/projects/utcode-learn/index.md
+++ b/contents/projects/utcode-learn/index.md
@@ -1,15 +1,22 @@
---
-title: ut.code(); Learn
+app:
+ name: ut.code(); Learn
+ url: https://learn.utcode.net/
+ description: ut.code(); 公式の学習カリキュラムです。ut.code(); 外部の方でもご利用いただけます。
+ platform: [web]
+ domain: [learn]
+
+order: 1
+date: 2022-04-01
kind: long-term
status: stable
-order: 1
+tags: [JavaScript, TypeScript, React, Node.js, Docusaurus]
+
thumbnail:
src: ./thumbnail.jpg
-date: 2022-04-01
-description: ut.code(); 公式の学習カリキュラムです。ut.code(); 外部の方でもご利用いただけます。
-tags: [JavaScript, TypeScript, React, Node.js, Docusaurus]
-github: https://github.com/ut-code/utcode-learn
-website: https://learn.utcode.net/
+
+social:
+ github: https://github.com/ut-code/utcode-learn
---
ut.code(); Learn は、全くの未経験の状態からでも、自力で Web サービスが開発できるようになれるようにするための、ut.code(); 公式の教材です。
diff --git a/docs/contents/projects.md b/docs/contents/projects.md
index 4bda7752..fb3202ad 100644
--- a/docs/contents/projects.md
+++ b/docs/contents/projects.md
@@ -14,26 +14,26 @@
## frontmatter
-| キー | 必須 | 型 | 説明 |
-| ----------------- | ---- | ------------ | ------------------------------------------------------------------------------------- |
-| `app.name` | ✅ | string | プロジェクト名 |
-| `app.description` | ✅ | string | プロジェクトの短い説明。 |
-| `app.url` | | string->url | アプリにアクセスできる URL。 |
-| `app.platform` | ✅ | string[] | ソフトウェアの配布プラットフォーム。`web`, `mobile`, `desktop`, `cli`。 |
-| `app.domain` | ✅ | string[] | ソフトウェアの種別 (クソ雑ドキュメント)。 `app`, `game`, `tool`, `site`, `lib` など。 |
-| `order` | | number? | 表示順。指定されなかった場合は `date` 降順でソートされます。 |
-| `date` | ✅ | date | 記事の初回執筆日。ソートのみで利用しています。 |
-| `kind` | ✅ | string | アプリケーションの開発体系。`long-term`, `festival`, `hackathon` の 3 つ。 |
-| `status` | ✅ | string | プロジェクトの現状。詳細は `src/schema.ts` を参照。 |
-| `members` | | string[]? | プロジェクトのメンバー。まだメンバーページを作ってなくても問題ないです。 |
-| `tags` | | string[]? | 使用されている技術。タグごとのフィルタリング機能等は提供していません。 |
-| `thumbnail.src` | ✅ | string->path | イメージファイルへの markdown からの相対パス。 |
-| `thumbnail.fit` | | string? | イメージのクロップ方法。 default = "cover"。 |
-| `thumbnail.bg` | | string? | イメージの背景色。ロード中と `crop` = "contain" のときの背景に使われています。 |
-| `social.github` | | string->url? | プロジェクトの GitHub 上での URL。 |
-| `social.website` | | string->url? | プロジェクトのウェブサイトの URL。(`app.url` と別で広報用などの Website がある場合) |
-| `social.youtube` | | string->url? | プロジェクトの YouTube 上での URL。 |
-| `social.twitter` | | string->url? | プロジェクトのツイッター (現 X) の URL。 |
+| キー | 必須 | 型 | 説明 |
+| ----------------- | ---- | ------------ | ----------------------------------------------------------------------------------------------------- |
+| `app.name` | ✅ | string | プロジェクト名 |
+| `app.description` | ✅ | string | プロジェクトの短い説明。 |
+| `app.url` | | string->url | アプリにアクセスできる URL。 |
+| `app.platform` | ✅ | string[] | ソフトウェアの配布プラットフォーム。`web`, `mobile`, `desktop`, `cli`。 |
+| `app.domain` | ✅ | string[] | ソフトウェアの種別 (クソ雑ドキュメント)。 `app`, `game`, `tool`, `site`, `learn` (教材), `lib` など。 |
+| `order` | | number? | 表示順。指定されなかった場合は `date` 降順でソートされます。 |
+| `date` | ✅ | date | 記事の初回執筆日。ソートのみで利用しています。 |
+| `kind` | ✅ | string | アプリケーションの開発体系。`long-term`, `festival`, `hackathon` の 3 つ。 |
+| `status` | ✅ | string | プロジェクトの現状。詳細は `src/schema.ts` を参照。 |
+| `members` | | string[]? | プロジェクトのメンバー。まだメンバーページを作ってなくても問題ないです。 |
+| `tags` | | string[]? | 使用されている技術。タグごとのフィルタリング機能等は提供していません。 |
+| `thumbnail.src` | ✅ | string->path | イメージファイルへの markdown からの相対パス。 |
+| `thumbnail.fit` | | string? | イメージのクロップ方法。 default = "cover"。 |
+| `thumbnail.bg` | | string? | イメージの背景色。ロード中と `crop` = "contain" のときの背景に使われています。 |
+| `social.github` | | string->url? | プロジェクトの GitHub 上での URL。 |
+| `social.website` | | string->url? | プロジェクトのウェブサイトの URL。(`app.url` と別で広報用などの Website がある場合) |
+| `social.youtube` | | string->url? | プロジェクトの YouTube 上での URL。 |
+| `social.twitter` | | string->url? | プロジェクトのツイッター (現 X) の URL。 |
## body について
diff --git a/src/lib/cache.ts b/src/lib/cache.ts
new file mode 100644
index 00000000..89ceada6
--- /dev/null
+++ b/src/lib/cache.ts
@@ -0,0 +1,79 @@
+import fs from "node:fs/promises";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+import type { Schema } from "astro/zod";
+/**
+ - this uses FS to cache. only use this for expensive things that need FS caching (such as fetching from the Internet)
+ - this does not cache `null` or `undefined`.
+ */
+export async function cache(
+ key: string,
+ schema: Schema,
+ load: () => Promise,
+): Promise {
+ const path = await toPath(sanitizeKey(key));
+
+ // cache load
+ if (await exists(path)) {
+ try {
+ const file = await fs.readFile(path, {
+ encoding: "utf8",
+ });
+ const val = schema.parse(JSON.parse(file));
+ // cache hit
+ return val;
+ } catch (err) {
+ console.warn("failed to parse retrieved cache:", err);
+ }
+ }
+
+ // cache miss
+ const val = await load();
+
+ const parse = schema.safeParse(val);
+ if (parse.success) {
+ if (val != null) {
+ // write cache
+ await fs.writeFile(path, JSON.stringify(val));
+ }
+ } else {
+ // skip lcache
+ console.warn(
+ "[warning] failed to validate freshly fetched value, skipping caching. error:",
+ parse.error,
+ );
+ }
+ return val;
+}
+
+async function toPath(scacheKey: string): Promise {
+ const basePath = `${await getBasedir()}/.astro/x-cache`;
+ if (!(await exists(basePath))) {
+ await fs.mkdir(basePath);
+ }
+ return `${basePath}/${scacheKey}`;
+}
+
+async function exists(path: string) {
+ try {
+ await fs.stat(path);
+ return true;
+ } catch (err) {
+ return false;
+ }
+}
+function sanitizeKey(k: string): string {
+ return k.replaceAll("/", "");
+}
+let baseDir: string | null = null;
+async function getBasedir() {
+ if (baseDir) return baseDir;
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
+ const entries = __dirname.split("/");
+ while (!(await exists(`${entries.join("/")}/.git`))) {
+ entries.pop();
+ }
+ const retval = entries.join("/");
+ baseDir = retval;
+ return retval;
+}
diff --git a/src/lib/fetch-favicon.ts b/src/lib/fetch-favicon.ts
index e35709f6..e588de65 100644
--- a/src/lib/fetch-favicon.ts
+++ b/src/lib/fetch-favicon.ts
@@ -1,4 +1,10 @@
import { load as cheerioLoad } from "cheerio";
+import { z } from "zod";
+import { cache } from "./cache";
+
+function getDomain(url: string): string {
+ return new URL(url).hostname;
+}
/**
@param siteBaseURL - it should only contain base url of the site
@@ -8,51 +14,55 @@ import { load as cheerioLoad } from "cheerio";
export async function fetchFavicon(
siteBaseURL: string,
): Promise {
- const websiteRes = await fetch(siteBaseURL);
- if (!websiteRes.ok) {
- return undefined;
- }
+ const cacheKey = `fetch-favicon:${getDomain(siteBaseURL)}`;
- // fetch link[rel="icon"].href
- {
- const html = await websiteRes.text();
- const querySelector = cheerioLoad(html);
- const iconHref = querySelector(
- "link[rel='icon'], link[rel='shortcut icon']",
- )[0]?.attribs.href;
- if (iconHref) {
- const iconUrl = new URL(iconHref, siteBaseURL);
- const iconRes = await fetch(iconUrl);
- const contentType = iconRes.headers.get("content-type");
- if (!contentType)
- console.warn(
- `header "Content-Type" not found while fetching ${iconUrl.toString()}`,
- );
- if (iconRes.ok) {
- return base64DataURL(
- contentType,
- Buffer.from(await iconRes.arrayBuffer()),
- );
+ return await cache(cacheKey, z.string().optional(), async () => {
+ const websiteRes = await fetch(siteBaseURL);
+ if (!websiteRes.ok) {
+ return undefined;
+ }
+
+ // fetch link[rel="icon"].href
+ {
+ const html = await websiteRes.text();
+ const querySelector = cheerioLoad(html);
+ const iconHref = querySelector(
+ "link[rel='icon'], link[rel='shortcut icon']",
+ )[0]?.attribs.href;
+ if (iconHref) {
+ const iconUrl = new URL(iconHref, siteBaseURL);
+ const iconRes = await fetch(iconUrl);
+ const contentType = iconRes.headers.get("content-type");
+ if (!contentType)
+ console.warn(
+ `header "Content-Type" not found while fetching ${iconUrl.toString()}`,
+ );
+ if (iconRes.ok) {
+ return base64DataURL(
+ contentType,
+ Buffer.from(await iconRes.arrayBuffer()),
+ );
+ }
}
}
- }
- // fetch /favicon.ico
- {
- const faviconRes = await fetch(
- `${new URL(siteBaseURL).origin}/favicon.ico`,
- );
- const contentType =
- faviconRes.headers.get("content-type") ?? "image/vnd.microsoft.icon";
- if (faviconRes.ok && !contentType.startsWith("text/")) {
- return base64DataURL(
- contentType,
- Buffer.from(await faviconRes.arrayBuffer()),
+ // fetch /favicon.ico
+ {
+ const faviconRes = await fetch(
+ `${new URL(siteBaseURL).origin}/favicon.ico`,
);
+ const contentType =
+ faviconRes.headers.get("content-type") ?? "image/vnd.microsoft.icon";
+ if (faviconRes.ok && !contentType.startsWith("text/")) {
+ return base64DataURL(
+ contentType,
+ Buffer.from(await faviconRes.arrayBuffer()),
+ );
+ }
}
- }
- return undefined;
+ return undefined;
+ });
}
function base64DataURL(contentType: string | null, buf: Buffer) {
diff --git a/src/pages/projects/[...id].astro b/src/pages/projects/[...id].astro
index 0d440b15..04fd6416 100644
--- a/src/pages/projects/[...id].astro
+++ b/src/pages/projects/[...id].astro
@@ -35,8 +35,8 @@ if (project.data.status !== "dead") {
---
@@ -64,7 +64,7 @@ if (project.data.status !== "dead") {
Project
- {project.data.title}
+ {project.data.app.name}
{
project.data.tags && (
@@ -81,12 +81,17 @@ if (project.data.status !== "dead") {
{
[
{
- href: project.data.github,
+ href: project.data.social?.github,
icon: "feather:github",
label: "GitHub",
},
{
- href: project.data.youtube,
+ href: project.data.social?.youtube,
+ icon: "feather:youtube",
+ label: "YouTube",
+ },
+ {
+ href: project.data.social?.website,
icon: "feather:youtube",
label: "YouTube",
},
@@ -107,14 +112,14 @@ if (project.data.status !== "dead") {
}
{
- project.data.website && project.data.status !== "dead" && (
-
+ project.data.app.url && project.data.status !== "dead" && (
+
{iconSrc ? (
-
+
) : (
)}
- {project.data.title} へ
+ {project.data.app.name} へ
)
}
@@ -124,7 +129,7 @@ if (project.data.status !== "dead") {
{
project.data.kind === "long-term" && (
- {project.data.description}
+ {project.data.app.description}
)
}
@@ -133,7 +138,7 @@ if (project.data.status !== "dead") {
project.data.kind === "long-term" ? (
) : (
- project.data.description
+ project.data.app.description
)
}
@@ -142,7 +147,7 @@ if (project.data.status !== "dead") {
{
project.data.kind === "long-term" && (
- ut.code(); で{project.data.title}を開発しませんか?
+ ut.code(); で{project.data.app.name}を開発しませんか?
)
}
diff --git a/src/schema.ts b/src/schema.ts
index 8a3028b5..2b1a5892 100644
--- a/src/schema.ts
+++ b/src/schema.ts
@@ -48,10 +48,19 @@ export const CreateProjectSchema = ({ image }: { image: ImageFunction }) =>
app: z.object({
name: z.string(),
description: z.string(),
- url: z.string().url(),
+ url: z.string().url().optional(),
// 各プロパティは必要に応じて追加
- platform: z.enum(["web", "mobile", "desktop", "cli"]),
- domain: z.enum(["app", "game", "tool", "site", "lib"]),
+ platform: z.array(z.enum(["web", "mobile", "desktop", "cli"])),
+ domain: z.array(
+ z.enum([
+ "app", // アプリ
+ "game", // ゲーム
+ "tool", // ツール (ログイン不要なものを想定)
+ "site", // サイト (Website)
+ "learn", // 学習教材
+ "lib", // ライブラリ
+ ]),
+ ),
}),
kind: z.enum(kinds.map((kind) => kind.frontmatter) as [Kind, ...Kind[]]),
status: z.enum([